From 4b9c84cd17f91d1f90ca40e4a83caa75a8446317 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Mon, 28 Oct 2024 20:30:28 -0500 Subject: [PATCH 01/78] #111 JavaFqnCapturingVisitor working as expected JavaFqnCapturingVisitor captures all possible type name references when specified as a generic type parameter --- circular-reference-detector/pom.xml | 33 ++++ .../java/org/hjug/parser/CustomVisitor.java | 157 ++++++++++++++++++ .../hjug/parser/JavaFqnCapturingVisitor.java | 72 ++++++++ .../org/hjug/parser/JavaProjectParser.java | 18 ++ .../org/hjug/parser/CustomVisitorTest.java | 40 +++++ .../parser/JavaFqnCapturingVisitorTest.java | 57 +++++++ .../org/hjug/testclasses/A.java | 22 +++ .../org/hjug/testclasses/B.java | 5 + .../org/hjug/testclasses/C.java | 6 + .../org/hjug/testclasses/D.java | 6 + .../org/hjug/testclasses/E.java | 6 + .../org/hjug/testclasses/F.java | 3 + .../org/hjug/testclasses/G.java | 6 + 13 files changed, 431 insertions(+) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/CustomVisitor.java create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/CustomVisitorTest.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml index b9823857..9517270f 100644 --- a/circular-reference-detector/pom.xml +++ b/circular-reference-detector/pom.xml @@ -12,6 +12,18 @@ Tool to help detecting circular references by parsing a java project. + + + + org.openrewrite.recipe + rewrite-recipe-bom + 2.20.0 + pom + import + + + + org.projectlombok @@ -28,6 +40,27 @@ jgrapht-core 1.5.2 + + org.openrewrite + rewrite-java-21 + + + org.openrewrite + rewrite-java-17 + + + org.openrewrite + rewrite-java-11 + + + org.openrewrite + rewrite-java + + + org.openrewrite + rewrite-core + + com.github.javaparser javaparser-symbol-solver-core diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/CustomVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/CustomVisitor.java new file mode 100644 index 00000000..81b680f4 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/CustomVisitor.java @@ -0,0 +1,157 @@ +package org.hjug.parser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.*; + +@Slf4j +public class CustomVisitor extends JavaIsoVisitor { + + JavaFqnCapturingVisitor javaFqnCapturingVisitor = new JavaFqnCapturingVisitor(); + + @Getter + private final List fqdns = new ArrayList<>(); + + // used for looking up declared generic FQDNs + @Getter + private final Map importedAndDeclaredClassFQDNsByName = new HashMap<>(); + + @Getter + private List genericTypeParameters = new ArrayList<>(); + + // Get imported classes to check declared generics + @Override + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) { + J.CompilationUnit compilationUnit = super.visitCompilationUnit(cu, executionContext); + + for (J.Import mport : compilationUnit.getImports()) { + importedAndDeclaredClassFQDNsByName.put(mport.getClassName(), mport.getTypeName()); + } + + for (J.ClassDeclaration aClass : compilationUnit.getClasses()) { + importedAndDeclaredClassFQDNsByName.put( + aClass.getName().getSimpleName(), aClass.getType().getFullyQualifiedName()); + } + + return compilationUnit; + } + + // collect generic types and then add them to the FQDN list once all visitors are processed + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { + J.ClassDeclaration classDeclaration = super.visitClassDeclaration(classDecl, executionContext); + + // fqdns.add(classDeclaration.getType().getFullyQualifiedName()); + + System.out.println("Declared Type: " + classDeclaration.getType().getFullyQualifiedName()); + + // make this into a tail recursive call + // for (J.TypeParameter typeParameter : classDeclaration.getTypeParameters()) { + // System.out.println("Type Parameter: " + typeParameter.getBounds().get(0)); + // } + + List typeParameters = classDeclaration.getType().getTypeParameters(); + + if (!typeParameters.isEmpty()) { + System.out.println("Type type parameters: " + typeParameters); + for (JavaType genericTypeParameter : typeParameters) { + System.out.println("Generic Type: " + genericTypeParameter); + System.out.println("Bounds: " + ((JavaType.GenericTypeVariable) genericTypeParameter).getBounds()); + } + } + + classDeclaration.getExtends(); + // recursively check for type params on extends type + classDeclaration.getImplements(); + // recursively check for type params on implemented interfaces + + return classDeclaration; + } + + // @Override + // public J.TypeParameter visitTypeParameter(J.TypeParameter typeParam, ExecutionContext executionContext) { + // for (TypeTree bound : typeParam.getBounds()) { + // System.out.println("Type Parameter type: " + bo); + // } + // + // + // return super.visitTypeParameter(typeParam, executionContext); + // } + + // member reference: System.out::println + @Override + public J.MemberReference visitMemberReference(J.MemberReference memberRef, ExecutionContext executionContext) { + J.MemberReference memberReference = super.visitMemberReference(memberRef, executionContext); + + System.out.println( + "Member reference type: " + memberReference.getVariableType().getType()); + System.out.println("Owning type: " + memberReference.getVariableType().getOwner()); + + return memberReference; + } + + @Override + public J.VariableDeclarations.NamedVariable visitVariable( + J.VariableDeclarations.NamedVariable variable, ExecutionContext executionContext) { + J.VariableDeclarations.NamedVariable visitVariable = super.visitVariable(variable, executionContext); + + System.out.println("Variable type: " + visitVariable.getVariableType().getType()); + System.out.println("Owning type: " + visitVariable.getVariableType().getOwner()); + + return visitVariable; + } + + @Override + public J.VariableDeclarations visitVariableDeclarations( + J.VariableDeclarations multiVariable, ExecutionContext executionContext) { + + System.out.println("Variable declaration type: " + multiVariable.getType()); + System.out.println("Variable declaration owning type: " + + multiVariable.getVariables().get(0).getVariableType().getOwner()); + + return super.visitVariableDeclarations(multiVariable, executionContext); + } + + @Override + public J.NullableType visitNullableType(J.NullableType nullableType, ExecutionContext executionContext) { + return super.visitNullableType(nullableType, executionContext); + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { + J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, executionContext); + + for (Statement parameter : method.getParameters()) { + JavaType type; + if (parameter instanceof J.VariableDeclarations) { + for (J.VariableDeclarations.NamedVariable variable : + ((J.VariableDeclarations) parameter).getVariables()) { + type = variable.getType(); + } + } else if (parameter instanceof Expression) { + type = ((Expression) parameter).getType(); + } + + // do something with the type + + } + + methodDeclaration.getReturnTypeExpression().getType(); + + return methodDeclaration; + } + + @Override + public J.Block visitBlock(J.Block block, ExecutionContext executionContext) { + + // block.getStatements(); + + return super.visitBlock(block, executionContext); + } +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java new file mode 100644 index 00000000..877ee9af --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java @@ -0,0 +1,72 @@ +package org.hjug.parser; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; + +/** + * Captures Fully Qualified Names (FQN) of classes as they will be imported in import statements. + * Output used to resolve Generic types. + * + * @param

+ */ +@Getter +public class JavaFqnCapturingVisitor

extends JavaIsoVisitor

{ + + // package -> name, FQN + private final Map> fqns = new HashMap<>(); + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { + // get class fqn (including "$") + String fqn = classDecl.getType().getFullyQualifiedName(); + + String currentPackage = getPackage(fqn); + String className = getClassName(fqn); + Map classesInPackage = fqns.getOrDefault(currentPackage, new HashMap<>()); + + if (className.contains("$")) { + String normalizedClassName = className.replace('$', '.'); + List parts = Arrays.asList(normalizedClassName.split("\\.")); + for (int i = 0; i < parts.size(); i++) { + String key = String.join(".", parts.subList(i, parts.size())); + classesInPackage.put(key, currentPackage + "." + normalizedClassName); + } + } else { + classesInPackage.put(className, fqn); + } + + fqns.put(currentPackage, classesInPackage); + return super.visitClassDeclaration(classDecl, p); + } + + + String getPackage(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return ""; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(0, lastIndex); + } + + /** + * + * @param fqn + * @return Class name (including "$") after last period in FQN + */ + String getClassName(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return fqn; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(lastIndex + 1); + } +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index f450d77d..c7bfa677 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -18,6 +18,9 @@ import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; @Slf4j public class JavaProjectParser { @@ -66,6 +69,20 @@ public Graph getClassReferences(String srcDirectory return classReferencesGraph; } + List getClassDeclarations(Path javaSrcFile, Path srcDirectory) { + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + CustomVisitor customVisitor = new CustomVisitor(); + + List fqdns = new ArrayList<>(); + + javaParser.parse(List.of(javaSrcFile), srcDirectory, ctx).forEach(cu -> customVisitor.visit(cu, ctx)); + + return fqdns; + } + /** * Get instance variables types of a java source file using java parser * @param classNamesToFilterBy - only add instance variable types which have these class names as type @@ -76,6 +93,7 @@ private List getInstanceVarTypes(List classNamesToFilterBy, File CompilationUnit compilationUnit; try { compilationUnit = StaticJavaParser.parse(javaSrcFile); + return compilationUnit.findAll(FieldDeclaration.class).stream() .map(f -> f.getVariables().get(0).getType()) .filter(v -> !v.isPrimitiveType()) diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/CustomVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/CustomVisitorTest.java new file mode 100644 index 00000000..6d574113 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/CustomVisitorTest.java @@ -0,0 +1,40 @@ +package org.hjug.parser; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +class CustomVisitorTest { + + @Test + void visitClasses() throws IOException { + + File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + CustomVisitor customVisitor = new CustomVisitor(); + + List fqdns = new ArrayList<>(); + + List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach((cu) -> { + System.out.println(cu.getSourcePath()); + customVisitor.visit(cu, ctx); + // fqdns.addAll(customVisitor.getFqdns()); + }); + + // return fqdns; + } +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java new file mode 100644 index 00000000..0ccfd29c --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java @@ -0,0 +1,57 @@ +package org.hjug.parser; + +import java.io.File; +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.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +class JavaFqnCapturingVisitorTest { + + @Test + void visitClasses() throws IOException { + + File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaFqnCapturingVisitor javaFqnCapturingVisitor = new JavaFqnCapturingVisitor(); + + List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + javaFqnCapturingVisitor.visit(cu, ctx); + }); + + Map> fqns = javaFqnCapturingVisitor.getFqns(); + Map processed = fqns.get("org.hjug.testclasses"); + Assertions.assertEquals("org.hjug.testclasses.A", processed.get("A")); + Assertions.assertEquals("org.hjug.testclasses.A.InnerClass", processed.get("A.InnerClass")); + Assertions.assertEquals("org.hjug.testclasses.A.InnerClass", processed.get("InnerClass")); + Assertions.assertEquals( + "org.hjug.testclasses.A.InnerClass.InnerInner", processed.get("A.InnerClass.InnerInner")); + Assertions.assertEquals("org.hjug.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); + Assertions.assertEquals("org.hjug.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); + Assertions.assertEquals( + "org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", + processed.get("A.InnerClass.InnerInner.MegaInner")); + Assertions.assertEquals( + "org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", + processed.get("InnerClass.InnerInner.MegaInner")); + Assertions.assertEquals( + "org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerInner.MegaInner")); + Assertions.assertEquals("org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); + Assertions.assertEquals("org.hjug.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); + Assertions.assertEquals("org.hjug.testclasses.A.StaticInnerClass", processed.get("StaticInnerClass")); + Assertions.assertEquals("org.hjug.testclasses.NonPublic", processed.get("NonPublic")); + } +} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java new file mode 100644 index 00000000..019778e1 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java @@ -0,0 +1,22 @@ +package org.hjug.testclasses; + +import java.util.Map; + +public class A implements F extends G { + + int intVar, intVar2; + B b; + + C doSomething(D d) { + E e; + } + + class InnerClass{ + class InnerInner{ + class MegaInner{} + } + } + static class StaticInnerClass{} +} + +class NonPublic {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java new file mode 100644 index 00000000..b552f75a --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java @@ -0,0 +1,5 @@ +package org.hjug.testclasses; + +public class B { + C c; +} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java new file mode 100644 index 00000000..d9b875f3 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java @@ -0,0 +1,6 @@ +package org.hjug.testclasses; + +public class C { + A a; + E e; +} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java new file mode 100644 index 00000000..83cb7a67 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java @@ -0,0 +1,6 @@ +package org.hjug.testclasses; + +public class D { + A a; + C c; +} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java new file mode 100644 index 00000000..b57cb672 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java @@ -0,0 +1,6 @@ +package org.hjug.testclasses; + +public class E { + D d; + D d2; +} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java new file mode 100644 index 00000000..ed566636 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java @@ -0,0 +1,3 @@ +package org.hjug.testclasses; + +public interface F {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java new file mode 100644 index 00000000..c9e3a178 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java @@ -0,0 +1,6 @@ +package org.hjug.testclasses; + +public class G { + D d; + D d2; +} From e469851cab7e0ec074f98d950730619965b2ada5 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Tue, 29 Oct 2024 20:18:47 -0500 Subject: [PATCH 02/78] #111 Extract FqnCapturingVisitor to simplify multi-language visiting Extract FqnCapturingVisitor to simplify implementation of multi-language visiting --- .../org/hjug/parser/FqnCapturingVisitor.java | 59 +++++++++++++++++++ .../hjug/parser/JavaFqnCapturingVisitor.java | 54 ++--------------- 2 files changed, 63 insertions(+), 50 deletions(-) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingVisitor.java diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingVisitor.java new file mode 100644 index 00000000..e571d147 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingVisitor.java @@ -0,0 +1,59 @@ +package org.hjug.parser; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.openrewrite.java.tree.J; + +public interface FqnCapturingVisitor { + + default J.ClassDeclaration captureClassDeclarations( + J.ClassDeclaration classDecl, Map> fqns) { + // get class fqn (including "$") + String fqn = classDecl.getType().getFullyQualifiedName(); + + String currentPackage = getPackage(fqn); + String className = getClassName(fqn); + Map classesInPackage = fqns.getOrDefault(currentPackage, new HashMap<>()); + + if (className.contains("$")) { + String normalizedClassName = className.replace('$', '.'); + List parts = Arrays.asList(normalizedClassName.split("\\.")); + for (int i = 0; i < parts.size(); i++) { + String key = String.join(".", parts.subList(i, parts.size())); + classesInPackage.put(key, currentPackage + "." + normalizedClassName); + } + } else { + classesInPackage.put(className, fqn); + } + + fqns.put(currentPackage, classesInPackage); + return classDecl; + } + + default String getPackage(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return ""; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(0, lastIndex); + } + + /** + * + * @param fqn + * @return Class name (including "$") after last period in FQN + */ + default String getClassName(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return fqn; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(lastIndex + 1); + } +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java index 877ee9af..23234cc0 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java @@ -1,8 +1,6 @@ package org.hjug.parser; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import lombok.Getter; import org.openrewrite.java.JavaIsoVisitor; @@ -10,63 +8,19 @@ /** * Captures Fully Qualified Names (FQN) of classes as they will be imported in import statements. - * Output used to resolve Generic types. + * fqns map that is populated by this visitor is used to resolve Generic types. * * @param

*/ @Getter -public class JavaFqnCapturingVisitor

extends JavaIsoVisitor

{ +public class JavaFqnCapturingVisitor

extends JavaIsoVisitor

implements FqnCapturingVisitor { + // consider using ConcurrentHashMap to scale performance // package -> name, FQN private final Map> fqns = new HashMap<>(); @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { - // get class fqn (including "$") - String fqn = classDecl.getType().getFullyQualifiedName(); - - String currentPackage = getPackage(fqn); - String className = getClassName(fqn); - Map classesInPackage = fqns.getOrDefault(currentPackage, new HashMap<>()); - - if (className.contains("$")) { - String normalizedClassName = className.replace('$', '.'); - List parts = Arrays.asList(normalizedClassName.split("\\.")); - for (int i = 0; i < parts.size(); i++) { - String key = String.join(".", parts.subList(i, parts.size())); - classesInPackage.put(key, currentPackage + "." + normalizedClassName); - } - } else { - classesInPackage.put(className, fqn); - } - - fqns.put(currentPackage, classesInPackage); - return super.visitClassDeclaration(classDecl, p); - } - - - String getPackage(String fqn) { - // handle no package - if (!fqn.contains(".")) { - return ""; - } - - int lastIndex = fqn.lastIndexOf("."); - return fqn.substring(0, lastIndex); - } - - /** - * - * @param fqn - * @return Class name (including "$") after last period in FQN - */ - String getClassName(String fqn) { - // handle no package - if (!fqn.contains(".")) { - return fqn; - } - - int lastIndex = fqn.lastIndexOf("."); - return fqn.substring(lastIndex + 1); + return super.visitClassDeclaration(captureClassDeclarations(classDecl, fqns), p); } } From 82b162c93a32093be74c95baf4f75de036290afc Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Wed, 13 Nov 2024 20:22:42 -0600 Subject: [PATCH 03/78] #111 Making progress on capturing types --- circular-reference-detector/pom.xml | 2 +- ...isitor.java => FqnCapturingProcessor.java} | 2 +- .../hjug/parser/JavaFqnCapturingVisitor.java | 2 +- .../org/hjug/parser/JavaProjectParser.java | 4 +- .../org/hjug/parser/JavaVariableVisitor.java | 68 +++++++++++++++++++ .../{CustomVisitor.java => JavaVisitor.java} | 31 ++++----- .../hjug/parser/JavaVariableVisitorTest.java | 33 +++++++++ ...mVisitorTest.java => JavaVisitorTest.java} | 6 +- .../org/hjug/testclasses/A.java | 21 +++++- .../org/hjug/testclasses/G.java | 2 +- .../org/hjug/testclasses/MyAnnotation.java | 6 ++ .../hjug/testclasses/MyOtherAnnotation.java | 6 ++ 12 files changed, 155 insertions(+), 28 deletions(-) rename circular-reference-detector/src/main/java/org/hjug/parser/{FqnCapturingVisitor.java => FqnCapturingProcessor.java} (97%) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java rename circular-reference-detector/src/main/java/org/hjug/parser/{CustomVisitor.java => JavaVisitor.java} (87%) create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java rename circular-reference-detector/src/test/java/org/hjug/parser/{CustomVisitorTest.java => JavaVisitorTest.java} (90%) create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyAnnotation.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyOtherAnnotation.java diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml index 9517270f..b57cc3bf 100644 --- a/circular-reference-detector/pom.xml +++ b/circular-reference-detector/pom.xml @@ -17,7 +17,7 @@ org.openrewrite.recipe rewrite-recipe-bom - 2.20.0 + 2.21.1 pom import diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingProcessor.java similarity index 97% rename from circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingVisitor.java rename to circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingProcessor.java index e571d147..ed69c0b0 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingProcessor.java @@ -6,7 +6,7 @@ import java.util.Map; import org.openrewrite.java.tree.J; -public interface FqnCapturingVisitor { +public interface FqnCapturingProcessor { default J.ClassDeclaration captureClassDeclarations( J.ClassDeclaration classDecl, Map> fqns) { diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java index 23234cc0..8f74a890 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java @@ -13,7 +13,7 @@ * @param

*/ @Getter -public class JavaFqnCapturingVisitor

extends JavaIsoVisitor

implements FqnCapturingVisitor { +public class JavaFqnCapturingVisitor

extends JavaIsoVisitor

implements FqnCapturingProcessor { // consider using ConcurrentHashMap to scale performance // package -> name, FQN diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index c7bfa677..4370929f 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -74,11 +74,11 @@ List getClassDeclarations(Path javaSrcFile, Path srcDirectory) { JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - CustomVisitor customVisitor = new CustomVisitor(); + JavaVisitor javaVisitor = new JavaVisitor(); List fqdns = new ArrayList<>(); - javaParser.parse(List.of(javaSrcFile), srcDirectory, ctx).forEach(cu -> customVisitor.visit(cu, ctx)); + javaParser.parse(List.of(javaSrcFile), srcDirectory, ctx).forEach(cu -> javaVisitor.visit(cu, ctx)); return fqdns; } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java new file mode 100644 index 00000000..092aa05a --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java @@ -0,0 +1,68 @@ +package org.hjug.parser; + +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; + +public class JavaVariableVisitor

extends JavaIsoVisitor

{ + + /** + * Variable type is B + * Owning type is A + * + * class A { + * B b1, b2; + * } + */ + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, P p) { + J.VariableDeclarations variableDeclarations = super.visitVariableDeclarations(multiVariable, p); + + // TODO: getAllAnnotations() is deprecated - need to get Cursor and call + // AnnotationService.getAllAnnotations(cursor) + // but I'm not sure how to get a cursor + // All types, including primitives can be annotated + System.out.println("Type annotations: " + variableDeclarations.getAllAnnotations()); + + // skip primitive variable declarations + if (variableDeclarations.getTypeExpression() instanceof J.Primitive) { + return variableDeclarations; + } else if (variableDeclarations.getTypeExpression() instanceof J.ParameterizedType) { + + // A --> A + // get Variable type + System.out.println("Parametrized Variable type: " + + ((J.ParameterizedType) variableDeclarations.getTypeExpression()) + .getClazz() + .getType()); + + // A --> E + // gets variable's type parameters + System.out.println("Type parameters: " + + ((J.ParameterizedType) variableDeclarations.getTypeExpression()).getTypeParameters()); + } else if (variableDeclarations.getTypeExpression() instanceof J.ArrayType) { + // D[] --> D + System.out.println( + "Array Element type: " + ((J.ArrayType) variableDeclarations.getTypeExpression()).getElementType()); + + } else if (variableDeclarations.getTypeExpression() instanceof J.Identifier) { + // G --> G + System.out.println("Identifier type: " + (variableDeclarations.getTypeExpression()).getType()); + } + + // *should* always have get(0) + variableDeclarations.getVariables().get(0).getVariableType().getOwner(); + + for (J.VariableDeclarations.NamedVariable variable : variableDeclarations.getVariables()) { + System.out.println( + "Varible VariableType Owner: " + variable.getVariableType().getOwner()); + System.out.println("Varible type: " + variable.getType()); + System.out.println("Varible name: " + variable.getName()); + } + + System.out.println("*************************"); + System.out.println(variableDeclarations.toString()); + System.out.println("*************************"); + + return variableDeclarations; + } +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/CustomVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java similarity index 87% rename from circular-reference-detector/src/main/java/org/hjug/parser/CustomVisitor.java rename to circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java index 81b680f4..9a0714dd 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/CustomVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java @@ -11,7 +11,7 @@ import org.openrewrite.java.tree.*; @Slf4j -public class CustomVisitor extends JavaIsoVisitor { +public class JavaVisitor extends JavaIsoVisitor { JavaFqnCapturingVisitor javaFqnCapturingVisitor = new JavaFqnCapturingVisitor(); @@ -96,6 +96,14 @@ public J.MemberReference visitMemberReference(J.MemberReference memberRef, Execu return memberReference; } + /** + * Variable type is B + * Owning type is A + * + * class A { + * B b1, b2; + * } + */ @Override public J.VariableDeclarations.NamedVariable visitVariable( J.VariableDeclarations.NamedVariable variable, ExecutionContext executionContext) { @@ -104,24 +112,15 @@ public J.VariableDeclarations.NamedVariable visitVariable( System.out.println("Variable type: " + visitVariable.getVariableType().getType()); System.out.println("Owning type: " + visitVariable.getVariableType().getOwner()); - return visitVariable; - } - - @Override - public J.VariableDeclarations visitVariableDeclarations( - J.VariableDeclarations multiVariable, ExecutionContext executionContext) { - - System.out.println("Variable declaration type: " + multiVariable.getType()); - System.out.println("Variable declaration owning type: " - + multiVariable.getVariables().get(0).getVariableType().getOwner()); + // visitVariable.getType() - return super.visitVariableDeclarations(multiVariable, executionContext); + return visitVariable; } - @Override - public J.NullableType visitNullableType(J.NullableType nullableType, ExecutionContext executionContext) { - return super.visitNullableType(nullableType, executionContext); - } + // @Override + // public J.NullableType visitNullableType(J.NullableType nullableType, ExecutionContext executionContext) { + // return super.visitNullableType(nullableType, executionContext); + // } @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java new file mode 100644 index 00000000..3a3a314f --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java @@ -0,0 +1,33 @@ +package org.hjug.parser; + +import java.io.File; +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.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +class JavaVariableVisitorTest { + + @Test + void visitClasses() throws IOException { + + File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaVariableVisitor javaVariableCapturingVisitor = new JavaVariableVisitor<>(); + + List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + javaVariableCapturingVisitor.visit(cu, ctx); + }); + } +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/CustomVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java similarity index 90% rename from circular-reference-detector/src/test/java/org/hjug/parser/CustomVisitorTest.java rename to circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java index 6d574113..d71ab686 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/CustomVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java @@ -13,7 +13,7 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; -class CustomVisitorTest { +class JavaVisitorTest { @Test void visitClasses() throws IOException { @@ -24,14 +24,14 @@ void visitClasses() throws IOException { JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - CustomVisitor customVisitor = new CustomVisitor(); + JavaVisitor javaVisitor = new JavaVisitor(); List fqdns = new ArrayList<>(); List list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach((cu) -> { System.out.println(cu.getSourcePath()); - customVisitor.visit(cu, ctx); + javaVisitor.visit(cu, ctx); // fqdns.addAll(customVisitor.getFqdns()); }); diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java index 019778e1..fd0d14a6 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java @@ -2,18 +2,33 @@ import java.util.Map; -public class A implements F extends G { +public class A implements F extends G { + @MyAnnotation + @MyOtherAnnotation int intVar, intVar2; - B b; + @MyAnnotation + @MyOtherAnnotation + G g; + A a; + B b, b3; + C c; + D[] ds; C doSomething(D d) { + A a2; + B b2; + C c2; E e; + F f; + return new C(); } class InnerClass{ class InnerInner{ - class MegaInner{} + class MegaInner{ + G g; + } } } static class StaticInnerClass{} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java index c9e3a178..a03bf6c2 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java @@ -1,6 +1,6 @@ package org.hjug.testclasses; public class G { - D d; + D d, d3; D d2; } diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyAnnotation.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyAnnotation.java new file mode 100644 index 00000000..f0eff8e7 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyAnnotation.java @@ -0,0 +1,6 @@ +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE_USE) +@interface MyAnnotation { +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyOtherAnnotation.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyOtherAnnotation.java new file mode 100644 index 00000000..54c5938b --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyOtherAnnotation.java @@ -0,0 +1,6 @@ +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE_USE) +@interface MyOtherAnnotation { +} \ No newline at end of file From f0d84112165a39cc0d288c29fa0c1f395921faf3 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sat, 16 Nov 2024 19:51:32 -0600 Subject: [PATCH 04/78] #111 Added variable owner sout stmts --- .../main/java/org/hjug/parser/JavaVariableVisitor.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java index 092aa05a..534ad1d8 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java @@ -50,14 +50,18 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m } // *should* always have get(0) - variableDeclarations.getVariables().get(0).getVariableType().getOwner(); + System.out.println( "Varible Owner: " + + variableDeclarations.getVariables().get(0).getVariableType().getOwner()); - for (J.VariableDeclarations.NamedVariable variable : variableDeclarations.getVariables()) { + System.out.println( "Varible Owner Class: " + + variableDeclarations.getVariables().get(0).getVariableType().getOwner().getClass()); + + /* for (J.VariableDeclarations.NamedVariable variable : variableDeclarations.getVariables()) { System.out.println( "Varible VariableType Owner: " + variable.getVariableType().getOwner()); System.out.println("Varible type: " + variable.getType()); System.out.println("Varible name: " + variable.getName()); - } + }*/ System.out.println("*************************"); System.out.println(variableDeclarations.toString()); From c1ff0151f84d7d48228fa6241c8fba8edb67fbcb Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Thu, 21 Nov 2024 20:20:53 -0600 Subject: [PATCH 05/78] Processing JavaType types Processing JavaType types instead of J types. This allows for types to be processed via dynamic type binding --- .../org/hjug/parser/JavaVariableVisitor.java | 161 +++++++++++++----- .../java/org/hjug/parser/JavaVisitorTest.java | 2 + .../javaVariableVisitorTestClasses/A.java | 46 +++++ .../javaVariableVisitorTestClasses/B.java | 6 + .../javaVariableVisitorTestClasses/C.java | 3 + .../javaVariableVisitorTestClasses/D.java | 6 + .../javaVariableVisitorTestClasses/E.java | 5 + .../MyAnnotation.java | 2 + .../MyOtherAnnotation.java | 2 + .../org/hjug/testclasses/A.java | 37 ---- .../org/hjug/testclasses/B.java | 5 - .../org/hjug/testclasses/C.java | 6 - .../org/hjug/testclasses/D.java | 6 - .../org/hjug/testclasses/E.java | 6 - .../org/hjug/testclasses/F.java | 3 - .../org/hjug/testclasses/G.java | 6 - 16 files changed, 192 insertions(+), 110 deletions(-) create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java rename circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/{testclasses => javaVariableVisitorTestClasses}/MyAnnotation.java (72%) rename circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/{testclasses => javaVariableVisitorTestClasses}/MyOtherAnnotation.java (73%) delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java index 534ad1d8..9639e1d0 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java @@ -1,72 +1,151 @@ package org.hjug.parser; +import lombok.extern.slf4j.Slf4j; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.*; +@Slf4j public class JavaVariableVisitor

extends JavaIsoVisitor

{ + Graph classReferencesGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + /** * Variable type is B * Owning type is A - * + *

* class A { - * B b1, b2; + * B b1, b2; * } */ @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, P p) { J.VariableDeclarations variableDeclarations = super.visitVariableDeclarations(multiVariable, p); - // TODO: getAllAnnotations() is deprecated - need to get Cursor and call - // AnnotationService.getAllAnnotations(cursor) + System.out.println("*************************"); + System.out.println(variableDeclarations); + System.out.println("*************************"); + + JavaType owner = + variableDeclarations.getVariables().get(0).getVariableType().getOwner(); + String ownerFqn = ""; + + if (owner instanceof JavaType.Method) { + JavaType.Method m = (JavaType.Method) owner; + // System.out.println("Method owner: " + m.getDeclaringType().getFullyQualifiedName()); + ownerFqn = m.getDeclaringType().getFullyQualifiedName(); + } else if (owner instanceof JavaType.Class) { + JavaType.Class c = (JavaType.Class) owner; + // System.out.println("Method owner: " + c.getFullyQualifiedName()); + ownerFqn = c.getFullyQualifiedName(); + } + + TypeTree typeTree = variableDeclarations.getTypeExpression(); + + // skip class definition to prevent all types from self-referencing + if (typeTree instanceof J.Identifier) { + return variableDeclarations; + } + + JavaType javaType; + if (null != typeTree) { + javaType = typeTree.getType(); + } else { + return variableDeclarations; + } + + // TODO: getAllAnnotations() is deprecated - need to call + // AnnotationService.getAllAnnotations() but not sure which one yet // but I'm not sure how to get a cursor // All types, including primitives can be annotated - System.out.println("Type annotations: " + variableDeclarations.getAllAnnotations()); + for (J.Annotation annotation : variableDeclarations.getAllAnnotations()) { + JavaType.Class type = (JavaType.Class) annotation.getType(); + if (null != type) { + String annotationFqn = type.getFullyQualifiedName(); + System.out.println("Variable Annotation FQN: " + annotationFqn); + addType(ownerFqn, annotationFqn); + } + } // skip primitive variable declarations - if (variableDeclarations.getTypeExpression() instanceof J.Primitive) { + if (javaType instanceof JavaType.Primitive) { return variableDeclarations; - } else if (variableDeclarations.getTypeExpression() instanceof J.ParameterizedType) { + } + + processType(javaType, ownerFqn); + return variableDeclarations; + } + + // processType methods are mutually recursive + void processType(JavaType javaType, String ownerFqn) { + if (javaType instanceof JavaType.Class) { + processType((JavaType.Class) javaType, ownerFqn); + } else if (javaType instanceof JavaType.Parameterized) { // A --> A - // get Variable type - System.out.println("Parametrized Variable type: " - + ((J.ParameterizedType) variableDeclarations.getTypeExpression()) - .getClazz() - .getType()); - - // A --> E - // gets variable's type parameters - System.out.println("Type parameters: " - + ((J.ParameterizedType) variableDeclarations.getTypeExpression()).getTypeParameters()); - } else if (variableDeclarations.getTypeExpression() instanceof J.ArrayType) { - // D[] --> D - System.out.println( - "Array Element type: " + ((J.ArrayType) variableDeclarations.getTypeExpression()).getElementType()); - - } else if (variableDeclarations.getTypeExpression() instanceof J.Identifier) { - // G --> G - System.out.println("Identifier type: " + (variableDeclarations.getTypeExpression()).getType()); + processType((JavaType.Parameterized) javaType, ownerFqn); + } else if (javaType instanceof JavaType.GenericTypeVariable) { + // T t; + processType((JavaType.GenericTypeVariable) javaType, ownerFqn); + } else if (javaType instanceof JavaType.Array) { + processType((JavaType.Array) javaType, ownerFqn); } + } - // *should* always have get(0) - System.out.println( "Varible Owner: " + - variableDeclarations.getVariables().get(0).getVariableType().getOwner()); + private void processType(JavaType.Parameterized parameterized, String ownerFqn) { + // List> --> A + processAnnotations(parameterized, ownerFqn); + System.out.println("Parameterized type FQN : " + parameterized.getFullyQualifiedName()); + addType(ownerFqn, parameterized.getFullyQualifiedName()); - System.out.println( "Varible Owner Class: " + - variableDeclarations.getVariables().get(0).getVariableType().getOwner().getClass()); + System.out.println("Nested Parameterized type parameters: " + parameterized.getTypeParameters()); + for (JavaType parameter : parameterized.getTypeParameters()) { + processType(parameter, ownerFqn); + } + } - /* for (J.VariableDeclarations.NamedVariable variable : variableDeclarations.getVariables()) { - System.out.println( - "Varible VariableType Owner: " + variable.getVariableType().getOwner()); - System.out.println("Varible type: " + variable.getType()); - System.out.println("Varible name: " + variable.getName()); - }*/ + private void processType(JavaType.Array arrayType, String ownerFqn) { + // D[] --> D + System.out.println("Array Element type: " + arrayType.getElemType()); + processType(arrayType.getElemType(), ownerFqn); + } - System.out.println("*************************"); - System.out.println(variableDeclarations.toString()); - System.out.println("*************************"); + private void processType(JavaType.GenericTypeVariable typeVariable, String ownerFqn) { + System.out.println("Type parameter type name: " + typeVariable.getName()); - return variableDeclarations; + for (JavaType bound : typeVariable.getBounds()) { + processType(bound, ownerFqn); + } + } + + private void processType(JavaType.Class classType, String ownerFqn) { + processAnnotations(classType, ownerFqn); + System.out.println("Class type FQN: " + classType.getFullyQualifiedName()); + addType(ownerFqn, classType.getFullyQualifiedName()); + } + + void processAnnotations(JavaType.FullyQualified fullyQualified, String ownerFqn) { + if (!fullyQualified.getAnnotations().isEmpty()) { + for (JavaType.FullyQualified annotation : fullyQualified.getAnnotations()) { + String annotationFqn = annotation.getFullyQualifiedName(); + System.out.println("Extra Annotation FQN: " + annotationFqn); + addType(ownerFqn, annotationFqn); + } + } + } + + void addType(String ownerFqn, String typeFqn) { + classReferencesGraph.addVertex(ownerFqn); + classReferencesGraph.addVertex(typeFqn); + + if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { + classReferencesGraph.addEdge(ownerFqn, typeFqn); + } else { + DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + } } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java index d71ab686..84578277 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java @@ -37,4 +37,6 @@ void visitClasses() throws IOException { // return fqdns; } + + class Inner {} } diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java new file mode 100644 index 00000000..de13c6f1 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java @@ -0,0 +1,46 @@ +package org.hjug.javaVariableVisitorTestClasses; + +import java.util.Map; +import java.util.List; + +public class A { + + public A(B cB, C cC){} + + @MyAnnotation + @MyOtherAnnotation + int intVar, intVar2; + @MyAnnotation + @MyOtherAnnotation + C rawC; + B b, b3; + C c; + D[] ds; + + @MyAnnotation + B[] arrayOfGenericBsWithCTypeParam; + @MyAnnotation + B bWithArrayOfCs; + List> listWithNestedGenric; + Map map; + List> listOfListsOfNumbers; + + C doSomething(B paramB) { + List> list3; + A a2; + B b2; + C c2; + return new C(); + } + + class InnerClass{ + class InnerInner{ + class MegaInner{ + D d; + } + } + } + static class StaticInnerClass{} +} + +class NonPublic {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java new file mode 100644 index 00000000..9e245b2e --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java @@ -0,0 +1,6 @@ +package org.hjug.javaVariableVisitorTestClasses; + +public class B { + + static class InnerB{} +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java new file mode 100644 index 00000000..649ae941 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java @@ -0,0 +1,3 @@ +package org.hjug.javaVariableVisitorTestClasses; + +public class C {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java new file mode 100644 index 00000000..0b6068df --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java @@ -0,0 +1,6 @@ +package org.hjug.javaVariableVisitorTestClasses; + +public class D { +// A a; +// C c; +} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java new file mode 100644 index 00000000..af70fd4a --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java @@ -0,0 +1,5 @@ +package org.hjug.javaVariableVisitorTestClasses; + +public interface E { + void foo(A a); +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyAnnotation.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyAnnotation.java similarity index 72% rename from circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyAnnotation.java rename to circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyAnnotation.java index f0eff8e7..7d94ccf9 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyAnnotation.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyAnnotation.java @@ -1,3 +1,5 @@ +package org.hjug.javaVariableVisitorTestClasses; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyOtherAnnotation.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyOtherAnnotation.java similarity index 73% rename from circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyOtherAnnotation.java rename to circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyOtherAnnotation.java index 54c5938b..37686d60 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/MyOtherAnnotation.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyOtherAnnotation.java @@ -1,3 +1,5 @@ +package org.hjug.javaVariableVisitorTestClasses; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java deleted file mode 100644 index fd0d14a6..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/A.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.hjug.testclasses; - -import java.util.Map; - -public class A implements F extends G { - - @MyAnnotation - @MyOtherAnnotation - int intVar, intVar2; - @MyAnnotation - @MyOtherAnnotation - G g; - A a; - B b, b3; - C c; - D[] ds; - - C doSomething(D d) { - A a2; - B b2; - C c2; - E e; - F f; - return new C(); - } - - class InnerClass{ - class InnerInner{ - class MegaInner{ - G g; - } - } - } - static class StaticInnerClass{} -} - -class NonPublic {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java deleted file mode 100644 index b552f75a..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/B.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.hjug.testclasses; - -public class B { - C c; -} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java deleted file mode 100644 index d9b875f3..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/C.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.hjug.testclasses; - -public class C { - A a; - E e; -} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java deleted file mode 100644 index 83cb7a67..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/D.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.hjug.testclasses; - -public class D { - A a; - C c; -} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java deleted file mode 100644 index b57cb672..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/E.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.hjug.testclasses; - -public class E { - D d; - D d2; -} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java deleted file mode 100644 index ed566636..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/F.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.testclasses; - -public interface F {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java deleted file mode 100644 index a03bf6c2..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/testclasses/G.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.hjug.testclasses; - -public class G { - D d, d3; - D d2; -} From f4a43841ed552d789b9c2b4e20e7449d80f8f565 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Thu, 28 Nov 2024 14:32:19 -0600 Subject: [PATCH 06/78] #111 Rendering large graphs with Sigma.js - Rendering large graphs (> 700 nodes + edges) on demand with Sigma.js, Graphology, graphlib, and graphlib-dot in the HTML report. Renders using WebGL. - Touched up type processing in JavaVariableVisitor - Removed the use of JavaParser --- circular-reference-detector/pom.xml | 14 +- .../hjug/parser/JavaFqnCapturingVisitor.java | 60 +- .../org/hjug/parser/JavaProjectParser.java | 128 +- .../org/hjug/parser/JavaVariableVisitor.java | 95 +- .../parser/JavaFqnCapturingVisitorTest.java | 4 +- .../hjug/parser/JavaProjectParserTests.java | 45 +- .../java/org/hjug/parser/JavaVisitorTest.java | 2 + .../javaVariableVisitorTestClasses/A.java | 10 +- .../javaVariableVisitorTestClasses/C.java | 2 +- .../org/hjug/cbc/CostBenefitCalculator.java | 8 +- .../hjug/refactorfirst/report/HtmlReport.java | 271 ++- .../report/SimpleHtmlReport.java | 23 +- .../refactorfirst/report/HtmlReportTest.java | 6 +- .../src/test/resources/sigmaPlayground.html | 2053 +++++++++++++++++ 14 files changed, 2499 insertions(+), 222 deletions(-) create mode 100644 report/src/test/resources/sigmaPlayground.html diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml index 2e30e1ed..639aed13 100644 --- a/circular-reference-detector/pom.xml +++ b/circular-reference-detector/pom.xml @@ -17,7 +17,7 @@ org.openrewrite.recipe rewrite-recipe-bom - 2.21.1 + 2.23.0 pom import @@ -56,16 +56,18 @@ org.openrewrite rewrite-java + + + + io.quarkus.gizmo + gizmo + 1.8.0 + org.openrewrite rewrite-core - - com.github.javaparser - javaparser-symbol-solver-core - 3.26.1 - org.junit.jupiter junit-jupiter-api diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java index 8f74a890..b31028c9 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java @@ -1,7 +1,6 @@ package org.hjug.parser; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import lombok.Getter; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.J; @@ -13,14 +12,65 @@ * @param

*/ @Getter -public class JavaFqnCapturingVisitor

extends JavaIsoVisitor

implements FqnCapturingProcessor { +public class JavaFqnCapturingVisitor

extends JavaIsoVisitor

{ // consider using ConcurrentHashMap to scale performance // package -> name, FQN - private final Map> fqns = new HashMap<>(); + private final Map> fqnMap = new HashMap<>(); + private final Set fqns = new HashSet<>(); @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { - return super.visitClassDeclaration(captureClassDeclarations(classDecl, fqns), p); + captureClassDeclarations(classDecl, fqnMap); + return classDecl; + } + + J.ClassDeclaration captureClassDeclarations(J.ClassDeclaration classDecl, Map> fqnMap) { + // get class fqn (including "$") + String fqn = classDecl.getType().getFullyQualifiedName(); + fqns.add(fqn); + + /* String currentPackage = getPackage(fqn); + String className = getClassName(fqn); + Map classesInPackage = fqnMap.getOrDefault(currentPackage, new HashMap<>()); + + if (className.contains("$")) { + String normalizedClassName = className.replace('$', '.'); + List parts = Arrays.asList(normalizedClassName.split("\\.")); + for (int i = 0; i < parts.size(); i++) { + String key = String.join(".", parts.subList(i, parts.size())); + classesInPackage.put(key, currentPackage + "." + normalizedClassName); + } + } else { + classesInPackage.put(className, fqn); + } + + fqnMap.put(currentPackage, classesInPackage);*/ + return classDecl; + } + + String getPackage(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return ""; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(0, lastIndex); + } + + /** + * + * @param fqn + * @return Class name (including "$") after last period in FQN + */ + String getClassName(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return fqn; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(lastIndex + 1); } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 4370929f..96d1a056 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -1,12 +1,6 @@ package org.hjug.parser; -import com.github.javaparser.StaticJavaParser; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.body.FieldDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.body.Parameter; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -27,6 +21,7 @@ public class JavaProjectParser { /** * Given a java source directory return a graph of class references + * * @param srcDirectory * @return * @throws IOException @@ -37,125 +32,28 @@ public Graph getClassReferences(String srcDirectory if (srcDirectory == null || srcDirectory.isEmpty()) { throw new IllegalArgumentException(); } else { - List classNames = getClassNames(srcDirectory); - try (Stream filesStream = Files.walk(Paths.get(srcDirectory))) { - filesStream - .filter(path -> path.getFileName().toString().endsWith(".java")) - .forEach(path -> { - log.info("Parsing {}", path); - List types = getInstanceVarTypes(classNames, path.toFile()); - types.addAll(getMethodArgumentTypes(classNames, path.toFile())); - if (!types.isEmpty()) { - String className = - getClassName(path.getFileName().toString()); - classReferencesGraph.addVertex(className); - types.forEach(classReferencesGraph::addVertex); - types.forEach(type -> { - if (!classReferencesGraph.containsEdge(className, type)) { - classReferencesGraph.addEdge(className, type); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(className, type); - classReferencesGraph.setEdgeWeight( - edge, classReferencesGraph.getEdgeWeight(edge) + 1); - } - }); - } - }); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } + processWithOpenRewrite(srcDirectory, classReferencesGraph); } return classReferencesGraph; } - List getClassDeclarations(Path javaSrcFile, Path srcDirectory) { + private void processWithOpenRewrite(String srcDir, Graph classReferencesGraph) + throws IOException { + File srcDirectory = new File(srcDir); + org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaVisitor javaVisitor = new JavaVisitor(); - - List fqdns = new ArrayList<>(); - - javaParser.parse(List.of(javaSrcFile), srcDirectory, ctx).forEach(cu -> javaVisitor.visit(cu, ctx)); - - return fqdns; - } - - /** - * Get instance variables types of a java source file using java parser - * @param classNamesToFilterBy - only add instance variable types which have these class names as type - * @param file - * @return - */ - private List getInstanceVarTypes(List classNamesToFilterBy, File javaSrcFile) { - CompilationUnit compilationUnit; - try { - compilationUnit = StaticJavaParser.parse(javaSrcFile); - - return compilationUnit.findAll(FieldDeclaration.class).stream() - .map(f -> f.getVariables().get(0).getType()) - .filter(v -> !v.isPrimitiveType()) - .map(Object::toString) - .filter(classNamesToFilterBy::contains) - .collect(Collectors.toList()); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - return new ArrayList<>(); - } + JavaVariableVisitor javaVariableCapturingVisitor = + new JavaVariableVisitor<>(classReferencesGraph); - /** - * Get parameter types of methods declared in a java source file using java parser - * @param classNamesToFilterBy - only add types which have these class names as type - * @param file - * @return - */ - private List getMethodArgumentTypes(List classNamesToFilterBy, File javaSrcFile) { - CompilationUnit compilationUnit; - try { - compilationUnit = StaticJavaParser.parse(javaSrcFile); - return compilationUnit.findAll(MethodDeclaration.class).stream() - .flatMap(f -> f.getParameters().stream() - .map(Parameter::getType) - .filter(type -> !type.isPrimitiveType()) - .collect(Collectors.toList()) - .stream()) - .map(Object::toString) - .filter(classNamesToFilterBy::contains) - .collect(Collectors.toList()); - } catch (FileNotFoundException e) { - e.printStackTrace(); + try (Stream walk = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) { + List list = walk.collect(Collectors.toList()); + javaParser + .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) + .forEach(cu -> javaVariableCapturingVisitor.visit(cu, ctx)); } - return new ArrayList<>(); - } - - /** - * Get all java classes in a source directory - * - * @param srcDirectory - * @return - * @throws IOException - */ - private List getClassNames(String srcDirectory) throws IOException { - try (Stream filesStream = Files.walk(Paths.get(srcDirectory))) { - return filesStream - .map(path -> path.getFileName().toString()) - .filter(fileName -> fileName.endsWith(".java")) - .map(this::getClassName) - .collect(Collectors.toList()); - } - } - - /** - * Extract class name from java file name - * Example : MyJavaClass.java becomes MyJavaClass - * - * @param javaFileName - * @return - */ - private String getClassName(String javaFileName) { - return javaFileName.substring(0, javaFileName.indexOf('.')); } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java index 9639e1d0..c6f82e02 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java @@ -1,5 +1,6 @@ package org.hjug.parser; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; @@ -12,43 +13,48 @@ public class JavaVariableVisitor

extends JavaIsoVisitor

{ Graph classReferencesGraph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + // Collection fqns; + + public JavaVariableVisitor() {} + + public JavaVariableVisitor(Graph classReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + // this.fqns = fqns; + } - /** - * Variable type is B - * Owning type is A - *

- * class A { - * B b1, b2; - * } - */ @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, P p) { J.VariableDeclarations variableDeclarations = super.visitVariableDeclarations(multiVariable, p); - System.out.println("*************************"); - System.out.println(variableDeclarations); - System.out.println("*************************"); + /* + * Handles + * java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.JavaType$Variable.getOwner()" + * because the return value of + * "org.openrewrite.java.tree.J$VariableDeclarations$NamedVariable.getVariableType()" is null + */ + List variables = variableDeclarations.getVariables(); + if (null == variables || variables.isEmpty() || null == variables.get(0).getVariableType()) { + return variableDeclarations; + } - JavaType owner = - variableDeclarations.getVariables().get(0).getVariableType().getOwner(); + JavaType owner = variables.get(0).getVariableType().getOwner(); String ownerFqn = ""; if (owner instanceof JavaType.Method) { JavaType.Method m = (JavaType.Method) owner; - // System.out.println("Method owner: " + m.getDeclaringType().getFullyQualifiedName()); + // log.debug("Method owner: " + m.getDeclaringType().getFullyQualifiedName()); ownerFqn = m.getDeclaringType().getFullyQualifiedName(); } else if (owner instanceof JavaType.Class) { JavaType.Class c = (JavaType.Class) owner; - // System.out.println("Method owner: " + c.getFullyQualifiedName()); + // log.debug("Method owner: " + c.getFullyQualifiedName()); ownerFqn = c.getFullyQualifiedName(); } - TypeTree typeTree = variableDeclarations.getTypeExpression(); + log.debug("*************************"); + log.debug("Processing " + ownerFqn + ":" + variableDeclarations); + log.debug("*************************"); - // skip class definition to prevent all types from self-referencing - if (typeTree instanceof J.Identifier) { - return variableDeclarations; - } + TypeTree typeTree = variableDeclarations.getTypeExpression(); JavaType javaType; if (null != typeTree) { @@ -62,11 +68,21 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m // but I'm not sure how to get a cursor // All types, including primitives can be annotated for (J.Annotation annotation : variableDeclarations.getAllAnnotations()) { + if (annotation.getType() instanceof JavaType.Unknown) { + continue; + } + JavaType.Class type = (JavaType.Class) annotation.getType(); if (null != type) { String annotationFqn = type.getFullyQualifiedName(); - System.out.println("Variable Annotation FQN: " + annotationFqn); + log.debug("Variable Annotation FQN: " + annotationFqn); addType(ownerFqn, annotationFqn); + + if (null != annotation.getArguments()) { + for (Expression argument : annotation.getArguments()) { + processType(argument.getType(), ownerFqn); + } + } } } @@ -80,7 +96,6 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return variableDeclarations; } - // processType methods are mutually recursive void processType(JavaType javaType, String ownerFqn) { if (javaType instanceof JavaType.Class) { processType((JavaType.Class) javaType, ownerFqn); @@ -98,10 +113,10 @@ void processType(JavaType javaType, String ownerFqn) { private void processType(JavaType.Parameterized parameterized, String ownerFqn) { // List> --> A processAnnotations(parameterized, ownerFqn); - System.out.println("Parameterized type FQN : " + parameterized.getFullyQualifiedName()); + log.debug("Parameterized type FQN : " + parameterized.getFullyQualifiedName()); addType(ownerFqn, parameterized.getFullyQualifiedName()); - System.out.println("Nested Parameterized type parameters: " + parameterized.getTypeParameters()); + log.debug("Nested Parameterized type parameters: " + parameterized.getTypeParameters()); for (JavaType parameter : parameterized.getTypeParameters()) { processType(parameter, ownerFqn); } @@ -109,21 +124,25 @@ private void processType(JavaType.Parameterized parameterized, String ownerFqn) private void processType(JavaType.Array arrayType, String ownerFqn) { // D[] --> D - System.out.println("Array Element type: " + arrayType.getElemType()); + log.debug("Array Element type: " + arrayType.getElemType()); processType(arrayType.getElemType(), ownerFqn); } private void processType(JavaType.GenericTypeVariable typeVariable, String ownerFqn) { - System.out.println("Type parameter type name: " + typeVariable.getName()); + log.debug("Type parameter type name: " + typeVariable.getName()); for (JavaType bound : typeVariable.getBounds()) { - processType(bound, ownerFqn); + if (bound instanceof JavaType.Class) { + addType(((JavaType.Class) bound).getFullyQualifiedName(), ownerFqn); + } else if (bound instanceof JavaType.Parameterized) { + addType(((JavaType.Parameterized) bound).getFullyQualifiedName(), ownerFqn); + } } } private void processType(JavaType.Class classType, String ownerFqn) { processAnnotations(classType, ownerFqn); - System.out.println("Class type FQN: " + classType.getFullyQualifiedName()); + log.debug("Class type FQN: " + classType.getFullyQualifiedName()); addType(ownerFqn, classType.getFullyQualifiedName()); } @@ -131,21 +150,23 @@ void processAnnotations(JavaType.FullyQualified fullyQualified, String ownerFqn) if (!fullyQualified.getAnnotations().isEmpty()) { for (JavaType.FullyQualified annotation : fullyQualified.getAnnotations()) { String annotationFqn = annotation.getFullyQualifiedName(); - System.out.println("Extra Annotation FQN: " + annotationFqn); + log.debug("Extra Annotation FQN: " + annotationFqn); addType(ownerFqn, annotationFqn); } } } void addType(String ownerFqn, String typeFqn) { - classReferencesGraph.addVertex(ownerFqn); - classReferencesGraph.addVertex(typeFqn); - - if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { - classReferencesGraph.addEdge(ownerFqn, typeFqn); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); - classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + if (!ownerFqn.contains("java.lang") && !typeFqn.contains("java.lang")) { + classReferencesGraph.addVertex(ownerFqn); + classReferencesGraph.addVertex(typeFqn); + + if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { + classReferencesGraph.addEdge(ownerFqn, typeFqn); + } else { + DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + } } } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java index 0ccfd29c..b328a0f3 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java @@ -9,11 +9,13 @@ import java.util.Map; import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; +@Disabled class JavaFqnCapturingVisitorTest { @Test @@ -32,7 +34,7 @@ void visitClasses() throws IOException { javaFqnCapturingVisitor.visit(cu, ctx); }); - Map> fqns = javaFqnCapturingVisitor.getFqns(); + Map> fqns = javaFqnCapturingVisitor.getFqnMap(); Map processed = fqns.get("org.hjug.testclasses"); Assertions.assertEquals("org.hjug.testclasses.A", processed.get("A")); Assertions.assertEquals("org.hjug.testclasses.A.InnerClass", processed.get("A.InnerClass")); diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java index c09f47e1..2e6d79ae 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java @@ -30,22 +30,39 @@ void parseSourceDirectoryTest() throws IOException { assertNotNull(classReferencesGraph); assertEquals(5, classReferencesGraph.vertexSet().size()); assertEquals(7, classReferencesGraph.edgeSet().size()); - assertTrue(classReferencesGraph.containsVertex("A")); - assertTrue(classReferencesGraph.containsVertex("B")); - assertTrue(classReferencesGraph.containsVertex("C")); - assertTrue(classReferencesGraph.containsVertex("D")); - assertTrue(classReferencesGraph.containsVertex("E")); - assertTrue(classReferencesGraph.containsEdge("A", "B")); - assertTrue(classReferencesGraph.containsEdge("B", "C")); - assertTrue(classReferencesGraph.containsEdge("C", "A")); - assertTrue(classReferencesGraph.containsEdge("C", "E")); - assertTrue(classReferencesGraph.containsEdge("D", "A")); - assertTrue(classReferencesGraph.containsEdge("D", "C")); - assertTrue(classReferencesGraph.containsEdge("E", "D")); + assertTrue(classReferencesGraph.containsVertex("com.ideacrest.parser.testclasses.A")); + assertTrue(classReferencesGraph.containsVertex("com.ideacrest.parser.testclasses.B")); + assertTrue(classReferencesGraph.containsVertex("com.ideacrest.parser.testclasses.C")); + assertTrue(classReferencesGraph.containsVertex("com.ideacrest.parser.testclasses.D")); + assertTrue(classReferencesGraph.containsVertex("com.ideacrest.parser.testclasses.E")); + assertTrue(classReferencesGraph.containsEdge( + "com.ideacrest.parser.testclasses.A", "com.ideacrest.parser.testclasses.B")); + assertTrue(classReferencesGraph.containsEdge( + "com.ideacrest.parser.testclasses.B", "com.ideacrest.parser.testclasses.C")); + assertTrue(classReferencesGraph.containsEdge( + "com.ideacrest.parser.testclasses.C", "com.ideacrest.parser.testclasses.A")); + assertTrue(classReferencesGraph.containsEdge( + "com.ideacrest.parser.testclasses.C", "com.ideacrest.parser.testclasses.E")); + assertTrue(classReferencesGraph.containsEdge( + "com.ideacrest.parser.testclasses.D", "com.ideacrest.parser.testclasses.A")); + assertTrue(classReferencesGraph.containsEdge( + "com.ideacrest.parser.testclasses.D", "com.ideacrest.parser.testclasses.C")); + assertTrue(classReferencesGraph.containsEdge( + "com.ideacrest.parser.testclasses.E", "com.ideacrest.parser.testclasses.D")); // confirm edge weight calculations - assertEquals(1, getEdgeWeight(classReferencesGraph, "A", "B")); - assertEquals(2, getEdgeWeight(classReferencesGraph, "E", "D")); + assertEquals( + 1, + getEdgeWeight( + classReferencesGraph, + "com.ideacrest.parser.testclasses.A", + "com.ideacrest.parser.testclasses.B")); + assertEquals( + 2, + getEdgeWeight( + classReferencesGraph, + "com.ideacrest.parser.testclasses.E", + "com.ideacrest.parser.testclasses.D")); } private static double getEdgeWeight( diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java index 84578277..8f23ce41 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java @@ -8,11 +8,13 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; +@Disabled class JavaVisitorTest { @Test diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java index de13c6f1..ee500365 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java @@ -1,11 +1,17 @@ package org.hjug.javaVariableVisitorTestClasses; +import java.io.IOException; +import java.io.InputStream; import java.util.Map; import java.util.List; +import edu.umd.cs.findbugs.annotations.NonNull; + public class A { - public A(B cB, C cC){} +// public A(B cB, C cC){} + + B crazyType; @MyAnnotation @MyOtherAnnotation @@ -25,7 +31,7 @@ public A(B cB, C cC){} Map map; List> listOfListsOfNumbers; - C doSomething(B paramB) { + C doSomething(@NonNull B paramB) { List> list3; A a2; B b2; diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java index 649ae941..fb129517 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java @@ -1,3 +1,3 @@ package org.hjug.javaVariableVisitorTestClasses; -public class C {} +public class C {} 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 fa098123..e3ac2e23 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 @@ -3,8 +3,6 @@ import static net.sourceforge.pmd.RuleViolation.CLASS_NAME; import static net.sourceforge.pmd.RuleViolation.PACKAGE_NAME; -import com.github.javaparser.ParserConfiguration; -import com.github.javaparser.StaticJavaParser; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -70,7 +68,6 @@ public void close() throws Exception { } public List runCycleAnalysis() { - StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE); List rankedCycles = new ArrayList<>(); try { boolean calculateCycleChurn = false; @@ -85,7 +82,6 @@ public List runCycleAnalysis() { } public List runCycleAnalysisAndCalculateCycleChurn() { - StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE); List rankedCycles = new ArrayList<>(); try { boolean calculateCycleChurn = true; @@ -135,9 +131,7 @@ private static void setPriorities(List rankedCycles) { private Map> getCycles() throws IOException { classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath); CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); - Map> cycles = - circularReferenceChecker.detectCycles(classReferencesGraph); - return cycles; + return circularReferenceChecker.detectCycles(classReferencesGraph); } private static void sortRankedCycles(List rankedCycles, boolean calculateChurnForCycles) { 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 11e60d47..2719c8c2 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -12,6 +12,182 @@ @Slf4j public class HtmlReport extends SimpleHtmlReport { + int d3Threshold = 700; + + // Created by generative AI and modified slightly + static final String SUGIYAMA_SIGMA_GRAPH = ""; + + // Created by generative AI and modified + static final String POPUP_STYLE = ""; + + // Created by generative AI and modified + static final String POPUP_FUNCTIONS = ""; + public static final String GOD_CLASS_CHART_LEGEND = "

God Class Chart Legend:

" + " \n" + " \n" @@ -36,14 +212,32 @@ public class HtmlReport extends SimpleHtmlReport { @Override public void printHead(StringBuilder stringBuilder) { - stringBuilder.append("" - + "\n" - + "\n" + stringBuilder.append("\n" + // google chart import + + "\n" + // d3 dot graph imports + "\n" + "\n" + "\n" - + " \n"); + // sigma graph imports - sigma, graphology, graphlib, and graphlib-dot + + "\n" + + "\n" + // may only need graphlib-dot + + "\n" + + "\n" + + SUGIYAMA_SIGMA_GRAPH + + POPUP_FUNCTIONS + + POPUP_STYLE + + "\n"); + } + + public void printOpenBodyTag(StringBuilder stringBuilder) { + stringBuilder.append(" \n"); + printOverlay(stringBuilder); + } + + private void printOverlay(StringBuilder stringBuilder) { + stringBuilder.append("
"); } @Override @@ -138,22 +332,33 @@ public void renderCycleImage( Graph classGraph, RankedCycle cycle, StringBuilder stringBuilder) { String dot = buildDot(classGraph, cycle); - stringBuilder.append("
\n"); - stringBuilder.append("\n"); - - stringBuilder.append("
"); - stringBuilder.append("

Red arrows represent relationship(s) to remove to decompose cycle

"); - stringBuilder.append("
"); - stringBuilder.append("
"); - stringBuilder.append("
"); + String cycleName = getClassName(cycle.getCycleName()).replace("$", "_"); + + if (cycle.getCycleNodes().size() + cycle.getEdgeSet().size() < d3Threshold) { + stringBuilder.append( + "
\n"); + stringBuilder.append("\n"); + } else { + stringBuilder.append("\n"); + stringBuilder.append(generatePopup(cycleName)); + } + + stringBuilder.append("
\n"); + stringBuilder.append( + "

Red lines represent relationship(s) to remove. Remove one to start decomposing the cycle.

\n"); + stringBuilder.append("

Zoom in / out with your mouse wheel and click/move to drag the image.

\n"); + stringBuilder.append("
\n"); + stringBuilder.append("
\n"); + stringBuilder.append("
\n"); } String buildDot(Graph classGraph, RankedCycle cycle) { @@ -165,7 +370,7 @@ String buildDot(Graph classGraph, RankedCycle cycle // e.g DownloadManager; for (String vertex : cycle.getVertexSet()) { dot.append("'"); - dot.append(vertex); + dot.append(getClassName(vertex)); dot.append(";\\n' +\n"); } @@ -176,8 +381,8 @@ String buildDot(Graph classGraph, RankedCycle cycle String[] vertexes = edge.toString().replace("(", "").replace(")", "").split(":"); - String start = vertexes[0].trim(); - String end = vertexes[1].trim(); + String start = getClassName(vertexes[0].trim()); + String end = getClassName(vertexes[1].trim()); dot.append("'"); dot.append(start); @@ -185,9 +390,13 @@ String buildDot(Graph classGraph, RankedCycle cycle dot.append(end); // render edge attributes + int edgeWeight = (int) classGraph.getEdgeWeight(edge); dot.append(" [ "); dot.append("label = \""); - dot.append((int) classGraph.getEdgeWeight(edge)); + dot.append(edgeWeight); + dot.append("\" "); + dot.append("weight = \""); + dot.append(edgeWeight); dot.append("\""); if (cycle.getMinCutEdges().contains(edge)) { @@ -199,6 +408,16 @@ String buildDot(Graph classGraph, RankedCycle cycle dot.append("'}'"); - return dot.toString(); + return dot.toString().replace("$", "_"); + } + + String generatePopup(String cycleName) { + return "\n" + "\n" + + "
\n" + " ×\n" + + "
\n" + "
"; } } 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 7c868c53..e8af6d93 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -91,6 +91,7 @@ public void execute( printTitle(projectName, projectVersion, stringBuilder); printHead(stringBuilder); + printOpenBodyTag(stringBuilder); printBreadcrumbs(stringBuilder); printProjectHeader(projectName, projectVersion, stringBuilder); @@ -268,7 +269,7 @@ private void renderCycles( rankedCycleData = new String[] { // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min // Cuts" - rankedCycle.getCycleName(), + getClassName(rankedCycle.getCycleName()), rankedCycle.getPriority().toString(), rankedCycle.getChangePronenessRank().toString(), String.valueOf(rankedCycle.getCycleNodes().size()), @@ -309,8 +310,7 @@ private void renderSingleCycle( stringBuilder.append("
\n"); stringBuilder.append("
\n"); - stringBuilder.append("

Class Cycle : " + cycle.getCycleName() + "

\n"); - // renderCycleImage(cycle.getCycleName(), stringBuilder, outputDirectory); + stringBuilder.append("

Class Cycle : " + getClassName(cycle.getCycleName()) + "

\n"); renderCycleImage(classGraph, cycle, stringBuilder); stringBuilder.append("
"); @@ -509,9 +509,12 @@ public void printHead(StringBuilder stringBuilder) { // empty on purpose } + public void printOpenBodyTag(StringBuilder stringBuilder) { + stringBuilder.append(" \n"); + } + public void printBreadcrumbs(StringBuilder stringBuilder) { - stringBuilder.append(" \n" - + "
\n" + stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n"); @@ -578,4 +581,14 @@ void renderCBOChart( StringBuilder stringBuilder) { // empty on purpose } + + String getClassName(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return fqn; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(lastIndex + 1); + } } diff --git a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java index 363576db..06835f2f 100644 --- a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java +++ b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java @@ -67,9 +67,9 @@ void buildDot() { expectedDot.append("'B;\\n' +\n"); expectedDot.append("'C;\\n' +\n"); // 'DownloadManager -> Download [ label = "1" color = "red" ];' - expectedDot.append("'A -> B [ label = \"2\" ];\\n' +\n"); - expectedDot.append("'B -> C [ label = \"1\" color = \"red\" ];\\n' +\n"); - expectedDot.append("'C -> A [ label = \"1\" color = \"red\" ];\\n' +\n"); + expectedDot.append("'A -> B [ label = \"2\" weight = \"2\" ];\\n' +\n"); + expectedDot.append("'B -> C [ label = \"1\" weight = \"1\" color = \"red\" ];\\n' +\n"); + expectedDot.append("'C -> A [ label = \"1\" weight = \"1\" color = \"red\" ];\\n' +\n"); expectedDot.append("'}'"); assertEquals(expectedDot.toString(), dot); diff --git a/report/src/test/resources/sigmaPlayground.html b/report/src/test/resources/sigmaPlayground.html new file mode 100644 index 00000000..519fb163 --- /dev/null +++ b/report/src/test/resources/sigmaPlayground.html @@ -0,0 +1,2053 @@ + + + + + + + DOT Graph with Sigma.js and Graphology + + + + + + + + + + + + + + + + +
+ + + + + + + + \ No newline at end of file From 928fc9a6b373698b21d669513a0f75372b937e4d Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sat, 30 Nov 2024 20:20:14 -0600 Subject: [PATCH 07/78] #111 Remove unneeded text Removed unneeded text from the report output (package names and excess JavaScript characters in DOT graph declaration) --- .../org/hjug/cbc/CostBenefitCalculator.java | 12 +- .../hjug/refactorfirst/report/HtmlReport.java | 64 ++--- .../report/SimpleHtmlReport.java | 247 +++++++++--------- .../refactorfirst/report/HtmlReportTest.java | 19 +- .../src/test/resources/sigmaPlayground.html | 2 +- 5 files changed, 166 insertions(+), 178 deletions(-) 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 e3ac2e23..5e995407 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 @@ -50,11 +50,6 @@ public CostBenefitCalculator(String repositoryPath) { log.info("Initiating Cost Benefit calculation"); try { gitLogReader = new GitLogReader(new File(repositoryPath)); - // repository = gitLogReader.gitRepository(new File(repositoryPath)); - // for (String file : - // gitLogReader.listRepositoryContentsAtHEAD(repository).keySet()) { - // log.info("Files at HEAD: {}", file); - // } } catch (IOException e) { log.error("Failure to access Git repository", e); } @@ -71,7 +66,7 @@ public List runCycleAnalysis() { List rankedCycles = new ArrayList<>(); try { boolean calculateCycleChurn = false; - idenfifyRankedCycles(rankedCycles, calculateCycleChurn); + identifyRankedCycles(rankedCycles, calculateCycleChurn); sortRankedCycles(rankedCycles, calculateCycleChurn); setPriorities(rankedCycles); } catch (IOException e) { @@ -85,7 +80,7 @@ public List runCycleAnalysisAndCalculateCycleChurn() { List rankedCycles = new ArrayList<>(); try { boolean calculateCycleChurn = true; - idenfifyRankedCycles(rankedCycles, calculateCycleChurn); + identifyRankedCycles(rankedCycles, calculateCycleChurn); sortRankedCycles(rankedCycles, calculateCycleChurn); setPriorities(rankedCycles); } catch (IOException e) { @@ -95,7 +90,7 @@ public List runCycleAnalysisAndCalculateCycleChurn() { return rankedCycles; } - private void idenfifyRankedCycles(List rankedCycles, boolean calculateChurnForCycles) + private void identifyRankedCycles(List rankedCycles, boolean calculateChurnForCycles) throws IOException { Map> cycles = getCycles(); Map classNamesAndPaths = getClassNamesAndPaths(); @@ -113,6 +108,7 @@ private void idenfifyRankedCycles(List rankedCycles, boolean calcul List cycleNodes = subGraph.vertexSet().stream() .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) + // .peek(cycleNode -> log.info(cycleNode.toString())) .collect(Collectors.toList()); rankedCycles.add( 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 2719c8c2..8fe70624 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -211,7 +211,7 @@ public class HtmlReport extends SimpleHtmlReport { + "
"; @Override - public void printHead(StringBuilder stringBuilder) { + public void printHead() { stringBuilder.append("\n" // google chart import + "\n" @@ -231,17 +231,18 @@ public void printHead(StringBuilder stringBuilder) { + "\n"); } - public void printOpenBodyTag(StringBuilder stringBuilder) { + @Override + public void printOpenBodyTag() { stringBuilder.append(" \n"); - printOverlay(stringBuilder); + printOverlay(); } - private void printOverlay(StringBuilder stringBuilder) { + private void printOverlay() { stringBuilder.append("
"); } @Override - public void printTitle(String projectName, String projectVersion, StringBuilder stringBuilder) { + public void printTitle(String projectName, String projectVersion) { stringBuilder .append("Refactor First Report for ") .append(projectName) @@ -251,7 +252,7 @@ public void printTitle(String projectName, String projectVersion, StringBuilder } @Override - void renderGithubButtons(StringBuilder stringBuilder) { + void renderGithubButtons() { stringBuilder.append("<div align=\"center\">\n"); stringBuilder.append("Show RefactorFirst some ❤️\n"); stringBuilder.append("<br/>\n"); @@ -269,8 +270,7 @@ void renderGithubButtons(StringBuilder stringBuilder) { } @Override - String writeGodClassGchartJs( - List<RankedDisharmony> rankedDisharmonies, int maxPriority, String reportOutputDirectory) { + String writeGodClassGchartJs(List<RankedDisharmony> rankedDisharmonies, int maxPriority) { GraphDataGenerator graphDataGenerator = new GraphDataGenerator(); String scriptStart = graphDataGenerator.getGodClassScriptStart(); String bubbleChartData = graphDataGenerator.generateGodClassBubbleChartData(rankedDisharmonies, maxPriority); @@ -280,7 +280,7 @@ String writeGodClassGchartJs( } @Override - String writeGCBOGchartJs(List<RankedDisharmony> rankedDisharmonies, int maxPriority, String reportOutputDirectory) { + String writeGCBOGchartJs(List<RankedDisharmony> rankedDisharmonies, int maxPriority) { GraphDataGenerator graphDataGenerator = new GraphDataGenerator(); String scriptStart = graphDataGenerator.getCBOScriptStart(); String bubbleChartData = graphDataGenerator.generateCBOBubbleChartData(rankedDisharmonies, maxPriority); @@ -301,35 +301,25 @@ public String getDescription(Locale locale) { } @Override - void renderGodClassChart( - String outputDirectory, - List<RankedDisharmony> rankedGodClassDisharmonies, - int maxGodClassPriority, - StringBuilder stringBuilder) { - String godClassChart = - writeGodClassGchartJs(rankedGodClassDisharmonies, maxGodClassPriority - 1, outputDirectory); + void renderGodClassChart(List<RankedDisharmony> rankedGodClassDisharmonies, int maxGodClassPriority) { + String godClassChart = writeGodClassGchartJs(rankedGodClassDisharmonies, maxGodClassPriority - 1); stringBuilder.append( "<div id=\"series_chart_div\" align=\"center\"><script>" + godClassChart + "</script></div>\n"); - renderGithubButtons(stringBuilder); + renderGithubButtons(); stringBuilder.append(GOD_CLASS_CHART_LEGEND); } @Override - void renderCBOChart( - String outputDirectory, - List<RankedDisharmony> rankedCBODisharmonies, - int maxCboPriority, - StringBuilder stringBuilder) { - String cboChart = writeGCBOGchartJs(rankedCBODisharmonies, maxCboPriority - 1, outputDirectory); + void renderCBOChart(List<RankedDisharmony> rankedCBODisharmonies, int maxCboPriority) { + String cboChart = writeGCBOGchartJs(rankedCBODisharmonies, maxCboPriority - 1); stringBuilder.append( "<div id=\"series_chart_div_2\" align=\"center\"><script>" + cboChart + "</script></div>\n"); - renderGithubButtons(stringBuilder); + renderGithubButtons(); stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND); } @Override - public void renderCycleImage( - Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle, StringBuilder stringBuilder) { + public void renderCycleImage(RankedCycle cycle) { String dot = buildDot(classGraph, cycle); String cycleName = getClassName(cycle.getCycleName()).replace("$", "_"); @@ -338,16 +328,17 @@ public void renderCycleImage( stringBuilder.append( "<div align=\"center\" id=\"" + cycleName + "\" style=\"border: thin solid black\"></div>\n"); stringBuilder.append("<script>\n"); + stringBuilder.append("const " + cycleName + "_dot = " + dot + "\n"); stringBuilder.append("d3.select(\"#" + cycleName + "\")\n"); stringBuilder.append(".graphviz()\n"); stringBuilder.append(".width(screen.width - 200)\n"); stringBuilder.append(".height(screen.height)\n"); stringBuilder.append(".fit(true)\n"); - stringBuilder.append(".renderDot(" + dot + ");\n"); + stringBuilder.append(".renderDot(" + cycleName + "_dot);\n"); stringBuilder.append("</script>\n"); } else { stringBuilder.append("<script>\n"); - stringBuilder.append("const " + cycleName + "_dot =" + dot + "\n"); + stringBuilder.append("const " + cycleName + "_dot = " + dot + "\n"); stringBuilder.append("</script>\n"); stringBuilder.append(generatePopup(cycleName)); } @@ -364,27 +355,23 @@ public void renderCycleImage( String buildDot(Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle) { StringBuilder dot = new StringBuilder(); - dot.append("'strict digraph G {\\n' +\n"); + dot.append("`strict digraph G {\n"); // render vertices // e.g DownloadManager; for (String vertex : cycle.getVertexSet()) { - dot.append("'"); dot.append(getClassName(vertex)); - dot.append(";\\n' +\n"); + dot.append(";\n"); } for (DefaultWeightedEdge edge : cycle.getEdgeSet()) { - // 'DownloadManager -> Download [ label="1" color="red" ];' + // DownloadManager -> Download [ label="1" color="red" ]; // render edge - String[] vertexes = - edge.toString().replace("(", "").replace(")", "").split(":"); - + String[] vertexes = extractVertexes(edge); String start = getClassName(vertexes[0].trim()); String end = getClassName(vertexes[1].trim()); - dot.append("'"); dot.append(start); dot.append(" -> "); dot.append(end); @@ -403,15 +390,16 @@ String buildDot(Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle dot.append(" color = \"red\""); } - dot.append(" ];\\n' +\n"); + dot.append(" ];\n"); } - dot.append("'}'"); + dot.append("}`;"); return dot.toString().replace("$", "_"); } String generatePopup(String cycleName) { + // Created by generative AI and modified return "<button style=\"display: block; margin: 0 auto;\" onclick=\"showPopup('popup-" + cycleName + "', 'graph-container-" + cycleName + "'," + cycleName + "_dot )\">Show " + cycleName + " Cycle Popup</button>\n" + "\n" 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 e8af6d93..57757830 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -65,15 +65,17 @@ public class SimpleHtmlReport { public final String[] classCycleTableHeadings = {"Classes", "Relationships"}; - private Graph<String, DefaultWeightedEdge> classGraph; + Graph<String, DefaultWeightedEdge> classGraph; - private boolean showDetails = false; + DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) + .withLocale(Locale.getDefault()) + .withZone(ZoneId.systemDefault()); + + StringBuilder stringBuilder = new StringBuilder(); public void execute( boolean showDetails, String projectName, String projectVersion, String outputDirectory, File baseDir) { - this.showDetails = showDetails; - final String[] godClassTableHeadings = showDetails ? godClassDetailedTableHeadings : godClassSimpleTableHeadings; @@ -81,19 +83,13 @@ public void execute( log.info("Generating {} for {} - {}", filename, projectName, projectVersion); - DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) - .withLocale(Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(THE_BEGINNING); - printTitle(projectName, projectVersion, stringBuilder); - printHead(stringBuilder); - printOpenBodyTag(stringBuilder); - printBreadcrumbs(stringBuilder); - printProjectHeader(projectName, projectVersion, stringBuilder); + printTitle(projectName, projectVersion); + printHead(); + printOpenBodyTag(); + printBreadcrumbs(); + printProjectHeader(projectName, projectVersion); GitLogReader gitLogReader = new GitLogReader(); String projectBaseDir; @@ -144,12 +140,15 @@ public void execute( costBenefitCalculator.runPmdAnalysis(); rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - if (showDetails) { + + // will revisit later + /*if (showDetails) { rankedCycles = costBenefitCalculator.runCycleAnalysisAndCalculateCycleChurn(); } else { rankedCycles = costBenefitCalculator.runCycleAnalysis(); - } + }*/ + rankedCycles = costBenefitCalculator.runCycleAnalysis(); classGraph = costBenefitCalculator.getClassReferencesGraph(); } catch (Exception e) { log.error("Error running analysis."); @@ -163,7 +162,7 @@ public void execute( .append(" ") .append(projectVersion) .append(" has no God classes, highly coupled classes, or cycles!"); - renderGithubButtons(stringBuilder); + renderGithubButtons(); log.info("Done! No Disharmonies found!"); stringBuilder.append(THE_END); writeReportToDisk(outputDirectory, filename, stringBuilder); @@ -185,13 +184,7 @@ public void execute( } if (!rankedGodClassDisharmonies.isEmpty()) { - renderGodClassInfo( - showDetails, - outputDirectory, - rankedGodClassDisharmonies, - stringBuilder, - godClassTableHeadings, - formatter); + renderGodClassInfo(showDetails, rankedGodClassDisharmonies, godClassTableHeadings); } if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { @@ -199,7 +192,7 @@ public void execute( } if (!rankedCBODisharmonies.isEmpty()) { - renderHighlyCoupledClassInfo(outputDirectory, stringBuilder, rankedCBODisharmonies, formatter); + renderHighlyCoupledClassInfo(rankedCBODisharmonies); } if (!rankedCycles.isEmpty()) { @@ -210,11 +203,11 @@ public void execute( stringBuilder.append("<br/>\n"); stringBuilder.append("<br/>\n"); } - renderCycles(outputDirectory, stringBuilder, rankedCycles, formatter); + renderCycles(rankedCycles); } stringBuilder.append("</section>\n"); - printProjectFooter(stringBuilder, formatter); + printProjectFooter(stringBuilder); stringBuilder.append(THE_END); log.debug(stringBuilder.toString()); @@ -223,12 +216,15 @@ public void execute( log.info("Done! View the report at target/site/{}", filename); } - private void renderCycles( - String outputDirectory, - StringBuilder stringBuilder, - List<RankedCycle> rankedCycles, - DateTimeFormatter formatter) { + private void renderCycles(List<RankedCycle> rankedCycles) { + renderClassCycleSummary(rankedCycles); + + for (RankedCycle rankedCycle : rankedCycles) { + renderSingleCycle(rankedCycle); + } + } + private void renderClassCycleSummary(List<RankedCycle> rankedCycles) { stringBuilder.append("<div style=\"text-align: center;\"><a id=\"CYCLES\"><h1>Class Cycles</h1></a></div>\n"); stringBuilder.append( @@ -237,19 +233,9 @@ private void renderCycles( "<p align=\"center\">Note: often only one minimum cut relationship needs to be removed</p>"); stringBuilder.append("<table align=\"center\" border=\"5px\">\n"); - String[] cycleTableHeadings; - if (showDetails) { - cycleTableHeadings = new String[] { - "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts" - }; - } else { - cycleTableHeadings = - new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; - } - // Content stringBuilder.append("<thead>\n<tr>\n"); - for (String heading : cycleTableHeadings) { + for (String heading : getCycleSummaryTableHeadings()) { stringBuilder.append("<th>").append(heading).append("</th>\n"); } stringBuilder.append("</thead>\n"); @@ -260,34 +246,12 @@ private void renderCycles( StringBuilder edgesToCut = new StringBuilder(); for (DefaultWeightedEdge minCutEdge : rankedCycle.getMinCutEdges()) { - edgesToCut.append(minCutEdge + ":" + (int) classGraph.getEdgeWeight(minCutEdge)); + edgesToCut.append(renderEdge(minCutEdge)); edgesToCut.append("</br>\n"); } - String[] rankedCycleData; - if (showDetails) { - rankedCycleData = new String[] { - // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min - // Cuts" - getClassName(rankedCycle.getCycleName()), - rankedCycle.getPriority().toString(), - rankedCycle.getChangePronenessRank().toString(), - String.valueOf(rankedCycle.getCycleNodes().size()), - String.valueOf(rankedCycle.getEdgeSet().size()), - edgesToCut.toString() - }; - } else { - rankedCycleData = new String[] { - // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts" - rankedCycle.getCycleName(), - rankedCycle.getPriority().toString(), - String.valueOf(rankedCycle.getCycleNodes().size()), - String.valueOf(rankedCycle.getEdgeSet().size()), - edgesToCut.toString() - }; - } - for (String rowData : rankedCycleData) { - drawTableCell(rowData, stringBuilder); + for (String rowData : getRankedCycleSummaryData(rankedCycle, edgesToCut)) { + drawTableCell(rowData); } stringBuilder.append("</tr>\n"); @@ -295,14 +259,74 @@ private void renderCycles( stringBuilder.append("</tbody>\n"); stringBuilder.append("</table>\n"); + } - for (RankedCycle rankedCycle : rankedCycles) { - renderSingleCycle(outputDirectory, stringBuilder, rankedCycle, formatter); + private String renderEdge(DefaultWeightedEdge edge) { + StringBuilder edgesToCut = new StringBuilder(); + String[] vertexes = extractVertexes(edge); + String start = getClassName(vertexes[0].trim()); + String end = getClassName(vertexes[1].trim()); + + // → is HTML "Right Arrow" code + return edgesToCut + .append(start + " → " + end + " : " + (int) classGraph.getEdgeWeight(edge)) + .toString(); + } + + private String[] getCycleSummaryTableHeadings() { + return new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; + // Will revisit later + /*String[] cycleTableHeadings; + if (showDetails) { + cycleTableHeadings = new String[] { + "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts" + }; + } else { + cycleTableHeadings = + new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; + } + return cycleTableHeadings; + */ + } + + private String[] getRankedCycleSummaryData(RankedCycle rankedCycle, StringBuilder edgesToCut) { + return new String[] { + // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts" + getClassName(rankedCycle.getCycleName()), + rankedCycle.getPriority().toString(), + String.valueOf(rankedCycle.getCycleNodes().size()), + String.valueOf(rankedCycle.getEdgeSet().size()), + edgesToCut.toString() + }; + + // Will revisit later + /*String[] rankedCycleData; + if (showDetails) { + rankedCycleData = new String[] { + // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min + // Cuts" + getClassName(rankedCycle.getCycleName()), + rankedCycle.getPriority().toString(), + rankedCycle.getChangePronenessRank().toString(), + String.valueOf(rankedCycle.getCycleNodes().size()), + String.valueOf(rankedCycle.getEdgeSet().size()), + edgesToCut.toString() + }; + } else { + rankedCycleData = new String[] { + // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts" + getClassName(rankedCycle.getCycleName()), + rankedCycle.getPriority().toString(), + String.valueOf(rankedCycle.getCycleNodes().size()), + String.valueOf(rankedCycle.getEdgeSet().size()), + edgesToCut.toString() + }; } + return rankedCycleData; + */ } - private void renderSingleCycle( - String outputDirectory, StringBuilder stringBuilder, RankedCycle cycle, DateTimeFormatter formatter) { + private void renderSingleCycle(RankedCycle cycle) { stringBuilder.append("<br/>\n"); stringBuilder.append("<br/>\n"); @@ -311,7 +335,7 @@ private void renderSingleCycle( stringBuilder.append("<br/>\n"); stringBuilder.append("<h2 align=\"center\">Class Cycle : " + getClassName(cycle.getCycleName()) + "</h2>\n"); - renderCycleImage(classGraph, cycle, stringBuilder); + renderCycleImage(cycle); stringBuilder.append("<div align=\"center\">"); stringBuilder.append("<strong>"); @@ -332,26 +356,22 @@ private void renderSingleCycle( for (String vertex : cycle.getVertexSet()) { stringBuilder.append("<tr>"); - drawTableCell(vertex, stringBuilder); + drawTableCell(getClassName(vertex)); StringBuilder edges = new StringBuilder(); - for (org.jgrapht.graph.DefaultWeightedEdge edge : cycle.getEdgeSet()) { + for (DefaultWeightedEdge edge : cycle.getEdgeSet()) { if (edge.toString().startsWith("(" + vertex + " :")) { if (cycle.getMinCutEdges().contains(edge)) { edges.append("<strong>"); - edges.append(edge); - edges.append(":") - .append((int) classGraph.getEdgeWeight(edge)) - .append("*"); + edges.append(renderEdge(edge)); edges.append("</strong>"); } else { - edges.append(edge); - edges.append(":").append((int) classGraph.getEdgeWeight(edge)); + edges.append(renderEdge(edge)); } edges.append("<br/>\n"); } } - drawTableCell(edges.toString(), stringBuilder); + drawTableCell(edges.toString()); stringBuilder.append("</tr>\n"); } @@ -360,25 +380,19 @@ private void renderSingleCycle( stringBuilder.append("</table>\n"); } - public void renderCycleImage( - Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle, StringBuilder stringBuilder) { + public void renderCycleImage(RankedCycle cycle) { // empty on purpose } private void renderGodClassInfo( - boolean showDetails, - String outputDirectory, - List<RankedDisharmony> rankedGodClassDisharmonies, - StringBuilder stringBuilder, - String[] godClassTableHeadings, - DateTimeFormatter formatter) { + boolean showDetails, List<RankedDisharmony> rankedGodClassDisharmonies, String[] godClassTableHeadings) { int maxGodClassPriority = rankedGodClassDisharmonies .get(rankedGodClassDisharmonies.size() - 1) .getPriority(); stringBuilder.append("<div style=\"text-align: center;\"><a id=\"GOD\"><h1>God Classes</h1></a></div>\n"); - renderGodClassChart(outputDirectory, rankedGodClassDisharmonies, maxGodClassPriority, stringBuilder); + renderGodClassChart(rankedGodClassDisharmonies, maxGodClassPriority); stringBuilder.append( "<h2 align=\"center\">God classes by the numbers: (Refactor Starting with Priority 1)</h2>\n"); @@ -427,7 +441,7 @@ private void renderGodClassInfo( showDetails ? detailedRankedGodClassDisharmonyData : simpleRankedGodClassDisharmonyData; for (String rowData : rankedDisharmonyData) { - drawTableCell(rowData, stringBuilder); + drawTableCell(rowData); } stringBuilder.append("</tr>\n"); @@ -437,18 +451,14 @@ private void renderGodClassInfo( stringBuilder.append("</table>\n"); } - private void renderHighlyCoupledClassInfo( - String outputDirectory, - StringBuilder stringBuilder, - List<RankedDisharmony> rankedCBODisharmonies, - DateTimeFormatter formatter) { + private void renderHighlyCoupledClassInfo(List<RankedDisharmony> rankedCBODisharmonies) { stringBuilder.append( "<div style=\"text-align: center;\"><a id=\"CBO\"><h1>Highly Coupled Classes</h1></a></div>"); int maxCboPriority = rankedCBODisharmonies.get(rankedCBODisharmonies.size() - 1).getPriority(); - renderCBOChart(outputDirectory, rankedCBODisharmonies, maxCboPriority, stringBuilder); + renderCBOChart(rankedCBODisharmonies, maxCboPriority); stringBuilder.append( "<h2 align=\"center\">Highly Coupled classes by the numbers: (Refactor starting with Priority 1)</h2>"); @@ -475,7 +485,7 @@ private void renderHighlyCoupledClassInfo( }; for (String rowData : rankedCboClassDisharmonyData) { - drawTableCell(rowData, stringBuilder); + drawTableCell(rowData); } stringBuilder.append("</tr>"); @@ -485,7 +495,7 @@ private void renderHighlyCoupledClassInfo( stringBuilder.append("</table>"); } - void drawTableCell(String rowData, StringBuilder stringBuilder) { + void drawTableCell(String rowData) { if (isNumber(rowData) || isDateTime(rowData)) { stringBuilder.append("<td align=\"right\">").append(rowData).append("</td>\n"); } else { @@ -501,26 +511,26 @@ boolean isDateTime(String rowData) { return rowData.contains(", "); } - public void printTitle(String projectName, String projectVersion, StringBuilder stringBuilder) { + public void printTitle(String projectName, String projectVersion) { // empty on purpose } - public void printHead(StringBuilder stringBuilder) { + public void printHead() { // empty on purpose } - public void printOpenBodyTag(StringBuilder stringBuilder) { + public void printOpenBodyTag() { stringBuilder.append(" <body class=\"composite\">\n"); } - public void printBreadcrumbs(StringBuilder stringBuilder) { + public void printBreadcrumbs() { stringBuilder.append(" <div id=\"banner\">\n" + " </div>\n" + " <div id=\"breadcrumbs\">\n" + " <div class=\"xleft\">\n"); } - public void printProjectHeader(String projectName, String projectVersion, StringBuilder stringBuilder) { + public void printProjectHeader(String projectName, String projectVersion) { stringBuilder.append("</div>\n" + " <div class=\"xright\"> </div>\n" + " </div>\n" @@ -537,7 +547,7 @@ public void printProjectHeader(String projectName, String projectVersion, String .append("</h2>\n"); } - public void printProjectFooter(StringBuilder stringBuilder, DateTimeFormatter formatter) { + public void printProjectFooter(StringBuilder stringBuilder) { stringBuilder .append(" <div class=\"clear\">\n" + " <hr/>\n" + " </div>\n") .append("<span id=\"publishDate\">Last Published: ") @@ -546,7 +556,7 @@ public void printProjectFooter(StringBuilder stringBuilder, DateTimeFormatter fo .append("</span>"); } - void renderGithubButtons(StringBuilder stringBuilder) { + void renderGithubButtons() { // empty on purpose } @@ -555,30 +565,21 @@ String getOutputName() { return "refactor-first-report"; } - void renderGodClassChart( - String outputDirectory, - List<RankedDisharmony> rankedGodClassDisharmonies, - int maxGodClassPriority, - StringBuilder stringBuilder) { + void renderGodClassChart(List<RankedDisharmony> rankedGodClassDisharmonies, int maxGodClassPriority) { // empty on purpose } - String writeGodClassGchartJs( - List<RankedDisharmony> rankedDisharmonies, int maxPriority, String reportOutputDirectory) { + String writeGodClassGchartJs(List<RankedDisharmony> rankedDisharmonies, int maxPriority) { // return empty string on purpose return ""; } - String writeGCBOGchartJs(List<RankedDisharmony> rankedDisharmonies, int maxPriority, String reportOutputDirectory) { + String writeGCBOGchartJs(List<RankedDisharmony> rankedDisharmonies, int maxPriority) { // return empty string on purpose return ""; } - void renderCBOChart( - String outputDirectory, - List<RankedDisharmony> rankedCBODisharmonies, - int maxCboPriority, - StringBuilder stringBuilder) { + void renderCBOChart(List<RankedDisharmony> rankedCBODisharmonies, int maxCboPriority) { // empty on purpose } @@ -591,4 +592,8 @@ String getClassName(String fqn) { int lastIndex = fqn.lastIndexOf("."); return fqn.substring(lastIndex + 1); } + + static String[] extractVertexes(DefaultWeightedEdge edge) { + return edge.toString().replace("(", "").replace(")", "").split(":"); + } } diff --git a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java index 06835f2f..27393bd9 100644 --- a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java +++ b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java @@ -12,7 +12,7 @@ import org.jgrapht.graph.DefaultWeightedEdge; import org.junit.jupiter.api.Test; -public class HtmlReportTest { +class HtmlReportTest { private HtmlReport mavenReport = new HtmlReport(); @@ -62,15 +62,14 @@ void buildDot() { String dot = htmlReport.buildDot(classGraph, rankedCycle); StringBuilder expectedDot = new StringBuilder(); - expectedDot.append("'strict digraph G {\\n' +\n"); - expectedDot.append("'A;\\n' +\n"); - expectedDot.append("'B;\\n' +\n"); - expectedDot.append("'C;\\n' +\n"); - // 'DownloadManager -> Download [ label = "1" color = "red" ];' - expectedDot.append("'A -> B [ label = \"2\" weight = \"2\" ];\\n' +\n"); - expectedDot.append("'B -> C [ label = \"1\" weight = \"1\" color = \"red\" ];\\n' +\n"); - expectedDot.append("'C -> A [ label = \"1\" weight = \"1\" color = \"red\" ];\\n' +\n"); - expectedDot.append("'}'"); + expectedDot.append("`strict digraph G {\n" + + "A;\n" + + "B;\n" + + "C;\n" + + "A -> B [ label = \"2\" weight = \"2\" ];\n" + + "B -> C [ label = \"1\" weight = \"1\" color = \"red\" ];\n" + + "C -> A [ label = \"1\" weight = \"1\" color = \"red\" ];\n" + + "}`;"); assertEquals(expectedDot.toString(), dot); } diff --git a/report/src/test/resources/sigmaPlayground.html b/report/src/test/resources/sigmaPlayground.html index 519fb163..bbcb4727 100644 --- a/report/src/test/resources/sigmaPlayground.html +++ b/report/src/test/resources/sigmaPlayground.html @@ -2040,7 +2040,7 @@ '}' </script> <button style="display: block; margin: 0 auto;" - onclick="showPopup('popup-cycleName', 'graph-container-cycleName', big_dot)"> + onclick="showPopup('popup-cycleName', 'graph-container-cycleName', dot)"> Show Graph Popup 1 </button> From f9d17c340808f35ea9c926a126d82fdc0fc6c311 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Sun, 1 Dec 2024 13:08:50 -0600 Subject: [PATCH 08/78] Added missing modules to coverage report --- coverage/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/coverage/pom.xml b/coverage/pom.xml index 09a54ca1..9c109cf7 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -29,6 +29,11 @@ <artifactId>effort-ranker</artifactId> </dependency> + <dependency> + <groupId>org.hjug.refactorfirst.circularreferencedetector</groupId> + <artifactId>circular-reference-detector</artifactId> + </dependency> + <dependency> <groupId>org.hjug.refactorfirst.costbenefitcalculator</groupId> <artifactId>cost-benefit-calculator</artifactId> @@ -43,6 +48,11 @@ <groupId>org.hjug.refactorfirst.plugin</groupId> <artifactId>refactor-first-maven-plugin</artifactId> </dependency> + + <dependency> + <groupId>org.hjug.refactorfirst.report</groupId> + <artifactId>report</artifactId> + </dependency> </dependencies> <build> From 39aa8f476e1772edaba96991e8258616f2e55606 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Sun, 1 Dec 2024 19:59:03 -0600 Subject: [PATCH 09/78] #111 Added JavaClassDeclarationVisitor - Added JavaClassDeclarationVisitor and pulled type processing up into the TypeProcessor interface and added corresponding unit test - Composing the JavaVistor with the various visitors needed to capture all types in the codebase --- .../parser/JavaClassDeclarationVisitor.java | 70 +++++++ .../org/hjug/parser/JavaProjectParser.java | 8 +- .../hjug/parser/JavaVariableTypeVisitor.java | 98 ++++++++++ .../org/hjug/parser/JavaVariableVisitor.java | 172 ------------------ .../java/org/hjug/parser/JavaVisitor.java | 153 ++++------------ .../java/org/hjug/parser/TypeProcessor.java | 95 ++++++++++ .../JavaClassDeclarationVisitorTest.java | 47 +++++ .../parser/JavaVariableTypeVisitorTest.java | 45 +++++ .../hjug/parser/JavaVariableVisitorTest.java | 33 ---- .../javaVariableVisitorTestClasses/A.java | 6 +- .../javaVariableVisitorTestClasses/C.java | 2 +- .../javaVariableVisitorTestClasses/F.java | 3 + .../javaVariableVisitorTestClasses/G.java | 3 + 13 files changed, 405 insertions(+), 330 deletions(-) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableTypeVisitor.java delete mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/TypeProcessor.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/JavaClassDeclarationVisitorTest.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableTypeVisitorTest.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java create mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java new file mode 100644 index 00000000..a3b732a7 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java @@ -0,0 +1,70 @@ +package org.hjug.parser; + +import lombok.Getter; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeTree; + +import java.util.List; + +public class JavaClassDeclarationVisitor <P> extends JavaIsoVisitor<P> implements TypeProcessor{ + + + @Getter + private Graph<String, DefaultWeightedEdge> classReferencesGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + public JavaClassDeclarationVisitor() { + } + + public JavaClassDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + } + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { + J.ClassDeclaration classDeclaration = super.visitClassDeclaration(classDecl, p); + + JavaType.FullyQualified type = classDeclaration.getType(); + String owningFqn = type.getFullyQualifiedName(); + + processType(type, owningFqn); + + TypeTree extendsTypeTree = classDeclaration.getExtends(); + if (null != extendsTypeTree) { + processType(extendsTypeTree.getType(), owningFqn); + } + + List<TypeTree> implementsTypeTree = classDeclaration.getImplements(); + if(null != implementsTypeTree) { + for (TypeTree typeTree : implementsTypeTree) { + processType(typeTree.getType(), owningFqn); + } + } + + for (J.Annotation leadingAnnotation : classDeclaration.getLeadingAnnotations()) { + processAnnotation(leadingAnnotation, owningFqn); + } + + return classDeclaration; + } + + public void addType(String ownerFqn, String typeFqn) { + if(ownerFqn.equals(typeFqn)) return; + + classReferencesGraph.addVertex(ownerFqn); + classReferencesGraph.addVertex(typeFqn); + + if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { + classReferencesGraph.addEdge(ownerFqn, typeFqn); + } else { + DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + } + } + +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 96d1a056..4a946993 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -46,14 +46,16 @@ private void processWithOpenRewrite(String srcDir, Graph<String, DefaultWeighted JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaVariableVisitor<ExecutionContext> javaVariableCapturingVisitor = - new JavaVariableVisitor<>(classReferencesGraph); + JavaVariableTypeVisitor<ExecutionContext> javaVariableTypeVisitor = + new JavaVariableTypeVisitor<>(classReferencesGraph); try (Stream<Path> walk = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) { List<Path> list = walk.collect(Collectors.toList()); javaParser .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) - .forEach(cu -> javaVariableCapturingVisitor.visit(cu, ctx)); + .forEach(cu -> javaVariableTypeVisitor.visit(cu, ctx)); } + + //TODO: classReferencesGraph.removeAllVertices(); if vertex package not in codebase } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableTypeVisitor.java new file mode 100644 index 00000000..467129eb --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableTypeVisitor.java @@ -0,0 +1,98 @@ +package org.hjug.parser; + +import java.util.List; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.*; + +@Slf4j +public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor{ + + @Getter + private Graph<String, DefaultWeightedEdge> classReferencesGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + public JavaVariableTypeVisitor() {} + + public JavaVariableTypeVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, P p) { + J.VariableDeclarations variableDeclarations = super.visitVariableDeclarations(multiVariable, p); + + /* + * Handles + * java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.JavaType$Variable.getOwner()" + * because the return value of + * "org.openrewrite.java.tree.J$VariableDeclarations$NamedVariable.getVariableType()" is null + */ + List<J.VariableDeclarations.NamedVariable> variables = variableDeclarations.getVariables(); + if (null == variables || variables.isEmpty() || null == variables.get(0).getVariableType()) { + return variableDeclarations; + } + + JavaType owner = variables.get(0).getVariableType().getOwner(); + String ownerFqn = ""; + + if (owner instanceof JavaType.Method) { + JavaType.Method m = (JavaType.Method) owner; + // log.debug("Method owner: " + m.getDeclaringType().getFullyQualifiedName()); + ownerFqn = m.getDeclaringType().getFullyQualifiedName(); + } else if (owner instanceof JavaType.Class) { + JavaType.Class c = (JavaType.Class) owner; + // log.debug("Method owner: " + c.getFullyQualifiedName()); + ownerFqn = c.getFullyQualifiedName(); + } + + log.debug("*************************"); + log.debug("Processing " + ownerFqn + ":" + variableDeclarations); + log.debug("*************************"); + + TypeTree typeTree = variableDeclarations.getTypeExpression(); + + JavaType javaType; + if (null != typeTree) { + javaType = typeTree.getType(); + } else { + return variableDeclarations; + } + + // TODO: getAllAnnotations() is deprecated - need to call + // AnnotationService.getAllAnnotations() but not sure which one yet + // but I'm not sure how to get a cursor + // All types, including primitives can be annotated + for (J.Annotation annotation : variableDeclarations.getAllAnnotations()) { + processAnnotation(annotation, ownerFqn); + } + + // skip primitive variable declarations + if (javaType instanceof JavaType.Primitive) { + return variableDeclarations; + } + + processType(javaType, ownerFqn); + + return variableDeclarations; + } + + + + public void addType(String ownerFqn, String typeFqn) { + classReferencesGraph.addVertex(ownerFqn); + classReferencesGraph.addVertex(typeFqn); + + if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { + classReferencesGraph.addEdge(ownerFqn, typeFqn); + } else { + DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + } + } +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java deleted file mode 100644 index c6f82e02..00000000 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableVisitor.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.hjug.parser; - -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.*; - -@Slf4j -public class JavaVariableVisitor<P> extends JavaIsoVisitor<P> { - - Graph<String, DefaultWeightedEdge> classReferencesGraph = - new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); - // Collection<String> fqns; - - public JavaVariableVisitor() {} - - public JavaVariableVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { - this.classReferencesGraph = classReferencesGraph; - // this.fqns = fqns; - } - - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, P p) { - J.VariableDeclarations variableDeclarations = super.visitVariableDeclarations(multiVariable, p); - - /* - * Handles - * java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.JavaType$Variable.getOwner()" - * because the return value of - * "org.openrewrite.java.tree.J$VariableDeclarations$NamedVariable.getVariableType()" is null - */ - List<J.VariableDeclarations.NamedVariable> variables = variableDeclarations.getVariables(); - if (null == variables || variables.isEmpty() || null == variables.get(0).getVariableType()) { - return variableDeclarations; - } - - JavaType owner = variables.get(0).getVariableType().getOwner(); - String ownerFqn = ""; - - if (owner instanceof JavaType.Method) { - JavaType.Method m = (JavaType.Method) owner; - // log.debug("Method owner: " + m.getDeclaringType().getFullyQualifiedName()); - ownerFqn = m.getDeclaringType().getFullyQualifiedName(); - } else if (owner instanceof JavaType.Class) { - JavaType.Class c = (JavaType.Class) owner; - // log.debug("Method owner: " + c.getFullyQualifiedName()); - ownerFqn = c.getFullyQualifiedName(); - } - - log.debug("*************************"); - log.debug("Processing " + ownerFqn + ":" + variableDeclarations); - log.debug("*************************"); - - TypeTree typeTree = variableDeclarations.getTypeExpression(); - - JavaType javaType; - if (null != typeTree) { - javaType = typeTree.getType(); - } else { - return variableDeclarations; - } - - // TODO: getAllAnnotations() is deprecated - need to call - // AnnotationService.getAllAnnotations() but not sure which one yet - // but I'm not sure how to get a cursor - // All types, including primitives can be annotated - for (J.Annotation annotation : variableDeclarations.getAllAnnotations()) { - if (annotation.getType() instanceof JavaType.Unknown) { - continue; - } - - JavaType.Class type = (JavaType.Class) annotation.getType(); - if (null != type) { - String annotationFqn = type.getFullyQualifiedName(); - log.debug("Variable Annotation FQN: " + annotationFqn); - addType(ownerFqn, annotationFqn); - - if (null != annotation.getArguments()) { - for (Expression argument : annotation.getArguments()) { - processType(argument.getType(), ownerFqn); - } - } - } - } - - // skip primitive variable declarations - if (javaType instanceof JavaType.Primitive) { - return variableDeclarations; - } - - processType(javaType, ownerFqn); - - return variableDeclarations; - } - - void processType(JavaType javaType, String ownerFqn) { - if (javaType instanceof JavaType.Class) { - processType((JavaType.Class) javaType, ownerFqn); - } else if (javaType instanceof JavaType.Parameterized) { - // A<E> --> A - processType((JavaType.Parameterized) javaType, ownerFqn); - } else if (javaType instanceof JavaType.GenericTypeVariable) { - // T t; - processType((JavaType.GenericTypeVariable) javaType, ownerFqn); - } else if (javaType instanceof JavaType.Array) { - processType((JavaType.Array) javaType, ownerFqn); - } - } - - private void processType(JavaType.Parameterized parameterized, String ownerFqn) { - // List<A<E>> --> A - processAnnotations(parameterized, ownerFqn); - log.debug("Parameterized type FQN : " + parameterized.getFullyQualifiedName()); - addType(ownerFqn, parameterized.getFullyQualifiedName()); - - log.debug("Nested Parameterized type parameters: " + parameterized.getTypeParameters()); - for (JavaType parameter : parameterized.getTypeParameters()) { - processType(parameter, ownerFqn); - } - } - - private void processType(JavaType.Array arrayType, String ownerFqn) { - // D[] --> D - log.debug("Array Element type: " + arrayType.getElemType()); - processType(arrayType.getElemType(), ownerFqn); - } - - private void processType(JavaType.GenericTypeVariable typeVariable, String ownerFqn) { - log.debug("Type parameter type name: " + typeVariable.getName()); - - for (JavaType bound : typeVariable.getBounds()) { - if (bound instanceof JavaType.Class) { - addType(((JavaType.Class) bound).getFullyQualifiedName(), ownerFqn); - } else if (bound instanceof JavaType.Parameterized) { - addType(((JavaType.Parameterized) bound).getFullyQualifiedName(), ownerFqn); - } - } - } - - private void processType(JavaType.Class classType, String ownerFqn) { - processAnnotations(classType, ownerFqn); - log.debug("Class type FQN: " + classType.getFullyQualifiedName()); - addType(ownerFqn, classType.getFullyQualifiedName()); - } - - void processAnnotations(JavaType.FullyQualified fullyQualified, String ownerFqn) { - if (!fullyQualified.getAnnotations().isEmpty()) { - for (JavaType.FullyQualified annotation : fullyQualified.getAnnotations()) { - String annotationFqn = annotation.getFullyQualifiedName(); - log.debug("Extra Annotation FQN: " + annotationFqn); - addType(ownerFqn, annotationFqn); - } - } - } - - void addType(String ownerFqn, String typeFqn) { - if (!ownerFqn.contains("java.lang") && !typeFqn.contains("java.lang")) { - classReferencesGraph.addVertex(ownerFqn); - classReferencesGraph.addVertex(typeFqn); - - if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { - classReferencesGraph.addEdge(ownerFqn, typeFqn); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); - classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); - } - } - } -} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java index 9a0714dd..d87bf4de 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java @@ -1,156 +1,71 @@ package org.hjug.parser; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; + import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.openrewrite.ExecutionContext; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.*; @Slf4j -public class JavaVisitor extends JavaIsoVisitor<ExecutionContext> { - - JavaFqnCapturingVisitor javaFqnCapturingVisitor = new JavaFqnCapturingVisitor(); - - @Getter - private final List<String> fqdns = new ArrayList<>(); +public class JavaVisitor<P> extends JavaIsoVisitor<P> { - // used for looking up declared generic FQDNs @Getter - private final Map<String, String> importedAndDeclaredClassFQDNsByName = new HashMap<>(); + private final Set<String> packages = new HashSet<>(); + // used for looking up files where classes reside @Getter - private List<String> genericTypeParameters = new ArrayList<>(); + private final Map<String, String> classToSourceFileMapping = new HashMap<>(); - // Get imported classes to check declared generics - @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) { - J.CompilationUnit compilationUnit = super.visitCompilationUnit(cu, executionContext); + private JavaVariableTypeVisitor<P> javaVariableTypeVisitor; + private JavaClassDeclarationVisitor<P> javaClassDeclarationVisitor; - for (J.Import mport : compilationUnit.getImports()) { - importedAndDeclaredClassFQDNsByName.put(mport.getClassName(), mport.getTypeName()); - } + Graph<String, DefaultWeightedEdge> classReferencesGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); - for (J.ClassDeclaration aClass : compilationUnit.getClasses()) { - importedAndDeclaredClassFQDNsByName.put( - aClass.getName().getSimpleName(), aClass.getType().getFullyQualifiedName()); - } + public JavaVisitor() {} - return compilationUnit; + public JavaVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + javaVariableTypeVisitor = new JavaVariableTypeVisitor<>(classReferencesGraph); + javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph); } - // collect generic types and then add them to the FQDN list once all visitors are processed @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { - J.ClassDeclaration classDeclaration = super.visitClassDeclaration(classDecl, executionContext); - - // fqdns.add(classDeclaration.getType().getFullyQualifiedName()); - - System.out.println("Declared Type: " + classDeclaration.getType().getFullyQualifiedName()); - - // make this into a tail recursive call - // for (J.TypeParameter typeParameter : classDeclaration.getTypeParameters()) { - // System.out.println("Type Parameter: " + typeParameter.getBounds().get(0)); - // } - - List<JavaType> typeParameters = classDeclaration.getType().getTypeParameters(); - - if (!typeParameters.isEmpty()) { - System.out.println("Type type parameters: " + typeParameters); - for (JavaType genericTypeParameter : typeParameters) { - System.out.println("Generic Type: " + genericTypeParameter); - System.out.println("Bounds: " + ((JavaType.GenericTypeVariable) genericTypeParameter).getBounds()); - } - } - - classDeclaration.getExtends(); - // recursively check for type params on extends type - classDeclaration.getImplements(); - // recursively check for type params on implemented interfaces - - return classDeclaration; + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, P p) { + return javaVariableTypeVisitor.visitVariableDeclarations(multiVariable, p); } - // @Override - // public J.TypeParameter visitTypeParameter(J.TypeParameter typeParam, ExecutionContext executionContext) { - // for (TypeTree bound : typeParam.getBounds()) { - // System.out.println("Type Parameter type: " + bo); - // } - // - // - // return super.visitTypeParameter(typeParam, executionContext); - // } - - // member reference: System.out::println @Override - public J.MemberReference visitMemberReference(J.MemberReference memberRef, ExecutionContext executionContext) { - J.MemberReference memberReference = super.visitMemberReference(memberRef, executionContext); - - System.out.println( - "Member reference type: " + memberReference.getVariableType().getType()); - System.out.println("Owning type: " + memberReference.getVariableType().getOwner()); - - return memberReference; + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { + return javaClassDeclarationVisitor.visitClassDeclaration(classDecl, p); } - /** - * Variable type is B - * Owning type is A - * - * class A { - * B b1, b2; - * } - */ + // Get imported classes to check declared generics @Override - public J.VariableDeclarations.NamedVariable visitVariable( - J.VariableDeclarations.NamedVariable variable, ExecutionContext executionContext) { - J.VariableDeclarations.NamedVariable visitVariable = super.visitVariable(variable, executionContext); + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { + J.CompilationUnit compilationUnit = super.visitCompilationUnit(cu, p); - System.out.println("Variable type: " + visitVariable.getVariableType().getType()); - System.out.println("Owning type: " + visitVariable.getVariableType().getOwner()); + packages.add(compilationUnit.getPackageDeclaration().getPackageName()); - // visitVariable.getType() + for (J.ClassDeclaration aClass : compilationUnit.getClasses()) { + classToSourceFileMapping.put( + aClass.getType().getFullyQualifiedName(), compilationUnit.getSourcePath().toUri().toString()); + } - return visitVariable; + return compilationUnit; } - // @Override - // public J.NullableType visitNullableType(J.NullableType nullableType, ExecutionContext executionContext) { - // return super.visitNullableType(nullableType, executionContext); - // } - @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { - J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, executionContext); - - for (Statement parameter : method.getParameters()) { - JavaType type; - if (parameter instanceof J.VariableDeclarations) { - for (J.VariableDeclarations.NamedVariable variable : - ((J.VariableDeclarations) parameter).getVariables()) { - type = variable.getType(); - } - } else if (parameter instanceof Expression) { - type = ((Expression) parameter).getType(); - } - - // do something with the type - - } + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P p) { + J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, p); + methodDeclaration.getLeadingAnnotations(); methodDeclaration.getReturnTypeExpression().getType(); return methodDeclaration; } - - @Override - public J.Block visitBlock(J.Block block, ExecutionContext executionContext) { - - // block.getStatements(); - - return super.visitBlock(block, executionContext); - } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/TypeProcessor.java new file mode 100644 index 00000000..d47ccfb2 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/TypeProcessor.java @@ -0,0 +1,95 @@ +package org.hjug.parser; + +import lombok.extern.slf4j.Slf4j; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + + +public interface TypeProcessor { + + @Slf4j + final class LogHolder{} + + default void processType(JavaType javaType, String ownerFqn) { + if (javaType instanceof JavaType.Class) { + processType((JavaType.Class) javaType, ownerFqn); + } else if (javaType instanceof JavaType.Parameterized) { + // A<E> --> A + processType((JavaType.Parameterized) javaType, ownerFqn); + } else if (javaType instanceof JavaType.GenericTypeVariable) { + // T t; + processType((JavaType.GenericTypeVariable) javaType, ownerFqn); + } else if (javaType instanceof JavaType.Array) { + processType((JavaType.Array) javaType, ownerFqn); + } + } + + private void processType(JavaType.Parameterized parameterized, String ownerFqn) { + // List<A<E>> --> A + processAnnotations(parameterized, ownerFqn); + LogHolder.log.debug("Parameterized type FQN : " + parameterized.getFullyQualifiedName()); + addType(ownerFqn, parameterized.getFullyQualifiedName()); + + LogHolder.log.debug("Nested Parameterized type parameters: " + parameterized.getTypeParameters()); + for (JavaType parameter : parameterized.getTypeParameters()) { + processType(parameter, ownerFqn); + } + } + + private void processType(JavaType.Array arrayType, String ownerFqn) { + // D[] --> D + LogHolder.log.debug("Array Element type: " + arrayType.getElemType()); + processType(arrayType.getElemType(), ownerFqn); + } + + private void processType(JavaType.GenericTypeVariable typeVariable, String ownerFqn) { + LogHolder.log.debug("Type parameter type name: " + typeVariable.getName()); + + for (JavaType bound : typeVariable.getBounds()) { + if (bound instanceof JavaType.Class) { + addType(((JavaType.Class) bound).getFullyQualifiedName(), ownerFqn); + } else if (bound instanceof JavaType.Parameterized) { + addType(((JavaType.Parameterized) bound).getFullyQualifiedName(), ownerFqn); + } + } + } + + private void processType(JavaType.Class classType, String ownerFqn) { + processAnnotations(classType, ownerFqn); + LogHolder.log.debug("Class type FQN: " + classType.getFullyQualifiedName()); + addType(ownerFqn, classType.getFullyQualifiedName()); + } + + private void processAnnotations(JavaType.FullyQualified fullyQualified, String ownerFqn) { + if (!fullyQualified.getAnnotations().isEmpty()) { + for (JavaType.FullyQualified annotation : fullyQualified.getAnnotations()) { + String annotationFqn = annotation.getFullyQualifiedName(); + LogHolder.log.debug("Extra Annotation FQN: " + annotationFqn); + addType(ownerFqn, annotationFqn); + } + } + } + + default void processAnnotation(J.Annotation annotation, String ownerFqn) { + if (annotation.getType() instanceof JavaType.Unknown) { + return; + } + + JavaType.Class type = (JavaType.Class) annotation.getType(); + if (null != type) { + String annotationFqn = type.getFullyQualifiedName(); + LogHolder.log.debug("Variable Annotation FQN: " + annotationFqn); + addType(ownerFqn, annotationFqn); + + if (null != annotation.getArguments()) { + for (Expression argument : annotation.getArguments()) { + processType(argument.getType(), ownerFqn); + } + } + } + } + + void addType(String ownerFqn, String typeFqn); + +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaClassDeclarationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaClassDeclarationVisitorTest.java new file mode 100644 index 00000000..9fea3c59 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaClassDeclarationVisitorTest.java @@ -0,0 +1,47 @@ +package org.hjug.parser; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +import java.io.File; +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 static org.junit.jupiter.api.Assertions.*; + +class JavaClassDeclarationVisitorTest { + + @Test + void visitClasses() throws IOException { + + File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaClassDeclarationVisitor<ExecutionContext> javaVariableCapturingVisitor = new JavaClassDeclarationVisitor<>(); + + List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + javaVariableCapturingVisitor.visit(cu, ctx); + }); + + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.B")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.C")); + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.D")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.MyAnnotation")); + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.E")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.F")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.G")); + } + +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableTypeVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableTypeVisitorTest.java new file mode 100644 index 00000000..6581944e --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableTypeVisitorTest.java @@ -0,0 +1,45 @@ +package org.hjug.parser; + +import java.io.File; +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.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +class JavaVariableTypeVisitorTest { + + @Test + void visitClasses() throws IOException { + + File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaVariableTypeVisitor<ExecutionContext> javaVariableCapturingVisitor = new JavaVariableTypeVisitor<>(); + + List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + javaVariableCapturingVisitor.visit(cu, ctx); + }); + + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.B")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.C")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.D")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.E")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.MyAnnotation")); + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.F")); + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.G")); + + } +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java deleted file mode 100644 index 3a3a314f..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableVisitorTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.hjug.parser; - -import java.io.File; -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.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.JavaParser; - -class JavaVariableVisitorTest { - - @Test - void visitClasses() throws IOException { - - File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); - - org.openrewrite.java.JavaParser javaParser = - JavaParser.fromJavaVersion().build(); - ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - - JavaVariableVisitor<ExecutionContext> javaVariableCapturingVisitor = new JavaVariableVisitor<>(); - - List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { - javaVariableCapturingVisitor.visit(cu, ctx); - }); - } -} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java index ee500365..dd5cc8bb 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java @@ -7,6 +7,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; +@MyAnnotation public class A<T> { // public A(B cB, C cC){} @@ -31,12 +32,13 @@ public class A<T> { Map<A,B> map; List<? extends List<? extends Number>> listOfListsOfNumbers; - C doSomething(@NonNull B<C> paramB) { + F doSomething(@NonNull B<C> paramB) { List<B<E>> list3; A<E> a2; B<C> b2; C<A,B> c2; - return new C(); + // G extends F + return new G(); } class InnerClass{ diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java index fb129517..b9085722 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java @@ -1,3 +1,3 @@ package org.hjug.javaVariableVisitorTestClasses; -public class C<T extends A, U extends A> {} +public class C<T extends A, U extends B> {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java new file mode 100644 index 00000000..cee6165a --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java @@ -0,0 +1,3 @@ +package org.hjug.javaVariableVisitorTestClasses; + +public class F {} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java new file mode 100644 index 00000000..0d29f257 --- /dev/null +++ b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java @@ -0,0 +1,3 @@ +package org.hjug.javaVariableVisitorTestClasses; + +public class G extends F {} \ No newline at end of file From 2e460ea2621fc534b80fadc8541e78e9554fee03 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Sun, 26 Jan 2025 19:41:55 -0600 Subject: [PATCH 10/78] #111 Added Method Declaration & Implementation visitors Added Method Declaration & Implementation visitors Moved classes around Pulled test classes into src/test/java Upgraded OpenRewrite BOM version --- circular-reference-detector/pom.xml | 3 +- .../parser/JavaClassDeclarationVisitor.java | 70 ------------ .../org/hjug/parser/JavaProjectParser.java | 1 + .../{ => visitor}/FqnCapturingProcessor.java | 2 +- .../visitor/JavaClassDeclarationVisitor.java | 101 ++++++++++++++++++ .../JavaFqnCapturingVisitor.java | 2 +- .../visitor/JavaMethodDeclarationVisitor.java | 78 ++++++++++++++ .../visitor/JavaMethodInvocationVisitor.java | 49 +++++++++ .../JavaVariableTypeVisitor.java | 6 +- .../parser/{ => visitor}/JavaVisitor.java | 12 +-- .../parser/{ => visitor}/TypeProcessor.java | 62 +++++++---- .../parser/JavaFqnCapturingVisitorTest.java | 59 ---------- .../JavaClassDeclarationVisitorTest.java | 22 ++-- .../visitor/JavaFqnCapturingVisitorTest.java | 60 +++++++++++ .../JavaMethodDeclarationVisitorTest.java | 51 +++++++++ .../JavaMethodInvocationVisitorTest.java | 50 +++++++++ .../JavaVariableTypeVisitorTest.java | 20 ++-- .../parser/{ => visitor}/JavaVisitorTest.java | 5 +- .../hjug/parser/visitor/testclasses}/A.java | 17 +-- .../hjug/parser/visitor/testclasses/B.java | 11 ++ .../hjug/parser/visitor/testclasses/C.java | 3 + .../hjug/parser/visitor/testclasses/D.java | 3 + .../hjug/parser/visitor/testclasses/E.java | 5 + .../hjug/parser/visitor/testclasses/F.java | 3 + .../hjug/parser/visitor/testclasses/G.java | 3 + .../hjug/parser/visitor/testclasses/H.java | 7 ++ .../visitor/testclasses}/MyAnnotation.java | 2 +- .../testclasses}/MyOtherAnnotation.java | 2 +- .../testclasses/methodInvocation/A.java | 10 ++ .../testclasses/methodInvocation/B.java | 7 ++ .../testclasses/methodInvocation/H.java | 3 + .../javaVariableVisitorTestClasses/B.java | 6 -- .../javaVariableVisitorTestClasses/C.java | 3 - .../javaVariableVisitorTestClasses/D.java | 6 -- .../javaVariableVisitorTestClasses/E.java | 5 - .../javaVariableVisitorTestClasses/F.java | 3 - .../javaVariableVisitorTestClasses/G.java | 3 - 37 files changed, 532 insertions(+), 223 deletions(-) delete mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java rename circular-reference-detector/src/main/java/org/hjug/parser/{ => visitor}/FqnCapturingProcessor.java (98%) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java rename circular-reference-detector/src/main/java/org/hjug/parser/{ => visitor}/JavaFqnCapturingVisitor.java (98%) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java rename circular-reference-detector/src/main/java/org/hjug/parser/{ => visitor}/JavaVariableTypeVisitor.java (96%) rename circular-reference-detector/src/main/java/org/hjug/parser/{ => visitor}/JavaVisitor.java (85%) rename circular-reference-detector/src/main/java/org/hjug/parser/{ => visitor}/TypeProcessor.java (56%) delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java rename circular-reference-detector/src/test/java/org/hjug/parser/{ => visitor}/JavaClassDeclarationVisitorTest.java (60%) create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java rename circular-reference-detector/src/test/java/org/hjug/parser/{ => visitor}/JavaVariableTypeVisitorTest.java (67%) rename circular-reference-detector/src/test/java/org/hjug/parser/{ => visitor}/JavaVisitorTest.java (90%) rename circular-reference-detector/src/test/{resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses => java/org/hjug/parser/visitor/testclasses}/A.java (74%) create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java rename circular-reference-detector/src/test/{resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses => java/org/hjug/parser/visitor/testclasses}/MyAnnotation.java (73%) rename circular-reference-detector/src/test/{resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses => java/org/hjug/parser/visitor/testclasses}/MyOtherAnnotation.java (74%) create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java delete mode 100644 circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml index 639aed13..9cab67f6 100644 --- a/circular-reference-detector/pom.xml +++ b/circular-reference-detector/pom.xml @@ -17,7 +17,8 @@ <dependency> <groupId>org.openrewrite.recipe</groupId> <artifactId>rewrite-recipe-bom</artifactId> - <version>2.23.0</version> +<!-- <version>2.23.0</version>--> + <version>3.1.0</version> <type>pom</type> <scope>import</scope> </dependency> diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java deleted file mode 100644 index a3b732a7..00000000 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaClassDeclarationVisitor.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.hjug.parser; - -import lombok.Getter; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeTree; - -import java.util.List; - -public class JavaClassDeclarationVisitor <P> extends JavaIsoVisitor<P> implements TypeProcessor{ - - - @Getter - private Graph<String, DefaultWeightedEdge> classReferencesGraph = - new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); - - public JavaClassDeclarationVisitor() { - } - - public JavaClassDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { - this.classReferencesGraph = classReferencesGraph; - } - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { - J.ClassDeclaration classDeclaration = super.visitClassDeclaration(classDecl, p); - - JavaType.FullyQualified type = classDeclaration.getType(); - String owningFqn = type.getFullyQualifiedName(); - - processType(type, owningFqn); - - TypeTree extendsTypeTree = classDeclaration.getExtends(); - if (null != extendsTypeTree) { - processType(extendsTypeTree.getType(), owningFqn); - } - - List<TypeTree> implementsTypeTree = classDeclaration.getImplements(); - if(null != implementsTypeTree) { - for (TypeTree typeTree : implementsTypeTree) { - processType(typeTree.getType(), owningFqn); - } - } - - for (J.Annotation leadingAnnotation : classDeclaration.getLeadingAnnotations()) { - processAnnotation(leadingAnnotation, owningFqn); - } - - return classDeclaration; - } - - public void addType(String ownerFqn, String typeFqn) { - if(ownerFqn.equals(typeFqn)) return; - - classReferencesGraph.addVertex(ownerFqn); - classReferencesGraph.addVertex(typeFqn); - - if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { - classReferencesGraph.addEdge(ownerFqn, typeFqn); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); - classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); - } - } - -} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 4a946993..708ad283 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; +import org.hjug.parser.visitor.JavaVariableTypeVisitor; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/FqnCapturingProcessor.java similarity index 98% rename from circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingProcessor.java rename to circular-reference-detector/src/main/java/org/hjug/parser/visitor/FqnCapturingProcessor.java index ed69c0b0..611d25cf 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/FqnCapturingProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/FqnCapturingProcessor.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import java.util.Arrays; import java.util.HashMap; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java new file mode 100644 index 00000000..1163ab08 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -0,0 +1,101 @@ +package org.hjug.parser.visitor; + +import lombok.Getter; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.TypeTree; + +import java.util.List; + +public class JavaClassDeclarationVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { + + + private final JavaMethodInvocationVisitor methodInvocationVisitor; + + @Getter + private Graph<String, DefaultWeightedEdge> classReferencesGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + public JavaClassDeclarationVisitor() { + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); + } + + public JavaClassDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); + } + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { + J.ClassDeclaration classDeclaration = super.visitClassDeclaration(classDecl, p); + + JavaType.FullyQualified type = classDeclaration.getType(); + String owningFqn = type.getFullyQualifiedName(); + + processType(owningFqn, type); + + TypeTree extendsTypeTree = classDeclaration.getExtends(); + if (null != extendsTypeTree) { + processType(owningFqn, extendsTypeTree.getType()); + } + + List<TypeTree> implementsTypeTree = classDeclaration.getImplements(); + if (null != implementsTypeTree) { + for (TypeTree typeTree : implementsTypeTree) { + processType(owningFqn, typeTree.getType()); + } + } + + for (J.Annotation leadingAnnotation : classDeclaration.getLeadingAnnotations()) { + processAnnotation(owningFqn, leadingAnnotation); + } + + if (null != classDeclaration.getTypeParameters()) { + for (J.TypeParameter typeParameter : classDeclaration.getTypeParameters()) { + processTypeParameter(owningFqn, typeParameter); + } + } + + // process method invocations and lambda invocations + for (Statement statement : classDeclaration.getBody().getStatements()) { + if (statement instanceof J.MethodDeclaration) { + J.MethodDeclaration methodDeclaration = (J.MethodDeclaration) statement; + J.Block block = methodDeclaration.getBody(); + if (null != block && null != block.getStatements()) { + for (Statement statementInBlock : block.getStatements()) { + if (statementInBlock instanceof J.MethodInvocation) { + J.MethodInvocation methodInvocation = (J.MethodInvocation) statementInBlock; + methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); + } + if (statementInBlock instanceof J.Lambda) { + J.Lambda lambda = (J.Lambda) statement; + lambda.getParameters(); + lambda.getType(); + lambda.getBody(); + } + } + } + } + } + + return classDeclaration; + } + + public void addType(String ownerFqn, String typeFqn) { + classReferencesGraph.addVertex(ownerFqn); + classReferencesGraph.addVertex(typeFqn); + + if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { + classReferencesGraph.addEdge(ownerFqn, typeFqn); + } else { + DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + } + } + +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaFqnCapturingVisitor.java similarity index 98% rename from circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java rename to circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaFqnCapturingVisitor.java index b31028c9..07c62fd4 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaFqnCapturingVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaFqnCapturingVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import java.util.*; import lombok.Getter; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java new file mode 100644 index 00000000..c75ced96 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java @@ -0,0 +1,78 @@ +package org.hjug.parser.visitor; + +import lombok.Getter; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.NameTree; + +import java.util.List; + +public class JavaMethodDeclarationVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { + + @Getter + private Graph<String, DefaultWeightedEdge> classReferencesGraph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + public JavaMethodDeclarationVisitor() { + } + + public JavaMethodDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + } + + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P p) { + J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, p); + + String owner = methodDeclaration.getMethodType().getDeclaringType().getFullyQualifiedName(); + JavaType returnType = methodDeclaration.getReturnTypeExpression().getType(); + + // skip primitive variable declarations + if (!(returnType instanceof JavaType.Primitive)) { + processType(owner, returnType); + } + + for (J.Annotation leadingAnnotation : methodDeclaration.getLeadingAnnotations()) { + processType(owner, leadingAnnotation.getType()); + } + + if (null != methodDeclaration.getTypeParameters()) { + for (J.TypeParameter typeParameter : methodDeclaration.getTypeParameters()) { + processTypeParameter(owner, typeParameter); + } + } + + // don't need to capture parameter declarations + // they are captured in JavaVariableTypeVisitor + + + List<NameTree> throwz = methodDeclaration.getThrows(); + if (null != throwz && !throwz.isEmpty()) { + for (NameTree thrown : throwz) { + processType(owner, thrown.getType()); + } + } + + return methodDeclaration; + } + + public void addType(String ownerFqn, String typeFqn) { + if (ownerFqn.equals(typeFqn)) return; + + classReferencesGraph.addVertex(ownerFqn); + classReferencesGraph.addVertex(typeFqn); + + if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { + classReferencesGraph.addEdge(ownerFqn, typeFqn); + } else { + DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + } + } + +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java new file mode 100644 index 00000000..406cc086 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java @@ -0,0 +1,49 @@ +package org.hjug.parser.visitor; + +import lombok.Getter; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; + +// TODO: See RemoveMethodInvocationsVisitor for other visitor methods to override +// Custom visitor - not extending IsoVisitor on purpose since it does not provide caller information +public class JavaMethodInvocationVisitor implements TypeProcessor { + + @Getter + private Graph<String, DefaultWeightedEdge> classReferencesGraph; + + public JavaMethodInvocationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + } + + public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvocation methodInvocation) { + processType(invokingFqn, methodInvocation.getMethodType().getDeclaringType()); + + if(null != methodInvocation.getTypeParameters() && !methodInvocation.getTypeParameters().isEmpty()) { + for (Expression typeParameter : methodInvocation.getTypeParameters()) { + processType(invokingFqn, typeParameter.getType()); + } + } + + for (Expression argument : methodInvocation.getArguments()) { + processType(invokingFqn, argument.getType()); + } + + return methodInvocation; + } + + public void addType(String invokingFqn, String typeFqn) { + if (invokingFqn.equals(typeFqn)) return; + + classReferencesGraph.addVertex(invokingFqn); + classReferencesGraph.addVertex(typeFqn); + + if (!classReferencesGraph.containsEdge(invokingFqn, typeFqn)) { + classReferencesGraph.addEdge(invokingFqn, typeFqn); + } else { + DefaultWeightedEdge edge = classReferencesGraph.getEdge(invokingFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + } + } +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java similarity index 96% rename from circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableTypeVisitor.java rename to circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index 467129eb..22dd31a1 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import java.util.List; @@ -69,7 +69,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m // but I'm not sure how to get a cursor // All types, including primitives can be annotated for (J.Annotation annotation : variableDeclarations.getAllAnnotations()) { - processAnnotation(annotation, ownerFqn); + processAnnotation(ownerFqn, annotation); } // skip primitive variable declarations @@ -77,7 +77,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return variableDeclarations; } - processType(javaType, ownerFqn); + processType(ownerFqn, javaType); return variableDeclarations; } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java similarity index 85% rename from circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java rename to circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index d87bf4de..134f79dd 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import java.util.*; @@ -58,14 +58,4 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { return compilationUnit; } - - @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P p) { - J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, p); - - methodDeclaration.getLeadingAnnotations(); - methodDeclaration.getReturnTypeExpression().getType(); - - return methodDeclaration; - } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java similarity index 56% rename from circular-reference-detector/src/main/java/org/hjug/parser/TypeProcessor.java rename to circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java index d47ccfb2..b992a8d7 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/TypeProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java @@ -1,49 +1,55 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import lombok.extern.slf4j.Slf4j; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeTree; public interface TypeProcessor { @Slf4j - final class LogHolder{} + final class LogHolder { + } - default void processType(JavaType javaType, String ownerFqn) { + /** + * @param ownerFqn The FQN that is the source of the relationship + * @param javaType The type that is used/referenced by the source of the relationship + */ + default void processType(String ownerFqn, JavaType javaType) { if (javaType instanceof JavaType.Class) { - processType((JavaType.Class) javaType, ownerFqn); + processType(ownerFqn, (JavaType.Class) javaType); } else if (javaType instanceof JavaType.Parameterized) { // A<E> --> A - processType((JavaType.Parameterized) javaType, ownerFqn); + processType(ownerFqn, (JavaType.Parameterized) javaType); } else if (javaType instanceof JavaType.GenericTypeVariable) { // T t; - processType((JavaType.GenericTypeVariable) javaType, ownerFqn); + processType(ownerFqn, (JavaType.GenericTypeVariable) javaType); } else if (javaType instanceof JavaType.Array) { - processType((JavaType.Array) javaType, ownerFqn); + processType(ownerFqn, (JavaType.Array) javaType); } } - private void processType(JavaType.Parameterized parameterized, String ownerFqn) { + private void processType(String ownerFqn, JavaType.Parameterized parameterized) { // List<A<E>> --> A - processAnnotations(parameterized, ownerFqn); + processAnnotations(ownerFqn, parameterized); LogHolder.log.debug("Parameterized type FQN : " + parameterized.getFullyQualifiedName()); addType(ownerFqn, parameterized.getFullyQualifiedName()); LogHolder.log.debug("Nested Parameterized type parameters: " + parameterized.getTypeParameters()); for (JavaType parameter : parameterized.getTypeParameters()) { - processType(parameter, ownerFqn); + processType(ownerFqn, parameter); } } - private void processType(JavaType.Array arrayType, String ownerFqn) { + private void processType(String ownerFqn, JavaType.Array arrayType) { // D[] --> D LogHolder.log.debug("Array Element type: " + arrayType.getElemType()); - processType(arrayType.getElemType(), ownerFqn); + processType(ownerFqn, arrayType.getElemType()); } - private void processType(JavaType.GenericTypeVariable typeVariable, String ownerFqn) { + private void processType(String ownerFqn, JavaType.GenericTypeVariable typeVariable) { LogHolder.log.debug("Type parameter type name: " + typeVariable.getName()); for (JavaType bound : typeVariable.getBounds()) { @@ -55,13 +61,13 @@ private void processType(JavaType.GenericTypeVariable typeVariable, String owner } } - private void processType(JavaType.Class classType, String ownerFqn) { - processAnnotations(classType, ownerFqn); + private void processType(String ownerFqn, JavaType.Class classType) { + processAnnotations(ownerFqn, classType); LogHolder.log.debug("Class type FQN: " + classType.getFullyQualifiedName()); addType(ownerFqn, classType.getFullyQualifiedName()); } - private void processAnnotations(JavaType.FullyQualified fullyQualified, String ownerFqn) { + private void processAnnotations(String ownerFqn, JavaType.FullyQualified fullyQualified) { if (!fullyQualified.getAnnotations().isEmpty()) { for (JavaType.FullyQualified annotation : fullyQualified.getAnnotations()) { String annotationFqn = annotation.getFullyQualifiedName(); @@ -71,7 +77,7 @@ private void processAnnotations(JavaType.FullyQualified fullyQualified, String o } } - default void processAnnotation(J.Annotation annotation, String ownerFqn) { + default void processAnnotation(String ownerFqn, J.Annotation annotation) { if (annotation.getType() instanceof JavaType.Unknown) { return; } @@ -84,12 +90,32 @@ default void processAnnotation(J.Annotation annotation, String ownerFqn) { if (null != annotation.getArguments()) { for (Expression argument : annotation.getArguments()) { - processType(argument.getType(), ownerFqn); + processType(ownerFqn, argument.getType()); } } } } + default void processTypeParameter(String ownerFqn, J.TypeParameter typeParameter) { + + if (null != typeParameter.getBounds()) { + for (TypeTree bound : typeParameter.getBounds()) { + processType(ownerFqn, bound.getType()); + } + } + + if (!typeParameter.getAnnotations().isEmpty()) { + for (J.Annotation annotation : typeParameter.getAnnotations()) { + processAnnotation(ownerFqn, annotation); + } + } + } + + /** + * + * @param ownerFqn The FQN that is the source of the relationship + * @param typeFqn The FQN of the type that is being used by the source + */ void addType(String ownerFqn, String typeFqn); } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java deleted file mode 100644 index b328a0f3..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaFqnCapturingVisitorTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.hjug.parser; - -import java.io.File; -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.Map; -import java.util.stream.Collectors; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.JavaParser; - -@Disabled -class JavaFqnCapturingVisitorTest { - - @Test - void visitClasses() throws IOException { - - File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); - - org.openrewrite.java.JavaParser javaParser = - JavaParser.fromJavaVersion().build(); - ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - - JavaFqnCapturingVisitor<ExecutionContext> javaFqnCapturingVisitor = new JavaFqnCapturingVisitor(); - - List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { - javaFqnCapturingVisitor.visit(cu, ctx); - }); - - Map<String, Map<String, String>> fqns = javaFqnCapturingVisitor.getFqnMap(); - Map<String, String> processed = fqns.get("org.hjug.testclasses"); - Assertions.assertEquals("org.hjug.testclasses.A", processed.get("A")); - Assertions.assertEquals("org.hjug.testclasses.A.InnerClass", processed.get("A.InnerClass")); - Assertions.assertEquals("org.hjug.testclasses.A.InnerClass", processed.get("InnerClass")); - Assertions.assertEquals( - "org.hjug.testclasses.A.InnerClass.InnerInner", processed.get("A.InnerClass.InnerInner")); - Assertions.assertEquals("org.hjug.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); - Assertions.assertEquals("org.hjug.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); - Assertions.assertEquals( - "org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", - processed.get("A.InnerClass.InnerInner.MegaInner")); - Assertions.assertEquals( - "org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", - processed.get("InnerClass.InnerInner.MegaInner")); - Assertions.assertEquals( - "org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerInner.MegaInner")); - Assertions.assertEquals("org.hjug.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); - Assertions.assertEquals("org.hjug.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); - Assertions.assertEquals("org.hjug.testclasses.A.StaticInnerClass", processed.get("StaticInnerClass")); - Assertions.assertEquals("org.hjug.testclasses.NonPublic", processed.get("NonPublic")); - } -} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaClassDeclarationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java similarity index 60% rename from circular-reference-detector/src/test/java/org/hjug/parser/JavaClassDeclarationVisitorTest.java rename to circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java index 9fea3c59..b9171d08 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaClassDeclarationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -14,14 +14,12 @@ import java.util.List; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; - class JavaClassDeclarationVisitorTest { @Test void visitClasses() throws IOException { - File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -34,14 +32,14 @@ void visitClasses() throws IOException { javaVariableCapturingVisitor.visit(cu, ctx); }); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.B")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.C")); - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.D")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.MyAnnotation")); - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.E")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.F")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.G")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.A")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.B")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.C")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.D")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.E")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.F")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.G")); } } \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java new file mode 100644 index 00000000..7f2a89ec --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java @@ -0,0 +1,60 @@ +package org.hjug.parser.visitor; + +import java.io.File; +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.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +@Disabled +class JavaFqnCapturingVisitorTest { + + @Test + void visitClasses() throws IOException { + + File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); + + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaFqnCapturingVisitor<ExecutionContext> javaFqnCapturingVisitor = new JavaFqnCapturingVisitor(); + + List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + javaFqnCapturingVisitor.visit(cu, ctx); + }); + + Map<String, Map<String, String>> fqns = javaFqnCapturingVisitor.getFqnMap(); + Map<String, String> processed = fqns.get("org.hjug.parser.visitor.testclasses"); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A", processed.get("A")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass", processed.get("A.InnerClass")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass", processed.get("InnerClass")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("A.InnerClass.InnerInner")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", + processed.get("A.InnerClass.InnerInner.MegaInner")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", + processed.get("InnerClass.InnerInner.MegaInner")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerInner.MegaInner")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("StaticInnerClass")); + Assertions.assertEquals("org.hjug.parser.visitor.testclasses.NonPublic", processed.get("NonPublic")); + } +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java new file mode 100644 index 00000000..726a616d --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java @@ -0,0 +1,51 @@ +package org.hjug.parser.visitor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +import java.io.File; +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; + +class JavaMethodDeclarationVisitorTest { + + @Test + void visitMethodDeclarations() throws IOException { + + File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); + + org.openrewrite.java.JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaMethodDeclarationVisitor<ExecutionContext> methodDeclarationVisitor = new JavaMethodDeclarationVisitor<>(); + + List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + methodDeclarationVisitor.visit(cu, ctx); + }); + + + methodDeclarationVisitor.getClassReferencesGraph(); + + Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.A")); + + //TODO: Assert stuff + /* Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); + Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.B")); + Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.C")); + Assertions.assertFalse(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.D")); + Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.MyAnnotation")); + Assertions.assertFalse(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.E")); + Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.F")); + Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.G"));*/ + } + +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java new file mode 100644 index 00000000..dd9a663c --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java @@ -0,0 +1,50 @@ +package org.hjug.parser.visitor; + +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +import java.io.File; +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; + +class JavaMethodInvocationVisitorTest { + + @Test + void visitMethodInvocations() throws IOException { + + File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation"); + + JavaParser javaParser = + JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaClassDeclarationVisitor<ExecutionContext> classDeclarationVisitor = new JavaClassDeclarationVisitor<>(); + + List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + classDeclarationVisitor.visit(cu, ctx); + }); + + Graph<String, DefaultWeightedEdge> graph = classDeclarationVisitor.getClassReferencesGraph(); + Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.A")); + Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.B")); + Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.H")); + + Assertions.assertEquals(1, + graph.getEdgeWeight(graph.getEdge("org.hjug.parser.visitor.testclasses.methodInvocation.A", "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); + Assertions.assertEquals(2, + graph.getEdgeWeight(graph.getEdge("org.hjug.parser.visitor.testclasses.methodInvocation.A", "org.hjug.parser.visitor.testclasses.methodInvocation.H"))); + Assertions.assertEquals(1, + graph.getEdgeWeight(graph.getEdge("org.hjug.parser.visitor.testclasses.methodInvocation.H", "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); + + } +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableTypeVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java similarity index 67% rename from circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableTypeVisitorTest.java rename to circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java index 6581944e..33b23edd 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVariableTypeVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import java.io.File; import java.io.IOException; @@ -19,7 +19,7 @@ class JavaVariableTypeVisitorTest { @Test void visitClasses() throws IOException { - File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -32,14 +32,14 @@ void visitClasses() throws IOException { javaVariableCapturingVisitor.visit(cu, ctx); }); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.B")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.C")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.D")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.E")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.MyAnnotation")); - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.F")); - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.G")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.A")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.B")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.C")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.D")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.E")); + Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.F")); + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.G")); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java similarity index 90% rename from circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java rename to circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java index 8f23ce41..73d9ceae 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.parser.visitor; import java.io.File; import java.io.IOException; @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; + import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; @@ -20,7 +21,7 @@ class JavaVisitorTest { @Test void visitClasses() throws IOException { - File srcDirectory = new File("src/test/resources/visitorSrcDirectory"); + File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java similarity index 74% rename from circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java rename to circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java index dd5cc8bb..51980fb1 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/A.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java @@ -1,11 +1,8 @@ -package org.hjug.javaVariableVisitorTestClasses; +package org.hjug.parser.visitor.testclasses; -import java.io.IOException; -import java.io.InputStream; import java.util.Map; import java.util.List; -import edu.umd.cs.findbugs.annotations.NonNull; @MyAnnotation public class A<T> { @@ -21,8 +18,9 @@ public class A<T> { @MyOtherAnnotation C rawC; B<B> b, b3; - C<B.InnerB, C> c; + C<B.InnerB, B> c; D[] ds; + D d; @MyAnnotation B<C>[] arrayOfGenericBsWithCTypeParam; @@ -32,12 +30,17 @@ public class A<T> { Map<A,B> map; List<? extends List<? extends Number>> listOfListsOfNumbers; - F doSomething(@NonNull B<C> paramB) { + @MyAnnotation + <T extends A, U extends B> F doSomething(B<C> paramB, C<B.InnerB, B> genericParam) { List<B<E>> list3; A<E> a2; B<C> b2; C<A,B> c2; - // G extends F + + H h = new H(); + + B.<H>invocationTest(h); + return new G(); } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java new file mode 100644 index 00000000..ab24f566 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java @@ -0,0 +1,11 @@ +package org.hjug.parser.visitor.testclasses; + +public class B<T> { + + static <T extends B> D invocationTest(T type) { + return new D(); + } + + static class InnerB extends A{} + +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java new file mode 100644 index 00000000..acb8af31 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java @@ -0,0 +1,3 @@ +package org.hjug.parser.visitor.testclasses; + +public class C<T extends A, U extends B> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java new file mode 100644 index 00000000..10c12035 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java @@ -0,0 +1,3 @@ +package org.hjug.parser.visitor.testclasses; + +public class D {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java new file mode 100644 index 00000000..8e0d8468 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java @@ -0,0 +1,5 @@ +package org.hjug.parser.visitor.testclasses; + +public interface E { + void foo(A a); +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java new file mode 100644 index 00000000..2420cc2d --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java @@ -0,0 +1,3 @@ +package org.hjug.parser.visitor.testclasses; + +public class F {} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java new file mode 100644 index 00000000..7433a074 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java @@ -0,0 +1,3 @@ +package org.hjug.parser.visitor.testclasses; + +public class G extends F {} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java new file mode 100644 index 00000000..bd7035f6 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java @@ -0,0 +1,7 @@ +package org.hjug.parser.visitor.testclasses; + +public class H<T> extends B<T> { + + + +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyAnnotation.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java similarity index 73% rename from circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyAnnotation.java rename to circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java index 7d94ccf9..181d3ed8 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyAnnotation.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java @@ -1,4 +1,4 @@ -package org.hjug.javaVariableVisitorTestClasses; +package org.hjug.parser.visitor.testclasses; import java.lang.annotation.ElementType; import java.lang.annotation.Target; diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyOtherAnnotation.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java similarity index 74% rename from circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyOtherAnnotation.java rename to circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java index 37686d60..5e9392ff 100644 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/MyOtherAnnotation.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java @@ -1,4 +1,4 @@ -package org.hjug.javaVariableVisitorTestClasses; +package org.hjug.parser.visitor.testclasses; import java.lang.annotation.ElementType; import java.lang.annotation.Target; diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java new file mode 100644 index 00000000..e1407e35 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java @@ -0,0 +1,10 @@ +package org.hjug.parser.visitor.testclasses.methodInvocation; + + +public class A { + + void doSomething(){ + B.<H>invocationTest(new H()); + } + +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java new file mode 100644 index 00000000..2faf1a59 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java @@ -0,0 +1,7 @@ +package org.hjug.parser.visitor.testclasses.methodInvocation; + +public class B<T> { + + static <T extends B> void invocationTest(T type) {} + +} \ No newline at end of file diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java new file mode 100644 index 00000000..6c4a0d56 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java @@ -0,0 +1,3 @@ +package org.hjug.parser.visitor.testclasses.methodInvocation; + +public class H<T> extends B<T> {} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java deleted file mode 100644 index 9e245b2e..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/B.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.hjug.javaVariableVisitorTestClasses; - -public class B<T> { - - static class InnerB{} -} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java deleted file mode 100644 index b9085722..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/C.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.javaVariableVisitorTestClasses; - -public class C<T extends A, U extends B> {} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java deleted file mode 100644 index 0b6068df..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/D.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.hjug.javaVariableVisitorTestClasses; - -public class D { -// A a; -// C c; -} diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java deleted file mode 100644 index af70fd4a..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/E.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.hjug.javaVariableVisitorTestClasses; - -public interface E { - void foo(A a); -} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java deleted file mode 100644 index cee6165a..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/F.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.javaVariableVisitorTestClasses; - -public class F {} \ No newline at end of file diff --git a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java b/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java deleted file mode 100644 index 0d29f257..00000000 --- a/circular-reference-detector/src/test/resources/visitorSrcDirectory/org/hjug/javaVariableVisitorTestClasses/G.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.javaVariableVisitorTestClasses; - -public class G extends F {} \ No newline at end of file From 4a5337f038bbdb0f02a9e18114a9a95f33b8ef4d Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 27 Jan 2025 19:00:51 -0600 Subject: [PATCH 11/78] #111 Pulled up addType into TypeProcessor - Pulled up addType into TypeProcessor - Classes no longer add edges to themselves - Updated unit tests --- .../visitor/JavaClassDeclarationVisitor.java | 55 ++++++++++--------- .../visitor/JavaMethodDeclarationVisitor.java | 15 ----- .../visitor/JavaMethodInvocationVisitor.java | 16 +----- .../visitor/JavaVariableTypeVisitor.java | 14 ----- .../hjug/parser/visitor/TypeProcessor.java | 22 +++++++- .../JavaClassDeclarationVisitorTest.java | 7 ++- 6 files changed, 54 insertions(+), 75 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index 1163ab08..b3286cbb 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -62,40 +62,41 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P } // process method invocations and lambda invocations + processInvocations(classDeclaration); + + return classDeclaration; + } + + private void processInvocations(J.ClassDeclaration classDeclaration) { + JavaType.FullyQualified type = classDeclaration.getType(); + String owningFqn = type.getFullyQualifiedName(); + for (Statement statement : classDeclaration.getBody().getStatements()) { + if (statement instanceof J.Block) { + //process an initializer + processBlock((J.Block) statement, owningFqn); + } if (statement instanceof J.MethodDeclaration) { J.MethodDeclaration methodDeclaration = (J.MethodDeclaration) statement; - J.Block block = methodDeclaration.getBody(); - if (null != block && null != block.getStatements()) { - for (Statement statementInBlock : block.getStatements()) { - if (statementInBlock instanceof J.MethodInvocation) { - J.MethodInvocation methodInvocation = (J.MethodInvocation) statementInBlock; - methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); - } - if (statementInBlock instanceof J.Lambda) { - J.Lambda lambda = (J.Lambda) statement; - lambda.getParameters(); - lambda.getType(); - lambda.getBody(); - } - } - } + processBlock(methodDeclaration.getBody(), owningFqn); } } - - return classDeclaration; } - public void addType(String ownerFqn, String typeFqn) { - classReferencesGraph.addVertex(ownerFqn); - classReferencesGraph.addVertex(typeFqn); - - if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { - classReferencesGraph.addEdge(ownerFqn, typeFqn); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); - classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); + private void processBlock(J.Block block, String owningFqn) { + if (null != block && null != block.getStatements()) { + for (Statement statementInBlock : block.getStatements()) { + if (statementInBlock instanceof J.MethodInvocation) { + J.MethodInvocation methodInvocation = (J.MethodInvocation) statementInBlock; + methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); + } + if (statementInBlock instanceof J.Lambda) { + J.Lambda lambda = (J.Lambda) statementInBlock; + lambda.getParameters(); + lambda.getType(); + lambda.getBody(); + } + } } } - } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java index c75ced96..7d3a287a 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java @@ -60,19 +60,4 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P return methodDeclaration; } - - public void addType(String ownerFqn, String typeFqn) { - if (ownerFqn.equals(typeFqn)) return; - - classReferencesGraph.addVertex(ownerFqn); - classReferencesGraph.addVertex(typeFqn); - - if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { - classReferencesGraph.addEdge(ownerFqn, typeFqn); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); - classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); - } - } - } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java index 406cc086..311789f1 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java @@ -6,7 +6,7 @@ import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -// TODO: See RemoveMethodInvocationsVisitor for other visitor methods to override +// See RemoveMethodInvocationsVisitor for other visitor methods to override // Custom visitor - not extending IsoVisitor on purpose since it does not provide caller information public class JavaMethodInvocationVisitor implements TypeProcessor { @@ -32,18 +32,4 @@ public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvo return methodInvocation; } - - public void addType(String invokingFqn, String typeFqn) { - if (invokingFqn.equals(typeFqn)) return; - - classReferencesGraph.addVertex(invokingFqn); - classReferencesGraph.addVertex(typeFqn); - - if (!classReferencesGraph.containsEdge(invokingFqn, typeFqn)) { - classReferencesGraph.addEdge(invokingFqn, typeFqn); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(invokingFqn, typeFqn); - classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); - } - } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index 22dd31a1..508f2291 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -81,18 +81,4 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return variableDeclarations; } - - - - public void addType(String ownerFqn, String typeFqn) { - classReferencesGraph.addVertex(ownerFqn); - classReferencesGraph.addVertex(typeFqn); - - if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { - classReferencesGraph.addEdge(ownerFqn, typeFqn); - } else { - DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); - classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); - } - } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java index b992a8d7..657383c2 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java @@ -1,6 +1,8 @@ package org.hjug.parser.visitor; import lombok.extern.slf4j.Slf4j; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -111,11 +113,29 @@ default void processTypeParameter(String ownerFqn, J.TypeParameter typeParameter } } + Graph<String, DefaultWeightedEdge> getClassReferencesGraph(); + /** * * @param ownerFqn The FQN that is the source of the relationship * @param typeFqn The FQN of the type that is being used by the source */ - void addType(String ownerFqn, String typeFqn); + default void addType(String ownerFqn, String typeFqn) { + if (ownerFqn.equals(typeFqn)) return; + + Graph<String, DefaultWeightedEdge> graph = getClassReferencesGraph(); + + graph.addVertex(ownerFqn); + graph.addVertex(typeFqn); + + if (!graph.containsEdge(ownerFqn, typeFqn)) { + graph.addEdge(ownerFqn, typeFqn); + } else { + DefaultWeightedEdge edge = graph.getEdge(ownerFqn, typeFqn); + graph.setEdgeWeight(edge, graph.getEdgeWeight(edge) + 1); + } + } + + } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java index b9171d08..d1ba9acc 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java @@ -35,11 +35,12 @@ void visitClasses() throws IOException { Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.A")); Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.B")); Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.C")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.D")); + //false because it doesn't reference any other classes + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.D")); Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.E")); + //false because the class declaration doesn't reference any other classes + Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.E")); Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.F")); Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.G")); } - } \ No newline at end of file From 14992b19073ed75d1873713d26dba3106d134faf Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 27 Jan 2025 19:19:22 -0600 Subject: [PATCH 12/78] #111 Added minimal processing of lambdas - Added minimal processing of lambdas - Applied Spotless formatting --- .../org/hjug/parser/JavaProjectParser.java | 2 +- .../visitor/JavaClassDeclarationVisitor.java | 11 +-- .../visitor/JavaMethodDeclarationVisitor.java | 8 +- .../visitor/JavaMethodInvocationVisitor.java | 4 +- .../visitor/JavaVariableTypeVisitor.java | 3 +- .../org/hjug/parser/visitor/JavaVisitor.java | 4 +- .../hjug/parser/visitor/TypeProcessor.java | 7 +- .../JavaClassDeclarationVisitorTest.java | 52 ++++++---- .../visitor/JavaFqnCapturingVisitorTest.java | 22 +++-- .../JavaMethodDeclarationVisitorTest.java | 23 +++-- .../JavaMethodInvocationVisitorTest.java | 42 ++++---- .../visitor/JavaVariableTypeVisitorTest.java | 34 +++++-- .../hjug/parser/visitor/JavaVisitorTest.java | 1 - .../hjug/parser/visitor/testclasses/A.java | 98 ++++++++++--------- .../hjug/parser/visitor/testclasses/B.java | 5 +- .../hjug/parser/visitor/testclasses/E.java | 2 +- .../hjug/parser/visitor/testclasses/F.java | 2 +- .../hjug/parser/visitor/testclasses/G.java | 2 +- .../hjug/parser/visitor/testclasses/H.java | 6 +- .../visitor/testclasses/MyAnnotation.java | 3 +- .../testclasses/MyOtherAnnotation.java | 3 +- .../testclasses/methodInvocation/A.java | 4 +- .../testclasses/methodInvocation/B.java | 3 +- .../testclasses/methodInvocation/H.java | 2 +- 24 files changed, 183 insertions(+), 160 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 708ad283..046ea58f 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -57,6 +57,6 @@ private void processWithOpenRewrite(String srcDir, Graph<String, DefaultWeighted .forEach(cu -> javaVariableTypeVisitor.visit(cu, ctx)); } - //TODO: classReferencesGraph.removeAllVertices(); if vertex package not in codebase + // TODO: classReferencesGraph.removeAllVertices(); if vertex package not in codebase } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index b3286cbb..a6b677df 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -1,5 +1,6 @@ package org.hjug.parser.visitor; +import java.util.List; import lombok.Getter; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; @@ -10,11 +11,8 @@ import org.openrewrite.java.tree.Statement; import org.openrewrite.java.tree.TypeTree; -import java.util.List; - public class JavaClassDeclarationVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { - private final JavaMethodInvocationVisitor methodInvocationVisitor; @Getter @@ -73,7 +71,6 @@ private void processInvocations(J.ClassDeclaration classDeclaration) { for (Statement statement : classDeclaration.getBody().getStatements()) { if (statement instanceof J.Block) { - //process an initializer processBlock((J.Block) statement, owningFqn); } if (statement instanceof J.MethodDeclaration) { @@ -92,9 +89,9 @@ private void processBlock(J.Block block, String owningFqn) { } if (statementInBlock instanceof J.Lambda) { J.Lambda lambda = (J.Lambda) statementInBlock; - lambda.getParameters(); - lambda.getType(); - lambda.getBody(); + processType(owningFqn, lambda.getType()); + // FOLLOW UP: process body of lambda + // lambda.getBody() } } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java index 7d3a287a..020459a9 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java @@ -1,5 +1,6 @@ package org.hjug.parser.visitor; +import java.util.List; import lombok.Getter; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; @@ -9,22 +10,18 @@ import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.NameTree; -import java.util.List; - public class JavaMethodDeclarationVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); - public JavaMethodDeclarationVisitor() { - } + public JavaMethodDeclarationVisitor() {} public JavaMethodDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { this.classReferencesGraph = classReferencesGraph; } - @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P p) { J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, p); @@ -50,7 +47,6 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P // don't need to capture parameter declarations // they are captured in JavaVariableTypeVisitor - List<NameTree> throwz = methodDeclaration.getThrows(); if (null != throwz && !throwz.isEmpty()) { for (NameTree thrown : throwz) { diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java index 311789f1..cf9e6806 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java @@ -18,9 +18,11 @@ public JavaMethodInvocationVisitor(Graph<String, DefaultWeightedEdge> classRefer } public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvocation methodInvocation) { + // getDeclaringType() returns the type that declared the method being invoked processType(invokingFqn, methodInvocation.getMethodType().getDeclaringType()); - if(null != methodInvocation.getTypeParameters() && !methodInvocation.getTypeParameters().isEmpty()) { + if (null != methodInvocation.getTypeParameters() + && !methodInvocation.getTypeParameters().isEmpty()) { for (Expression typeParameter : methodInvocation.getTypeParameters()) { processType(invokingFqn, typeParameter.getType()); } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index 508f2291..33ae0f0d 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -1,7 +1,6 @@ package org.hjug.parser.visitor; import java.util.List; - import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; @@ -11,7 +10,7 @@ import org.openrewrite.java.tree.*; @Slf4j -public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor{ +public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph = diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index 134f79dd..01669332 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -1,7 +1,6 @@ package org.hjug.parser.visitor; import java.util.*; - import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; @@ -53,7 +52,8 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { for (J.ClassDeclaration aClass : compilationUnit.getClasses()) { classToSourceFileMapping.put( - aClass.getType().getFullyQualifiedName(), compilationUnit.getSourcePath().toUri().toString()); + aClass.getType().getFullyQualifiedName(), + compilationUnit.getSourcePath().toUri().toString()); } return compilationUnit; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java index 657383c2..8a705b35 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java @@ -8,12 +8,10 @@ import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeTree; - public interface TypeProcessor { @Slf4j - final class LogHolder { - } + final class LogHolder {} /** * @param ownerFqn The FQN that is the source of the relationship @@ -135,7 +133,4 @@ default void addType(String ownerFqn, String typeFqn) { graph.setEdgeWeight(edge, graph.getEdgeWeight(edge) + 1); } } - - - } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java index d1ba9acc..f28c2382 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java @@ -1,11 +1,5 @@ package org.hjug.parser.visitor; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.JavaParser; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -13,6 +7,11 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; class JavaClassDeclarationVisitorTest { @@ -25,22 +24,39 @@ void visitClasses() throws IOException { JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaClassDeclarationVisitor<ExecutionContext> javaVariableCapturingVisitor = new JavaClassDeclarationVisitor<>(); + JavaClassDeclarationVisitor<ExecutionContext> javaVariableCapturingVisitor = + new JavaClassDeclarationVisitor<>(); List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { javaVariableCapturingVisitor.visit(cu, ctx); }); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.A")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.B")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.C")); - //false because it doesn't reference any other classes - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.D")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); - //false because the class declaration doesn't reference any other classes - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.E")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.F")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.G")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.A")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.B")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.C")); + // false because it doesn't reference any other classes + Assertions.assertFalse(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.D")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); + // false because the class declaration doesn't reference any other classes + Assertions.assertFalse(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.E")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.F")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.G")); } -} \ No newline at end of file +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java index 7f2a89ec..e1f72832 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -41,9 +40,12 @@ void visitClasses() throws IOException { Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass", processed.get("A.InnerClass")); Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass", processed.get("InnerClass")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("A.InnerClass.InnerInner")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", + processed.get("A.InnerClass.InnerInner")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); Assertions.assertEquals( "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("A.InnerClass.InnerInner.MegaInner")); @@ -51,10 +53,14 @@ void visitClasses() throws IOException { "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerClass.InnerInner.MegaInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerInner.MegaInner")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("StaticInnerClass")); + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", + processed.get("InnerInner.MegaInner")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); + Assertions.assertEquals( + "org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("StaticInnerClass")); Assertions.assertEquals("org.hjug.parser.visitor.testclasses.NonPublic", processed.get("NonPublic")); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java index 726a616d..84c7d945 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java @@ -1,11 +1,5 @@ package org.hjug.parser.visitor; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.JavaParser; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -13,6 +7,11 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; class JavaMethodDeclarationVisitorTest { @@ -32,13 +31,14 @@ void visitMethodDeclarations() throws IOException { methodDeclarationVisitor.visit(cu, ctx); }); - methodDeclarationVisitor.getClassReferencesGraph(); - Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.A")); + Assertions.assertTrue(methodDeclarationVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.A")); - //TODO: Assert stuff - /* Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); + // TODO: Assert stuff + /* Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.B")); Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.C")); Assertions.assertFalse(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.D")); @@ -47,5 +47,4 @@ void visitMethodDeclarations() throws IOException { Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.F")); Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.G"));*/ } - -} \ No newline at end of file +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java index dd9a663c..ae748ea3 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java @@ -1,13 +1,5 @@ package org.hjug.parser.visitor; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.JavaParser; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -15,6 +7,13 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; class JavaMethodInvocationVisitorTest { @@ -23,8 +22,7 @@ void visitMethodInvocations() throws IOException { File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation"); - JavaParser javaParser = - JavaParser.fromJavaVersion().build(); + JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); JavaClassDeclarationVisitor<ExecutionContext> classDeclarationVisitor = new JavaClassDeclarationVisitor<>(); @@ -39,12 +37,20 @@ void visitMethodInvocations() throws IOException { Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.B")); Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.H")); - Assertions.assertEquals(1, - graph.getEdgeWeight(graph.getEdge("org.hjug.parser.visitor.testclasses.methodInvocation.A", "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); - Assertions.assertEquals(2, - graph.getEdgeWeight(graph.getEdge("org.hjug.parser.visitor.testclasses.methodInvocation.A", "org.hjug.parser.visitor.testclasses.methodInvocation.H"))); - Assertions.assertEquals(1, - graph.getEdgeWeight(graph.getEdge("org.hjug.parser.visitor.testclasses.methodInvocation.H", "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); - + Assertions.assertEquals( + 1, + graph.getEdgeWeight(graph.getEdge( + "org.hjug.parser.visitor.testclasses.methodInvocation.A", + "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); + Assertions.assertEquals( + 2, + graph.getEdgeWeight(graph.getEdge( + "org.hjug.parser.visitor.testclasses.methodInvocation.A", + "org.hjug.parser.visitor.testclasses.methodInvocation.H"))); + Assertions.assertEquals( + 1, + graph.getEdgeWeight(graph.getEdge( + "org.hjug.parser.visitor.testclasses.methodInvocation.H", + "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); } -} \ No newline at end of file +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java index 33b23edd..ccb4c3c8 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java @@ -7,7 +7,6 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; @@ -32,14 +31,29 @@ void visitClasses() throws IOException { javaVariableCapturingVisitor.visit(cu, ctx); }); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.A")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.B")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.C")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.D")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.E")); - Assertions.assertTrue(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.F")); - Assertions.assertFalse(javaVariableCapturingVisitor.getClassReferencesGraph().containsVertex("org.hjug.parser.visitor.testclasses.G")); - + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.A")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.B")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.C")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.D")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.E")); + Assertions.assertTrue(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); + Assertions.assertFalse(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.F")); + Assertions.assertFalse(javaVariableCapturingVisitor + .getClassReferencesGraph() + .containsVertex("org.hjug.parser.visitor.testclasses.G")); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java index 73d9ceae..8e93f651 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java index 51980fb1..a5396353 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java @@ -1,57 +1,61 @@ package org.hjug.parser.visitor.testclasses; -import java.util.Map; import java.util.List; - +import java.util.Map; @MyAnnotation public class A<T> { -// public A(B cB, C cC){} - - B<? extends C> crazyType; - - @MyAnnotation - @MyOtherAnnotation - int intVar, intVar2; - @MyAnnotation - @MyOtherAnnotation - C rawC; - B<B> b, b3; - C<B.InnerB, B> c; - D[] ds; - D d; - - @MyAnnotation - B<C>[] arrayOfGenericBsWithCTypeParam; - @MyAnnotation - B<C[]> bWithArrayOfCs; - List<A<B>> listWithNestedGenric; - Map<A,B> map; - List<? extends List<? extends Number>> listOfListsOfNumbers; - - @MyAnnotation - <T extends A, U extends B> F doSomething(B<C> paramB, C<B.InnerB, B> genericParam) { - List<B<E>> list3; - A<E> a2; - B<C> b2; - C<A,B> c2; - - H h = new H(); - - B.<H>invocationTest(h); - - return new G(); - } - - class InnerClass{ - class InnerInner{ - class MegaInner{ - D d; - } - } - } - static class StaticInnerClass{} + // public A(B cB, C cC){} + + B<? extends C> crazyType; + + @MyAnnotation + @MyOtherAnnotation + int intVar, intVar2; + + @MyAnnotation + @MyOtherAnnotation + C rawC; + + B<B> b, b3; + C<B.InnerB, B> c; + D[] ds; + D d; + + @MyAnnotation + B<C>[] arrayOfGenericBsWithCTypeParam; + + @MyAnnotation + B<C[]> bWithArrayOfCs; + + List<A<B>> listWithNestedGenric; + Map<A, B> map; + List<? extends List<? extends Number>> listOfListsOfNumbers; + + @MyAnnotation + <T extends A, U extends B> F doSomething(B<C> paramB, C<B.InnerB, B> genericParam) { + List<B<E>> list3; + A<E> a2; + B<C> b2; + C<A, B> c2; + + H h = new H(); + + B.<H>invocationTest(h); + + return new G(); + } + + class InnerClass { + class InnerInner { + class MegaInner { + D d; + } + } + } + + static class StaticInnerClass {} } class NonPublic {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java index ab24f566..439a6862 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java @@ -6,6 +6,5 @@ static <T extends B> D invocationTest(T type) { return new D(); } - static class InnerB extends A{} - -} \ No newline at end of file + static class InnerB extends A {} +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java index 8e0d8468..d408ccc7 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java @@ -2,4 +2,4 @@ public interface E { void foo(A a); -} \ No newline at end of file +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java index 2420cc2d..4485e0f2 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java @@ -1,3 +1,3 @@ package org.hjug.parser.visitor.testclasses; -public class F {} \ No newline at end of file +public class F {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java index 7433a074..ff42f18c 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java @@ -1,3 +1,3 @@ package org.hjug.parser.visitor.testclasses; -public class G extends F {} \ No newline at end of file +public class G extends F {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java index bd7035f6..a7741b3d 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java @@ -1,7 +1,3 @@ package org.hjug.parser.visitor.testclasses; -public class H<T> extends B<T> { - - - -} \ No newline at end of file +public class H<T> extends B<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java index 181d3ed8..858447f0 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java @@ -4,5 +4,4 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE_USE) -@interface MyAnnotation { -} \ No newline at end of file +@interface MyAnnotation {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java index 5e9392ff..d55959ac 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java @@ -4,5 +4,4 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE_USE) -@interface MyOtherAnnotation { -} \ No newline at end of file +@interface MyOtherAnnotation {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java index e1407e35..df603d19 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java @@ -1,10 +1,8 @@ package org.hjug.parser.visitor.testclasses.methodInvocation; - public class A { - void doSomething(){ + void doSomething() { B.<H>invocationTest(new H()); } - } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java index 2faf1a59..8eba118d 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java @@ -3,5 +3,4 @@ public class B<T> { static <T extends B> void invocationTest(T type) {} - -} \ No newline at end of file +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java index 6c4a0d56..378e1e04 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java @@ -1,3 +1,3 @@ package org.hjug.parser.visitor.testclasses.methodInvocation; -public class H<T> extends B<T> {} \ No newline at end of file +public class H<T> extends B<T> {} From 6c5cb6b4d9886c6a74633097efd3a0926296034a Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Tue, 28 Jan 2025 20:18:07 -0600 Subject: [PATCH 13/78] #111 WIP Adding package cycle detection WIP Adding package cycle detection --- .../org/hjug/parser/JavaProjectParser.java | 14 ++++----- .../visitor/JavaClassDeclarationVisitor.java | 18 ++++++----- .../visitor/JavaMethodDeclarationVisitor.java | 13 ++++++-- .../visitor/JavaMethodInvocationVisitor.java | 8 ++++- .../visitor/JavaVariableTypeVisitor.java | 8 +++-- .../org/hjug/parser/visitor/JavaVisitor.java | 30 +++++++++++++------ .../hjug/parser/visitor/TypeProcessor.java | 16 +++++----- .../hjug/refactorfirst/report/HtmlReport.java | 1 + 8 files changed, 72 insertions(+), 36 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 046ea58f..66e898b2 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -9,7 +9,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; -import org.hjug.parser.visitor.JavaVariableTypeVisitor; +import org.hjug.parser.visitor.JavaVisitor; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; @@ -39,24 +39,24 @@ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory return classReferencesGraph; } - private void processWithOpenRewrite(String srcDir, Graph<String, DefaultWeightedEdge> classReferencesGraph) + private JavaVisitor processWithOpenRewrite(String srcDir, Graph<String, DefaultWeightedEdge> classReferencesGraph) throws IOException { File srcDirectory = new File(srcDir); - org.openrewrite.java.JavaParser javaParser = - JavaParser.fromJavaVersion().build(); + JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaVariableTypeVisitor<ExecutionContext> javaVariableTypeVisitor = - new JavaVariableTypeVisitor<>(classReferencesGraph); + JavaVisitor<ExecutionContext> javaVisitor = new JavaVisitor<>(classReferencesGraph); try (Stream<Path> walk = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) { List<Path> list = walk.collect(Collectors.toList()); javaParser .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) - .forEach(cu -> javaVariableTypeVisitor.visit(cu, ctx)); + .forEach(cu -> javaVisitor.visit(cu, ctx)); } + return javaVisitor; + // TODO: classReferencesGraph.removeAllVertices(); if vertex package not in codebase } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index a6b677df..fb6e792a 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -3,8 +3,8 @@ import java.util.List; import lombok.Getter; import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -17,15 +17,21 @@ public class JavaClassDeclarationVisitor<P> extends JavaIsoVisitor<P> implements @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph = - new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + @Getter + private Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); public JavaClassDeclarationVisitor() { - methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); } - public JavaClassDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + public JavaClassDeclarationVisitor( + Graph<String, DefaultWeightedEdge> classReferencesGraph, + Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; - methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); } @Override @@ -90,8 +96,6 @@ private void processBlock(J.Block block, String owningFqn) { if (statementInBlock instanceof J.Lambda) { J.Lambda lambda = (J.Lambda) statementInBlock; processType(owningFqn, lambda.getType()); - // FOLLOW UP: process body of lambda - // lambda.getBody() } } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java index 020459a9..e8af1bb8 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java @@ -3,8 +3,8 @@ import java.util.List; import lombok.Getter; import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -14,12 +14,19 @@ public class JavaMethodDeclarationVisitor<P> extends JavaIsoVisitor<P> implement @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph = - new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + @Getter + private Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); public JavaMethodDeclarationVisitor() {} - public JavaMethodDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + public JavaMethodDeclarationVisitor( + Graph<String, DefaultWeightedEdge> classReferencesGraph, + Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; + this.packageReferencesGraph = packageReferencesGraph; } @Override diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java index cf9e6806..01928a9d 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java @@ -13,8 +13,14 @@ public class JavaMethodInvocationVisitor implements TypeProcessor { @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph; - public JavaMethodInvocationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + @Getter + private Graph<String, DefaultWeightedEdge> packageReferencesGraph; + + public JavaMethodInvocationVisitor( + Graph<String, DefaultWeightedEdge> classReferencesGraph, + Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; + this.packageReferencesGraph = packageReferencesGraph; } public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvocation methodInvocation) { diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index 33ae0f0d..d594dbb5 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -4,8 +4,8 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.*; @@ -14,7 +14,11 @@ public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements Typ @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph = - new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + @Getter + private Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); public JavaVariableTypeVisitor() {} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index 01669332..3cf5abba 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -4,33 +4,40 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.*; @Slf4j -public class JavaVisitor<P> extends JavaIsoVisitor<P> { +public class JavaVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { @Getter - private final Set<String> packages = new HashSet<>(); + private final Set<String> packagesInCodebase = new HashSet<>(); // used for looking up files where classes reside @Getter private final Map<String, String> classToSourceFileMapping = new HashMap<>(); + @Getter + private Graph<String, DefaultWeightedEdge> classReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + @Getter + private final Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + private JavaVariableTypeVisitor<P> javaVariableTypeVisitor; private JavaClassDeclarationVisitor<P> javaClassDeclarationVisitor; - - Graph<String, DefaultWeightedEdge> classReferencesGraph = - new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + private JavaMethodDeclarationVisitor<P> javaMethodDeclarationVisitor; public JavaVisitor() {} public JavaVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { this.classReferencesGraph = classReferencesGraph; javaVariableTypeVisitor = new JavaVariableTypeVisitor<>(classReferencesGraph); - javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph); + javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); + javaMethodDeclarationVisitor = new JavaMethodDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); } @Override @@ -43,12 +50,17 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P return javaClassDeclarationVisitor.visitClassDeclaration(classDecl, p); } - // Get imported classes to check declared generics + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl, P p) { + return javaMethodDeclarationVisitor.visitMethodDeclaration(methodDecl, p); + } + + // Map each class to its source file @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { J.CompilationUnit compilationUnit = super.visitCompilationUnit(cu, p); - packages.add(compilationUnit.getPackageDeclaration().getPackageName()); + packagesInCodebase.add(compilationUnit.getPackageDeclaration().getPackageName()); for (J.ClassDeclaration aClass : compilationUnit.getClasses()) { classToSourceFileMapping.put( diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java index 8a705b35..48797940 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java @@ -111,6 +111,8 @@ default void processTypeParameter(String ownerFqn, J.TypeParameter typeParameter } } + Graph<String, DefaultWeightedEdge> getPackageReferencesGraph(); + Graph<String, DefaultWeightedEdge> getClassReferencesGraph(); /** @@ -121,16 +123,16 @@ default void processTypeParameter(String ownerFqn, J.TypeParameter typeParameter default void addType(String ownerFqn, String typeFqn) { if (ownerFqn.equals(typeFqn)) return; - Graph<String, DefaultWeightedEdge> graph = getClassReferencesGraph(); + Graph<String, DefaultWeightedEdge> classReferencesGraph = getClassReferencesGraph(); - graph.addVertex(ownerFqn); - graph.addVertex(typeFqn); + classReferencesGraph.addVertex(ownerFqn); + classReferencesGraph.addVertex(typeFqn); - if (!graph.containsEdge(ownerFqn, typeFqn)) { - graph.addEdge(ownerFqn, typeFqn); + if (!classReferencesGraph.containsEdge(ownerFqn, typeFqn)) { + classReferencesGraph.addEdge(ownerFqn, typeFqn); } else { - DefaultWeightedEdge edge = graph.getEdge(ownerFqn, typeFqn); - graph.setEdgeWeight(edge, graph.getEdgeWeight(edge) + 1); + DefaultWeightedEdge edge = classReferencesGraph.getEdge(ownerFqn, typeFqn); + classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); } } } 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 8fe70624..5d70544c 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -14,6 +14,7 @@ public class HtmlReport extends SimpleHtmlReport { int d3Threshold = 700; + // use Files.readString(Path.of(file)) // Created by generative AI and modified slightly static final String SUGIYAMA_SIGMA_GRAPH = "<script>\n" + "function sugiyamaLayout(graph) {\n" + " var layers = [];\n" From 4fe35e6b080b6b3997589eeccf448535feeaaa62 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Wed, 29 Jan 2025 20:26:23 -0600 Subject: [PATCH 14/78] #111 Attempted to add newType processing --- .../org/hjug/parser/JavaProjectParser.java | 6 +-- .../visitor/JavaClassDeclarationVisitor.java | 5 ++- .../visitor/JavaMethodInvocationVisitor.java | 18 +++----- .../parser/visitor/JavaNewClassVisitor.java | 14 +++++++ .../visitor/JavaVariableTypeVisitor.java | 4 +- .../org/hjug/parser/visitor/JavaVisitor.java | 13 +++--- .../visitor/JavaNewClassVisitorTest.java | 41 +++++++++++++++++++ 7 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 66e898b2..8a87f6bc 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -33,20 +33,20 @@ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory if (srcDirectory == null || srcDirectory.isEmpty()) { throw new IllegalArgumentException(); } else { - processWithOpenRewrite(srcDirectory, classReferencesGraph); + processWithOpenRewrite(srcDirectory); } return classReferencesGraph; } - private JavaVisitor processWithOpenRewrite(String srcDir, Graph<String, DefaultWeightedEdge> classReferencesGraph) + private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { File srcDirectory = new File(srcDir); JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaVisitor<ExecutionContext> javaVisitor = new JavaVisitor<>(classReferencesGraph); + JavaVisitor<ExecutionContext> javaVisitor = new JavaVisitor<>(); try (Stream<Path> walk = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) { List<Path> list = walk.collect(Collectors.toList()); diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index fb6e792a..de74c65c 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -31,6 +31,7 @@ public JavaClassDeclarationVisitor( Graph<String, DefaultWeightedEdge> classReferencesGraph, Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; + this.packageReferencesGraph = packageReferencesGraph; methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); } @@ -89,11 +90,13 @@ private void processInvocations(J.ClassDeclaration classDeclaration) { private void processBlock(J.Block block, String owningFqn) { if (null != block && null != block.getStatements()) { for (Statement statementInBlock : block.getStatements()) { + //TODO: handle case where method invocation result return value is an assignment + if (statementInBlock instanceof J.MethodInvocation) { J.MethodInvocation methodInvocation = (J.MethodInvocation) statementInBlock; methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); } - if (statementInBlock instanceof J.Lambda) { + else if (statementInBlock instanceof J.Lambda) { J.Lambda lambda = (J.Lambda) statementInBlock; processType(owningFqn, lambda.getType()); } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java index 01928a9d..cb3f0904 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java @@ -1,6 +1,7 @@ package org.hjug.parser.visitor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; import org.openrewrite.java.tree.Expression; @@ -8,20 +9,13 @@ // See RemoveMethodInvocationsVisitor for other visitor methods to override // Custom visitor - not extending IsoVisitor on purpose since it does not provide caller information -public class JavaMethodInvocationVisitor implements TypeProcessor { - - @Getter - private Graph<String, DefaultWeightedEdge> classReferencesGraph; - @Getter - private Graph<String, DefaultWeightedEdge> packageReferencesGraph; +@RequiredArgsConstructor +@Getter +public class JavaMethodInvocationVisitor implements TypeProcessor { - public JavaMethodInvocationVisitor( - Graph<String, DefaultWeightedEdge> classReferencesGraph, - Graph<String, DefaultWeightedEdge> packageReferencesGraph) { - this.classReferencesGraph = classReferencesGraph; - this.packageReferencesGraph = packageReferencesGraph; - } + private final Graph<String, DefaultWeightedEdge> classReferencesGraph; + private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvocation methodInvocation) { // getDeclaringType() returns the type that declared the method being invoked diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java new file mode 100644 index 00000000..e99039c6 --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java @@ -0,0 +1,14 @@ +package org.hjug.parser.visitor; + +import lombok.Getter; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; + +public class JavaNewClassVisitor {//<P> extends JavaIsoVisitor<P> implements TypeProcessor { + + // figure out later +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index d594dbb5..7146a257 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -22,8 +22,10 @@ public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements Typ public JavaVariableTypeVisitor() {} - public JavaVariableTypeVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { + public JavaVariableTypeVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph, + Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; + this.packageReferencesGraph = packageReferencesGraph; } @Override diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index 3cf5abba..b89c0704 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -27,15 +27,12 @@ public class JavaVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { private final Graph<String, DefaultWeightedEdge> packageReferencesGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - private JavaVariableTypeVisitor<P> javaVariableTypeVisitor; - private JavaClassDeclarationVisitor<P> javaClassDeclarationVisitor; - private JavaMethodDeclarationVisitor<P> javaMethodDeclarationVisitor; + private final JavaVariableTypeVisitor<P> javaVariableTypeVisitor; + private final JavaClassDeclarationVisitor<P> javaClassDeclarationVisitor; + private final JavaMethodDeclarationVisitor<P> javaMethodDeclarationVisitor; - public JavaVisitor() {} - - public JavaVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { - this.classReferencesGraph = classReferencesGraph; - javaVariableTypeVisitor = new JavaVariableTypeVisitor<>(classReferencesGraph); + public JavaVisitor() { + javaVariableTypeVisitor = new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); javaMethodDeclarationVisitor = new JavaMethodDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java new file mode 100644 index 00000000..7b597d78 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java @@ -0,0 +1,41 @@ +package org.hjug.parser.visitor; + +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; + +import java.io.File; +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; + +public class JavaNewClassVisitorTest { + + //@Test + void visitNewClass() throws IOException { + + File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses/newClass"); + + JavaParser javaParser = JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + + JavaNewClassVisitor classDeclarationVisitor = new JavaNewClassVisitor(); + +// List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); +// javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { +// classDeclarationVisitor.visit(cu, ctx); +// }); +// +// Graph<String, DefaultWeightedEdge> graph = classDeclarationVisitor.getClassReferencesGraph(); +// Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); + + } + +} From 440ed9a061acf3ce167bb7ab5d7bbc150fb42e04 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Thu, 30 Jan 2025 20:20:31 -0600 Subject: [PATCH 15/78] #111 newClass processing now works --- .../visitor/JavaClassDeclarationVisitor.java | 10 +++++-- .../parser/visitor/JavaNewClassVisitor.java | 24 ++++++++++++++--- .../visitor/JavaVariableTypeVisitor.java | 17 +++++++++++- .../hjug/parser/visitor/TypeProcessor.java | 2 ++ .../visitor/JavaNewClassVisitorTest.java | 27 ++++++++++++------- .../visitor/testclasses/newClass/A.java | 11 ++++++++ .../visitor/testclasses/newClass/B.java | 7 +++++ .../visitor/testclasses/newClass/C.java | 4 +++ .../visitor/testclasses/newClass/D.java | 4 +++ 9 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index de74c65c..d1fa0f96 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -1,6 +1,7 @@ package org.hjug.parser.visitor; import java.util.List; + import lombok.Getter; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; @@ -14,6 +15,7 @@ public class JavaClassDeclarationVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { private final JavaMethodInvocationVisitor methodInvocationVisitor; + private final JavaNewClassVisitor newClassVisitor; @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph = @@ -25,6 +27,7 @@ public class JavaClassDeclarationVisitor<P> extends JavaIsoVisitor<P> implements public JavaClassDeclarationVisitor() { methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); } public JavaClassDeclarationVisitor( @@ -33,6 +36,7 @@ public JavaClassDeclarationVisitor( this.classReferencesGraph = classReferencesGraph; this.packageReferencesGraph = packageReferencesGraph; methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); } @Override @@ -95,10 +99,12 @@ private void processBlock(J.Block block, String owningFqn) { if (statementInBlock instanceof J.MethodInvocation) { J.MethodInvocation methodInvocation = (J.MethodInvocation) statementInBlock; methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); - } - else if (statementInBlock instanceof J.Lambda) { + } else if (statementInBlock instanceof J.Lambda) { J.Lambda lambda = (J.Lambda) statementInBlock; processType(owningFqn, lambda.getType()); + } else if (statementInBlock instanceof J.NewClass) { + J.NewClass newClass = (J.NewClass) statementInBlock; + newClassVisitor.visitNewClass(owningFqn, newClass); } } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java index e99039c6..8ced066f 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java @@ -1,14 +1,30 @@ package org.hjug.parser.visitor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; -import org.jgrapht.graph.SimpleDirectedWeightedGraph; -import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -public class JavaNewClassVisitor {//<P> extends JavaIsoVisitor<P> implements TypeProcessor { +@RequiredArgsConstructor +@Getter +public class JavaNewClassVisitor implements TypeProcessor { - // figure out later + private final Graph<String, DefaultWeightedEdge> classReferencesGraph; + private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; + + public J.NewClass visitNewClass(String invokingFqn, J.NewClass newClass) { + + // getDeclaringType() returns the type that declared the method being invoked + processType(invokingFqn, newClass.getType()); + + for (Expression argument : newClass.getArguments()) { + processType(invokingFqn, argument.getType()); + } + + // TASK: process initializer block + + return newClass; + } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index 7146a257..231b169a 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -1,6 +1,7 @@ package org.hjug.parser.visitor; import java.util.List; + import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; @@ -20,12 +21,17 @@ public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements Typ private Graph<String, DefaultWeightedEdge> packageReferencesGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - public JavaVariableTypeVisitor() {} + private final JavaNewClassVisitor newClassVisitor; + + public JavaVariableTypeVisitor() { + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); + } public JavaVariableTypeVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph, Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; this.packageReferencesGraph = packageReferencesGraph; + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); } @Override @@ -84,6 +90,15 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m processType(ownerFqn, javaType); + // process variable instantiation if present + Expression initializer = variables.get(0).getInitializer(); + if (null != initializer && null != initializer.getType() + && initializer instanceof J.NewClass) { + J.NewClass newClassType = (J.NewClass) initializer; + newClassVisitor.visitNewClass(ownerFqn, newClassType); + } + + return variableDeclarations; } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java index 48797940..753b1384 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java @@ -135,4 +135,6 @@ default void addType(String ownerFqn, String typeFqn) { classReferencesGraph.setEdgeWeight(edge, classReferencesGraph.getEdgeWeight(edge) + 1); } } + + // TODO: process packages } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java index 7b597d78..6d9e853e 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java @@ -18,7 +18,7 @@ public class JavaNewClassVisitorTest { - //@Test + @Test void visitNewClass() throws IOException { File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses/newClass"); @@ -26,15 +26,24 @@ void visitNewClass() throws IOException { JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaNewClassVisitor classDeclarationVisitor = new JavaNewClassVisitor(); + JavaClassDeclarationVisitor classDeclarationVisitor = new JavaClassDeclarationVisitor(); + JavaVariableTypeVisitor variableTypeVisitor = new JavaVariableTypeVisitor(); -// List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); -// javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { -// classDeclarationVisitor.visit(cu, ctx); -// }); -// -// Graph<String, DefaultWeightedEdge> graph = classDeclarationVisitor.getClassReferencesGraph(); -// Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); + List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { + classDeclarationVisitor.visit(cu, ctx); + variableTypeVisitor.visit(cu, ctx); + }); + + Graph<String, DefaultWeightedEdge> assignmentGraph = variableTypeVisitor.getClassReferencesGraph(); + Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); + Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.B")); + Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.C")); + Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.D")); + + Graph<String, DefaultWeightedEdge> newInstanceGraph = classDeclarationVisitor.getClassReferencesGraph(); + Assertions.assertTrue(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); + Assertions.assertTrue(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.D")); } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java new file mode 100644 index 00000000..2d6663ca --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java @@ -0,0 +1,11 @@ +package org.hjug.parser.visitor.testclasses.newClass; + +public class A { + + B newClassMethod() { + D d = null; + new D(); + C c = new C(); + return new B(c, d); + } +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java new file mode 100644 index 00000000..e84120f4 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java @@ -0,0 +1,7 @@ +package org.hjug.parser.visitor.testclasses.newClass; + +public class B { + + public B(C c, D d) {} + +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java new file mode 100644 index 00000000..a3533b64 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java @@ -0,0 +1,4 @@ +package org.hjug.parser.visitor.testclasses.newClass; + +public class C { +} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java new file mode 100644 index 00000000..70a75f93 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java @@ -0,0 +1,4 @@ +package org.hjug.parser.visitor.testclasses.newClass; + +public class D { +} From 50fa0fc047c2412c76fa0c9b25fc9e7a639b7c83 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 3 Feb 2025 18:53:59 -0600 Subject: [PATCH 16/78] #111 Added additional assertions to JavaNewClassVisitorTest --- .../java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java index 6d9e853e..c8d1551d 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java @@ -43,8 +43,8 @@ void visitNewClass() throws IOException { Graph<String, DefaultWeightedEdge> newInstanceGraph = classDeclarationVisitor.getClassReferencesGraph(); Assertions.assertTrue(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); + Assertions.assertFalse(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.B")); + Assertions.assertFalse(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.C")); Assertions.assertTrue(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.D")); - } - } From 7f953f45fb304dee3338d10def2987b45d764a6c Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Tue, 4 Feb 2025 19:51:32 -0600 Subject: [PATCH 17/78] #111 Simplified newClass test Simplified newClass test Handling constructors in JavaMethodDeclarationVisitor --- .../org/hjug/parser/JavaProjectParser.java | 6 +-- .../visitor/JavaClassDeclarationVisitor.java | 3 -- .../visitor/JavaMethodDeclarationVisitor.java | 14 ++++-- .../visitor/JavaMethodInvocationVisitor.java | 4 -- .../parser/visitor/JavaNewClassVisitor.java | 4 +- .../visitor/JavaVariableTypeVisitor.java | 16 +++--- .../JavaMethodInvocationVisitorTest.java | 24 +++++---- .../visitor/JavaNewClassVisitorTest.java | 50 +++++++++++-------- .../testclasses/methodInvocation/A.java | 7 ++- .../testclasses/methodInvocation/B.java | 4 +- .../methodInvocation/{H.java => C.java} | 2 +- .../testclasses/methodInvocation/D.java | 3 ++ .../visitor/testclasses/newClass/A.java | 7 +-- .../visitor/testclasses/newClass/B.java | 3 +- .../visitor/testclasses/newClass/C.java | 3 +- .../visitor/testclasses/newClass/D.java | 4 -- 16 files changed, 86 insertions(+), 68 deletions(-) rename circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/{H.java => C.java} (64%) create mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 8a87f6bc..dc5fe3b7 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -13,6 +13,7 @@ import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; @@ -29,7 +30,7 @@ public class JavaProjectParser { */ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory) throws IOException { Graph<String, DefaultWeightedEdge> classReferencesGraph = - new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); if (srcDirectory == null || srcDirectory.isEmpty()) { throw new IllegalArgumentException(); } else { @@ -39,8 +40,7 @@ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory return classReferencesGraph; } - private JavaVisitor processWithOpenRewrite(String srcDir) - throws IOException { + private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { File srcDirectory = new File(srcDir); JavaParser javaParser = JavaParser.fromJavaVersion().build(); diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index d1fa0f96..acef0d8b 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -1,7 +1,6 @@ package org.hjug.parser.visitor; import java.util.List; - import lombok.Getter; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; @@ -94,8 +93,6 @@ private void processInvocations(J.ClassDeclaration classDeclaration) { private void processBlock(J.Block block, String owningFqn) { if (null != block && null != block.getStatements()) { for (Statement statementInBlock : block.getStatements()) { - //TODO: handle case where method invocation result return value is an assignment - if (statementInBlock instanceof J.MethodInvocation) { J.MethodInvocation methodInvocation = (J.MethodInvocation) statementInBlock; methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java index e8af1bb8..bf63c407 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java @@ -9,6 +9,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.NameTree; +import org.openrewrite.java.tree.TypeTree; public class JavaMethodDeclarationVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { @@ -34,11 +35,16 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, p); String owner = methodDeclaration.getMethodType().getDeclaringType().getFullyQualifiedName(); - JavaType returnType = methodDeclaration.getReturnTypeExpression().getType(); - // skip primitive variable declarations - if (!(returnType instanceof JavaType.Primitive)) { - processType(owner, returnType); + // if returnTypeExpression is null, a constructor declaration is being processed + TypeTree returnTypeExpression = methodDeclaration.getReturnTypeExpression(); + if (returnTypeExpression != null) { + JavaType returnType = returnTypeExpression.getType(); + + // skip primitive variable declarations + if (!(returnType instanceof JavaType.Primitive)) { + processType(owner, returnType); + } } for (J.Annotation leadingAnnotation : methodDeclaration.getLeadingAnnotations()) { diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java index cb3f0904..8fc2765f 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java @@ -28,10 +28,6 @@ public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvo } } - for (Expression argument : methodInvocation.getArguments()) { - processType(invokingFqn, argument.getType()); - } - return methodInvocation; } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java index 8ced066f..d8c6c599 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java @@ -15,15 +15,13 @@ public class JavaNewClassVisitor implements TypeProcessor { private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; public J.NewClass visitNewClass(String invokingFqn, J.NewClass newClass) { - - // getDeclaringType() returns the type that declared the method being invoked processType(invokingFqn, newClass.getType()); for (Expression argument : newClass.getArguments()) { processType(invokingFqn, argument.getType()); } - // TASK: process initializer block + // TASK: process initializer block??? return newClass; } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index 231b169a..6b59be69 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -1,7 +1,6 @@ package org.hjug.parser.visitor; import java.util.List; - import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; @@ -22,16 +21,20 @@ public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements Typ new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); private final JavaNewClassVisitor newClassVisitor; + private final JavaMethodInvocationVisitor methodInvocationVisitor; public JavaVariableTypeVisitor() { newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); } - public JavaVariableTypeVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph, - Graph<String, DefaultWeightedEdge> packageReferencesGraph) { + public JavaVariableTypeVisitor( + Graph<String, DefaultWeightedEdge> classReferencesGraph, + Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; this.packageReferencesGraph = packageReferencesGraph; newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); } @Override @@ -92,13 +95,14 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m // process variable instantiation if present Expression initializer = variables.get(0).getInitializer(); - if (null != initializer && null != initializer.getType() - && initializer instanceof J.NewClass) { + if (null != initializer && null != initializer.getType() && initializer instanceof J.MethodInvocation) { + J.MethodInvocation methodInvocation = (J.MethodInvocation) initializer; + methodInvocationVisitor.visitMethodInvocation(ownerFqn, methodInvocation); + } else if (null != initializer && null != initializer.getType() && initializer instanceof J.NewClass) { J.NewClass newClassType = (J.NewClass) initializer; newClassVisitor.visitNewClass(ownerFqn, newClassType); } - return variableDeclarations; } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java index ae748ea3..651722ce 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; @@ -25,20 +26,30 @@ void visitMethodInvocations() throws IOException { JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaClassDeclarationVisitor<ExecutionContext> classDeclarationVisitor = new JavaClassDeclarationVisitor<>(); + Graph<String, DefaultWeightedEdge> classReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + JavaClassDeclarationVisitor<ExecutionContext> classDeclarationVisitor = + new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); + JavaVariableTypeVisitor<ExecutionContext> variableTypeVisitor = + new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { classDeclarationVisitor.visit(cu, ctx); + variableTypeVisitor.visit(cu, ctx); }); Graph<String, DefaultWeightedEdge> graph = classDeclarationVisitor.getClassReferencesGraph(); Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.A")); Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.B")); - Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.H")); + Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.C")); Assertions.assertEquals( - 1, + 2, graph.getEdgeWeight(graph.getEdge( "org.hjug.parser.visitor.testclasses.methodInvocation.A", "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); @@ -46,11 +57,6 @@ void visitMethodInvocations() throws IOException { 2, graph.getEdgeWeight(graph.getEdge( "org.hjug.parser.visitor.testclasses.methodInvocation.A", - "org.hjug.parser.visitor.testclasses.methodInvocation.H"))); - Assertions.assertEquals( - 1, - graph.getEdgeWeight(graph.getEdge( - "org.hjug.parser.visitor.testclasses.methodInvocation.H", - "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); + "org.hjug.parser.visitor.testclasses.methodInvocation.C"))); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java index c8d1551d..81b467e0 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java @@ -1,13 +1,5 @@ package org.hjug.parser.visitor; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.java.JavaParser; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -15,6 +7,14 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; public class JavaNewClassVisitorTest { @@ -26,8 +26,16 @@ void visitNewClass() throws IOException { JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaClassDeclarationVisitor classDeclarationVisitor = new JavaClassDeclarationVisitor(); - JavaVariableTypeVisitor variableTypeVisitor = new JavaVariableTypeVisitor(); + Graph<String, DefaultWeightedEdge> classReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + JavaClassDeclarationVisitor<ExecutionContext> classDeclarationVisitor = + new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); + JavaVariableTypeVisitor<ExecutionContext> variableTypeVisitor = + new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { @@ -35,16 +43,16 @@ void visitNewClass() throws IOException { variableTypeVisitor.visit(cu, ctx); }); - Graph<String, DefaultWeightedEdge> assignmentGraph = variableTypeVisitor.getClassReferencesGraph(); - Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); - Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.B")); - Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.C")); - Assertions.assertTrue(assignmentGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.D")); - - Graph<String, DefaultWeightedEdge> newInstanceGraph = classDeclarationVisitor.getClassReferencesGraph(); - Assertions.assertTrue(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); - Assertions.assertFalse(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.B")); - Assertions.assertFalse(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.C")); - Assertions.assertTrue(newInstanceGraph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.D")); + Graph<String, DefaultWeightedEdge> graph = variableTypeVisitor.getClassReferencesGraph(); + Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); + Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.B")); + Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.C")); + + // TODO: Investigate further to confirm correctness + Assertions.assertEquals( + 3, + graph.getEdgeWeight(graph.getEdge( + "org.hjug.parser.visitor.testclasses.newClass.A", + "org.hjug.parser.visitor.testclasses.newClass.C"))); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java index df603d19..c214ea8c 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java @@ -2,7 +2,10 @@ public class A { - void doSomething() { - B.<H>invocationTest(new H()); + A doSomething() { + B.<C>invocationTest(new D()); + A a = B.<C>invocationTest(new D()); + // TODO: add visitor for J.ReturnType + return B.<C>invocationTest(new D()); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java index 8eba118d..69e6ffe5 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java @@ -2,5 +2,7 @@ public class B<T> { - static <T extends B> void invocationTest(T type) {} + static <T extends B> A invocationTest(T type) { + return new A(); + } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/C.java similarity index 64% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java rename to circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/C.java index 378e1e04..1b5d71d1 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/H.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/C.java @@ -1,3 +1,3 @@ package org.hjug.parser.visitor.testclasses.methodInvocation; -public class H<T> extends B<T> {} +public class C<T> extends B<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java new file mode 100644 index 00000000..ad1abcd9 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java @@ -0,0 +1,3 @@ +package org.hjug.parser.visitor.testclasses.methodInvocation; + +public class D<T> extends C<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java index 2d6663ca..b0b957fd 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java @@ -3,9 +3,10 @@ public class A { B newClassMethod() { - D d = null; - new D(); + new C(); C c = new C(); - return new B(c, d); + + // TODO: add visitor for J.ReturnType + return new B(c); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java index e84120f4..64a1bfeb 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java @@ -2,6 +2,5 @@ public class B { - public B(C c, D d) {} - + public B(C c) {} } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java index a3533b64..9e27dc22 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java @@ -1,4 +1,3 @@ package org.hjug.parser.visitor.testclasses.newClass; -public class C { -} +public class C {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java deleted file mode 100644 index 70a75f93..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/D.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.hjug.parser.visitor.testclasses.newClass; - -public class D { -} From fa4649c8a7d57ff8a2999a9c3435b172000ca561 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Tue, 4 Feb 2025 20:17:58 -0600 Subject: [PATCH 18/78] #111 Separated visitors since not all were working under JavaVisitor - Pulled visitors up since not all were working under JavaVisitor - Passing back class graph - JavaProjectParserTests now passing --- .../org/hjug/parser/JavaProjectParser.java | 24 +++++++++++++++---- .../org/hjug/parser/visitor/JavaVisitor.java | 18 ++++---------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index dc5fe3b7..35bb9c4c 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -9,9 +9,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; +import org.hjug.parser.visitor.JavaMethodDeclarationVisitor; +import org.hjug.parser.visitor.JavaVariableTypeVisitor; import org.hjug.parser.visitor.JavaVisitor; import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.ExecutionContext; @@ -34,7 +35,7 @@ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory if (srcDirectory == null || srcDirectory.isEmpty()) { throw new IllegalArgumentException(); } else { - processWithOpenRewrite(srcDirectory); + classReferencesGraph = processWithOpenRewrite(srcDirectory).getClassReferencesGraph(); } return classReferencesGraph; @@ -46,13 +47,28 @@ private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaVisitor<ExecutionContext> javaVisitor = new JavaVisitor<>(); + final Graph<String, DefaultWeightedEdge> classReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + final Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + final JavaVisitor<ExecutionContext> javaVisitor = + new JavaVisitor<>(classReferencesGraph, packageReferencesGraph); + final JavaVariableTypeVisitor<ExecutionContext> javaVariableTypeVisitor = + new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); + final JavaMethodDeclarationVisitor<ExecutionContext> javaMethodDeclarationVisitor = + new JavaMethodDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); try (Stream<Path> walk = Files.walk(Paths.get(srcDirectory.getAbsolutePath()))) { List<Path> list = walk.collect(Collectors.toList()); javaParser .parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx) - .forEach(cu -> javaVisitor.visit(cu, ctx)); + .forEach(cu -> { + javaVisitor.visit(cu, ctx); + javaVariableTypeVisitor.visit(cu, ctx); + javaMethodDeclarationVisitor.visit(cu, ctx); + }); } return javaVisitor; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index b89c0704..7b6b4cdb 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -24,22 +24,19 @@ public class JavaVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); @Getter - private final Graph<String, DefaultWeightedEdge> packageReferencesGraph = + private Graph<String, DefaultWeightedEdge> packageReferencesGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - private final JavaVariableTypeVisitor<P> javaVariableTypeVisitor; private final JavaClassDeclarationVisitor<P> javaClassDeclarationVisitor; - private final JavaMethodDeclarationVisitor<P> javaMethodDeclarationVisitor; public JavaVisitor() { - javaVariableTypeVisitor = new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); - javaMethodDeclarationVisitor = new JavaMethodDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); } - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, P p) { - return javaVariableTypeVisitor.visitVariableDeclarations(multiVariable, p); + public JavaVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph, Graph<String, DefaultWeightedEdge> packageReferencesGraph) { + this.classReferencesGraph = classReferencesGraph; + this.packageReferencesGraph = packageReferencesGraph; + javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); } @Override @@ -47,11 +44,6 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P return javaClassDeclarationVisitor.visitClassDeclaration(classDecl, p); } - @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl, P p) { - return javaMethodDeclarationVisitor.visitMethodDeclaration(methodDecl, p); - } - // Map each class to its source file @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { From da334f0f0e5692619e4d95e58a19bd19e9244246 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Tue, 4 Feb 2025 20:18:16 -0600 Subject: [PATCH 19/78] #111 Separated visitors since not all were working under JavaVisitor - Pulled visitors up since not all were working under JavaVisitor - Passing back class graph - JavaProjectParserTests now passing --- .../src/main/java/org/hjug/parser/visitor/JavaVisitor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index 7b6b4cdb..13c1b91a 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -33,7 +33,9 @@ public JavaVisitor() { javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); } - public JavaVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph, Graph<String, DefaultWeightedEdge> packageReferencesGraph) { + public JavaVisitor( + Graph<String, DefaultWeightedEdge> classReferencesGraph, + Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; this.packageReferencesGraph = packageReferencesGraph; javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); From 535c41ad347d4d978ebee7d6854749e430e39f27 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Wed, 5 Feb 2025 18:23:06 -0600 Subject: [PATCH 20/78] #111 Minor code cleanup --- .../main/java/org/hjug/parser/JavaProjectParser.java | 5 +++-- .../main/java/org/hjug/parser/visitor/JavaVisitor.java | 10 ++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 35bb9c4c..6e3d1b8e 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -30,8 +30,7 @@ public class JavaProjectParser { * @throws IOException */ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory) throws IOException { - Graph<String, DefaultWeightedEdge> classReferencesGraph = - new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + Graph<String, DefaultWeightedEdge> classReferencesGraph; if (srcDirectory == null || srcDirectory.isEmpty()) { throw new IllegalArgumentException(); } else { @@ -71,6 +70,8 @@ private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { }); } + + return javaVisitor; // TODO: classReferencesGraph.removeAllVertices(); if vertex package not in codebase diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index 13c1b91a..5fa6f566 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -20,19 +20,13 @@ public class JavaVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { private final Map<String, String> classToSourceFileMapping = new HashMap<>(); @Getter - private Graph<String, DefaultWeightedEdge> classReferencesGraph = - new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + private final Graph<String, DefaultWeightedEdge> classReferencesGraph; @Getter - private Graph<String, DefaultWeightedEdge> packageReferencesGraph = - new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; private final JavaClassDeclarationVisitor<P> javaClassDeclarationVisitor; - public JavaVisitor() { - javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); - } - public JavaVisitor( Graph<String, DefaultWeightedEdge> classReferencesGraph, Graph<String, DefaultWeightedEdge> packageReferencesGraph) { From 3a2a5d941358786f85c8a3b63bf350a2943647aa Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Wed, 5 Feb 2025 19:04:09 -0600 Subject: [PATCH 21/78] #111 Using Directed graphs instead of simple ones - Using Directed graphs instead of simple ones - Declaring JGraphT version in parent pom --- circular-reference-detector/pom.xml | 1 - .../org/hjug/parser/JavaProjectParser.java | 6 +++--- .../hjug/parser/visitor/JavaVisitorTest.java | 19 ++++++++++++++----- pom.xml | 7 +++++++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml index 9cab67f6..6f5acc5a 100644 --- a/circular-reference-detector/pom.xml +++ b/circular-reference-detector/pom.xml @@ -39,7 +39,6 @@ <dependency> <groupId>org.jgrapht</groupId> <artifactId>jgrapht-core</artifactId> - <version>1.5.2</version> </dependency> <dependency> <groupId>org.openrewrite</groupId> diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 6e3d1b8e..d105d98f 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -13,8 +13,8 @@ import org.hjug.parser.visitor.JavaVariableTypeVisitor; import org.hjug.parser.visitor.JavaVisitor; import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; -import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; @@ -47,10 +47,10 @@ private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); final Graph<String, DefaultWeightedEdge> classReferencesGraph = - new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); final Graph<String, DefaultWeightedEdge> packageReferencesGraph = - new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); final JavaVisitor<ExecutionContext> javaVisitor = new JavaVisitor<>(classReferencesGraph, packageReferencesGraph); diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java index 8e93f651..47381076 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java @@ -8,6 +8,10 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; + +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; @@ -26,7 +30,14 @@ void visitClasses() throws IOException { JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); - JavaVisitor javaVisitor = new JavaVisitor(); + final Graph<String, DefaultWeightedEdge> classReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + final Graph<String, DefaultWeightedEdge> packageReferencesGraph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + final JavaVisitor<ExecutionContext> javaVisitor = + new JavaVisitor<>(classReferencesGraph, packageReferencesGraph); List<String> fqdns = new ArrayList<>(); @@ -34,11 +45,9 @@ void visitClasses() throws IOException { javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach((cu) -> { System.out.println(cu.getSourcePath()); javaVisitor.visit(cu, ctx); - // fqdns.addAll(customVisitor.getFqdns()); }); - // return fqdns; - } + fqdns.addAll(javaVisitor.getPackagesInCodebase()); - class Inner {} + } } diff --git a/pom.xml b/pom.xml index 62946c38..f5af32f4 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ <modules> <module>test-resources</module> + <module>dsm</module> <module>circular-reference-detector</module> <module>change-proneness-ranker</module> <module>effort-ranker</module> @@ -144,6 +145,12 @@ <scope>compile</scope> </dependency> + <dependency> + <groupId>org.jgrapht</groupId> + <artifactId>jgrapht-core</artifactId> + <version>1.5.2</version> + </dependency> + <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-java</artifactId> From fe9401f8f656f58023b456f2e1dc0fbe2a64744d Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Wed, 5 Feb 2025 20:08:13 -0600 Subject: [PATCH 22/78] #111 Applied Spotless --- .../src/main/java/org/hjug/parser/JavaProjectParser.java | 2 -- .../src/main/java/org/hjug/parser/visitor/JavaVisitor.java | 1 - .../src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java | 2 -- 3 files changed, 5 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index d105d98f..324b7754 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -70,8 +70,6 @@ private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { }); } - - return javaVisitor; // TODO: classReferencesGraph.removeAllVertices(); if vertex package not in codebase diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index 5fa6f566..8f1faad7 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -5,7 +5,6 @@ import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; -import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.*; diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java index 47381076..69cf1f67 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; @@ -48,6 +47,5 @@ void visitClasses() throws IOException { }); fqdns.addAll(javaVisitor.getPackagesInCodebase()); - } } From 26d64b200eb0074ad56f736b2f4744d7a0c45f35 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Wed, 5 Feb 2025 20:09:34 -0600 Subject: [PATCH 23/78] #129 initial commit of DSM Initial commit of DSM and corresponding unit tests --- dsm/pom.xml | 21 +++ dsm/src/main/java/org/hjug/dsm/DSM.java | 176 ++++++++++++++++++++ dsm/src/test/java/org/hjug/dsm/DSMTest.java | 75 +++++++++ 3 files changed, 272 insertions(+) create mode 100644 dsm/pom.xml create mode 100644 dsm/src/main/java/org/hjug/dsm/DSM.java create mode 100644 dsm/src/test/java/org/hjug/dsm/DSMTest.java diff --git a/dsm/pom.xml b/dsm/pom.xml new file mode 100644 index 00000000..72b9b4e4 --- /dev/null +++ b/dsm/pom.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.hjug.refactorfirst</groupId> + <artifactId>refactor-first</artifactId> + <version>0.6.3-SNAPSHOT</version> + </parent> + + <artifactId>org.hjug.refactorfirst.dsm</artifactId> + + <dependencies> + <dependency> + <groupId>org.jgrapht</groupId> + <artifactId>jgrapht-core</artifactId> + </dependency> + </dependencies> +</project> \ No newline at end of file diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java new file mode 100644 index 00000000..4dd45ab3 --- /dev/null +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -0,0 +1,176 @@ +package org.hjug.dsm; + +import java.util.*; +import org.jgrapht.Graph; +import org.jgrapht.alg.cycle.CycleDetector; +import org.jgrapht.graph.DefaultWeightedEdge; + +/* +Based on https://sookocheff.com/post/dsm/improving-software-architecture-using-design-structure-matrix/#optimizing-processes +and modified slightly +Used the following prompt to generate this class with generative AI: + +Provide a complete java implementation of a dsm using jgrapht, labeling the rows and columns, +with the node values representing the edge weight between nodes. +Edge weights should be cast to integers. +Identify which edge above the diagonal in the set of cycles contained in the graph that will be the easiest to remove first. +Order nodes using the following logic: +Order nodes with empty rows at the top of the DSM. +Nodes with empty columns should be placed on the right of the DSM. +Find any cycles in the remaining elements through a depth-first search. +Group together all activities in a cycle as a single activity, and treat the group as its own step in the sequence. +*/ +public class DSM { + + static List<String> orderVertices(Graph<String, DefaultWeightedEdge> graph) { + List<String> orderedVertices = new ArrayList<>(); + Set<String> vertices = graph.vertexSet(); + + // Find nodes with empty rows (no outgoing edges) + for (String vertex : vertices) { + if (graph.outgoingEdgesOf(vertex).isEmpty()) { + orderedVertices.add(vertex); + } + } + + // Find nodes with empty columns (no incoming edges) + for (String vertex : vertices) { + if (graph.incomingEdgesOf(vertex).isEmpty() && !orderedVertices.contains(vertex)) { + orderedVertices.add(vertex); + } + } + + // Find cycles in the remaining elements + CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); + Set<String> remainingVertices = new HashSet<>(vertices); + orderedVertices.forEach(remainingVertices::remove); + + while (!remainingVertices.isEmpty()) { + String startVertex = remainingVertices.iterator().next(); + Set<String> cycle = cycleDetector.findCyclesContainingVertex(startVertex); + + if (!cycle.isEmpty()) { + orderedVertices.addAll(cycle); + remainingVertices.removeAll(cycle); + } else { + orderedVertices.add(startVertex); + remainingVertices.remove(startVertex); + } + } + + return orderedVertices; + } + + public static DefaultWeightedEdge identifyOptimalBakcwardEdgeToRemove( + Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { + CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); + Set<String> cycleVertices = cycleDetector.findCycles(); + + if (!cycleVertices.isEmpty()) { + DefaultWeightedEdge minEdge = null; + double minWeight = Double.MAX_VALUE; + + for (String vertex : cycleVertices) { + for (DefaultWeightedEdge edge : graph.edgesOf(vertex)) { + if (cycleVertices.contains(graph.getEdgeTarget(edge))) { + int rowIndex = orderedVertices.indexOf(graph.getEdgeSource(edge)); + int colIndex = orderedVertices.indexOf(graph.getEdgeTarget(edge)); + // Check if the edge is above the diagonal + if (rowIndex < colIndex) { + double weight = graph.getEdgeWeight(edge); + if (weight < minWeight) { + minWeight = weight; + minEdge = edge; + } + // return immediately b/c we shouldn't have edge weight less than 1 + if (weight == 1) { + return edge; + } + } + } + } + } + return minEdge; + } + return null; + } + + static List<DefaultWeightedEdge> identifyAllBackwardMinWeightEdgesToRemove( + Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { + CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); + Set<String> cycleVertices = cycleDetector.findCycles(); + + if (!cycleVertices.isEmpty()) { + List<DefaultWeightedEdge> edgesToRemove = new ArrayList<>(); + double minWeight = Double.MAX_VALUE; + + for (String vertex : cycleVertices) { + for (DefaultWeightedEdge edge : graph.edgesOf(vertex)) { + if (cycleVertices.contains(graph.getEdgeTarget(edge))) { + int rowIndex = orderedVertices.indexOf(graph.getEdgeSource(edge)); + int colIndex = orderedVertices.indexOf(graph.getEdgeTarget(edge)); + // Check if the edge is above the diagonal + if (rowIndex < colIndex) { + double weight = graph.getEdgeWeight(edge); + if (weight < minWeight) { + edgesToRemove.clear(); + minWeight = weight; + edgesToRemove.add(edge); + } else if (weight == minWeight && !edgesToRemove.contains(edge)) { + edgesToRemove.add(edge); + } + } + } + } + } + return edgesToRemove; + } + return new ArrayList<>(); + } + + static List<DefaultWeightedEdge> getAllBackwardEdges( + Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { + CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); + Set<String> cycleVertices = cycleDetector.findCycles(); + + if (!cycleVertices.isEmpty()) { + List<DefaultWeightedEdge> edgesToRemove = new ArrayList<>(); + + for (String vertex : cycleVertices) { + for (DefaultWeightedEdge edge : graph.edgesOf(vertex)) { + if (cycleVertices.contains(graph.getEdgeTarget(edge))) { + int rowIndex = orderedVertices.indexOf(graph.getEdgeSource(edge)); + int colIndex = orderedVertices.indexOf(graph.getEdgeTarget(edge)); + // Check if the edge is above the diagonal + // and has not been added yet + if (rowIndex < colIndex && !edgesToRemove.contains(edge)) { + edgesToRemove.add(edge); + } + } + } + } + return edgesToRemove; + } + return new ArrayList<>(); + } + + public static void printDSM(Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { + // Print column labels + System.out.print(" "); + for (String vertex : orderedVertices) { + System.out.print(vertex + " "); + } + System.out.println(); + + // Print rows with edge weights + for (String rowVertex : orderedVertices) { + System.out.print(rowVertex + " "); + for (String colVertex : orderedVertices) { + DefaultWeightedEdge edge = graph.getEdge(rowVertex, colVertex); + int weight = (edge != null) ? (int) graph.getEdgeWeight(edge) : 0; + System.out.print(weight + " "); + } + System.out.println(); + } + } +} diff --git a/dsm/src/test/java/org/hjug/dsm/DSMTest.java b/dsm/src/test/java/org/hjug/dsm/DSMTest.java new file mode 100644 index 00000000..bb257fbf --- /dev/null +++ b/dsm/src/test/java/org/hjug/dsm/DSMTest.java @@ -0,0 +1,75 @@ +package org.hjug.dsm; + +import java.util.List; +import org.jgrapht.Graph; +import org.jgrapht.Graphs; +import org.jgrapht.graph.DefaultDirectedWeightedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DSMTest { + + Graph<String, DefaultWeightedEdge> graph; + List<String> orderedVertices; + + @BeforeEach + void setUp() { + // Create a weighted graph + graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + // Add vertices + graph.addVertex("A"); + graph.addVertex("B"); + graph.addVertex("C"); + graph.addVertex("D"); + graph.addVertex("E"); + + // Add edges with weights + Graphs.addEdgeWithVertices(graph, "A", "B", 1.0); + Graphs.addEdgeWithVertices(graph, "A", "C", 1.0); + Graphs.addEdgeWithVertices(graph, "B", "D", 3.0); + Graphs.addEdgeWithVertices(graph, "C", "D", 4.0); + Graphs.addEdgeWithVertices(graph, "D", "A", 5.0); // Adding a cycle + + // Order nodes according to the specified logic + orderedVertices = DSM.orderVertices(graph); + } + + @Test + void minWeightEdges() { + // Identify which edge above the diagonal in the set of cycles should be removed first + List<DefaultWeightedEdge> edgesToRemove = DSM.identifyAllBackwardMinWeightEdgesToRemove(graph, orderedVertices); + + // Print the DSM + DSM.printDSM(graph, orderedVertices); + + // find the edges to remove + if (!edgesToRemove.isEmpty()) { + for (DefaultWeightedEdge edgeToRemove : edgesToRemove) { + System.out.println("Edge to remove: " + graph.getEdgeSource(edgeToRemove) + " -> " + + graph.getEdgeTarget(edgeToRemove)); + } + } else { + System.out.println("No cycles detected."); + } + + // Contains "(A : B)" and "(A : C)" + Assertions.assertEquals(2, edgesToRemove.size()); + } + + @Test + void optimalBakcwardEdgeToRemove() { + // Identify which edge above the diagonal in the set of cycles should be removed first + DefaultWeightedEdge edgeToRemove = DSM.identifyOptimalBakcwardEdgeToRemove(graph, orderedVertices); + Assertions.assertEquals("(A : B)", edgeToRemove.toString()); + } + + @Test + void optimalEdge() { + // Identify which edge above the diagonal in the set of cycles should be removed first + List<DefaultWeightedEdge> edges = DSM.getAllBackwardEdges(graph, orderedVertices); + Assertions.assertEquals(3, edges.size()); + } +} From 4321870e1d216887d38029caf38f4d65c5662d3a Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 10 Feb 2025 19:21:24 -0600 Subject: [PATCH 24/78] #129 Second pass at DSM --- dsm/src/main/java/org/hjug/dsm/DSM.java | 307 +++++++++++--------- dsm/src/test/java/org/hjug/dsm/DSMTest.java | 75 ----- 2 files changed, 167 insertions(+), 215 deletions(-) delete mode 100644 dsm/src/test/java/org/hjug/dsm/DSMTest.java diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index 4dd45ab3..c06a3a1f 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -1,176 +1,203 @@ package org.hjug.dsm; -import java.util.*; import org.jgrapht.Graph; -import org.jgrapht.alg.cycle.CycleDetector; +import org.jgrapht.Graphs; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; +import org.jgrapht.alg.cycle.TarjanSimpleCycles; + +import java.util.*; /* -Based on https://sookocheff.com/post/dsm/improving-software-architecture-using-design-structure-matrix/#optimizing-processes -and modified slightly -Used the following prompt to generate this class with generative AI: - -Provide a complete java implementation of a dsm using jgrapht, labeling the rows and columns, -with the node values representing the edge weight between nodes. -Edge weights should be cast to integers. -Identify which edge above the diagonal in the set of cycles contained in the graph that will be the easiest to remove first. -Order nodes using the following logic: -Order nodes with empty rows at the top of the DSM. -Nodes with empty columns should be placed on the right of the DSM. -Find any cycles in the remaining elements through a depth-first search. -Group together all activities in a cycle as a single activity, and treat the group as its own step in the sequence. -*/ + * Generated with Generative AI using a prompt similar to the following: +Provide a complete implementation of a Numerical DSM with integer weighted edges in Java. +Include as many comments as possible in the implementation to make it easy to understand. +Use JGraphT classes and methods to the greatest extent possible. +Construction of the DSM should take place as follows. +First, Place nodes with empty rows at the top of the DSM. +Second, Place nodes with empty columns on the right of the DSM. +Third, identify strongly connected nodes and treat them as a single node using JGraphT's TarjanSimpleCycles class. +Fourth, order all edges in the DSM with a topological sort that permits cycles in the graph after identifying strongly connected components. +Fifth, Print the DSM in a method that performs no sorting or ordering - it should only print rows and columns. +When the DSM is printed, label the columns and rows. +Place dashes on the diagonal when printing. +include a method tht returns all edges above the diagonal. +include another method that returns the optimal edge above the diagonal to remove, +include a third method that identifies all minimum weight edges to remove above the diagonal. + */ + + +//This is looking good, but is still a work in progress. This may not be needed at all. public class DSM { + private Graph<String, DefaultWeightedEdge> graph; + private List<String> sortedActivities; - static List<String> orderVertices(Graph<String, DefaultWeightedEdge> graph) { - List<String> orderedVertices = new ArrayList<>(); - Set<String> vertices = graph.vertexSet(); + public DSM() { + graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + sortedActivities = new ArrayList<>(); + } - // Find nodes with empty rows (no outgoing edges) - for (String vertex : vertices) { - if (graph.outgoingEdgesOf(vertex).isEmpty()) { - orderedVertices.add(vertex); - } - } + public void addActivity(String activity) { + graph.addVertex(activity); + } - // Find nodes with empty columns (no incoming edges) - for (String vertex : vertices) { - if (graph.incomingEdgesOf(vertex).isEmpty() && !orderedVertices.contains(vertex)) { - orderedVertices.add(vertex); - } + public void addDependency(String from, String to, int weight) { + DefaultWeightedEdge edge = graph.addEdge(from, to); + if (edge != null) { + graph.setEdgeWeight(edge, weight); } + } - // Find cycles in the remaining elements - CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); - Set<String> remainingVertices = new HashSet<>(vertices); - orderedVertices.forEach(remainingVertices::remove); - - while (!remainingVertices.isEmpty()) { - String startVertex = remainingVertices.iterator().next(); - Set<String> cycle = cycleDetector.findCyclesContainingVertex(startVertex); - - if (!cycle.isEmpty()) { - orderedVertices.addAll(cycle); - remainingVertices.removeAll(cycle); - } else { - orderedVertices.add(startVertex); - remainingVertices.remove(startVertex); + public void orderVertices() { + List<Set<String>> sccs = findStronglyConnectedComponents(); + sortedActivities = topologicalSort(sccs); + } + + public void printDSM() { + System.out.println("Design Structure Matrix:"); + System.out.print(" "); + for (String col : sortedActivities) { + System.out.print(col + " "); + } + System.out.println(); + for (String row : sortedActivities) { + System.out.print(row + " "); + for (String col : sortedActivities) { + if (col.equals(row)) { + System.out.print("- "); + } else { + DefaultWeightedEdge edge = graph.getEdge(col, row); // Flipped matrix + if (edge != null) { + System.out.print((int) graph.getEdgeWeight(edge) + " "); + } else { + System.out.print("0 "); + } + } } + System.out.println(); } + } - return orderedVertices; + private List<Set<String>> findStronglyConnectedComponents() { + TarjanSimpleCycles<String, DefaultWeightedEdge> tarjan = new TarjanSimpleCycles<>(graph); + List<List<String>> cycles = tarjan.findSimpleCycles(); + List<Set<String>> sccs = new ArrayList<>(); + for (List<String> cycle : cycles) { + sccs.add(new HashSet<>(cycle)); + } + return sccs; } - public static DefaultWeightedEdge identifyOptimalBakcwardEdgeToRemove( - Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { - CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); - Set<String> cycleVertices = cycleDetector.findCycles(); - - if (!cycleVertices.isEmpty()) { - DefaultWeightedEdge minEdge = null; - double minWeight = Double.MAX_VALUE; - - for (String vertex : cycleVertices) { - for (DefaultWeightedEdge edge : graph.edgesOf(vertex)) { - if (cycleVertices.contains(graph.getEdgeTarget(edge))) { - int rowIndex = orderedVertices.indexOf(graph.getEdgeSource(edge)); - int colIndex = orderedVertices.indexOf(graph.getEdgeTarget(edge)); - // Check if the edge is above the diagonal - if (rowIndex < colIndex) { - double weight = graph.getEdgeWeight(edge); - if (weight < minWeight) { - minWeight = weight; - minEdge = edge; - } - // return immediately b/c we shouldn't have edge weight less than 1 - if (weight == 1) { - return edge; - } - } - } + private List<String> topologicalSort(List<Set<String>> sccs) { + List<String> sortedActivities = new ArrayList<>(); + Set<String> visited = new HashSet<>(); + + for (Set<String> scc : sccs) { + for (String activity : scc) { + if (!visited.contains(activity)) { + topologicalSortUtil(activity, visited, sortedActivities); } } - return minEdge; } - return null; + + Collections.reverse(sortedActivities); + return sortedActivities; } - static List<DefaultWeightedEdge> identifyAllBackwardMinWeightEdgesToRemove( - Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { - CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); - Set<String> cycleVertices = cycleDetector.findCycles(); - - if (!cycleVertices.isEmpty()) { - List<DefaultWeightedEdge> edgesToRemove = new ArrayList<>(); - double minWeight = Double.MAX_VALUE; - - for (String vertex : cycleVertices) { - for (DefaultWeightedEdge edge : graph.edgesOf(vertex)) { - if (cycleVertices.contains(graph.getEdgeTarget(edge))) { - int rowIndex = orderedVertices.indexOf(graph.getEdgeSource(edge)); - int colIndex = orderedVertices.indexOf(graph.getEdgeTarget(edge)); - // Check if the edge is above the diagonal - if (rowIndex < colIndex) { - double weight = graph.getEdgeWeight(edge); - if (weight < minWeight) { - edgesToRemove.clear(); - minWeight = weight; - edgesToRemove.add(edge); - } else if (weight == minWeight && !edgesToRemove.contains(edge)) { - edgesToRemove.add(edge); - } - } - } - } + private void topologicalSortUtil(String activity, Set<String> visited, List<String> sortedActivities) { + visited.add(activity); + + for (String neighbor : Graphs.successorListOf(graph, activity)) { + if (!visited.contains(neighbor)) { + topologicalSortUtil(neighbor, visited, sortedActivities); } - return edgesToRemove; } - return new ArrayList<>(); + + sortedActivities.add(activity); } - static List<DefaultWeightedEdge> getAllBackwardEdges( - Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { - CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); - Set<String> cycleVertices = cycleDetector.findCycles(); - - if (!cycleVertices.isEmpty()) { - List<DefaultWeightedEdge> edgesToRemove = new ArrayList<>(); - - for (String vertex : cycleVertices) { - for (DefaultWeightedEdge edge : graph.edgesOf(vertex)) { - if (cycleVertices.contains(graph.getEdgeTarget(edge))) { - int rowIndex = orderedVertices.indexOf(graph.getEdgeSource(edge)); - int colIndex = orderedVertices.indexOf(graph.getEdgeTarget(edge)); - // Check if the edge is above the diagonal - // and has not been added yet - if (rowIndex < colIndex && !edgesToRemove.contains(edge)) { - edgesToRemove.add(edge); - } - } + public List<DefaultWeightedEdge> getEdgesAboveDiagonal() { + List<DefaultWeightedEdge> edgesAboveDiagonal = new ArrayList<>(); + + for (int i = 0; i < sortedActivities.size(); i++) { + for (int j = i + 1; j < sortedActivities.size(); j++) { + // source / destination vertex was flipped after solution generation + // to correctly identify the vertex above the diagonal to remove + DefaultWeightedEdge edge = graph.getEdge(sortedActivities.get(i), sortedActivities.get(j)); + if (edge != null) { + edgesAboveDiagonal.add(edge); } } - return edgesToRemove; } - return new ArrayList<>(); + + return edgesAboveDiagonal; } - public static void printDSM(Graph<String, DefaultWeightedEdge> graph, List<String> orderedVertices) { - // Print column labels - System.out.print(" "); - for (String vertex : orderedVertices) { - System.out.print(vertex + " "); + public DefaultWeightedEdge getOptimalEdgeAboveDiagonalToRemove() { + List<DefaultWeightedEdge> edgesAboveDiagonal = getEdgesAboveDiagonal(); + DefaultWeightedEdge optimalEdge = null; + double minWeight = Double.MAX_VALUE; + + for (DefaultWeightedEdge edge : edgesAboveDiagonal) { + double weight = graph.getEdgeWeight(edge); + if (weight < minWeight) { + minWeight = weight; + optimalEdge = edge; + } } - System.out.println(); - // Print rows with edge weights - for (String rowVertex : orderedVertices) { - System.out.print(rowVertex + " "); - for (String colVertex : orderedVertices) { - DefaultWeightedEdge edge = graph.getEdge(rowVertex, colVertex); - int weight = (edge != null) ? (int) graph.getEdgeWeight(edge) : 0; - System.out.print(weight + " "); + return optimalEdge; + } + + public List<DefaultWeightedEdge> getMinimumWeightEdgesToRemove() { + List<DefaultWeightedEdge> edgesAboveDiagonal = getEdgesAboveDiagonal(); + List<DefaultWeightedEdge> minWeightEdges = new ArrayList<>(); + double minWeight = Double.MAX_VALUE; + + for (DefaultWeightedEdge edge : edgesAboveDiagonal) { + double weight = graph.getEdgeWeight(edge); + if (weight < minWeight) { + minWeight = weight; + minWeightEdges.clear(); + minWeightEdges.add(edge); + } else if (weight == minWeight) { + minWeightEdges.add(edge); } - System.out.println(); } + + return minWeightEdges; + } + + public static void main(String[] args) { + DSM dsm = new DSM(); + dsm.addActivity("A"); + dsm.addActivity("B"); + dsm.addActivity("C"); + dsm.addActivity("D"); + dsm.addActivity("E"); + dsm.addActivity("F"); + dsm.addActivity("G"); + dsm.addActivity("H"); + + dsm.addDependency("A", "B", 1); +// dsm.addDependency("A", "C", 6); + dsm.addDependency("B", "C", 2); +// dsm.addDependency("B", "D", 3); + dsm.addDependency("C", "D", 2); + dsm.addDependency("D", "A", 2); // Adding a cycle + + dsm.addDependency("C", "A", 7); // Adding a cycle + dsm.addDependency("G", "E", 2); // Adding a cycle + dsm.addDependency("E", "H", 2); // Adding a cycle + dsm.addDependency("H", "G", 5); // Adding a cycle + dsm.addDependency("A", "H", 7); // Adding a cycle + dsm.addDependency("H", "D", 9); // Adding a cycle + dsm.addDependency("C", "E", 9); // Adding a cycle + + + dsm.orderVertices(); + dsm.printDSM(); + + System.out.println("Optimal edge above diagonal to remove: " + dsm.getOptimalEdgeAboveDiagonalToRemove()); } -} +} \ No newline at end of file diff --git a/dsm/src/test/java/org/hjug/dsm/DSMTest.java b/dsm/src/test/java/org/hjug/dsm/DSMTest.java deleted file mode 100644 index bb257fbf..00000000 --- a/dsm/src/test/java/org/hjug/dsm/DSMTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.hjug.dsm; - -import java.util.List; -import org.jgrapht.Graph; -import org.jgrapht.Graphs; -import org.jgrapht.graph.DefaultDirectedWeightedGraph; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class DSMTest { - - Graph<String, DefaultWeightedEdge> graph; - List<String> orderedVertices; - - @BeforeEach - void setUp() { - // Create a weighted graph - graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); - - // Add vertices - graph.addVertex("A"); - graph.addVertex("B"); - graph.addVertex("C"); - graph.addVertex("D"); - graph.addVertex("E"); - - // Add edges with weights - Graphs.addEdgeWithVertices(graph, "A", "B", 1.0); - Graphs.addEdgeWithVertices(graph, "A", "C", 1.0); - Graphs.addEdgeWithVertices(graph, "B", "D", 3.0); - Graphs.addEdgeWithVertices(graph, "C", "D", 4.0); - Graphs.addEdgeWithVertices(graph, "D", "A", 5.0); // Adding a cycle - - // Order nodes according to the specified logic - orderedVertices = DSM.orderVertices(graph); - } - - @Test - void minWeightEdges() { - // Identify which edge above the diagonal in the set of cycles should be removed first - List<DefaultWeightedEdge> edgesToRemove = DSM.identifyAllBackwardMinWeightEdgesToRemove(graph, orderedVertices); - - // Print the DSM - DSM.printDSM(graph, orderedVertices); - - // find the edges to remove - if (!edgesToRemove.isEmpty()) { - for (DefaultWeightedEdge edgeToRemove : edgesToRemove) { - System.out.println("Edge to remove: " + graph.getEdgeSource(edgeToRemove) + " -> " - + graph.getEdgeTarget(edgeToRemove)); - } - } else { - System.out.println("No cycles detected."); - } - - // Contains "(A : B)" and "(A : C)" - Assertions.assertEquals(2, edgesToRemove.size()); - } - - @Test - void optimalBakcwardEdgeToRemove() { - // Identify which edge above the diagonal in the set of cycles should be removed first - DefaultWeightedEdge edgeToRemove = DSM.identifyOptimalBakcwardEdgeToRemove(graph, orderedVertices); - Assertions.assertEquals("(A : B)", edgeToRemove.toString()); - } - - @Test - void optimalEdge() { - // Identify which edge above the diagonal in the set of cycles should be removed first - List<DefaultWeightedEdge> edges = DSM.getAllBackwardEdges(graph, orderedVertices); - Assertions.assertEquals(3, edges.size()); - } -} From 20e23127f5ee4be3e2edeba7a6d4a06ccb3bbd7b Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 10 Feb 2025 19:22:30 -0600 Subject: [PATCH 25/78] #130 Identifying backward edges in a cycle --- .../cycledetector/BackwardEdgesFinder.java | 57 +++++++++++++++++++ .../BackwardEdgesFinderTest.java | 38 +++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java create mode 100644 circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java new file mode 100644 index 00000000..9a5e458d --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java @@ -0,0 +1,57 @@ +package org.hjug.cycledetector; + +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.alg.cycle.TarjanSimpleCycles; + +import java.util.*; + +/* +Prompt: +provide a java implementation of an algorithm that will identify all backward edges in a cycle +identified by JGraphT that is part of a graph with weighted edges that have integer weights. +If there are multiple backward edges, order the backward edges in the order they should be removed. + */ +public class BackwardEdgesFinder<V> { + + private final Graph<V, DefaultWeightedEdge> graph; + + public BackwardEdgesFinder(Graph<V, DefaultWeightedEdge> graph) { + this.graph = graph; + } + + public List<DefaultWeightedEdge> findBackwardEdges() { + TarjanSimpleCycles<V, DefaultWeightedEdge> tarjan = new TarjanSimpleCycles<>(graph); + List<List<V>> cycles = tarjan.findSimpleCycles(); + List<DefaultWeightedEdge> backwardEdges = new ArrayList<>(); + + for (List<V> cycle : cycles) { + Set<DefaultWeightedEdge> cycleEdges = new HashSet<>(); + for (int i = 0; i < cycle.size(); i++) { + V source = cycle.get(i); + V target = cycle.get((i + 1) % cycle.size()); + DefaultWeightedEdge edge = graph.getEdge(source, target); + if (edge != null) { + cycleEdges.add(edge); + } + } + + for (DefaultWeightedEdge edge : cycleEdges) { + if (isBackwardEdge(edge, cycle)) { + backwardEdges.add(edge); + } + } + } + + backwardEdges.sort(Comparator.comparingDouble(graph::getEdgeWeight)); + return backwardEdges; + } + + private boolean isBackwardEdge(DefaultWeightedEdge edge, List<V> cycle) { + V source = graph.getEdgeSource(edge); + V target = graph.getEdgeTarget(edge); + int sourceIndex = cycle.indexOf(source); + int targetIndex = cycle.indexOf(target); + return sourceIndex > targetIndex; + } +} diff --git a/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java b/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java new file mode 100644 index 00000000..af747e17 --- /dev/null +++ b/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java @@ -0,0 +1,38 @@ +package org.hjug.cycledetector; + +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class BackwardEdgesFinderTest { + + @Test + void findBackwardEdges() { + Graph<String, DefaultWeightedEdge> graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + graph.addVertex("A"); + graph.addVertex("B"); + graph.addVertex("C"); + graph.addVertex("D"); + + graph.setEdgeWeight(graph.addEdge("A", "B"), 1); + graph.setEdgeWeight(graph.addEdge("B", "C"), 2); + graph.setEdgeWeight(graph.addEdge("C", "D"), 3); + graph.setEdgeWeight(graph.addEdge("D", "A"), 4); + graph.setEdgeWeight(graph.addEdge("C", "A"), 5); + + BackwardEdgesFinder<String> finder = new BackwardEdgesFinder<>(graph); + List<DefaultWeightedEdge> backwardEdges = finder.findBackwardEdges(); + + Assertions.assertEquals("(D : A)", backwardEdges.get(0).toString()); + Assertions.assertEquals(4, graph.getEdgeWeight(backwardEdges.get(0))); + + Assertions.assertEquals("(C : A)", backwardEdges.get(1).toString()); + Assertions.assertEquals(5, graph.getEdgeWeight(backwardEdges.get(1))); + } + +} From fc74b6bfcc1a7d044488ce6ec396728cfd10b35b Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 10 Feb 2025 19:37:42 -0600 Subject: [PATCH 26/78] #111 Detecting multiple assignments Detecting multiple assignments if multiple take place in a single declaration. Applied Spotless. --- .../hjug/cycledetector/BackwardEdgesFinder.java | 5 ++--- .../parser/visitor/JavaVariableTypeVisitor.java | 16 +++++++++------- .../cycledetector/BackwardEdgesFinderTest.java | 4 +--- dsm/src/main/java/org/hjug/dsm/DSM.java | 15 ++++++--------- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java index 9a5e458d..6505d05b 100644 --- a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java +++ b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java @@ -1,10 +1,9 @@ package org.hjug.cycledetector; +import java.util.*; import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.alg.cycle.TarjanSimpleCycles; - -import java.util.*; +import org.jgrapht.graph.DefaultWeightedEdge; /* Prompt: diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index 6b59be69..f5599fa6 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -94,13 +94,15 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m processType(ownerFqn, javaType); // process variable instantiation if present - Expression initializer = variables.get(0).getInitializer(); - if (null != initializer && null != initializer.getType() && initializer instanceof J.MethodInvocation) { - J.MethodInvocation methodInvocation = (J.MethodInvocation) initializer; - methodInvocationVisitor.visitMethodInvocation(ownerFqn, methodInvocation); - } else if (null != initializer && null != initializer.getType() && initializer instanceof J.NewClass) { - J.NewClass newClassType = (J.NewClass) initializer; - newClassVisitor.visitNewClass(ownerFqn, newClassType); + for (J.VariableDeclarations.NamedVariable variable : variables) { + Expression initializer = variable.getInitializer(); + if (null != initializer && null != initializer.getType() && initializer instanceof J.MethodInvocation) { + J.MethodInvocation methodInvocation = (J.MethodInvocation) initializer; + methodInvocationVisitor.visitMethodInvocation(ownerFqn, methodInvocation); + } else if (null != initializer && null != initializer.getType() && initializer instanceof J.NewClass) { + J.NewClass newClassType = (J.NewClass) initializer; + newClassVisitor.visitNewClass(ownerFqn, newClassType); + } } return variableDeclarations; diff --git a/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java b/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java index af747e17..fba3bccd 100644 --- a/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java @@ -1,13 +1,12 @@ package org.hjug.cycledetector; +import java.util.List; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.List; - class BackwardEdgesFinderTest { @Test @@ -34,5 +33,4 @@ void findBackwardEdges() { Assertions.assertEquals("(C : A)", backwardEdges.get(1).toString()); Assertions.assertEquals(5, graph.getEdgeWeight(backwardEdges.get(1))); } - } diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index c06a3a1f..b5e257c0 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -1,12 +1,11 @@ package org.hjug.dsm; +import java.util.*; import org.jgrapht.Graph; import org.jgrapht.Graphs; +import org.jgrapht.alg.cycle.TarjanSimpleCycles; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; -import org.jgrapht.alg.cycle.TarjanSimpleCycles; - -import java.util.*; /* * Generated with Generative AI using a prompt similar to the following: @@ -26,8 +25,7 @@ include a third method that identifies all minimum weight edges to remove above the diagonal. */ - -//This is looking good, but is still a work in progress. This may not be needed at all. +// This is looking good, but is still a work in progress. This may not be needed at all. public class DSM { private Graph<String, DefaultWeightedEdge> graph; private List<String> sortedActivities; @@ -180,9 +178,9 @@ public static void main(String[] args) { dsm.addActivity("H"); dsm.addDependency("A", "B", 1); -// dsm.addDependency("A", "C", 6); + // dsm.addDependency("A", "C", 6); dsm.addDependency("B", "C", 2); -// dsm.addDependency("B", "D", 3); + // dsm.addDependency("B", "D", 3); dsm.addDependency("C", "D", 2); dsm.addDependency("D", "A", 2); // Adding a cycle @@ -194,10 +192,9 @@ public static void main(String[] args) { dsm.addDependency("H", "D", 9); // Adding a cycle dsm.addDependency("C", "E", 9); // Adding a cycle - dsm.orderVertices(); dsm.printDSM(); System.out.println("Optimal edge above diagonal to remove: " + dsm.getOptimalEdgeAboveDiagonalToRemove()); } -} \ No newline at end of file +} From cdbf30d5d12ab0ba42fc98278ee3794280c8f109 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 10 Feb 2025 19:39:10 -0600 Subject: [PATCH 27/78] Moved DSM to the circular-reference-detector module --- .../java/org/hjug/cycledetector}/DSM.java | 2 +- dsm/pom.xml | 21 ------------------- pom.xml | 1 - 3 files changed, 1 insertion(+), 23 deletions(-) rename {dsm/src/main/java/org/hjug/dsm => circular-reference-detector/src/main/java/org/hjug/cycledetector}/DSM.java (99%) delete mode 100644 dsm/pom.xml diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/circular-reference-detector/src/main/java/org/hjug/cycledetector/DSM.java similarity index 99% rename from dsm/src/main/java/org/hjug/dsm/DSM.java rename to circular-reference-detector/src/main/java/org/hjug/cycledetector/DSM.java index b5e257c0..4acf0cfe 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/circular-reference-detector/src/main/java/org/hjug/cycledetector/DSM.java @@ -1,4 +1,4 @@ -package org.hjug.dsm; +package org.hjug.cycledetector; import java.util.*; import org.jgrapht.Graph; diff --git a/dsm/pom.xml b/dsm/pom.xml deleted file mode 100644 index 72b9b4e4..00000000 --- a/dsm/pom.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>org.hjug.refactorfirst</groupId> - <artifactId>refactor-first</artifactId> - <version>0.6.3-SNAPSHOT</version> - </parent> - - <artifactId>org.hjug.refactorfirst.dsm</artifactId> - - <dependencies> - <dependency> - <groupId>org.jgrapht</groupId> - <artifactId>jgrapht-core</artifactId> - </dependency> - </dependencies> -</project> \ No newline at end of file diff --git a/pom.xml b/pom.xml index f5af32f4..3944a9a7 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,6 @@ <modules> <module>test-resources</module> - <module>dsm</module> <module>circular-reference-detector</module> <module>change-proneness-ranker</module> <module>effort-ranker</module> From 19691cfac87e9bb8afddc04a414ffd4c073933c5 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 10 Feb 2025 19:52:27 -0600 Subject: [PATCH 28/78] #130 Added comment --- .../main/java/org/hjug/cycledetector/BackwardEdgesFinder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java index 6505d05b..bb76a809 100644 --- a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java +++ b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java @@ -36,12 +36,13 @@ public List<DefaultWeightedEdge> findBackwardEdges() { } for (DefaultWeightedEdge edge : cycleEdges) { - if (isBackwardEdge(edge, cycle)) { + if (isBackwardEdge(edge, cycle) && !backwardEdges.contains(edge)) { backwardEdges.add(edge); } } } + // The ordering is naive, but it's a start backwardEdges.sort(Comparator.comparingDouble(graph::getEdgeWeight)); return backwardEdges; } From 4b4abad4654ea0c9e73cd52c11266e23f047255c Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Tue, 11 Feb 2025 13:05:53 -0600 Subject: [PATCH 29/78] #130 Added return visiting and removed type processing of parameter arguments - Added return expression visiting - Removed type processing of parameter arguments since they will be captured as part of the variable declaration visitor - Updated unit tests --- .../visitor/JavaClassDeclarationVisitor.java | 25 ++++++++++++++++--- .../parser/visitor/JavaNewClassVisitor.java | 6 ----- .../JavaClassDeclarationVisitorTest.java | 2 +- .../JavaMethodInvocationVisitorTest.java | 4 +-- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index acef0d8b..719eabba 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -6,10 +6,7 @@ import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.Statement; -import org.openrewrite.java.tree.TypeTree; +import org.openrewrite.java.tree.*; public class JavaClassDeclarationVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { @@ -102,8 +99,28 @@ private void processBlock(J.Block block, String owningFqn) { } else if (statementInBlock instanceof J.NewClass) { J.NewClass newClass = (J.NewClass) statementInBlock; newClassVisitor.visitNewClass(owningFqn, newClass); + } else if (statementInBlock instanceof J.Return) { + J.Return returnStmt = (J.Return) statementInBlock; + visitReturn(owningFqn, returnStmt); } } } } + + + public J.Return visitReturn(String owningFqn, J.Return visitedReturn) { + Expression expression = visitedReturn.getExpression(); + if(expression instanceof J.MethodInvocation) { + J.MethodInvocation methodInvocation = (J.MethodInvocation) expression; + methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); + } else if (expression instanceof J.NewClass) { + J.NewClass newClass = (J.NewClass) expression; + newClassVisitor.visitNewClass(owningFqn, newClass); + } else if (expression instanceof J.Lambda) { + J.Lambda lambda = (J.Lambda) expression; + processType(owningFqn, lambda.getType()); + } + + return visitedReturn; + } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java index d8c6c599..1e50ed63 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java @@ -16,13 +16,7 @@ public class JavaNewClassVisitor implements TypeProcessor { public J.NewClass visitNewClass(String invokingFqn, J.NewClass newClass) { processType(invokingFqn, newClass.getType()); - - for (Expression argument : newClass.getArguments()) { - processType(invokingFqn, argument.getType()); - } - // TASK: process initializer block??? - return newClass; } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java index f28c2382..ced49dd0 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java @@ -42,7 +42,7 @@ void visitClasses() throws IOException { .getClassReferencesGraph() .containsVertex("org.hjug.parser.visitor.testclasses.C")); // false because it doesn't reference any other classes - Assertions.assertFalse(javaVariableCapturingVisitor + Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() .containsVertex("org.hjug.parser.visitor.testclasses.D")); Assertions.assertTrue(javaVariableCapturingVisitor diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java index 651722ce..b0f6ebe9 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java @@ -49,12 +49,12 @@ void visitMethodInvocations() throws IOException { Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.C")); Assertions.assertEquals( - 2, + 3, graph.getEdgeWeight(graph.getEdge( "org.hjug.parser.visitor.testclasses.methodInvocation.A", "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); Assertions.assertEquals( - 2, + 3, graph.getEdgeWeight(graph.getEdge( "org.hjug.parser.visitor.testclasses.methodInvocation.A", "org.hjug.parser.visitor.testclasses.methodInvocation.C"))); From 480e35975a187d69e9f2865c69882594dc45a714 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Tue, 11 Feb 2025 13:06:57 -0600 Subject: [PATCH 30/78] Applied Spotless --- .../org/hjug/parser/visitor/JavaClassDeclarationVisitor.java | 5 ++--- .../java/org/hjug/parser/visitor/JavaNewClassVisitor.java | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index 719eabba..1383332c 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -107,14 +107,13 @@ private void processBlock(J.Block block, String owningFqn) { } } - public J.Return visitReturn(String owningFqn, J.Return visitedReturn) { Expression expression = visitedReturn.getExpression(); - if(expression instanceof J.MethodInvocation) { + if (expression instanceof J.MethodInvocation) { J.MethodInvocation methodInvocation = (J.MethodInvocation) expression; methodInvocationVisitor.visitMethodInvocation(owningFqn, methodInvocation); } else if (expression instanceof J.NewClass) { - J.NewClass newClass = (J.NewClass) expression; + J.NewClass newClass = (J.NewClass) expression; newClassVisitor.visitNewClass(owningFqn, newClass); } else if (expression instanceof J.Lambda) { J.Lambda lambda = (J.Lambda) expression; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java index 1e50ed63..1b37e90e 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java @@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; -import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; @RequiredArgsConstructor From e04354ad1bbef7144f7d13b1ae86b2b967194a48 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Tue, 11 Feb 2025 19:37:05 -0600 Subject: [PATCH 31/78] #130 Tracking the number of cycles a backward edge participates in - Tracking the number of cycles a backward edge participates in - Added unit test - Added comments --- .../cycledetector/BackwardEdgesFinder.java | 19 +++++--- .../BackwardEdgesFinderTest.java | 46 ++++++++++++++++--- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java index bb76a809..134eb925 100644 --- a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java +++ b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java @@ -10,6 +10,8 @@ provide a java implementation of an algorithm that will identify all backward edges in a cycle identified by JGraphT that is part of a graph with weighted edges that have integer weights. If there are multiple backward edges, order the backward edges in the order they should be removed. + +Modified slightly to track the number of cycles a backward edge participates in */ public class BackwardEdgesFinder<V> { @@ -19,32 +21,37 @@ public BackwardEdgesFinder(Graph<V, DefaultWeightedEdge> graph) { this.graph = graph; } - public List<DefaultWeightedEdge> findBackwardEdges() { + public Map<DefaultWeightedEdge, Integer> findBackwardEdges() { TarjanSimpleCycles<V, DefaultWeightedEdge> tarjan = new TarjanSimpleCycles<>(graph); List<List<V>> cycles = tarjan.findSimpleCycles(); - List<DefaultWeightedEdge> backwardEdges = new ArrayList<>(); + Map<DefaultWeightedEdge, Integer> backwardEdgesWithCounts = new TreeMap<>(((Comparator<DefaultWeightedEdge>) + // compare based on weight + (e1, e2) -> Integer.compare((int) graph.getEdgeWeight(e1), (int) graph.getEdgeWeight(e2))) + // then compare based on name (this will result in edges being in alpha order - not exactly ideal) + .thenComparing(DefaultWeightedEdge::toString)); for (List<V> cycle : cycles) { Set<DefaultWeightedEdge> cycleEdges = new HashSet<>(); for (int i = 0; i < cycle.size(); i++) { V source = cycle.get(i); + // getting (i + 1) % cycle size produces the next node added to the cycle V target = cycle.get((i + 1) % cycle.size()); DefaultWeightedEdge edge = graph.getEdge(source, target); + // if there is an edge between the nodes, add it to cycleEdges if (edge != null) { cycleEdges.add(edge); } } for (DefaultWeightedEdge edge : cycleEdges) { - if (isBackwardEdge(edge, cycle) && !backwardEdges.contains(edge)) { - backwardEdges.add(edge); + if (isBackwardEdge(edge, cycle)) { + backwardEdgesWithCounts.put(edge, backwardEdgesWithCounts.getOrDefault(edge, 0) + 1); } } } // The ordering is naive, but it's a start - backwardEdges.sort(Comparator.comparingDouble(graph::getEdgeWeight)); - return backwardEdges; + return backwardEdgesWithCounts; } private boolean isBackwardEdge(DefaultWeightedEdge edge, List<V> cycle) { diff --git a/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java b/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java index fba3bccd..93a28add 100644 --- a/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java @@ -1,10 +1,13 @@ package org.hjug.cycledetector; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class BackwardEdgesFinderTest { @@ -25,12 +28,43 @@ void findBackwardEdges() { graph.setEdgeWeight(graph.addEdge("C", "A"), 5); BackwardEdgesFinder<String> finder = new BackwardEdgesFinder<>(graph); - List<DefaultWeightedEdge> backwardEdges = finder.findBackwardEdges(); + Map<DefaultWeightedEdge, Integer> backwardEdgesWithCounts = finder.findBackwardEdges(); + List<DefaultWeightedEdge> backwardEdges = new ArrayList<>(backwardEdgesWithCounts.keySet()); + + assertEquals("(D : A)", backwardEdges.get(0).toString()); + assertEquals(4, graph.getEdgeWeight(backwardEdges.get(0))); + assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(0))); + + assertEquals("(C : A)", backwardEdges.get(1).toString()); + assertEquals(5, graph.getEdgeWeight(backwardEdges.get(1))); + assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(1))); + } + + @Test + void findBackwardEdgesWithSameWeight() { + Graph<String, DefaultWeightedEdge> graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + + graph.addVertex("A"); + graph.addVertex("B"); + graph.addVertex("C"); + graph.addVertex("D"); + + graph.setEdgeWeight(graph.addEdge("A", "B"), 1); + graph.setEdgeWeight(graph.addEdge("B", "C"), 2); + graph.setEdgeWeight(graph.addEdge("C", "D"), 3); + graph.setEdgeWeight(graph.addEdge("D", "A"), 4); + graph.setEdgeWeight(graph.addEdge("C", "A"), 4); + + BackwardEdgesFinder<String> finder = new BackwardEdgesFinder<>(graph); + Map<DefaultWeightedEdge, Integer> backwardEdgesWithCounts = finder.findBackwardEdges(); + List<DefaultWeightedEdge> backwardEdges = new ArrayList<>(backwardEdgesWithCounts.keySet()); - Assertions.assertEquals("(D : A)", backwardEdges.get(0).toString()); - Assertions.assertEquals(4, graph.getEdgeWeight(backwardEdges.get(0))); + assertEquals("(C : A)", backwardEdges.get(0).toString()); + assertEquals(4, graph.getEdgeWeight(backwardEdges.get(0))); + assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(0))); - Assertions.assertEquals("(C : A)", backwardEdges.get(1).toString()); - Assertions.assertEquals(5, graph.getEdgeWeight(backwardEdges.get(1))); + assertEquals("(D : A)", backwardEdges.get(1).toString()); + assertEquals(4, graph.getEdgeWeight(backwardEdges.get(1))); + assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(1))); } } From ebb600bc203b1e737beecb7988d421cfc37e977d Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Wed, 12 Feb 2025 07:41:57 -0600 Subject: [PATCH 32/78] #130 Removing nodes of classes not in codebase - Removing nodes of classes not in codebase from the classGraph - Added GraphDTO type - Removed packageReferencesGraph where not needed --- .../main/java/org/hjug/parser/GraphDTO.java | 16 +++++++ .../org/hjug/parser/JavaProjectParser.java | 32 ++++++++++++-- .../visitor/JavaClassDeclarationVisitor.java | 16 +++---- .../visitor/JavaMethodInvocationVisitor.java | 1 - .../parser/visitor/JavaNewClassVisitor.java | 1 - .../visitor/JavaVariableTypeVisitor.java | 8 ++-- .../org/hjug/parser/visitor/JavaVisitor.java | 8 ++-- .../hjug/parser/visitor/TypeProcessor.java | 4 +- ...rTests.java => JavaProjectParserTest.java} | 43 ++++++++++++++++--- .../JavaMethodInvocationVisitorTest.java | 2 +- .../visitor/JavaNewClassVisitorTest.java | 2 +- 11 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java rename circular-reference-detector/src/test/java/org/hjug/parser/{JavaProjectParserTests.java => JavaProjectParserTest.java} (65%) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java b/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java new file mode 100644 index 00000000..806f412b --- /dev/null +++ b/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java @@ -0,0 +1,16 @@ +package org.hjug.parser; + +import lombok.Data; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; + +import java.util.Map; + +@Data +public class GraphDTO { + + private final Graph<String, DefaultWeightedEdge> classReferencesGraph; + private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; + // used for looking up files where classes reside + private final Map<String, String> classToSourceFilePathMapping; +} diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 324b7754..5dc830e5 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -40,7 +40,7 @@ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory return classReferencesGraph; } - private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { + private GraphDTO processWithOpenRewrite(String srcDir) throws IOException { File srcDirectory = new File(srcDir); JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -70,8 +70,34 @@ private JavaVisitor processWithOpenRewrite(String srcDir) throws IOException { }); } - return javaVisitor; + removeClassesNotInCodebase(javaVisitor.getPackagesInCodebase(), classReferencesGraph); - // TODO: classReferencesGraph.removeAllVertices(); if vertex package not in codebase + return new GraphDTO(classReferencesGraph, + packageReferencesGraph, + javaVisitor.getClassToSourceFilePathMapping()); + } + + // remove node if package not in codebase + void removeClassesNotInCodebase(Set<String> packagesInCodebase, Graph<String, DefaultWeightedEdge> classReferencesGraph) { + + // collect nodes to remove + Set<String> classesToRemove = new HashSet<>(); + for (String classFqn : classReferencesGraph.vertexSet()) { + if(!packagesInCodebase.contains(getPackage(classFqn))) { + classesToRemove.add(classFqn); + } + } + + classReferencesGraph.removeAllVertices(classesToRemove); + } + + String getPackage(String fqn) { + // handle no package + if (!fqn.contains(".")) { + return ""; + } + + int lastIndex = fqn.lastIndexOf("."); + return fqn.substring(0, lastIndex); } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index 1383332c..64f2ecf5 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -17,22 +17,16 @@ public class JavaClassDeclarationVisitor<P> extends JavaIsoVisitor<P> implements private Graph<String, DefaultWeightedEdge> classReferencesGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - @Getter - private Graph<String, DefaultWeightedEdge> packageReferencesGraph = - new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - public JavaClassDeclarationVisitor() { - methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); - newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph); } public JavaClassDeclarationVisitor( - Graph<String, DefaultWeightedEdge> classReferencesGraph, - Graph<String, DefaultWeightedEdge> packageReferencesGraph) { + Graph<String, DefaultWeightedEdge> classReferencesGraph) { this.classReferencesGraph = classReferencesGraph; - this.packageReferencesGraph = packageReferencesGraph; - methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); - newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph); } @Override diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java index 8fc2765f..29dc3704 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java @@ -15,7 +15,6 @@ public class JavaMethodInvocationVisitor implements TypeProcessor { private final Graph<String, DefaultWeightedEdge> classReferencesGraph; - private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvocation methodInvocation) { // getDeclaringType() returns the type that declared the method being invoked diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java index 1b37e90e..61fdac71 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java @@ -11,7 +11,6 @@ public class JavaNewClassVisitor implements TypeProcessor { private final Graph<String, DefaultWeightedEdge> classReferencesGraph; - private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; public J.NewClass visitNewClass(String invokingFqn, J.NewClass newClass) { processType(invokingFqn, newClass.getType()); diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java index f5599fa6..ffc25ee2 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java @@ -24,8 +24,8 @@ public class JavaVariableTypeVisitor<P> extends JavaIsoVisitor<P> implements Typ private final JavaMethodInvocationVisitor methodInvocationVisitor; public JavaVariableTypeVisitor() { - newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); - methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); } public JavaVariableTypeVisitor( @@ -33,8 +33,8 @@ public JavaVariableTypeVisitor( Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; this.packageReferencesGraph = packageReferencesGraph; - newClassVisitor = new JavaNewClassVisitor(classReferencesGraph, packageReferencesGraph); - methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph, packageReferencesGraph); + newClassVisitor = new JavaNewClassVisitor(classReferencesGraph); + methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); } @Override diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java index 8f1faad7..0b4e1f4b 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java @@ -11,12 +11,14 @@ @Slf4j public class JavaVisitor<P> extends JavaIsoVisitor<P> implements TypeProcessor { + // used to keep track of what packages are in the codebase + // used to remove the nodes that are not in the codebase @Getter private final Set<String> packagesInCodebase = new HashSet<>(); // used for looking up files where classes reside @Getter - private final Map<String, String> classToSourceFileMapping = new HashMap<>(); + private final Map<String, String> classToSourceFilePathMapping = new HashMap<>(); @Getter private final Graph<String, DefaultWeightedEdge> classReferencesGraph; @@ -31,7 +33,7 @@ public JavaVisitor( Graph<String, DefaultWeightedEdge> packageReferencesGraph) { this.classReferencesGraph = classReferencesGraph; this.packageReferencesGraph = packageReferencesGraph; - javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); + javaClassDeclarationVisitor = new JavaClassDeclarationVisitor<>(classReferencesGraph); } @Override @@ -47,7 +49,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { packagesInCodebase.add(compilationUnit.getPackageDeclaration().getPackageName()); for (J.ClassDeclaration aClass : compilationUnit.getClasses()) { - classToSourceFileMapping.put( + classToSourceFilePathMapping.put( aClass.getType().getFullyQualifiedName(), compilationUnit.getSourcePath().toUri().toString()); } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java index 753b1384..b92e430b 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java @@ -111,7 +111,9 @@ default void processTypeParameter(String ownerFqn, J.TypeParameter typeParameter } } - Graph<String, DefaultWeightedEdge> getPackageReferencesGraph(); + default Graph<String, DefaultWeightedEdge> getPackageReferencesGraph(){ + return null; + } Graph<String, DefaultWeightedEdge> getClassReferencesGraph(); diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java similarity index 65% rename from circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java rename to circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java index 2e6d79ae..0ec308ba 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java @@ -4,21 +4,24 @@ import java.io.File; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class JavaProjectParserTests { +class JavaProjectParserTest { - JavaProjectParser sutJavaProjectParser = new JavaProjectParser(); + JavaProjectParser javaProjectParser = new JavaProjectParser(); @DisplayName("When source directory input param is empty or null throw IllegalArgumentException.") @Test void parseSourceDirectoryEmptyTest() { - Assertions.assertThrows(IllegalArgumentException.class, () -> sutJavaProjectParser.getClassReferences("")); - Assertions.assertThrows(IllegalArgumentException.class, () -> sutJavaProjectParser.getClassReferences(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> javaProjectParser.getClassReferences("")); + Assertions.assertThrows(IllegalArgumentException.class, () -> javaProjectParser.getClassReferences(null)); } @DisplayName("Given a valid source directory input parameter return a valid graph.") @@ -26,7 +29,7 @@ void parseSourceDirectoryEmptyTest() { void parseSourceDirectoryTest() throws IOException { File srcDirectory = new File("src/test/resources/javaSrcDirectory"); Graph<String, DefaultWeightedEdge> classReferencesGraph = - sutJavaProjectParser.getClassReferences(srcDirectory.getAbsolutePath()); + javaProjectParser.getClassReferences(srcDirectory.getAbsolutePath()); assertNotNull(classReferencesGraph); assertEquals(5, classReferencesGraph.vertexSet().size()); assertEquals(7, classReferencesGraph.edgeSet().size()); @@ -69,4 +72,34 @@ private static double getEdgeWeight( Graph<String, DefaultWeightedEdge> classReferencesGraph, String sourceVertex, String targetVertex) { return classReferencesGraph.getEdgeWeight(classReferencesGraph.getEdge(sourceVertex, targetVertex)); } + + + @Test + void removeClassesNotInCodebase() throws IOException { + File srcDirectory = new File("src/test/resources/javaSrcDirectory"); + Graph<String, DefaultWeightedEdge> classReferencesGraph = + javaProjectParser.getClassReferences(srcDirectory.getAbsolutePath()); + + classReferencesGraph.addVertex("org.favioriteoss.FunClass"); + classReferencesGraph.addVertex("org.favioriteoss.AnotherFunClass"); + + DefaultWeightedEdge edge1 = + classReferencesGraph.addEdge("com.ideacrest.parser.testclasses.A", "org.favioriteoss.FunClass"); + DefaultWeightedEdge edge2 = + classReferencesGraph.addEdge("com.ideacrest.parser.testclasses.A", "org.favioriteoss.AnotherFunClass"); + + assertTrue(classReferencesGraph.containsVertex("org.favioriteoss.FunClass")); + assertTrue(classReferencesGraph.containsVertex("org.favioriteoss.AnotherFunClass")); + + Set<String> packagesInCodebase = new HashSet<>(); + packagesInCodebase.add("com.ideacrest.parser.testclasses"); + + javaProjectParser.removeClassesNotInCodebase(packagesInCodebase, classReferencesGraph); + + assertFalse(classReferencesGraph.containsVertex("org.favioriteoss.FunClass")); + assertFalse(classReferencesGraph.containsVertex("org.favioriteoss.AnotherFunClass")); + assertFalse(classReferencesGraph.containsEdge(edge1)); + assertFalse(classReferencesGraph.containsEdge(edge2)); + } + } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java index b0f6ebe9..4fbdc898 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java @@ -33,7 +33,7 @@ void visitMethodInvocations() throws IOException { new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); JavaClassDeclarationVisitor<ExecutionContext> classDeclarationVisitor = - new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); + new JavaClassDeclarationVisitor<>(classReferencesGraph); JavaVariableTypeVisitor<ExecutionContext> variableTypeVisitor = new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java index 81b467e0..9f77aaf2 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java @@ -33,7 +33,7 @@ void visitNewClass() throws IOException { new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); JavaClassDeclarationVisitor<ExecutionContext> classDeclarationVisitor = - new JavaClassDeclarationVisitor<>(classReferencesGraph, packageReferencesGraph); + new JavaClassDeclarationVisitor<>(classReferencesGraph); JavaVariableTypeVisitor<ExecutionContext> variableTypeVisitor = new JavaVariableTypeVisitor<>(classReferencesGraph, packageReferencesGraph); From 5efea7c6a7df1d6175a9c56ae8373b38a0ebaa8a Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Wed, 12 Feb 2025 07:42:53 -0600 Subject: [PATCH 33/78] Applied Spotless --- .../src/main/java/org/hjug/parser/GraphDTO.java | 3 +-- .../main/java/org/hjug/parser/JavaProjectParser.java | 10 +++++----- .../parser/visitor/JavaClassDeclarationVisitor.java | 3 +-- .../java/org/hjug/parser/visitor/TypeProcessor.java | 2 +- .../java/org/hjug/parser/JavaProjectParserTest.java | 7 ++----- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java b/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java index 806f412b..35f6ccb3 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java @@ -1,11 +1,10 @@ package org.hjug.parser; +import java.util.Map; import lombok.Data; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; -import java.util.Map; - @Data public class GraphDTO { diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java index 5dc830e5..92b4f4ec 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java @@ -72,18 +72,18 @@ private GraphDTO processWithOpenRewrite(String srcDir) throws IOException { removeClassesNotInCodebase(javaVisitor.getPackagesInCodebase(), classReferencesGraph); - return new GraphDTO(classReferencesGraph, - packageReferencesGraph, - javaVisitor.getClassToSourceFilePathMapping()); + return new GraphDTO( + classReferencesGraph, packageReferencesGraph, javaVisitor.getClassToSourceFilePathMapping()); } // remove node if package not in codebase - void removeClassesNotInCodebase(Set<String> packagesInCodebase, Graph<String, DefaultWeightedEdge> classReferencesGraph) { + void removeClassesNotInCodebase( + Set<String> packagesInCodebase, Graph<String, DefaultWeightedEdge> classReferencesGraph) { // collect nodes to remove Set<String> classesToRemove = new HashSet<>(); for (String classFqn : classReferencesGraph.vertexSet()) { - if(!packagesInCodebase.contains(getPackage(classFqn))) { + if (!packagesInCodebase.contains(getPackage(classFqn))) { classesToRemove.add(classFqn); } } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java index 64f2ecf5..32c9dcb3 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java @@ -22,8 +22,7 @@ public JavaClassDeclarationVisitor() { newClassVisitor = new JavaNewClassVisitor(classReferencesGraph); } - public JavaClassDeclarationVisitor( - Graph<String, DefaultWeightedEdge> classReferencesGraph) { + public JavaClassDeclarationVisitor(Graph<String, DefaultWeightedEdge> classReferencesGraph) { this.classReferencesGraph = classReferencesGraph; methodInvocationVisitor = new JavaMethodInvocationVisitor(classReferencesGraph); newClassVisitor = new JavaNewClassVisitor(classReferencesGraph); diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java index b92e430b..ff78017e 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java +++ b/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java @@ -111,7 +111,7 @@ default void processTypeParameter(String ownerFqn, J.TypeParameter typeParameter } } - default Graph<String, DefaultWeightedEdge> getPackageReferencesGraph(){ + default Graph<String, DefaultWeightedEdge> getPackageReferencesGraph() { return null; } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java b/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java index 0ec308ba..407b0c65 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java +++ b/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.util.HashSet; import java.util.Set; - import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; import org.junit.jupiter.api.Assertions; @@ -73,7 +72,6 @@ private static double getEdgeWeight( return classReferencesGraph.getEdgeWeight(classReferencesGraph.getEdge(sourceVertex, targetVertex)); } - @Test void removeClassesNotInCodebase() throws IOException { File srcDirectory = new File("src/test/resources/javaSrcDirectory"); @@ -84,9 +82,9 @@ void removeClassesNotInCodebase() throws IOException { classReferencesGraph.addVertex("org.favioriteoss.AnotherFunClass"); DefaultWeightedEdge edge1 = - classReferencesGraph.addEdge("com.ideacrest.parser.testclasses.A", "org.favioriteoss.FunClass"); + classReferencesGraph.addEdge("com.ideacrest.parser.testclasses.A", "org.favioriteoss.FunClass"); DefaultWeightedEdge edge2 = - classReferencesGraph.addEdge("com.ideacrest.parser.testclasses.A", "org.favioriteoss.AnotherFunClass"); + classReferencesGraph.addEdge("com.ideacrest.parser.testclasses.A", "org.favioriteoss.AnotherFunClass"); assertTrue(classReferencesGraph.containsVertex("org.favioriteoss.FunClass")); assertTrue(classReferencesGraph.containsVertex("org.favioriteoss.AnotherFunClass")); @@ -101,5 +99,4 @@ void removeClassesNotInCodebase() throws IOException { assertFalse(classReferencesGraph.containsEdge(edge1)); assertFalse(classReferencesGraph.containsEdge(edge2)); } - } From f35624c3b8c6c66e1554e99af7b4aa332ff42ea6 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Thu, 13 Feb 2025 07:51:02 -0600 Subject: [PATCH 34/78] #129 DSM now behaves as expected --- .../cycledetector/BackwardEdgesFinder.java | 64 --------- .../BackwardEdgesFinderTest.java | 70 ---------- dsm/pom.xml | 29 +++++ .../src/main/java/org/hjug/dsm}/DSM.java | 122 +++++++++--------- dsm/src/test/java/org/hjug/dsm/DSMTest.java | 96 ++++++++++++++ pom.xml | 1 + 6 files changed, 187 insertions(+), 195 deletions(-) delete mode 100644 circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java create mode 100644 dsm/pom.xml rename {circular-reference-detector/src/main/java/org/hjug/cycledetector => dsm/src/main/java/org/hjug/dsm}/DSM.java (78%) create mode 100644 dsm/src/test/java/org/hjug/dsm/DSMTest.java diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java b/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java deleted file mode 100644 index 134eb925..00000000 --- a/circular-reference-detector/src/main/java/org/hjug/cycledetector/BackwardEdgesFinder.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.hjug.cycledetector; - -import java.util.*; -import org.jgrapht.Graph; -import org.jgrapht.alg.cycle.TarjanSimpleCycles; -import org.jgrapht.graph.DefaultWeightedEdge; - -/* -Prompt: -provide a java implementation of an algorithm that will identify all backward edges in a cycle -identified by JGraphT that is part of a graph with weighted edges that have integer weights. -If there are multiple backward edges, order the backward edges in the order they should be removed. - -Modified slightly to track the number of cycles a backward edge participates in - */ -public class BackwardEdgesFinder<V> { - - private final Graph<V, DefaultWeightedEdge> graph; - - public BackwardEdgesFinder(Graph<V, DefaultWeightedEdge> graph) { - this.graph = graph; - } - - public Map<DefaultWeightedEdge, Integer> findBackwardEdges() { - TarjanSimpleCycles<V, DefaultWeightedEdge> tarjan = new TarjanSimpleCycles<>(graph); - List<List<V>> cycles = tarjan.findSimpleCycles(); - Map<DefaultWeightedEdge, Integer> backwardEdgesWithCounts = new TreeMap<>(((Comparator<DefaultWeightedEdge>) - // compare based on weight - (e1, e2) -> Integer.compare((int) graph.getEdgeWeight(e1), (int) graph.getEdgeWeight(e2))) - // then compare based on name (this will result in edges being in alpha order - not exactly ideal) - .thenComparing(DefaultWeightedEdge::toString)); - - for (List<V> cycle : cycles) { - Set<DefaultWeightedEdge> cycleEdges = new HashSet<>(); - for (int i = 0; i < cycle.size(); i++) { - V source = cycle.get(i); - // getting (i + 1) % cycle size produces the next node added to the cycle - V target = cycle.get((i + 1) % cycle.size()); - DefaultWeightedEdge edge = graph.getEdge(source, target); - // if there is an edge between the nodes, add it to cycleEdges - if (edge != null) { - cycleEdges.add(edge); - } - } - - for (DefaultWeightedEdge edge : cycleEdges) { - if (isBackwardEdge(edge, cycle)) { - backwardEdgesWithCounts.put(edge, backwardEdgesWithCounts.getOrDefault(edge, 0) + 1); - } - } - } - - // The ordering is naive, but it's a start - return backwardEdgesWithCounts; - } - - private boolean isBackwardEdge(DefaultWeightedEdge edge, List<V> cycle) { - V source = graph.getEdgeSource(edge); - V target = graph.getEdgeTarget(edge); - int sourceIndex = cycle.indexOf(source); - int targetIndex = cycle.indexOf(target); - return sourceIndex > targetIndex; - } -} diff --git a/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java b/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java deleted file mode 100644 index 93a28add..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/cycledetector/BackwardEdgesFinderTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.hjug.cycledetector; - -import static org.junit.jupiter.api.Assertions.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultWeightedEdge; -import org.jgrapht.graph.SimpleDirectedWeightedGraph; -import org.junit.jupiter.api.Test; - -class BackwardEdgesFinderTest { - - @Test - void findBackwardEdges() { - Graph<String, DefaultWeightedEdge> graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - - graph.addVertex("A"); - graph.addVertex("B"); - graph.addVertex("C"); - graph.addVertex("D"); - - graph.setEdgeWeight(graph.addEdge("A", "B"), 1); - graph.setEdgeWeight(graph.addEdge("B", "C"), 2); - graph.setEdgeWeight(graph.addEdge("C", "D"), 3); - graph.setEdgeWeight(graph.addEdge("D", "A"), 4); - graph.setEdgeWeight(graph.addEdge("C", "A"), 5); - - BackwardEdgesFinder<String> finder = new BackwardEdgesFinder<>(graph); - Map<DefaultWeightedEdge, Integer> backwardEdgesWithCounts = finder.findBackwardEdges(); - List<DefaultWeightedEdge> backwardEdges = new ArrayList<>(backwardEdgesWithCounts.keySet()); - - assertEquals("(D : A)", backwardEdges.get(0).toString()); - assertEquals(4, graph.getEdgeWeight(backwardEdges.get(0))); - assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(0))); - - assertEquals("(C : A)", backwardEdges.get(1).toString()); - assertEquals(5, graph.getEdgeWeight(backwardEdges.get(1))); - assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(1))); - } - - @Test - void findBackwardEdgesWithSameWeight() { - Graph<String, DefaultWeightedEdge> graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - - graph.addVertex("A"); - graph.addVertex("B"); - graph.addVertex("C"); - graph.addVertex("D"); - - graph.setEdgeWeight(graph.addEdge("A", "B"), 1); - graph.setEdgeWeight(graph.addEdge("B", "C"), 2); - graph.setEdgeWeight(graph.addEdge("C", "D"), 3); - graph.setEdgeWeight(graph.addEdge("D", "A"), 4); - graph.setEdgeWeight(graph.addEdge("C", "A"), 4); - - BackwardEdgesFinder<String> finder = new BackwardEdgesFinder<>(graph); - Map<DefaultWeightedEdge, Integer> backwardEdgesWithCounts = finder.findBackwardEdges(); - List<DefaultWeightedEdge> backwardEdges = new ArrayList<>(backwardEdgesWithCounts.keySet()); - - assertEquals("(C : A)", backwardEdges.get(0).toString()); - assertEquals(4, graph.getEdgeWeight(backwardEdges.get(0))); - assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(0))); - - assertEquals("(D : A)", backwardEdges.get(1).toString()); - assertEquals(4, graph.getEdgeWeight(backwardEdges.get(1))); - assertEquals(1, backwardEdgesWithCounts.get(backwardEdges.get(1))); - } -} diff --git a/dsm/pom.xml b/dsm/pom.xml new file mode 100644 index 00000000..37f04bf7 --- /dev/null +++ b/dsm/pom.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.hjug.refactorfirst</groupId> + <artifactId>refactor-first</artifactId> + <version>0.6.3-SNAPSHOT</version> + </parent> + + <groupId>org.hjug.refactorfirst.dsm</groupId> + <artifactId>dsm</artifactId> + <version>0.6.3-SNAPSHOT</version> + + <description> + Implementation of a DSM that only has JGraphT-Core as a dependency. + Can be used by other projects. + </description> + + <dependencies> + <dependency> + <groupId>org.jgrapht</groupId> + <artifactId>jgrapht-core</artifactId> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java similarity index 78% rename from circular-reference-detector/src/main/java/org/hjug/cycledetector/DSM.java rename to dsm/src/main/java/org/hjug/dsm/DSM.java index 4acf0cfe..a7c21611 100644 --- a/circular-reference-detector/src/main/java/org/hjug/cycledetector/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -1,4 +1,4 @@ -package org.hjug.cycledetector; +package org.hjug.dsm; import java.util.*; import org.jgrapht.Graph; @@ -8,7 +8,7 @@ import org.jgrapht.graph.SimpleDirectedWeightedGraph; /* - * Generated with Generative AI using a prompt similar to the following: +Generated with Generative AI using a prompt similar to the following and iterated on: Provide a complete implementation of a Numerical DSM with integer weighted edges in Java. Include as many comments as possible in the implementation to make it easy to understand. Use JGraphT classes and methods to the greatest extent possible. @@ -23,18 +23,26 @@ include a method tht returns all edges above the diagonal. include another method that returns the optimal edge above the diagonal to remove, include a third method that identifies all minimum weight edges to remove above the diagonal. + +Used https://sookocheff.com/post/dsm/improving-software-architecture-using-design-structure-matrix/#optimizing-processes +as a starting point. */ -// This is looking good, but is still a work in progress. This may not be needed at all. public class DSM { private Graph<String, DefaultWeightedEdge> graph; private List<String> sortedActivities; + boolean activitiesSorted = false; public DSM() { graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); sortedActivities = new ArrayList<>(); } + public DSM(Graph<String, DefaultWeightedEdge> graph) { + this.graph = graph; + sortedActivities = new ArrayList<>(); + } + public void addActivity(String activity) { graph.addVertex(activity); } @@ -49,31 +57,11 @@ public void addDependency(String from, String to, int weight) { public void orderVertices() { List<Set<String>> sccs = findStronglyConnectedComponents(); sortedActivities = topologicalSort(sccs); - } - - public void printDSM() { - System.out.println("Design Structure Matrix:"); - System.out.print(" "); - for (String col : sortedActivities) { - System.out.print(col + " "); - } - System.out.println(); - for (String row : sortedActivities) { - System.out.print(row + " "); - for (String col : sortedActivities) { - if (col.equals(row)) { - System.out.print("- "); - } else { - DefaultWeightedEdge edge = graph.getEdge(col, row); // Flipped matrix - if (edge != null) { - System.out.print((int) graph.getEdgeWeight(edge) + " "); - } else { - System.out.print("0 "); - } - } - } - System.out.println(); - } + // reversing corrects rendering of the DSM + // with sources as rows and targets as columns + // was needed after AI solution was generated and iterated + Collections.reverse(sortedActivities); + activitiesSorted = true; } private List<Set<String>> findStronglyConnectedComponents() { @@ -115,6 +103,10 @@ private void topologicalSortUtil(String activity, Set<String> visited, List<Stri } public List<DefaultWeightedEdge> getEdgesAboveDiagonal() { + if (!activitiesSorted) { + orderVertices(); + } + List<DefaultWeightedEdge> edgesAboveDiagonal = new ArrayList<>(); for (int i = 0; i < sortedActivities.size(); i++) { @@ -132,22 +124,33 @@ public List<DefaultWeightedEdge> getEdgesAboveDiagonal() { } public DefaultWeightedEdge getOptimalEdgeAboveDiagonalToRemove() { + if (!activitiesSorted) { + orderVertices(); + } + List<DefaultWeightedEdge> edgesAboveDiagonal = getEdgesAboveDiagonal(); DefaultWeightedEdge optimalEdge = null; - double minWeight = Double.MAX_VALUE; + int minWeight = Integer.MAX_VALUE; for (DefaultWeightedEdge edge : edgesAboveDiagonal) { - double weight = graph.getEdgeWeight(edge); + int weight = (int) graph.getEdgeWeight(edge); if (weight < minWeight) { minWeight = weight; optimalEdge = edge; + if (minWeight == 1) { + break; + } } } return optimalEdge; } - public List<DefaultWeightedEdge> getMinimumWeightEdgesToRemove() { + public List<DefaultWeightedEdge> getMinimumWeightEdgesAboveDiagonal() { + if (!activitiesSorted) { + orderVertices(); + } + List<DefaultWeightedEdge> edgesAboveDiagonal = getEdgesAboveDiagonal(); List<DefaultWeightedEdge> minWeightEdges = new ArrayList<>(); double minWeight = Double.MAX_VALUE; @@ -166,35 +169,32 @@ public List<DefaultWeightedEdge> getMinimumWeightEdgesToRemove() { return minWeightEdges; } - public static void main(String[] args) { - DSM dsm = new DSM(); - dsm.addActivity("A"); - dsm.addActivity("B"); - dsm.addActivity("C"); - dsm.addActivity("D"); - dsm.addActivity("E"); - dsm.addActivity("F"); - dsm.addActivity("G"); - dsm.addActivity("H"); - - dsm.addDependency("A", "B", 1); - // dsm.addDependency("A", "C", 6); - dsm.addDependency("B", "C", 2); - // dsm.addDependency("B", "D", 3); - dsm.addDependency("C", "D", 2); - dsm.addDependency("D", "A", 2); // Adding a cycle - - dsm.addDependency("C", "A", 7); // Adding a cycle - dsm.addDependency("G", "E", 2); // Adding a cycle - dsm.addDependency("E", "H", 2); // Adding a cycle - dsm.addDependency("H", "G", 5); // Adding a cycle - dsm.addDependency("A", "H", 7); // Adding a cycle - dsm.addDependency("H", "D", 9); // Adding a cycle - dsm.addDependency("C", "E", 9); // Adding a cycle - - dsm.orderVertices(); - dsm.printDSM(); - - System.out.println("Optimal edge above diagonal to remove: " + dsm.getOptimalEdgeAboveDiagonalToRemove()); + public void printDSM() { + if (!activitiesSorted) { + orderVertices(); + } + + System.out.println("Design Structure Matrix:"); + System.out.print(" "); + for (String col : sortedActivities) { + System.out.print(col + " "); + } + System.out.println(); + for (String row : sortedActivities) { + System.out.print(row + " "); + for (String col : sortedActivities) { + if (col.equals(row)) { + System.out.print("- "); + } else { + DefaultWeightedEdge edge = graph.getEdge(row, col); + if (edge != null) { + System.out.print((int) graph.getEdgeWeight(edge) + " "); + } else { + System.out.print("0 "); + } + } + } + System.out.println(); + } } } diff --git a/dsm/src/test/java/org/hjug/dsm/DSMTest.java b/dsm/src/test/java/org/hjug/dsm/DSMTest.java new file mode 100644 index 00000000..876b0704 --- /dev/null +++ b/dsm/src/test/java/org/hjug/dsm/DSMTest.java @@ -0,0 +1,96 @@ +package org.hjug.dsm; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DSMTest { + + DSM dsm; + + @BeforeEach + void setUp() { + dsm = new DSM(); + dsm.addActivity("A"); + dsm.addActivity("B"); + dsm.addActivity("C"); + dsm.addActivity("D"); + + dsm.addDependency("A", "B", 1); + dsm.addDependency("B", "C", 2); + dsm.addDependency("C", "D", 3); + dsm.addDependency("D", "A", 4); // Adding a cycle + dsm.addDependency("C", "A", 5); // Adding a cycle + dsm.addDependency("B", "A", 6); // Adding a cycle + + /* + D C B A + D - 0 0 4 + C 3 - 0 5 + B 0 2 - 6 + A 0 0 1 - + */ + + dsm.addActivity("E"); + dsm.addActivity("F"); + dsm.addActivity("G"); + dsm.addActivity("H"); + dsm.addDependency("D", "C", 2); + dsm.addDependency("A", "H", 7); + dsm.addDependency("E", "H", 2); + dsm.addDependency("E", "C", 9); + dsm.addDependency("G", "E", 2); + dsm.addDependency("H", "D", 9); + dsm.addDependency("H", "G", 5); + + // dsm.printDSM(); + } + + @Test + void optimalBackwardEdgeToRemove() { + // Identify which edge above the diagonal should be removed first + DefaultWeightedEdge edge = dsm.getOptimalEdgeAboveDiagonalToRemove(); + assertEquals("(D : C)", edge.toString()); + } + + @Test + void optimalBackwardEdgeToRemoveWithWeightOfOne() { + DSM dsm2 = new DSM(); + dsm2.addActivity("A"); + dsm2.addActivity("B"); + dsm2.addActivity("C"); + + dsm2.addDependency("A", "B", 1); + dsm2.addDependency("B", "C", 1); + dsm2.addDependency("B", "A", 1); + dsm2.addDependency("C", "A", 1); + + // Identify which edge above the diagonal should be removed first + DefaultWeightedEdge edge = dsm2.getOptimalEdgeAboveDiagonalToRemove(); + assertEquals("(C : A)", edge.toString()); + } + + @Test + void minWeightBackwardEdges() { + // Identify which edge above the diagonal in the set of cycles should be removed first + List<DefaultWeightedEdge> edges = dsm.getMinimumWeightEdgesAboveDiagonal(); + assertEquals(2, edges.size()); + assertEquals("(D : C)", edges.get(0).toString()); + assertEquals("(E : H)", edges.get(1).toString()); + } + + @Test + void edgesAboveDiagonal() { + // Identify edges above the diagonal + List<DefaultWeightedEdge> edges = dsm.getEdgesAboveDiagonal(); + assertEquals(5, edges.size()); + assertEquals("(D : C)", edges.get(0).toString()); + assertEquals("(D : A)", edges.get(1).toString()); + assertEquals("(C : A)", edges.get(2).toString()); + assertEquals("(B : A)", edges.get(3).toString()); + assertEquals("(E : H)", edges.get(4).toString()); + } +} diff --git a/pom.xml b/pom.xml index 3944a9a7..f5af32f4 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ <modules> <module>test-resources</module> + <module>dsm</module> <module>circular-reference-detector</module> <module>change-proneness-ranker</module> <module>effort-ranker</module> From 1b4d7c8e6d7a24b3a7bd2234d32f78b8edeff51d Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Sat, 15 Feb 2025 20:47:15 -0600 Subject: [PATCH 35/78] #129 Capturing impact of each backward edge if removed Capturing impact of each backward edge if removed --- dsm/src/main/java/org/hjug/dsm/DSM.java | 141 ++++++++++++++++-- .../java/org/hjug/dsm/EdgeToRemoveInfo.java | 13 ++ dsm/src/test/java/org/hjug/dsm/DSMTest.java | 18 ++- 3 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index a7c21611..0b7ce554 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -1,6 +1,8 @@ package org.hjug.dsm; import java.util.*; + +import lombok.Getter; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.cycle.TarjanSimpleCycles; @@ -33,6 +35,11 @@ public class DSM { private List<String> sortedActivities; boolean activitiesSorted = false; + @Getter + int stronglyConnectedComponentCount = 0; + @Getter + double averageStronglyConnectedComponentSize = 0.0; + public DSM() { graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); sortedActivities = new ArrayList<>(); @@ -54,9 +61,19 @@ public void addDependency(String from, String to, int weight) { } } - public void orderVertices() { - List<Set<String>> sccs = findStronglyConnectedComponents(); - sortedActivities = topologicalSort(sccs); + private void orderVertices() { + List<Set<String>> sccs = findStronglyConnectedComponents(graph); + + // capture the number of cycles + stronglyConnectedComponentCount = sccs.size(); + + // capture the average size of the cycles + averageStronglyConnectedComponentSize = sccs.stream() + .mapToInt(Set::size) + .average() + .orElse(0.0); + + sortedActivities = topologicalSort(sccs, graph); // reversing corrects rendering of the DSM // with sources as rows and targets as columns // was needed after AI solution was generated and iterated @@ -64,7 +81,7 @@ public void orderVertices() { activitiesSorted = true; } - private List<Set<String>> findStronglyConnectedComponents() { + private List<Set<String>> findStronglyConnectedComponents(Graph<String, DefaultWeightedEdge> graph) { TarjanSimpleCycles<String, DefaultWeightedEdge> tarjan = new TarjanSimpleCycles<>(graph); List<List<String>> cycles = tarjan.findSimpleCycles(); List<Set<String>> sccs = new ArrayList<>(); @@ -74,14 +91,14 @@ private List<Set<String>> findStronglyConnectedComponents() { return sccs; } - private List<String> topologicalSort(List<Set<String>> sccs) { + private List<String> topologicalSort(List<Set<String>> sccs, Graph<String, DefaultWeightedEdge> graph) { List<String> sortedActivities = new ArrayList<>(); Set<String> visited = new HashSet<>(); for (Set<String> scc : sccs) { for (String activity : scc) { if (!visited.contains(activity)) { - topologicalSortUtil(activity, visited, sortedActivities); + topologicalSortUtil(activity, visited, sortedActivities, graph); } } } @@ -90,12 +107,12 @@ private List<String> topologicalSort(List<Set<String>> sccs) { return sortedActivities; } - private void topologicalSortUtil(String activity, Set<String> visited, List<String> sortedActivities) { + private void topologicalSortUtil(String activity, Set<String> visited, List<String> sortedActivities, Graph<String, DefaultWeightedEdge> graph) { visited.add(activity); for (String neighbor : Graphs.successorListOf(graph, activity)) { if (!visited.contains(neighbor)) { - topologicalSortUtil(neighbor, visited, sortedActivities); + topologicalSortUtil(neighbor, visited, sortedActivities, graph); } } @@ -123,7 +140,7 @@ public List<DefaultWeightedEdge> getEdgesAboveDiagonal() { return edgesAboveDiagonal; } - public DefaultWeightedEdge getOptimalEdgeAboveDiagonalToRemove() { + public DefaultWeightedEdge getFirstLowestWeightEdgeAboveDiagonalToRemove() { if (!activitiesSorted) { orderVertices(); } @@ -197,4 +214,110 @@ public void printDSM() { System.out.println(); } } + + + /** + * Captures the impact of the removal of each edge above the diagonal. + * This algorithm will have at worst a runtime of O(E^2 + EV): + * Tarjan's algorithm runtime is E + V and is run E/2 times (at worst) + * Topological sort runtime is E + V and is run E/2 times (at worst) + * with E/2 iterations + * O(2E + 2V) * O(E/2) = O(E^2 + EV) + * + * @return List<EdgeToRemoveInfo> Impact of each edge if removed. + */ + public List<EdgeToRemoveInfo> getImpactOfEdgesAboveDiagonalIfRemoved() { + // get edges above diagonal for DSM graph + List<DefaultWeightedEdge> edgesAboveDiagonal = getEdgesAboveDiagonal(); + + //build the cloned graph + Graph<String, DefaultWeightedEdge> clonedGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + graph.vertexSet().forEach(clonedGraph::addVertex); + for (DefaultWeightedEdge weightedEdge : graph.edgeSet()) { + clonedGraph.addEdge(graph.getEdgeSource(weightedEdge), graph.getEdgeTarget(weightedEdge), weightedEdge); + } + + List<EdgeToRemoveInfo> edgesToRemove = new ArrayList<>(); + // capture impact of each edge on graph when removed + for (DefaultWeightedEdge edge : edgesAboveDiagonal) { + //remove the edge + clonedGraph.removeEdge(edge); + + //sort the graph + List<Set<String>> sccs = findStronglyConnectedComponents(clonedGraph); + List<String> sortedClonedActivities = topologicalSort(sccs, clonedGraph); + Collections.reverse(sortedClonedActivities); + + // get the number of edges above the diagonal + List<DefaultWeightedEdge> clonedGraphEdgesAboveDiagonal = getEdgesAboveDiagonal(clonedGraph, sortedClonedActivities); + int edgeCount = clonedGraphEdgesAboveDiagonal.size(); + + // get the new number of cycles + int cycleCount = sccs.size(); + + // calculate the average size of the new cycle + double averageCycleSize = sccs.stream() + .mapToInt(Set::size) + .average() + .orElse(0.0); + + EdgeToRemoveInfo edgeToRemoveInfo = new EdgeToRemoveInfo(edge, graph.getEdgeWeight(edge), edgeCount, cycleCount, averageCycleSize); + edgesToRemove.add(edgeToRemoveInfo); + + //add the edge back + clonedGraph.addEdge(graph.getEdgeSource(edge), graph.getEdgeTarget(edge), edge); + clonedGraph.setEdgeWeight(edge, graph.getEdgeWeight(edge)); + } + + return edgesToRemove; + } + + // only used by getImpactOfEdgesAboveDiagonalIfRemoved() + private List<DefaultWeightedEdge> getEdgesAboveDiagonal(Graph<String, DefaultWeightedEdge> graph, List<String> sortedActivities) { + + List<DefaultWeightedEdge> edgesAboveDiagonal = new ArrayList<>(); + + for (int i = 0; i < sortedActivities.size(); i++) { + for (int j = i + 1; j < sortedActivities.size(); j++) { + // source / destination vertex was flipped after solution generation + // to correctly identify the vertex above the diagonal to remove + DefaultWeightedEdge edge = graph.getEdge(sortedActivities.get(i), sortedActivities.get(j)); + if (edge != null) { + edgesAboveDiagonal.add(edge); + } + } + } + + return edgesAboveDiagonal; + } + + void printDSM(Graph<String, DefaultWeightedEdge> graph, List<String> sortedActivities) { + + System.out.println("Design Structure Matrix:"); + System.out.print(" "); + for (String col : sortedActivities) { + System.out.print(col + " "); + } + System.out.println(); + for (String row : sortedActivities) { + System.out.print(row + " "); + for (String col : sortedActivities) { + if (col.equals(row)) { + System.out.print("- "); + } else { + DefaultWeightedEdge edge = graph.getEdge(row, col); + if (edge != null) { + System.out.print((int) graph.getEdgeWeight(edge) + " "); + } else { + System.out.print("0 "); + } + } + } + System.out.println(); + } + } } + + + + diff --git a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java new file mode 100644 index 00000000..cfbfffb8 --- /dev/null +++ b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java @@ -0,0 +1,13 @@ +package org.hjug.dsm; + +import lombok.Data; +import org.jgrapht.graph.DefaultWeightedEdge; + +@Data +public class EdgeToRemoveInfo { + private final DefaultWeightedEdge edge; + private final double edgeWeight; + private final int newEdgeCount; + private final int newStronglyConnectedComponentCount; + private final double newAverageStronglyConnectedComponentSize; +} \ No newline at end of file diff --git a/dsm/src/test/java/org/hjug/dsm/DSMTest.java b/dsm/src/test/java/org/hjug/dsm/DSMTest.java index 876b0704..d79f14b8 100644 --- a/dsm/src/test/java/org/hjug/dsm/DSMTest.java +++ b/dsm/src/test/java/org/hjug/dsm/DSMTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.List; + import org.jgrapht.graph.DefaultWeightedEdge; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -46,13 +47,13 @@ void setUp() { dsm.addDependency("H", "D", 9); dsm.addDependency("H", "G", 5); - // dsm.printDSM(); +// dsm.printDSM(); } @Test void optimalBackwardEdgeToRemove() { // Identify which edge above the diagonal should be removed first - DefaultWeightedEdge edge = dsm.getOptimalEdgeAboveDiagonalToRemove(); + DefaultWeightedEdge edge = dsm.getFirstLowestWeightEdgeAboveDiagonalToRemove(); assertEquals("(D : C)", edge.toString()); } @@ -69,7 +70,7 @@ void optimalBackwardEdgeToRemoveWithWeightOfOne() { dsm2.addDependency("C", "A", 1); // Identify which edge above the diagonal should be removed first - DefaultWeightedEdge edge = dsm2.getOptimalEdgeAboveDiagonalToRemove(); + DefaultWeightedEdge edge = dsm2.getFirstLowestWeightEdgeAboveDiagonalToRemove(); assertEquals("(C : A)", edge.toString()); } @@ -93,4 +94,15 @@ void edgesAboveDiagonal() { assertEquals("(B : A)", edges.get(3).toString()); assertEquals("(E : H)", edges.get(4).toString()); } + + @Test + void getImpactOfEdgesAboveDiagonalIfRemoved() { + List<EdgeToRemoveInfo> infos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(); + assertEquals(5, infos.size()); + + assertEquals("(D : C)", infos.get(0).getEdge().toString()); + assertEquals(4, infos.get(0).getNewEdgeCount()); + assertEquals(7, infos.get(0).getNewStronglyConnectedComponentCount()); + assertEquals(3.7142857142857144, infos.get(0).getNewAverageStronglyConnectedComponentSize()); + } } From 3102729b58f1ea9831576ce450dce43766be5208 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Mon, 17 Feb 2025 20:22:16 -0600 Subject: [PATCH 36/78] Lots of refactoring - Extracted codebase processing to a separate module - Moved cycle detection from CostBenefitCalculator to CircularReferenceChecker --- circular-reference-detector/pom.xml | 29 +------ .../app/CircularReferenceDetectorApp.java | 28 ------ .../CircularReferenceChecker.java | 36 ++++++++ .../hjug/parser/visitor/testclasses/C.java | 3 - .../hjug/parser/visitor/testclasses/D.java | 3 - .../hjug/parser/visitor/testclasses/E.java | 5 -- .../hjug/parser/visitor/testclasses/F.java | 3 - .../hjug/parser/visitor/testclasses/G.java | 3 - .../hjug/parser/visitor/testclasses/H.java | 3 - .../testclasses/methodInvocation/C.java | 3 - .../testclasses/methodInvocation/D.java | 3 - .../visitor/testclasses/newClass/B.java | 6 -- .../visitor/testclasses/newClass/C.java | 3 - codebase-graph-builder/pom.xml | 85 +++++++++++++++++++ .../hjug/graphbuilder/CodebaseGraphDTO.java | 4 +- .../hjug/graphbuilder/JavaGraphBuilder.java | 14 +-- .../visitor/FqnCapturingProcessor.java | 2 +- .../visitor/JavaClassDeclarationVisitor.java | 2 +- .../visitor/JavaFqnCapturingVisitor.java | 2 +- .../visitor/JavaMethodDeclarationVisitor.java | 2 +- .../visitor/JavaMethodInvocationVisitor.java | 2 +- .../visitor/JavaNewClassVisitor.java | 2 +- .../visitor/JavaVariableTypeVisitor.java | 2 +- .../graphbuilder}/visitor/JavaVisitor.java | 2 +- .../graphbuilder}/visitor/TypeProcessor.java | 2 +- .../graphbuilder/JavaGraphBuilderTest.java | 16 ++-- .../JavaClassDeclarationVisitorTest.java | 20 ++--- .../visitor/JavaFqnCapturingVisitorTest.java | 32 +++---- .../JavaMethodDeclarationVisitorTest.java | 6 +- .../JavaMethodInvocationVisitorTest.java | 18 ++-- .../visitor/JavaNewClassVisitorTest.java | 14 +-- .../visitor/JavaVariableTypeVisitorTest.java | 20 ++--- .../visitor/JavaVisitorTest.java | 4 +- .../graphbuilder}/visitor/testclasses/A.java | 2 +- .../graphbuilder}/visitor/testclasses/B.java | 2 +- .../graphbuilder/visitor/testclasses/C.java | 3 + .../graphbuilder/visitor/testclasses/D.java | 3 + .../graphbuilder/visitor/testclasses/E.java | 5 ++ .../graphbuilder/visitor/testclasses/F.java | 3 + .../graphbuilder/visitor/testclasses/G.java | 3 + .../graphbuilder/visitor/testclasses/H.java | 3 + .../visitor/testclasses/MyAnnotation.java | 2 +- .../testclasses/MyOtherAnnotation.java | 2 +- .../testclasses/methodInvocation/A.java | 2 +- .../testclasses/methodInvocation/B.java | 2 +- .../testclasses/methodInvocation/C.java | 3 + .../testclasses/methodInvocation/D.java | 3 + .../visitor/testclasses/newClass/A.java | 2 +- .../visitor/testclasses/newClass/B.java | 6 ++ .../visitor/testclasses/newClass/C.java | 3 + .../com/ideacrest/parser/testclasses/A.java | 0 .../com/ideacrest/parser/testclasses/B.java | 0 .../com/ideacrest/parser/testclasses/C.java | 0 .../com/ideacrest/parser/testclasses/D.java | 0 .../com/ideacrest/parser/testclasses/E.java | 0 cost-benefit-calculator/pom.xml | 5 ++ .../org/hjug/cbc/CostBenefitCalculator.java | 80 +++++++---------- dsm/src/main/java/org/hjug/dsm/DSM.java | 42 +++++---- .../java/org/hjug/dsm/EdgeToRemoveInfo.java | 2 +- dsm/src/test/java/org/hjug/dsm/DSMTest.java | 3 +- pom.xml | 9 +- 61 files changed, 315 insertions(+), 254 deletions(-) delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/C.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java delete mode 100644 circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java create mode 100644 codebase-graph-builder/pom.xml rename circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java => codebase-graph-builder/src/main/java/org/hjug/graphbuilder/CodebaseGraphDTO.java (86%) rename circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java => codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java (90%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/FqnCapturingProcessor.java (97%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/JavaClassDeclarationVisitor.java (99%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/JavaFqnCapturingVisitor.java (98%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/JavaMethodDeclarationVisitor.java (98%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/JavaMethodInvocationVisitor.java (96%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/JavaNewClassVisitor.java (93%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/JavaVariableTypeVisitor.java (99%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/JavaVisitor.java (98%) rename {circular-reference-detector/src/main/java/org/hjug/parser => codebase-graph-builder/src/main/java/org/hjug/graphbuilder}/visitor/TypeProcessor.java (99%) rename circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java => codebase-graph-builder/src/test/java/org/hjug/graphbuilder/JavaGraphBuilderTest.java (90%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/JavaClassDeclarationVisitorTest.java (72%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/JavaFqnCapturingVisitorTest.java (51%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/JavaMethodDeclarationVisitorTest.java (91%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/JavaMethodInvocationVisitorTest.java (70%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/JavaNewClassVisitorTest.java (75%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/JavaVariableTypeVisitorTest.java (70%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/JavaVisitorTest.java (92%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/testclasses/A.java (95%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/testclasses/B.java (73%) create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/C.java create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/D.java create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/E.java create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/F.java create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/G.java create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/H.java rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/testclasses/MyAnnotation.java (72%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/testclasses/MyOtherAnnotation.java (73%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/testclasses/methodInvocation/A.java (76%) rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/testclasses/methodInvocation/B.java (60%) create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/C.java create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/D.java rename {circular-reference-detector/src/test/java/org/hjug/parser => codebase-graph-builder/src/test/java/org/hjug/graphbuilder}/visitor/testclasses/newClass/A.java (73%) create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/B.java create mode 100644 codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/C.java rename {circular-reference-detector => codebase-graph-builder}/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/A.java (100%) rename {circular-reference-detector => codebase-graph-builder}/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/B.java (100%) rename {circular-reference-detector => codebase-graph-builder}/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/C.java (100%) rename {circular-reference-detector => codebase-graph-builder}/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/D.java (100%) rename {circular-reference-detector => codebase-graph-builder}/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/E.java (100%) diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml index 6f5acc5a..523dbd37 100644 --- a/circular-reference-detector/pom.xml +++ b/circular-reference-detector/pom.xml @@ -41,33 +41,10 @@ <artifactId>jgrapht-core</artifactId> </dependency> <dependency> - <groupId>org.openrewrite</groupId> - <artifactId>rewrite-java-21</artifactId> - </dependency> - <dependency> - <groupId>org.openrewrite</groupId> - <artifactId>rewrite-java-17</artifactId> - </dependency> - <dependency> - <groupId>org.openrewrite</groupId> - <artifactId>rewrite-java-11</artifactId> - </dependency> - <dependency> - <groupId>org.openrewrite</groupId> - <artifactId>rewrite-java</artifactId> - </dependency> - - <!--gizmo 1.0.11, used by rewrite-core has a CVSS score > 8.0 --> - <dependency> - <groupId>io.quarkus.gizmo</groupId> - <artifactId>gizmo</artifactId> - <version>1.8.0</version> - </dependency> - <dependency> - <groupId>org.openrewrite</groupId> - <artifactId>rewrite-core</artifactId> + <groupId>org.hjug.refactorfirst.codebasegraphbuilder</groupId> + <artifactId>codebase-graph-builder</artifactId> + <scope>test</scope> </dependency> - <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> diff --git a/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java b/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java index 71497697..ace7bb26 100644 --- a/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java +++ b/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java @@ -4,7 +4,6 @@ import java.util.Map; import java.util.Set; import org.hjug.cycledetector.CircularReferenceChecker; -import org.hjug.parser.JavaProjectParser; import org.jgrapht.Graph; import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree; import org.jgrapht.graph.AsSubgraph; @@ -21,33 +20,6 @@ public class CircularReferenceDetectorApp { private Map<String, AsSubgraph> renderedSubGraphs = new HashMap<>(); - // public static void main(String[] args) { - // CircularReferenceDetectorApp circularReferenceDetectorApp = new CircularReferenceDetectorApp(); - // circularReferenceDetectorApp.launchApp(args); - // } - - /** - * Parses source project files and creates a graph of class references of the java project. - * Detects cycles in the class references graph and stores the cycle graphs in the given output directory - * - * @param args - */ - public void launchApp(String[] args) { - if (!validateArgs(args)) { - printCommandUsage(); - } else { - String srcDirectoryPath = args[0]; - JavaProjectParser javaProjectParser = new JavaProjectParser(); - try { - Graph<String, DefaultWeightedEdge> classReferencesGraph = - javaProjectParser.getClassReferences(srcDirectoryPath); - detectAndStoreCyclesInDirectory(classReferencesGraph); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - private void detectAndStoreCyclesInDirectory(Graph<String, DefaultWeightedEdge> classReferencesGraph) { CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap = diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java b/circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java index 19f2b49d..6e840d17 100644 --- a/circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java +++ b/circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java @@ -2,13 +2,49 @@ import java.util.HashMap; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; import org.jgrapht.alg.cycle.CycleDetector; import org.jgrapht.graph.AsSubgraph; import org.jgrapht.graph.DefaultWeightedEdge; +@Slf4j public class CircularReferenceChecker { + private final Map<String, AsSubgraph<String, DefaultWeightedEdge>> uniqueSubGraphs = new HashMap<>(); + + public Map<String, AsSubgraph<String, DefaultWeightedEdge>> getCycles(Graph<String, DefaultWeightedEdge> graph) { + + Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles = detectCycles(graph); + + cycles.forEach((vertex, subGraph) -> { + int vertexCount = subGraph.vertexSet().size(); + int edgeCount = subGraph.edgeSet().size(); + + if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) { + uniqueSubGraphs.put(vertex, subGraph); + log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount); + } + }); + + return uniqueSubGraphs; + } + + private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) { + if (!uniqueSubGraphs.isEmpty()) { + for (AsSubgraph<String, DefaultWeightedEdge> renderedSubGraph : uniqueSubGraphs.values()) { + if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size() + && renderedSubGraph.edgeSet().size() + == subGraph.edgeSet().size() + && renderedSubGraph.vertexSet().contains(vertex)) { + return true; + } + } + } + + return false; + } + /** * Detects cycles in the classReferencesGraph parameter * and stores the cycles of a class as a subgraph in a Map diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java deleted file mode 100644 index acb8af31..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/C.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses; - -public class C<T extends A, U extends B> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java deleted file mode 100644 index 10c12035..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/D.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses; - -public class D {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java deleted file mode 100644 index d408ccc7..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/E.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.hjug.parser.visitor.testclasses; - -public interface E { - void foo(A a); -} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java deleted file mode 100644 index 4485e0f2..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/F.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses; - -public class F {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java deleted file mode 100644 index ff42f18c..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/G.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses; - -public class G extends F {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java deleted file mode 100644 index a7741b3d..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/H.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses; - -public class H<T> extends B<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/C.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/C.java deleted file mode 100644 index 1b5d71d1..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/C.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses.methodInvocation; - -public class C<T> extends B<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java deleted file mode 100644 index ad1abcd9..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/D.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses.methodInvocation; - -public class D<T> extends C<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java deleted file mode 100644 index 64a1bfeb..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/B.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.hjug.parser.visitor.testclasses.newClass; - -public class B { - - public B(C c) {} -} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java b/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java deleted file mode 100644 index 9e27dc22..00000000 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/C.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.hjug.parser.visitor.testclasses.newClass; - -public class C {} diff --git a/codebase-graph-builder/pom.xml b/codebase-graph-builder/pom.xml new file mode 100644 index 00000000..f1c67690 --- /dev/null +++ b/codebase-graph-builder/pom.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.hjug.refactorfirst</groupId> + <artifactId>refactor-first</artifactId> + <version>0.6.3-SNAPSHOT</version> + </parent> + + <groupId>org.hjug.refactorfirst.codebasegraphbuilder</groupId> + <artifactId>codebase-graph-builder</artifactId> + <version>0.6.3-SNAPSHOT</version> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.openrewrite.recipe</groupId> + <artifactId>rewrite-recipe-bom</artifactId> + <version>3.1.0</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>${lombok.version}</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.26</version> + </dependency> + <dependency> + <groupId>org.jgrapht</groupId> + <artifactId>jgrapht-core</artifactId> + </dependency> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-java-21</artifactId> + </dependency> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-java-17</artifactId> + </dependency> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-java-11</artifactId> + </dependency> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-java</artifactId> + </dependency> + + <!--gizmo 1.0.11, used by rewrite-core has a CVSS score > 8.0 --> + <dependency> + <groupId>io.quarkus.gizmo</groupId> + <artifactId>gizmo</artifactId> + <version>1.8.0</version> + </dependency> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + </dependency> + + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.9.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.9.0</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> \ No newline at end of file diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/CodebaseGraphDTO.java similarity index 86% rename from circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/CodebaseGraphDTO.java index 35f6ccb3..4933eb05 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/GraphDTO.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/CodebaseGraphDTO.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.graphbuilder; import java.util.Map; import lombok.Data; @@ -6,7 +6,7 @@ import org.jgrapht.graph.DefaultWeightedEdge; @Data -public class GraphDTO { +public class CodebaseGraphDTO { private final Graph<String, DefaultWeightedEdge> classReferencesGraph; private final Graph<String, DefaultWeightedEdge> packageReferencesGraph; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java similarity index 90% rename from circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java index 92b4f4ec..43bf60d5 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/JavaGraphBuilder.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.graphbuilder; import java.io.File; import java.io.IOException; @@ -9,9 +9,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; -import org.hjug.parser.visitor.JavaMethodDeclarationVisitor; -import org.hjug.parser.visitor.JavaVariableTypeVisitor; -import org.hjug.parser.visitor.JavaVisitor; +import org.hjug.graphbuilder.visitor.JavaMethodDeclarationVisitor; +import org.hjug.graphbuilder.visitor.JavaVariableTypeVisitor; +import org.hjug.graphbuilder.visitor.JavaVisitor; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; @@ -20,7 +20,7 @@ import org.openrewrite.java.JavaParser; @Slf4j -public class JavaProjectParser { +public class JavaGraphBuilder { /** * Given a java source directory return a graph of class references @@ -40,7 +40,7 @@ public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory return classReferencesGraph; } - private GraphDTO processWithOpenRewrite(String srcDir) throws IOException { + private CodebaseGraphDTO processWithOpenRewrite(String srcDir) throws IOException { File srcDirectory = new File(srcDir); JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -72,7 +72,7 @@ private GraphDTO processWithOpenRewrite(String srcDir) throws IOException { removeClassesNotInCodebase(javaVisitor.getPackagesInCodebase(), classReferencesGraph); - return new GraphDTO( + return new CodebaseGraphDTO( classReferencesGraph, packageReferencesGraph, javaVisitor.getClassToSourceFilePathMapping()); } diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/FqnCapturingProcessor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/FqnCapturingProcessor.java similarity index 97% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/FqnCapturingProcessor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/FqnCapturingProcessor.java index 611d25cf..475b03e4 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/FqnCapturingProcessor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/FqnCapturingProcessor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.util.Arrays; import java.util.HashMap; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaClassDeclarationVisitor.java similarity index 99% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaClassDeclarationVisitor.java index 32c9dcb3..a87c393d 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaClassDeclarationVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaClassDeclarationVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.util.List; import lombok.Getter; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaFqnCapturingVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitor.java similarity index 98% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaFqnCapturingVisitor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitor.java index 07c62fd4..7bb536fe 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaFqnCapturingVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.util.*; import lombok.Getter; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitor.java similarity index 98% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitor.java index bf63c407..31847331 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.util.List; import lombok.Getter; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitor.java similarity index 96% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitor.java index 29dc3704..45e15e6c 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaMethodInvocationVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitor.java similarity index 93% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitor.java index 61fdac71..1be95649 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaNewClassVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVariableTypeVisitor.java similarity index 99% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVariableTypeVisitor.java index ffc25ee2..117681cb 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVariableTypeVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVariableTypeVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.util.List; import lombok.Getter; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java similarity index 98% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java index 0b4e1f4b..5a00dc51 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/JavaVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaVisitor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.util.*; import lombok.Getter; diff --git a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeProcessor.java similarity index 99% rename from circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java rename to codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeProcessor.java index ff78017e..5f2e83da 100644 --- a/circular-reference-detector/src/main/java/org/hjug/parser/visitor/TypeProcessor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/TypeProcessor.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import lombok.extern.slf4j.Slf4j; import org.jgrapht.Graph; diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/JavaGraphBuilderTest.java similarity index 90% rename from circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/JavaGraphBuilderTest.java index 407b0c65..3b0d3689 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/JavaGraphBuilderTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser; +package org.hjug.graphbuilder; import static org.junit.jupiter.api.Assertions.*; @@ -12,15 +12,15 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class JavaProjectParserTest { +class JavaGraphBuilderTest { - JavaProjectParser javaProjectParser = new JavaProjectParser(); + JavaGraphBuilder javaGraphBuilder = new JavaGraphBuilder(); @DisplayName("When source directory input param is empty or null throw IllegalArgumentException.") @Test void parseSourceDirectoryEmptyTest() { - Assertions.assertThrows(IllegalArgumentException.class, () -> javaProjectParser.getClassReferences("")); - Assertions.assertThrows(IllegalArgumentException.class, () -> javaProjectParser.getClassReferences(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> javaGraphBuilder.getClassReferences("")); + Assertions.assertThrows(IllegalArgumentException.class, () -> javaGraphBuilder.getClassReferences(null)); } @DisplayName("Given a valid source directory input parameter return a valid graph.") @@ -28,7 +28,7 @@ void parseSourceDirectoryEmptyTest() { void parseSourceDirectoryTest() throws IOException { File srcDirectory = new File("src/test/resources/javaSrcDirectory"); Graph<String, DefaultWeightedEdge> classReferencesGraph = - javaProjectParser.getClassReferences(srcDirectory.getAbsolutePath()); + javaGraphBuilder.getClassReferences(srcDirectory.getAbsolutePath()); assertNotNull(classReferencesGraph); assertEquals(5, classReferencesGraph.vertexSet().size()); assertEquals(7, classReferencesGraph.edgeSet().size()); @@ -76,7 +76,7 @@ private static double getEdgeWeight( void removeClassesNotInCodebase() throws IOException { File srcDirectory = new File("src/test/resources/javaSrcDirectory"); Graph<String, DefaultWeightedEdge> classReferencesGraph = - javaProjectParser.getClassReferences(srcDirectory.getAbsolutePath()); + javaGraphBuilder.getClassReferences(srcDirectory.getAbsolutePath()); classReferencesGraph.addVertex("org.favioriteoss.FunClass"); classReferencesGraph.addVertex("org.favioriteoss.AnotherFunClass"); @@ -92,7 +92,7 @@ void removeClassesNotInCodebase() throws IOException { Set<String> packagesInCodebase = new HashSet<>(); packagesInCodebase.add("com.ideacrest.parser.testclasses"); - javaProjectParser.removeClassesNotInCodebase(packagesInCodebase, classReferencesGraph); + javaGraphBuilder.removeClassesNotInCodebase(packagesInCodebase, classReferencesGraph); assertFalse(classReferencesGraph.containsVertex("org.favioriteoss.FunClass")); assertFalse(classReferencesGraph.containsVertex("org.favioriteoss.AnotherFunClass")); diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaClassDeclarationVisitorTest.java similarity index 72% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaClassDeclarationVisitorTest.java index ced49dd0..1cf523a1 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaClassDeclarationVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaClassDeclarationVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.io.File; import java.io.IOException; @@ -18,7 +18,7 @@ class JavaClassDeclarationVisitorTest { @Test void visitClasses() throws IOException { - File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -34,29 +34,29 @@ void visitClasses() throws IOException { Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.A")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.A")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.B")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.B")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.C")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.C")); // false because it doesn't reference any other classes Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.D")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.D")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.MyAnnotation")); // false because the class declaration doesn't reference any other classes Assertions.assertFalse(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.E")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.E")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.F")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.F")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.G")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.G")); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitorTest.java similarity index 51% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitorTest.java index e1f72832..fc94e660 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaFqnCapturingVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.io.File; import java.io.IOException; @@ -21,7 +21,7 @@ class JavaFqnCapturingVisitorTest { @Test void visitClasses() throws IOException { - File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -35,32 +35,32 @@ void visitClasses() throws IOException { }); Map<String, Map<String, String>> fqns = javaFqnCapturingVisitor.getFqnMap(); - Map<String, String> processed = fqns.get("org.hjug.parser.visitor.testclasses"); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A", processed.get("A")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass", processed.get("A.InnerClass")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.A.InnerClass", processed.get("InnerClass")); + Map<String, String> processed = fqns.get("org.hjug.graphbuilder.visitor.testclasses"); + Assertions.assertEquals("org.hjug.graphbuilder.visitor.testclasses.A", processed.get("A")); + Assertions.assertEquals("org.hjug.graphbuilder.visitor.testclasses.A.InnerClass", processed.get("A.InnerClass")); + Assertions.assertEquals("org.hjug.graphbuilder.visitor.testclasses.A.InnerClass", processed.get("InnerClass")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner", processed.get("A.InnerClass.InnerInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("A.InnerClass.InnerInner.MegaInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerClass.InnerInner.MegaInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerInner.MegaInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); + "org.hjug.graphbuilder.visitor.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); Assertions.assertEquals( - "org.hjug.parser.visitor.testclasses.A.StaticInnerClass", processed.get("StaticInnerClass")); - Assertions.assertEquals("org.hjug.parser.visitor.testclasses.NonPublic", processed.get("NonPublic")); + "org.hjug.graphbuilder.visitor.testclasses.A.StaticInnerClass", processed.get("StaticInnerClass")); + Assertions.assertEquals("org.hjug.graphbuilder.visitor.testclasses.NonPublic", processed.get("NonPublic")); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitorTest.java similarity index 91% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitorTest.java index 84c7d945..3e20d16f 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodDeclarationVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.io.File; import java.io.IOException; @@ -18,7 +18,7 @@ class JavaMethodDeclarationVisitorTest { @Test void visitMethodDeclarations() throws IOException { - File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -35,7 +35,7 @@ void visitMethodDeclarations() throws IOException { Assertions.assertTrue(methodDeclarationVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.A")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.A")); // TODO: Assert stuff /* Assertions.assertTrue(methodDeclarationVisitor.getClassReferencesGraph().containsVertex("org.hjug.javaVariableVisitorTestClasses.A")); diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitorTest.java similarity index 70% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitorTest.java index 4fbdc898..1f92a05b 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaMethodInvocationVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.io.File; import java.io.IOException; @@ -21,7 +21,7 @@ class JavaMethodInvocationVisitorTest { @Test void visitMethodInvocations() throws IOException { - File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation"); + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation"); JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); @@ -44,19 +44,19 @@ void visitMethodInvocations() throws IOException { }); Graph<String, DefaultWeightedEdge> graph = classDeclarationVisitor.getClassReferencesGraph(); - Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.A")); - Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.B")); - Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.methodInvocation.C")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.methodInvocation.A")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.methodInvocation.B")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.methodInvocation.C")); Assertions.assertEquals( 3, graph.getEdgeWeight(graph.getEdge( - "org.hjug.parser.visitor.testclasses.methodInvocation.A", - "org.hjug.parser.visitor.testclasses.methodInvocation.B"))); + "org.hjug.graphbuilder.visitor.testclasses.methodInvocation.A", + "org.hjug.graphbuilder.visitor.testclasses.methodInvocation.B"))); Assertions.assertEquals( 3, graph.getEdgeWeight(graph.getEdge( - "org.hjug.parser.visitor.testclasses.methodInvocation.A", - "org.hjug.parser.visitor.testclasses.methodInvocation.C"))); + "org.hjug.graphbuilder.visitor.testclasses.methodInvocation.A", + "org.hjug.graphbuilder.visitor.testclasses.methodInvocation.C"))); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorTest.java similarity index 75% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorTest.java index 9f77aaf2..8612b12b 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaNewClassVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaNewClassVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.io.File; import java.io.IOException; @@ -21,7 +21,7 @@ public class JavaNewClassVisitorTest { @Test void visitNewClass() throws IOException { - File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses/newClass"); + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass"); JavaParser javaParser = JavaParser.fromJavaVersion().build(); ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); @@ -44,15 +44,15 @@ void visitNewClass() throws IOException { }); Graph<String, DefaultWeightedEdge> graph = variableTypeVisitor.getClassReferencesGraph(); - Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.A")); - Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.B")); - Assertions.assertTrue(graph.containsVertex("org.hjug.parser.visitor.testclasses.newClass.C")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.A")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.B")); + Assertions.assertTrue(graph.containsVertex("org.hjug.graphbuilder.visitor.testclasses.newClass.C")); // TODO: Investigate further to confirm correctness Assertions.assertEquals( 3, graph.getEdgeWeight(graph.getEdge( - "org.hjug.parser.visitor.testclasses.newClass.A", - "org.hjug.parser.visitor.testclasses.newClass.C"))); + "org.hjug.graphbuilder.visitor.testclasses.newClass.A", + "org.hjug.graphbuilder.visitor.testclasses.newClass.C"))); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVariableTypeVisitorTest.java similarity index 70% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVariableTypeVisitorTest.java index ccb4c3c8..fbaf7523 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVariableTypeVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVariableTypeVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.io.File; import java.io.IOException; @@ -18,7 +18,7 @@ class JavaVariableTypeVisitorTest { @Test void visitClasses() throws IOException { - File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); @@ -33,27 +33,27 @@ void visitClasses() throws IOException { Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.A")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.A")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.B")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.B")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.C")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.C")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.D")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.D")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.E")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.E")); Assertions.assertTrue(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.MyAnnotation")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.MyAnnotation")); Assertions.assertFalse(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.F")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.F")); Assertions.assertFalse(javaVariableCapturingVisitor .getClassReferencesGraph() - .containsVertex("org.hjug.parser.visitor.testclasses.G")); + .containsVertex("org.hjug.graphbuilder.visitor.testclasses.G")); } } diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java similarity index 92% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java index 69cf1f67..3b41d13b 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/JavaVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaVisitorTest.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor; +package org.hjug.graphbuilder.visitor; import java.io.File; import java.io.IOException; @@ -23,7 +23,7 @@ class JavaVisitorTest { @Test void visitClasses() throws IOException { - File srcDirectory = new File("src/test/java/org/hjug/parser/visitor/testclasses"); + File srcDirectory = new File("src/test/java/org/hjug/graphbuilder/visitor/testclasses"); org.openrewrite.java.JavaParser javaParser = JavaParser.fromJavaVersion().build(); diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/A.java similarity index 95% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/A.java index a5396353..9b42e2c4 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/A.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/A.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor.testclasses; +package org.hjug.graphbuilder.visitor.testclasses; import java.util.List; import java.util.Map; diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/B.java similarity index 73% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/B.java index 439a6862..991ea9c9 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/B.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/B.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor.testclasses; +package org.hjug.graphbuilder.visitor.testclasses; public class B<T> { diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/C.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/C.java new file mode 100644 index 00000000..11daf502 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/C.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses; + +public class C<T extends A, U extends B> {} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/D.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/D.java new file mode 100644 index 00000000..d12051e0 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/D.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses; + +public class D {} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/E.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/E.java new file mode 100644 index 00000000..e715afef --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/E.java @@ -0,0 +1,5 @@ +package org.hjug.graphbuilder.visitor.testclasses; + +public interface E { + void foo(A a); +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/F.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/F.java new file mode 100644 index 00000000..770c8ee1 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/F.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses; + +public class F {} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/G.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/G.java new file mode 100644 index 00000000..86c80a3f --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/G.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses; + +public class G extends F {} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/H.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/H.java new file mode 100644 index 00000000..1a427e58 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/H.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses; + +public class H<T> extends B<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/MyAnnotation.java similarity index 72% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/MyAnnotation.java index 858447f0..71089ab8 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyAnnotation.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/MyAnnotation.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor.testclasses; +package org.hjug.graphbuilder.visitor.testclasses; import java.lang.annotation.ElementType; import java.lang.annotation.Target; diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/MyOtherAnnotation.java similarity index 73% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/MyOtherAnnotation.java index d55959ac..3907769f 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/MyOtherAnnotation.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/MyOtherAnnotation.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor.testclasses; +package org.hjug.graphbuilder.visitor.testclasses; import java.lang.annotation.ElementType; import java.lang.annotation.Target; diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/A.java similarity index 76% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/A.java index c214ea8c..733c521a 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/A.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/A.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor.testclasses.methodInvocation; +package org.hjug.graphbuilder.visitor.testclasses.methodInvocation; public class A { diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/B.java similarity index 60% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/B.java index 69e6ffe5..0d4cfe91 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/methodInvocation/B.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/B.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor.testclasses.methodInvocation; +package org.hjug.graphbuilder.visitor.testclasses.methodInvocation; public class B<T> { diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/C.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/C.java new file mode 100644 index 00000000..9b4af8a1 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/C.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses.methodInvocation; + +public class C<T> extends B<T> {} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/D.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/D.java new file mode 100644 index 00000000..f4218105 --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/methodInvocation/D.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses.methodInvocation; + +public class D<T> extends C<T> {} diff --git a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/A.java similarity index 73% rename from circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java rename to codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/A.java index b0b957fd..f99d037e 100644 --- a/circular-reference-detector/src/test/java/org/hjug/parser/visitor/testclasses/newClass/A.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/A.java @@ -1,4 +1,4 @@ -package org.hjug.parser.visitor.testclasses.newClass; +package org.hjug.graphbuilder.visitor.testclasses.newClass; public class A { diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/B.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/B.java new file mode 100644 index 00000000..2c48703a --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/B.java @@ -0,0 +1,6 @@ +package org.hjug.graphbuilder.visitor.testclasses.newClass; + +public class B { + + public B(C c) {} +} diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/C.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/C.java new file mode 100644 index 00000000..7eb36fad --- /dev/null +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/testclasses/newClass/C.java @@ -0,0 +1,3 @@ +package org.hjug.graphbuilder.visitor.testclasses.newClass; + +public class C {} diff --git a/circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/A.java b/codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/A.java similarity index 100% rename from circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/A.java rename to codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/A.java diff --git a/circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/B.java b/codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/B.java similarity index 100% rename from circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/B.java rename to codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/B.java diff --git a/circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/C.java b/codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/C.java similarity index 100% rename from circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/C.java rename to codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/C.java diff --git a/circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/D.java b/codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/D.java similarity index 100% rename from circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/D.java rename to codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/D.java diff --git a/circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/E.java b/codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/E.java similarity index 100% rename from circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/E.java rename to codebase-graph-builder/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/E.java diff --git a/cost-benefit-calculator/pom.xml b/cost-benefit-calculator/pom.xml index fe6490dc..a33a0b8b 100644 --- a/cost-benefit-calculator/pom.xml +++ b/cost-benefit-calculator/pom.xml @@ -19,6 +19,11 @@ <version>2.0.7</version> </dependency> + <dependency> + <groupId>org.hjug.refactorfirst.codebasegraphbuilder</groupId> + <artifactId>codebase-graph-builder</artifactId> + </dependency> + <dependency> <groupId>org.hjug.refactorfirst.changepronenessranker</groupId> <artifactId>change-proneness-ranker</artifactId> 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 5e995407..75a9246c 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 @@ -20,9 +20,9 @@ import org.hjug.git.ChangePronenessRanker; import org.hjug.git.GitLogReader; import org.hjug.git.ScmLogInfo; +import org.hjug.graphbuilder.JavaGraphBuilder; import org.hjug.metrics.*; import org.hjug.metrics.rules.CBORule; -import org.hjug.parser.JavaProjectParser; import org.jgrapht.Graph; import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree; import org.jgrapht.graph.AsSubgraph; @@ -32,14 +32,12 @@ @Slf4j public class CostBenefitCalculator implements AutoCloseable { - private final Map<String, AsSubgraph<String, DefaultWeightedEdge>> renderedSubGraphs = new HashMap<>(); - private Report report; private String repositoryPath; private GitLogReader gitLogReader; private final ChangePronenessRanker changePronenessRanker; - private final JavaProjectParser javaProjectParser = new JavaProjectParser(); + private final JavaGraphBuilder javaGraphBuilder = new JavaGraphBuilder(); @Getter private Graph<String, DefaultWeightedEdge> classReferencesGraph; @@ -66,7 +64,7 @@ public List<RankedCycle> runCycleAnalysis() { List<RankedCycle> rankedCycles = new ArrayList<>(); try { boolean calculateCycleChurn = false; - identifyRankedCycles(rankedCycles, calculateCycleChurn); + identifyRankedCycles(rankedCycles); sortRankedCycles(rankedCycles, calculateCycleChurn); setPriorities(rankedCycles); } catch (IOException e) { @@ -80,7 +78,7 @@ public List<RankedCycle> runCycleAnalysisAndCalculateCycleChurn() { List<RankedCycle> rankedCycles = new ArrayList<>(); try { boolean calculateCycleChurn = true; - identifyRankedCycles(rankedCycles, calculateCycleChurn); + identifyRankedCycles(rankedCycles); sortRankedCycles(rankedCycles, calculateCycleChurn); setPriorities(rankedCycles); } catch (IOException e) { @@ -90,30 +88,25 @@ public List<RankedCycle> runCycleAnalysisAndCalculateCycleChurn() { return rankedCycles; } - private void identifyRankedCycles(List<RankedCycle> rankedCycles, boolean calculateChurnForCycles) - throws IOException { - Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles = getCycles(); + private void identifyRankedCycles(List<RankedCycle> rankedCycles) throws IOException { + classReferencesGraph = javaGraphBuilder.getClassReferences(repositoryPath); + CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); + Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles = + circularReferenceChecker.getCycles(classReferencesGraph); Map<String, String> classNamesAndPaths = getClassNamesAndPaths(); cycles.forEach((vertex, subGraph) -> { - int vertexCount = subGraph.vertexSet().size(); - int edgeCount = subGraph.edgeSet().size(); - - if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) { - renderedSubGraphs.put(vertex, subGraph); - log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount); - GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree = - new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); - double minCut = gusfieldGomoryHuCutTree.calculateMinCut(); - Set<DefaultWeightedEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); - - List<CycleNode> cycleNodes = subGraph.vertexSet().stream() - .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) - // .peek(cycleNode -> log.info(cycleNode.toString())) - .collect(Collectors.toList()); - - rankedCycles.add( - createRankedCycle(calculateChurnForCycles, vertex, subGraph, cycleNodes, minCut, minCutEdges)); - } + Set<DefaultWeightedEdge> minCutEdges; + GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree = + new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); + double minCut = gusfieldGomoryHuCutTree.calculateMinCut(); + minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); + + List<CycleNode> cycleNodes = subGraph.vertexSet().stream() + .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) + // .peek(cycleNode -> log.info(cycleNode.toString())) + .collect(Collectors.toList()); + + rankedCycles.add(createRankedCycle(vertex, subGraph, cycleNodes, minCut, minCutEdges)); }); } @@ -124,12 +117,6 @@ private static void setPriorities(List<RankedCycle> rankedCycles) { } } - private Map<String, AsSubgraph<String, DefaultWeightedEdge>> getCycles() throws IOException { - classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath); - CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); - return circularReferenceChecker.detectCycles(classReferencesGraph); - } - private static void sortRankedCycles(List<RankedCycle> rankedCycles, boolean calculateChurnForCycles) { if (calculateChurnForCycles) { rankedCycles.sort(Comparator.comparing(RankedCycle::getAverageChangeProneness)); @@ -144,6 +131,16 @@ private static void sortRankedCycles(List<RankedCycle> rankedCycles, boolean cal } private RankedCycle createRankedCycle( + String vertex, + AsSubgraph<String, DefaultWeightedEdge> subGraph, + List<CycleNode> cycleNodes, + double minCut, + Set<DefaultWeightedEdge> minCutEdges) { + + return new RankedCycle(vertex, subGraph.vertexSet(), subGraph.edgeSet(), minCut, minCutEdges, cycleNodes); + } + + private RankedCycle createRankedCycleWithChurn( boolean calculateChurnForCycles, String vertex, AsSubgraph<String, DefaultWeightedEdge> subGraph, @@ -184,21 +181,6 @@ private RankedCycle createRankedCycle( return rankedCycle; } - private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) { - if (!renderedSubGraphs.isEmpty()) { - for (AsSubgraph<String, DefaultWeightedEdge> renderedSubGraph : renderedSubGraphs.values()) { - if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size() - && renderedSubGraph.edgeSet().size() - == subGraph.edgeSet().size() - && renderedSubGraph.vertexSet().contains(vertex)) { - return true; - } - } - } - - return false; - } - // copied from PMD's PmdTaskImpl.java and modified public void runPmdAnalysis() throws IOException { PMDConfiguration configuration = new PMDConfiguration(); diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index 0b7ce554..26ae519e 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -1,7 +1,6 @@ package org.hjug.dsm; import java.util.*; - import lombok.Getter; import org.jgrapht.Graph; import org.jgrapht.Graphs; @@ -37,6 +36,7 @@ public class DSM { @Getter int stronglyConnectedComponentCount = 0; + @Getter double averageStronglyConnectedComponentSize = 0.0; @@ -68,10 +68,8 @@ private void orderVertices() { stronglyConnectedComponentCount = sccs.size(); // capture the average size of the cycles - averageStronglyConnectedComponentSize = sccs.stream() - .mapToInt(Set::size) - .average() - .orElse(0.0); + averageStronglyConnectedComponentSize = + sccs.stream().mapToInt(Set::size).average().orElse(0.0); sortedActivities = topologicalSort(sccs, graph); // reversing corrects rendering of the DSM @@ -107,7 +105,11 @@ private List<String> topologicalSort(List<Set<String>> sccs, Graph<String, Defau return sortedActivities; } - private void topologicalSortUtil(String activity, Set<String> visited, List<String> sortedActivities, Graph<String, DefaultWeightedEdge> graph) { + private void topologicalSortUtil( + String activity, + Set<String> visited, + List<String> sortedActivities, + Graph<String, DefaultWeightedEdge> graph) { visited.add(activity); for (String neighbor : Graphs.successorListOf(graph, activity)) { @@ -215,7 +217,6 @@ public void printDSM() { } } - /** * Captures the impact of the removal of each edge above the diagonal. * This algorithm will have at worst a runtime of O(E^2 + EV): @@ -230,7 +231,7 @@ public List<EdgeToRemoveInfo> getImpactOfEdgesAboveDiagonalIfRemoved() { // get edges above diagonal for DSM graph List<DefaultWeightedEdge> edgesAboveDiagonal = getEdgesAboveDiagonal(); - //build the cloned graph + // build the cloned graph Graph<String, DefaultWeightedEdge> clonedGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.vertexSet().forEach(clonedGraph::addVertex); for (DefaultWeightedEdge weightedEdge : graph.edgeSet()) { @@ -240,31 +241,31 @@ public List<EdgeToRemoveInfo> getImpactOfEdgesAboveDiagonalIfRemoved() { List<EdgeToRemoveInfo> edgesToRemove = new ArrayList<>(); // capture impact of each edge on graph when removed for (DefaultWeightedEdge edge : edgesAboveDiagonal) { - //remove the edge + // remove the edge clonedGraph.removeEdge(edge); - //sort the graph + // sort the graph List<Set<String>> sccs = findStronglyConnectedComponents(clonedGraph); List<String> sortedClonedActivities = topologicalSort(sccs, clonedGraph); Collections.reverse(sortedClonedActivities); // get the number of edges above the diagonal - List<DefaultWeightedEdge> clonedGraphEdgesAboveDiagonal = getEdgesAboveDiagonal(clonedGraph, sortedClonedActivities); + List<DefaultWeightedEdge> clonedGraphEdgesAboveDiagonal = + getEdgesAboveDiagonal(clonedGraph, sortedClonedActivities); int edgeCount = clonedGraphEdgesAboveDiagonal.size(); // get the new number of cycles int cycleCount = sccs.size(); // calculate the average size of the new cycle - double averageCycleSize = sccs.stream() - .mapToInt(Set::size) - .average() - .orElse(0.0); + double averageCycleSize = + sccs.stream().mapToInt(Set::size).average().orElse(0.0); - EdgeToRemoveInfo edgeToRemoveInfo = new EdgeToRemoveInfo(edge, graph.getEdgeWeight(edge), edgeCount, cycleCount, averageCycleSize); + EdgeToRemoveInfo edgeToRemoveInfo = + new EdgeToRemoveInfo(edge, graph.getEdgeWeight(edge), edgeCount, cycleCount, averageCycleSize); edgesToRemove.add(edgeToRemoveInfo); - //add the edge back + // add the edge back clonedGraph.addEdge(graph.getEdgeSource(edge), graph.getEdgeTarget(edge), edge); clonedGraph.setEdgeWeight(edge, graph.getEdgeWeight(edge)); } @@ -273,7 +274,8 @@ public List<EdgeToRemoveInfo> getImpactOfEdgesAboveDiagonalIfRemoved() { } // only used by getImpactOfEdgesAboveDiagonalIfRemoved() - private List<DefaultWeightedEdge> getEdgesAboveDiagonal(Graph<String, DefaultWeightedEdge> graph, List<String> sortedActivities) { + private List<DefaultWeightedEdge> getEdgesAboveDiagonal( + Graph<String, DefaultWeightedEdge> graph, List<String> sortedActivities) { List<DefaultWeightedEdge> edgesAboveDiagonal = new ArrayList<>(); @@ -317,7 +319,3 @@ void printDSM(Graph<String, DefaultWeightedEdge> graph, List<String> sortedActiv } } } - - - - diff --git a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java index cfbfffb8..4dbdfef1 100644 --- a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java +++ b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java @@ -10,4 +10,4 @@ public class EdgeToRemoveInfo { private final int newEdgeCount; private final int newStronglyConnectedComponentCount; private final double newAverageStronglyConnectedComponentSize; -} \ No newline at end of file +} diff --git a/dsm/src/test/java/org/hjug/dsm/DSMTest.java b/dsm/src/test/java/org/hjug/dsm/DSMTest.java index d79f14b8..c9c04917 100644 --- a/dsm/src/test/java/org/hjug/dsm/DSMTest.java +++ b/dsm/src/test/java/org/hjug/dsm/DSMTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.List; - import org.jgrapht.graph.DefaultWeightedEdge; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,7 +46,7 @@ void setUp() { dsm.addDependency("H", "D", 9); dsm.addDependency("H", "G", 5); -// dsm.printDSM(); + // dsm.printDSM(); } @Test diff --git a/pom.xml b/pom.xml index f5af32f4..aaff0466 100644 --- a/pom.xml +++ b/pom.xml @@ -74,8 +74,9 @@ <modules> <module>test-resources</module> - <module>dsm</module> + <module>codebase-graph-builder</module> <module>circular-reference-detector</module> + <module>dsm</module> <module>change-proneness-ranker</module> <module>effort-ranker</module> <module>cost-benefit-calculator</module> @@ -138,6 +139,12 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>org.hjug.refactorfirst.codebasegraphbuilder</groupId> + <artifactId>codebase-graph-builder</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit</artifactId> From 9917dd8801d3fe79b1fdcf1c6a9cb70ebcb07235 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Sat, 22 Feb 2025 15:58:43 -0600 Subject: [PATCH 37/78] #129 #130 Moved CircularReferenceChecker to the DSM module -Moved CircularReferenceChecker to the DSM module and using it to determine impact of removing each backward edge. - Removed the circular-reference-detector module --- circular-reference-detector/LICENSE | 201 ------------------ circular-reference-detector/README.md | 10 - circular-reference-detector/pom.xml | 75 ------- .../app/CircularReferenceDetectorApp.java | 70 ------ .../testOutputDirectory/graphtestGraph.png | Bin 9705 -> 0 bytes .../visitor/JavaFqnCapturingVisitorTest.java | 9 +- cost-benefit-calculator/pom.xml | 4 +- .../org/hjug/cbc/CostBenefitCalculator.java | 2 +- coverage/pom.xml | 4 +- dsm/pom.xml | 5 + .../hjug/dsm}/CircularReferenceChecker.java | 28 +-- dsm/src/main/java/org/hjug/dsm/DSM.java | 88 ++------ .../java/org/hjug/dsm/EdgeToRemoveInfo.java | 5 +- .../dsm}/CircularReferenceCheckerTests.java | 24 +-- dsm/src/test/java/org/hjug/dsm/DSMTest.java | 43 +++- pom.xml | 5 +- 16 files changed, 94 insertions(+), 479 deletions(-) delete mode 100644 circular-reference-detector/LICENSE delete mode 100644 circular-reference-detector/README.md delete mode 100644 circular-reference-detector/pom.xml delete mode 100644 circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java delete mode 100644 circular-reference-detector/src/test/resources/testOutputDirectory/graphtestGraph.png rename {circular-reference-detector/src/main/java/org/hjug/cycledetector => dsm/src/main/java/org/hjug/dsm}/CircularReferenceChecker.java (75%) rename {circular-reference-detector/src/test/java/org/hjug/cycledetector => dsm/src/test/java/org/hjug/dsm}/CircularReferenceCheckerTests.java (50%) diff --git a/circular-reference-detector/LICENSE b/circular-reference-detector/LICENSE deleted file mode 100644 index 0df1eda7..00000000 --- a/circular-reference-detector/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023 Nikhil Pereira - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/circular-reference-detector/README.md b/circular-reference-detector/README.md deleted file mode 100644 index a9fbfba4..00000000 --- a/circular-reference-detector/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Java Circular Reference Detector - -Tool to create graph images of cyclic references in a java project. - -Usage : -- Pass two command line arguments: - - File path of source directory of the java project. - - Output directory path to store images of the circular reference graphs. - -By [Ideacrest Solutions](https://www.ideacrestsolutions.com/) diff --git a/circular-reference-detector/pom.xml b/circular-reference-detector/pom.xml deleted file mode 100644 index 523dbd37..00000000 --- a/circular-reference-detector/pom.xml +++ /dev/null @@ -1,75 +0,0 @@ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>org.hjug.refactorfirst</groupId> - <artifactId>refactor-first</artifactId> - <version>0.6.3-SNAPSHOT</version> - </parent> - - <groupId>org.hjug.refactorfirst.circularreferencedetector</groupId> - <artifactId>circular-reference-detector</artifactId> - - <description>Tool to help detecting circular references by parsing a java project.</description> - - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.openrewrite.recipe</groupId> - <artifactId>rewrite-recipe-bom</artifactId> -<!-- <version>2.23.0</version>--> - <version>3.1.0</version> - <type>pom</type> - <scope>import</scope> - </dependency> - </dependencies> - </dependencyManagement> - - <dependencies> - <dependency> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <version>${lombok.version}</version> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - <version>1.7.26</version> - </dependency> - <dependency> - <groupId>org.jgrapht</groupId> - <artifactId>jgrapht-core</artifactId> - </dependency> - <dependency> - <groupId>org.hjug.refactorfirst.codebasegraphbuilder</groupId> - <artifactId>codebase-graph-builder</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <version>5.9.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-engine</artifactId> - <version>5.9.0</version> - <scope>test</scope> - </dependency> - - </dependencies> - - <build> - <plugins> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.2</version> - </plugin> - <plugin> - <artifactId>maven-failsafe-plugin</artifactId> - <version>2.22.2</version> - </plugin> - </plugins> - </build> -</project> \ No newline at end of file diff --git a/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java b/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java deleted file mode 100644 index ace7bb26..00000000 --- a/circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.hjug.app; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import org.hjug.cycledetector.CircularReferenceChecker; -import org.jgrapht.Graph; -import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree; -import org.jgrapht.graph.AsSubgraph; -import org.jgrapht.graph.AsUndirectedGraph; -import org.jgrapht.graph.DefaultWeightedEdge; - -/** - * Command line application to detect circular references in a java project. - * Takes two arguments : source folder of java project, directory to store images of the circular reference graphs. - * - * @author nikhil_pereira - */ -public class CircularReferenceDetectorApp { - - private Map<String, AsSubgraph> renderedSubGraphs = new HashMap<>(); - - private void detectAndStoreCyclesInDirectory(Graph<String, DefaultWeightedEdge> classReferencesGraph) { - CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); - Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap = - circularReferenceChecker.detectCycles(classReferencesGraph); - cyclesForEveryVertexMap.forEach((vertex, subGraph) -> { - int vertexCount = subGraph.vertexSet().size(); - int edgeCount = subGraph.edgeSet().size(); - if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) { - renderedSubGraphs.put(vertex, subGraph); - System.out.println("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount); - GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree = - new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); - double minCut = gusfieldGomoryHuCutTree.calculateMinCut(); - System.out.println("Min cut weight: " + minCut); - Set<DefaultWeightedEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); - System.out.println("Minimum Cut Edges:"); - for (DefaultWeightedEdge minCutEdge : minCutEdges) { - System.out.println(minCutEdge); - } - } - }); - } - - private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) { - if (!renderedSubGraphs.isEmpty()) { - for (AsSubgraph renderedSubGraph : renderedSubGraphs.values()) { - if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size() - && renderedSubGraph.edgeSet().size() - == subGraph.edgeSet().size() - && renderedSubGraph.vertexSet().contains(vertex)) { - return true; - } - } - } - - return false; - } - - private boolean validateArgs(String[] args) { - return args.length == 2; - } - - private void printCommandUsage() { - System.out.println("Usage:\n" - + "argument 1 : file path of source directory of the java project." - + "argument 2 : output directory path to store images of the circular reference graphs."); - } -} diff --git a/circular-reference-detector/src/test/resources/testOutputDirectory/graphtestGraph.png b/circular-reference-detector/src/test/resources/testOutputDirectory/graphtestGraph.png deleted file mode 100644 index 811aea4c6f3dab0d66d613fc3884e31783141f3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9705 zcmb7qWmr^E*Y*G+$VkUXw;(ZegCHGBiR92FjYCN|fRv<!3@F{DfDAQ~(jg!%AnA~j z1A_E>@Ohu>d;fkv=DN<=v(H{@ueJ8t_qz9v*40)cCZZz(fk4C>>dN{c5RNzSGa|$V zTI8hIGC?5DY7ON_1}|rKvhL_UGx@M?`OHIvTu!U&v9gZZ<Hz13M|_NO<07N;ql}Dl z^Nf_{b|2oSnoyf$nK;=$b0`AGq!Set=N1#Ou@Oacu*E8})o`*s)IILKAIrj$!B-PQ znE&yi$*s^+Cd&ZI#lUv6-M~HQwdGHiCG1q-@a4SMu-}NE=wQ{x2J<-5cMj!q{8j)D zgo%5@^qo*ZGqB-weGX-x>QmqfN)%%YXg7wcL4QU!PPy&)N|w{WaTJ8$<lT5Z@I1Aj zTd;cHbLj&5|28IqK_EB|NC650_2B~#joMJoCr@Wi8&KGCpFcV*5Zck%KaIuy?GemY z0c$FOBkE=&eHhft<ar=-Lh96UZpZ{L#Jux)SDIPEt9@qDD|L3)sNjn#yoQTzshpw2 z*siiKbJxSeTEuQ)7waXhWy}Fw*yCuO7(d;qQ_MirTQ0_fsi$W%s24>UC+4T4$miC9 zBO(i(0>A8P6PN@qj%LhuAyw4%x#@)hkn5rAKO5)4eHqtl*YJRKd1d_Vp{7ZvCfHcp zknGtNbK;W?#~-1jD;;=k(UOdt4ZkLkZP*v*p?eu^V6-}IP0ySR6p^Y6-S+n|V#r!< zJAUFL|KSQHcPFT$4b6Y7J*f0z@k@T*9$wYp(aO=de2w7g+WbUjPjxuf{A&7Vy{ZTN z?P^rfy2PmM)~=kgwFV4&GuJ-6tG2%$T=JKP0>Ni{=%nSt9uLc=Y{yub`^N5B<7J`+ z+r)Wqcl<Ba7@jYOlAH&^7&a3m5_x>vSz=EoK<Ii*9jYi09q_pAM~-qJ;#8&PjmP|@ z)r6bN-InbnFZ9`_RKazE&JdO1Jml(#WDF6+;^g4Yq;mfi+733jERTZoz777H^Zf1N z$n_5zbmqcA@Miy4rfcJc&*3=cGmp3gvldjdNZ8S2-D2}U5%078S*Oebv&P22S8Lu= zV`Dv}#j2EYfAv-QV;-x*^Z5B6M9c{QQ83lNx;4p%So=d?l&=<h|BshB9woO*RE@mk zdAysD>VRM>IA?BeySX-x5I!(h7FN(SUw746PJcdQ_=Hi31~D;oUKv!oH;~N9gPWWL z_IdMsB(LFmb*m>z1&*e|a{7JlB35}D_k_!?A>cMOal(dPOv11J^aVGUql8~Z@y8hh zoZDFbS<15~Gm%xa+0S3vxdsfKk)Gc_Bq9B6eC33fAG7hGqf{g@0>*afJC=ReTW>bc zFTH+9E+=BQH)qoyQHy=1c3HpdsDvZ0H0_xZjVAYSRskJjZ~J}A9Xbf&)CWGtN2 z@%YJA-X@Wbocr+;{U{KFiFrU0v1)qdv}w;jN|X2+R+^P;InPYJ;dW1C=Jcf7kLx2+ zYYEww%VoL0Xx|2F9Uq9^bF>?0qRLa!n3$@{tZWz>Smnw-@IYG@TK=r>fem>e(2d+H zW`Te0zw<s7>BW{{DoYc8jVlbmr7m{c4(5*nBPEmONChkj`8_PFIjLS@$K@sZIG4Nv zF*fDYvn#cYRbQ9GyV6ECKLj0CnxMZsJ#NXn{5mt?6EMvV<@pqVSS~X5*g=S9EwHhb zjbTfEmhTmnyv!QQU*dqKc|FW1lkj$!zCL_${x#1JA%8Z}S`+AHILPUGeSYj6`_0)t zP$4u>0p|n1_R>)|g|zuoFS==FugyTwUBoue&J2Aib;TuIHotw=UC&;LS<e0>`Y}}| ztOo|yG}S?zwK?e|o-clKZ!_jdOCKj7#`}_S-DQeSpCoc?yTkto)$IfuC>87StD5>K z0I_j}f1nen6=|w{@~$D_NmZWFc^6kVwuH*SckJoKFeG;g!<LpRGap%cpeR!Xi>uLw zJ}|2E<UX7IgFCpoLTS-zxF+LX|I?keMnsNFT&q4sDAGOYWHUqcd~w10p0iY%0V-e} z*%{e0SV?KBw;(N9`C4hzvSh;eFV!_T(s$}g1kEk^!1e1LKfWZeo1?tnuhwKAcwX^v zwB^+UIg$P8>yqZn)kwIM4|*mU^WnH;z00Bhcr(dW+B~?K_v`a(`nkIj!&e<AXIohw zdvOwRSVT6*fSJdikwBNIya&nxO+zF3B^TZv!g7eiuP)q@o32Q}l_}Tuv9RzAJIN)3 zm*;s&Pq4g!`8v#k0iy+asf({9Q{2gS9u0QCpBo8IsdWl;Ev|IAM1t$+e%8~`(IFr) z)4^Xoz_Tgt^~Dfh*S}MiT+{GhtCWkig_(h>*DaazSQYh!Eji|wT&*pK`l@<dnom^u z(+2g{gMK0uWGM4nYwTB6B-TefF`IpX*T)rj@=a80Irr$vUya2^#X7ak2&GB7f&IHa z+6b<#i2hwms*#Z^wuJ_2TbvLN9gqy1eV0k%uO7Ufjl^LGd*s`;4!Nv6Khg2)J<k$Q z|5iS|Wy=1vTvA!(JVNbQmO&&?izIO=T?>ec?0MAY(QvTj*;ViovbT&sxK7&2&U_%^ zXN`COe~ttq&}RUjxq&(R;G)FmKJ<gpH_c!8qipMfG7b9&kbWivZ83Z~UK>l=4BdN} z<S*9dzuVmrDeycM_J%--5>qA7&|RsGUaA*cYkyB)!3E8Zn`-$@e)5va&vj4%TBUu> z%<$@(RNL+R_aH=3%dB|k3C**qW44mQKXVfv<4>y`)~7eNa!g<5#Fp87fBAwA*r$rU zAX(o!I&2mTUN|eHPXDPtNqD?vIS6?+E~}6*$kQ6tk=zL5_^^f*>s}%;Ur#Rmw1{Dv z6A|S3IwF4RdVDE&r^LoP{WBr@hfKp8s!ljMeI}00n{EQiz3`j|rM0mgNRettj8cH+ z55=q*-gQusp?u*;`r!O2j=$qIS2s%z7?cP`(%^vLp)e5e2<*_nLkNro0s>)h|MS2C z>B9$E<-ip6XN%a<zE6+@xSJwm25y5)QVgvm(@0f+B4c1AnIeVJ*w`|~84!!5<`xt7 zR(q#j-O6Bu#XBbJoi(r45Au||zX=q8>8)#b8G_5e&58qV`=A+HIrI-SewU#D)Fz)7 zB<xbXeV31n_qvi6?S1@q+#;jlTQYQytn-XDqTyW66x8?mtK!`HBb6f&o|aJu>O_sW zRU7%qA*G|w#}UZkmY2~scnS%`NV%`wN2pIm;kRt*UdZphPn}pUJ7wgS0@+Pik=M_c z_#Amz{e6$YDCYFPvcE(vmyGCzgZkXVyG8m~(nh0c`}-pr)18c2;y(y-tAGlptPTkI zV8N2U=bF&VQ+#sltr&r{&6x<fueVQqj^5@;;v^ChGqPJa#`51e(H&8G=Qkm_;({?a zf*YGsUE_eZ!z+)Pb8RAb5sJRN*V)L$7qaEp6|rA+>7?PQ8b(meJN6{%qX&oOXS$Uj zbxNkMZ-$?*NLE#k_Vb#f$@u4Wj5<uEx7PBga1!Yhw<D&>!8%T47z!#&_5ri!3@4jX zd|8>XGjSmGgzCZoX}Pkru3&w3tG)Mos&>OA^Qm88x7CiaAE^|o>e;M;K%coi?uoW6 zKHFf3;F!`dhbjkHb9Ib?)QKntLaT+Pg*1dzui56`{Fw9n2s;`-ym;Ya7Nu7TQpd^8 z58|ll@J=}RJF%T@Wv;DKbr+*(ApS7l1J1od<DiAN1P6BRY~H7o=aI$V9_Ci=e0GXe z&4;t%8afEE@DYa$=0QgymFg!DN%64R0JV8MB3WE;_t|$4h+dFLUWP8-tMyrCeMFFk zyk0w~k!V-R4(TnmzLUuT5#}68H?1fk0C{rDo^5{f)}kwUC6Vn(MP(V5WDd&wcG1BW zmE%)Knt5DIw;n7f%uPfOa!fXwT)&{2rgKCnvGT>wjDaMHS;3kkONAnfM!zt7w8_S5 zmilHfb+kC16lJH8KEoYP=`t@`hml}{PT8;+<^BQ!ka13B=p7Py(u56aXxgfg2}kCh z9>_plH)v>UW`rm$wjv+|R2pA=t^;eFecQvi_`bLD(x#jYdAt45_x{z{@Wjfb!HbTS zulsORN-GX1U)NM(Y1Uwh&gj)){es?bm_`f71Rki%USGbqAr<XOP7)7pyN)<g<r^Uk zVPBdFswB>6)Q1*Oiu8ru3E8-tupvbn>QaJrf(24ZK0q4J^x%L7lhMy6yW#Hx50ce< zf6p=%XoIZeo6Fx$&)B5Bb#R6u_+CEFpbD!N1s$_zb-ktAWFT?WzD=Nzr0&dVuhCaj z5d2LpH-rujMZYBj*|Tsg`da@rfx}Hyjp;y5%a}Q9jXxBpvieDbe<%2WIQj8ye|((* zQEbTqv2}lUV&$(2at$*|_Uqqvr0rAb3!Cx2zOzLHLUt}YI-mb8S!abDPSnZW3|5hw zvZ~v~Fa4r)N1%+fIB7QNCSfo=2JuDhQKeWpI|IuOh4sHh!$x`^M9)@ek+LMWOLvD! zU^onY1+NEz*bW@%qjESUlUY>I`>syXb`vp=c5tltReOrF(8nkWOidgxOBw}Y8tEs= zUUNmK2OK<Urx7YlAT>Zd%KPZ;=$#dEW66hNV6-s-Fqe<`Sya9GJe<PkCJ4Am4>k_; zF_a8}#1<Ki!8hqYtW?+9(IY^T%nzb+?AZbLTRWXa^WcM0@|s5atvMgsu=t<}AMgO7 ztXBbA+DCaO*Y_Di2dj{d?_QozfYw<K_EFz3Y~PT;$O@~Tf8WnyYwkr%^sS^%)stv( z<Vk@l6$#}BIT7VagGq8Jo>3$&z*^*?)#YM9AV)b3#o?QD>ACE&vgrz0gv;w&Ypm!X zhxoVzfpumQjgOsuP+fYU<Fu=cRKE(LmaV^PE>HMAsu_FhImYLmh}M^v9pr|RxLcu} zBg+qnv1dPoQ(_aLRFnxJnq(a#V<sZX@8Kp0YUBdt#{vrqXC__o8nFDJ88Cs4u@|#6 z1`l&xdFYw-o-IO5d3s-fOa|;dl?8ea_(30S0(?Vj75_Bv`?fL2x2YEQW*t%xSbT}+ zA$&&^l~ZQMF&-#@;ejk?vpGxT)8MYMhUWk&!S0M&6f1WoMcqQEg;zw8HOkSuk5a_E zt`^nh0RjpcyWKHDlr)H~_|x!NmR*-Z@vjMuh8YzvV0Z}ueR_&i3T`};s2ple2(A4k zo|RJgzL(Q{$j`P|Rh(7UlQ)bY+}Sbr@^5Nvyi~gHQ&}LK{Y<5_)~Vk1qMc#`+|;|p zyLYBoN$)2LK*s1(Yn8t!V%Y9hKZV@!q3TNlR{2Q?F6XN01b4A(xEUT0QhHBAw+Uma z^o)_u<C9~eJe==pm8(6$$L8nRD*ImssPwDcBX!wC#GDafI)28c5p)igEvy+F@oWY< zQ5kA8^mhVIPe}DeSd+t(pa!xr&4i#Lwr_hz3+B0WtEn_tD_q*7#Q6d68uwm>6P1Y- zu)^&^u^l4=X<8#o9BDI4Z<xgHsbBDLpUjMGC4(Og(QwXh6TVX$zvOB@M(Lxn9vDJX z&ZL;QD%==V<!<ZSBCHP-jA<uF=vU=5FB9nS)0hDn{XiD%U3SDb(&ik*_ZJKmB?lz5 z&OI&I)>meF%<^aqEHf#nqf+qq_08)8V5jh|OZoGGHF4071+WzrYqXC6J*7wegR2bZ z3EfQNY;yHm+ILrc>Wzs_h74FCK%y#aUs*}Ee4HA9{p@U@Uulrp48sE?8v$N_CTwrX z4=2Z3IUv3Bka!UB9C~S018l0;)&uk1-GF~f-SeUMvtRqD0AXS$gJ<mw$kxP#H(y9H z`~RgG!^y{{J~z=_QQ|~Y2+}Ed(&zqcvSF(9Q@X$Ij<G>ehdvSRgOlFP<a#dSlAP=) z*t<AE`c%HK0MblQ6>a{~%t+$k$-XejxuTYk8J*+@Y~-U!L-Ba&*i4a-qBXzVW4tKw zl$K$d3h}Z;%~fH+i@Si43YNu`44O1^RO|5H%BHvH#pw)TDPG0oT;4_B6DiC2RpasH z`j#Uu8KVWvm>8AAnkHuUX~1lgq>-VO5oYZouLL!$Zfx}CsKFf+!5v+G6FTraRdqEr zq)Ko?KA%Zg`wk0HYW&-3D=Cakh)9@q2RRx37Qqgf|5*^$t*@Ic{Eiy1Le7^%WtCZ3 z)ArPd_Hv2Hv&TY|aJH?zZ5(u}NltVgL94{<Pqj5U4sIW}OX!U1Fc9^v2EHWQRky?M z7;6^~xXtB%d{~GeQNx3uGO_v#-!zgTB4Ja7n^%<KMWJ&1mf<&=O92(DwUZV9MCw|T z(4^EkE&THv01)|ikgJ}DuCiR*Md#*&4j%nAiHAGw1i;2!5l&5;?;$&K>~3=GDI*R~ zG8!`p#z&w0T!^wQs*@{y4gGjK-^%N+$pN*(XZZi4WGei$HyuZMQr#E)R@Kb%BX;CQ zUXr%OrHo21TW1oaP}v4McZj?w0!W*&-va#aeZOY6tRfCfS$Cs7eZ_uMKj(AI+CKzX zoujknS1TzijPHCNqOr@rH2xTX><1!Z_IDT|ZJOMcEvWynDt44-o6IKZL5J?n+P21u zr_gH`ce4TsbZ@KSp<zeNgi6i2?c>KouCq*oL={aZ-;Z!MPoKD4f+b7ojEzN+7P3<$ z@@);g2^JUAt!2Av<H7Y@p^LwV+&=r6GL;wNa_d61w6p^8>M~BaTGFpu=STm;qEKUQ zSQMvUvETkqJhq;^oD4jREZ!#7N@)&19*qV^I57WWEWG@yF!t@IENm&<+3MM-Yv0+j zBQo|od%?ntz%kcowk0CzV=_^$0y^=2DPg>6hDAwj?V&^oiq8Yj>%LG1q$Xb?zeliT zv%i$>OoN|&>)rc@ah0}5i(0gG-$N<OL!YUj9@0nV#A6hxroB<a#f}!Gc{d85hoKgK z(4f9t0K5rWH$ys-FE|STyUu9cw*h}Qz0O2RnzN*zewjm7+6FgVlDGV-XJ6D;il$pX z3RHcBno+y)T)`_NB+~OR)wUv(Sj9F*msmWG=fgmR92Vf+R8>Ar5ao)ZWogHo+~OBr zTx@M+E!r(CFI3f4)qlZ_;9AIsL_eweypIlghT&Z=`(aZ#<iJc@ja5g5B`9<`TPcmE zcTBQeXs$*sJ3d8+n~Tdz9AyZn8i0!WIrj_+QpaZ+^~VRix%s9ei}wIX<$gA`NdI`y z&2_i7bO%_MuLdvK%&BwaB;xkIqZ&6SrfePJodZlNYVlnwZi`dJKY$Ty_davKhoHeI z@+bEeoPo=p9hLYc8p>ZPa#AZe!EYFqcjG&gGIAHfca1W-%*2~noFdA}AM})0i>l#h zcLy;qTy5*p?c9kL5P9XfJMg|*nudfykJy!)4rMHP{(copIr0)td$|G>iOz__!EO~G zJd64x$_Z1SVuzY4{Jt<hDaj(&NUd)vCbr%jAt|9HJ}6wL-8yPwdQto$)a*~okwDc` zQ}wTTlkJiDpGzKA!_;Xf=x3QTpUjH}#Fo}>okpbblz}u;1~+3l4YbRos)l4`ne?p8 z8lL}29bm8a^tS0q?v?N(8J~M~p@PMrOq|0HkFB1s#JfcrztlEq8vVsffA;)(m-2n$ zYo!}6qO`SqJH?dZdSGWv@lDK4nt=dY7(XkZ`RPVVQJkrg)!vrNywTRs*sxBJzAM6{ zwOdBIpg);_;8PrVW!(;mV5Jg%YF6#yUJy><()Sxb-m{+Eb{xl<|J?87-28obdrVyH zpB-Z22R48H?&MXOH1}PI)o5|qvNcO4NGZt3sPB#Bd`IubK$ZK+W4{i9HvFL|1C;qJ z_)&&<^f5hz75D3aIFo#NT)ep-qt6>Mh8YtPbe?@xWVnG$%{~TLJh$Q1l&7Ow)V%v+ zJAaswCU&LVS4j@;XE0OK;@eV}CqY=30(sWOd$$QSoxiaD@3{5gZ_@CYR^bar4oHz9 z2!BDjVQ0|$ivoXhHc{H1<%;oA%E7v-4sC!i_2JI#sr0dtT~hu9xNJks!RXmut2X5W zp+U0lm%%z6TY7R7i-Fk(UIH}#Rl3%N!ROCziqNH(*U6e2H^e!w(JfxQ#Zjk>S|Jp^ z+;n?j_nwZ!wRlx@*lYQDMx9Hu^2es;ymc!69685T*EK;W6C=$}Pa=?&V(HUUB8qQM zC%?X{bFUAQzRK(+iM2E@4q!_7<fC?*>@(dDUC}DB2#biEvO>OQu87r|b&SvAcZR*( zkEAV>r`URcPH_%?(0G#vP+s7Pu0Q*Of|t`_d3Si$Zg>r*9<Mn%ucdFq1<|oC{r2ZV z{N}B~-fPT34n=G4<3orTNCt~~zsq2ACKM6V?7Iit*t=5o|AeM)SL~x!C7e+(i%FqQ z*u^}TaMhXweVr~94Cmc@cwU~H=O+!9TVko_O!4DlpPDNhTKw$BK4`dvu?Kv4Jt{6N z?smOiC@%7>>ElYEXXs%?=&CUjLKR=P6^1n09sT(qzT=8_(Y2tQ25I22{`>L6j3TD1 zvU`jUe41!FJ}8tirg8PnxA2%Mkfyem1|-}jmP^Qt#Jp9~*=k+$SJo??{IPOtnrI_D z^BuedikMc1H!Hj$9hBY~Sp(Mc49I$~Bszju;w&q9OgSG`eGmXt#pp}1Lj2i9Q?-8I z_7tVM%XPO2U{;iH(2}HElzMdqS4kUTK?-cTE)fKK{FzU0vp_}(waU5LI2K$x^K?1# zIh>+kA^dT1PYzRwE36-oT1W<E(cj0xtQbr3#jny*_W*#gH~%hrtvn;jQ<McV%@SVp z85f=0_kF%Z_)lyZg&eT&f_S+<(O2IfR=!_PQ9uE2P&^^hF&YAcMUx?W`f`K;a05Wu z7u>-2?BPYzBR}pIz7_Oywfak&a2vyRN0P7Q%9K*!EdG1Q4`-g<qYiBb;yx7~PvZ|; z_S5aR5+WZj*X5Mqm*@11%_qjbTO#X>ya9;sEsbT#3{^SPjV7ZCaM0>tR=ySlfDNDY zJuvVE!1WqLFpmnJ#KeH?am%VvQg5CAF7*1QV^ViFXDiC<6Hu;v;IM0mNe10jfRYpz z1jm(^83Q9zk~zq;1M^a6)W)@0#>Ta;?r*PSe)<B)MP-Z7+6inlf(s2#@d(V~@7V@4 zW%4vWGdAFKXw{%)y$C93CIFyJ62UkM3}CsksV!&brRVB2tPtT3{E4xdWbP+j;q3hu zpvD=SbsiEB`~?mGUDHe1O!N<B1OZf&#EaKCBH1H#aU_)xSqPYp0|MY52pTSgdhhJ{ zQWK9PqA-`@5!|OwWdR_mUfKbuXtTY)0l<c~NWh#Pi3CseZ-OoQWBY60#8^dVl;vY; ztd-`i?91&pbvb}@alhQY{}UiJ!eV+#9A1dR=CoF4MhF2kIItWvYhFHnXXcY66*S!O zpB1=czLK}7S+OD=z1_&aAC%~~hXbOW3L(B@_97ggNuoM9t#%yyR6Y})WY~5j9WJk0 zCUTI$_4s6$-;_&sj14OrH<E0|vqCth;Urq@q{Sl3Gb4Q=IB0GL60JZ0S@Z4GE^tTX zyhstn<Oczz;AK~yCvLRFTw)MCTn`LT3KQ_JpJ3V{PZQWIr7`(zl6<cKz*+zziJ0E+ zA%tM^D?>pfT9Ad^-ce671_V$gD*dH}>p=jfEB)`DWca>_128pbj{Pw=%vW!+%+1#q zuM@a1`Kn|{Usx3-0E;1Yoz|O)UD7ZsmGI6HM~dAgT?OctJ+MQGl)f1b|5D#8Nte$O zo+E_A2-lOzq~Fo)`UI5g48-Tl`(By#_))k(b4Px@9W4W~3{bZi$CAKtZUZYwMtED9 zP=4LuXyv&AbV=r=f6v4P2?J5aBS1kM2eK(p7sh-JsB!B)bxP7Df?vi1e6wLoHSLYV ztM4lsfBE81sMg6Nc@gK!jKQ5!Hk*Co*awhbn6I@&|N7t@f@7P1aNlPzA*nOmcw;?8 z)`#y35=cq7P^GB>kUd{Hg-fIYw4Lpxzp5yfAE>Z_^6y0~83tEel~C*CuJ+3Fk6~np z9-xi_SHA<43onx3Q1W9|y938*ydQhFV+3d?2htJdD~y0dUd>T-88A(|Gp<d=<^@`B zAxz2oR#Xo8(=b>_yeBTsNG==DG5)^;W`Q^3y=iVBKMsj}K_?*^)YM*GMD8VEu?yz! zPS!~Tli`pw0xU|3$yh`oKk!Q6eDTY4Xrp9ZO7ZO+m=(f8l1V^`Dw0|yR?TIBu-s=r z8XmejIe*&!z}l&kQ}9p$svru`8qW~U)_=_U@FnY?dfs17AnFz7rDwJdy#=(y9F&<b z4ZCjK)0>3kSQ=x*lm_T;>k+d+mdF5B=(`jRnc1iE9&qsQ+`n`;CS7WG`3pd{XXc${ zEj|8&O}Fps;sBi$BuR?_qU!RfnXc6X?k@9cG4}lscK{%OAt9sG+Fw<BAPOkqz@8L< zUWefYq05c~SH%FGaU=#19J(7f|2Q`q)m82Yz3P3HS7h7`Sa{eLpwp~30fJ2h@#vp$ zVIq%{akL*tjbak22;f;a=1#i}SZ{JH1UBdIuPRMSeLM+}owDSxvK`?*e09d6$f9wu zj1Z7GaG;TCj*pvXiqkzrg~fNsjo;iT)LzCde3M{+k`ScP?l0JxyhXilF3C@GvnUiO z2nR)YSfU$%4Dss5%~ZS?Tn?P7`WJ%Du&I-b1GW@^b|MF=v5@d3*>o(i7I@jQ&b}fP z7BT_wPH<B0+^eL=ony@oeW$fe+?s$wBo^6v2{U&9UP3M`<|d!vP^k1(D1W-={|cUc z`7x4%A`zU(n@KLrS7xDVo6mS@`!nxmz2b~TWOk%P1AgmY6*<%uok|G@ge*Uk#84pD zSk69|YJLnSMuTWYEdWo{*P5*`rylj5_WaXL_TD|j8k6o4K$%Gkv9FG3oc*$2(6s{u z5d(Q-qd9)OUj3MoXnFlBx!eF4j#Fa|*uA9E?vVe$T&f?v!_C)+-xrq|W6|#rvrU%{ zFl1da?!X-Y2eOVaif=Do0U^ak>5bc`!e8qxpK29Am3<i}rUb;JM`JuomdtMrZi0@1 zn{<U!Q{w13CQ{nRUaiaKFZfS6_>$e3zGRMCPjT5>KuMOSvecWQ6NiNr08$J)F9Ezw zeYGu(Vsl4(-c6N$KH-q}a2DV&HE+Rjh^!ITNnVysJZDXfjT|S+nLy{Y%?)NRYvT|9 zNzWkrj^FW8!F^$~PZ$uD?o5ggfHe^c?C4DdR3CE)y!7R{&d#yQw{F5K*ACcYHe^fg z$IV<%)ydJwMa*w>#o0``;w6HAG)CF*Bms-122>~9^-Aquf0`-N>`|FQ<)dia%=(zj zh?`005=rtje@W8UKS@0PIBr{m!@7L`CQayqGqqKW!(H{|8$_x@R@qiQL}l_YEIan$ zm&O8VE$eiY_Q}zCbZX>Xk4Fb$`OU3@jZf#K;|c($QVTs43lu{6d)ugPkRA8}(f@9` zq>`+K&L`HcR+c^l7UBbNqE1>(PJ`d)b^6>_7yW<9E>uW3yZ^XO2vQ>c7s{varik~4 z?$f*5-8x@Pm<94K5a0a!0ifR*_UCsxbz~s&n0=I>>D`+t0C1+&r6W_2J<Yu2?(t_X z_BO%uQ&oS`1aS<m!F2c?9Vfv#_)OhcZC-KIkU+w|NC@nExX^g`sjRxAPKWfC&cW<R zzK$*~5Mq9mJ?zzq2n%%&7^7ZI5ZJ=RN#Vm_H4d;A^6%J=OpRSqsZj$t^D$+QOrh+G zX+z!I?h=K2{nAU^KKy$U*2d7N0%s2&s994)F`aOC;i|2*KL`z?93#5>TJo<?Dlz<2 zy0d!L#Lz^7gdH%2&?0|S`owKCa5?3TK-wFZ`9yFC3^)V>j>2x*6rjasU1>RtzzH9o z{P`d@f%B$KfdNf58UMHSRE8k9FmDGqHvZq%Aj%gXh7{+}0uRG)22oA~{|UR0xQ>3r z`;d85XHg>9Xj+iyX51gVY2UVO;F|4R!G=F2ch9HVau<Ok)Yms(ZKr}IfD1NJqkGG( zZFZx;%^5h3Z@-Gs^@QK}o?e)WpI_Y7!~);1q_mIC&NqVFebF4sk%^kE$Ct)GyjFf) zYX=T9&;LgbG>ww^npD3ZxMES^?S(vM2v|SS%J_3L%MgAz8iC4`g<o{ak4?(wU6FgB z?cE-FT+r<3B)pBdQN=ZBa5@UX`E$YMOV;eM#J{g5;mhqP;KB>#W<%$58NdDY?X=7> zntysoqd8QgQXY1YT%a~D8&?^%CXDj0Rq?m~jrB1_tG()K-Bcv{IdCwnbA5^Bx~|+_ zV7RfhRT-=XTi(xzV7_!jdF49sex$`%&({`G{To&vtsHbp8_f=t^~_>`vRdqYw;g_Y zMfvYTl?KB8{Qv8(e@B`B$AAC7?+S8oDTZfS#nrwAj*o6k|GU3Cn;4Fh2o_Xwyq28# injLR}6GcvT&HQFq!^B6;&Iq`|1=3K_R<2aEdi_7;EsF5~ diff --git a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitorTest.java b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitorTest.java index fc94e660..5ceb8534 100644 --- a/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitorTest.java +++ b/codebase-graph-builder/src/test/java/org/hjug/graphbuilder/visitor/JavaFqnCapturingVisitorTest.java @@ -37,13 +37,15 @@ void visitClasses() throws IOException { Map<String, Map<String, String>> fqns = javaFqnCapturingVisitor.getFqnMap(); Map<String, String> processed = fqns.get("org.hjug.graphbuilder.visitor.testclasses"); Assertions.assertEquals("org.hjug.graphbuilder.visitor.testclasses.A", processed.get("A")); - Assertions.assertEquals("org.hjug.graphbuilder.visitor.testclasses.A.InnerClass", processed.get("A.InnerClass")); + Assertions.assertEquals( + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass", processed.get("A.InnerClass")); Assertions.assertEquals("org.hjug.graphbuilder.visitor.testclasses.A.InnerClass", processed.get("InnerClass")); Assertions.assertEquals( "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner", processed.get("A.InnerClass.InnerInner")); Assertions.assertEquals( - "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerClass.InnerInner")); + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner", + processed.get("InnerClass.InnerInner")); Assertions.assertEquals( "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner", processed.get("InnerInner")); Assertions.assertEquals( @@ -56,7 +58,8 @@ void visitClasses() throws IOException { "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("InnerInner.MegaInner")); Assertions.assertEquals( - "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", processed.get("MegaInner")); + "org.hjug.graphbuilder.visitor.testclasses.A.InnerClass.InnerInner.MegaInner", + processed.get("MegaInner")); Assertions.assertEquals( "org.hjug.graphbuilder.visitor.testclasses.A.StaticInnerClass", processed.get("A.StaticInnerClass")); Assertions.assertEquals( diff --git a/cost-benefit-calculator/pom.xml b/cost-benefit-calculator/pom.xml index a33a0b8b..d9e7921f 100644 --- a/cost-benefit-calculator/pom.xml +++ b/cost-benefit-calculator/pom.xml @@ -35,8 +35,8 @@ </dependency> <dependency> - <groupId>org.hjug.refactorfirst.circularreferencedetector</groupId> - <artifactId>circular-reference-detector</artifactId> + <groupId>org.hjug.refactorfirst.dsm</groupId> + <artifactId>dsm</artifactId> </dependency> <dependency> 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 75a9246c..db9c5b09 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 @@ -16,7 +16,7 @@ import net.sourceforge.pmd.*; import net.sourceforge.pmd.lang.LanguageRegistry; import org.eclipse.jgit.api.errors.GitAPIException; -import org.hjug.cycledetector.CircularReferenceChecker; +import org.hjug.dsm.CircularReferenceChecker; import org.hjug.git.ChangePronenessRanker; import org.hjug.git.GitLogReader; import org.hjug.git.ScmLogInfo; diff --git a/coverage/pom.xml b/coverage/pom.xml index 9c109cf7..0f0ec3a3 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -30,8 +30,8 @@ </dependency> <dependency> - <groupId>org.hjug.refactorfirst.circularreferencedetector</groupId> - <artifactId>circular-reference-detector</artifactId> + <groupId>org.hjug.refactorfirst.dsm</groupId> + <artifactId>dsm</artifactId> </dependency> <dependency> diff --git a/dsm/pom.xml b/dsm/pom.xml index 37f04bf7..a16c0e24 100644 --- a/dsm/pom.xml +++ b/dsm/pom.xml @@ -24,6 +24,11 @@ <groupId>org.jgrapht</groupId> <artifactId>jgrapht-core</artifactId> </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.26</version> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java b/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java similarity index 75% rename from circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java rename to dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java index 6e840d17..25622583 100644 --- a/circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java +++ b/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java @@ -1,4 +1,4 @@ -package org.hjug.cycledetector; +package org.hjug.dsm; import java.util.HashMap; import java.util.Map; @@ -13,8 +13,17 @@ public class CircularReferenceChecker { private final Map<String, AsSubgraph<String, DefaultWeightedEdge>> uniqueSubGraphs = new HashMap<>(); + /** + * Detects cycles in the graph that is passed in + * and returns the unique cycles in the graph as a map of subgraphs + * + * @param graph + * @return a Map of unique cycles in the graph + */ + public Map<String, AsSubgraph<String, DefaultWeightedEdge>> getCycles(Graph<String, DefaultWeightedEdge> graph) { + // use CycleDetector.findCycles()? Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles = detectCycles(graph); cycles.forEach((vertex, subGraph) -> { @@ -23,7 +32,7 @@ public Map<String, AsSubgraph<String, DefaultWeightedEdge>> getCycles(Graph<Stri if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) { uniqueSubGraphs.put(vertex, subGraph); - log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount); + log.debug("Vertex: {} vertex count: {} edge count: {}", vertex, vertexCount, edgeCount); } }); @@ -45,20 +54,13 @@ private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subG return false; } - /** - * Detects cycles in the classReferencesGraph parameter - * and stores the cycles of a class as a subgraph in a Map - * - * @param classReferencesGraph - * @return a Map of Class and its Cycle Graph - */ - public Map<String, AsSubgraph<String, DefaultWeightedEdge>> detectCycles( - Graph<String, DefaultWeightedEdge> classReferencesGraph) { + private Map<String, AsSubgraph<String, DefaultWeightedEdge>> detectCycles( + Graph<String, DefaultWeightedEdge> graph) { Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap = new HashMap<>(); - CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(classReferencesGraph); + CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(graph); cycleDetector.findCycles().forEach(v -> { AsSubgraph<String, DefaultWeightedEdge> subGraph = - new AsSubgraph<>(classReferencesGraph, cycleDetector.findCyclesContainingVertex(v)); + new AsSubgraph<>(graph, cycleDetector.findCyclesContainingVertex(v)); cyclesForEveryVertexMap.put(v, subGraph); }); return cyclesForEveryVertexMap; diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index 26ae519e..d8ed7f81 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -5,6 +5,7 @@ import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.cycle.TarjanSimpleCycles; +import org.jgrapht.graph.AsSubgraph; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; @@ -193,6 +194,11 @@ public void printDSM() { orderVertices(); } + printDSM(graph, sortedActivities); + } + + void printDSM(Graph<String, DefaultWeightedEdge> graph, List<String> sortedActivities) { + System.out.println("Design Structure Matrix:"); System.out.print(" "); for (String col : sortedActivities) { @@ -219,11 +225,6 @@ public void printDSM() { /** * Captures the impact of the removal of each edge above the diagonal. - * This algorithm will have at worst a runtime of O(E^2 + EV): - * Tarjan's algorithm runtime is E + V and is run E/2 times (at worst) - * Topological sort runtime is E + V and is run E/2 times (at worst) - * with E/2 iterations - * O(2E + 2V) * O(E/2) = O(E^2 + EV) * * @return List<EdgeToRemoveInfo> Impact of each edge if removed. */ @@ -244,28 +245,10 @@ public List<EdgeToRemoveInfo> getImpactOfEdgesAboveDiagonalIfRemoved() { // remove the edge clonedGraph.removeEdge(edge); - // sort the graph - List<Set<String>> sccs = findStronglyConnectedComponents(clonedGraph); - List<String> sortedClonedActivities = topologicalSort(sccs, clonedGraph); - Collections.reverse(sortedClonedActivities); - - // get the number of edges above the diagonal - List<DefaultWeightedEdge> clonedGraphEdgesAboveDiagonal = - getEdgesAboveDiagonal(clonedGraph, sortedClonedActivities); - int edgeCount = clonedGraphEdgesAboveDiagonal.size(); - - // get the new number of cycles - int cycleCount = sccs.size(); + // identify updated cycles and calculate updated graph information + edgesToRemove.add(getEdgeToRemoveInfo(edge, new CircularReferenceChecker().getCycles(clonedGraph))); - // calculate the average size of the new cycle - double averageCycleSize = - sccs.stream().mapToInt(Set::size).average().orElse(0.0); - - EdgeToRemoveInfo edgeToRemoveInfo = - new EdgeToRemoveInfo(edge, graph.getEdgeWeight(edge), edgeCount, cycleCount, averageCycleSize); - edgesToRemove.add(edgeToRemoveInfo); - - // add the edge back + // add the edge back for next iteration clonedGraph.addEdge(graph.getEdgeSource(edge), graph.getEdgeTarget(edge), edge); clonedGraph.setEdgeWeight(edge, graph.getEdgeWeight(edge)); } @@ -273,49 +256,18 @@ public List<EdgeToRemoveInfo> getImpactOfEdgesAboveDiagonalIfRemoved() { return edgesToRemove; } - // only used by getImpactOfEdgesAboveDiagonalIfRemoved() - private List<DefaultWeightedEdge> getEdgesAboveDiagonal( - Graph<String, DefaultWeightedEdge> graph, List<String> sortedActivities) { - - List<DefaultWeightedEdge> edgesAboveDiagonal = new ArrayList<>(); - - for (int i = 0; i < sortedActivities.size(); i++) { - for (int j = i + 1; j < sortedActivities.size(); j++) { - // source / destination vertex was flipped after solution generation - // to correctly identify the vertex above the diagonal to remove - DefaultWeightedEdge edge = graph.getEdge(sortedActivities.get(i), sortedActivities.get(j)); - if (edge != null) { - edgesAboveDiagonal.add(edge); - } - } - } + private EdgeToRemoveInfo getEdgeToRemoveInfo( + DefaultWeightedEdge edge, Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles) { + // get the new number of cycles + int cycleCount = cycles.size(); - return edgesAboveDiagonal; - } + // calculate the average cycle node count + double averageCycleNodeCount = cycles.values().stream() + .mapToInt(cycle -> cycle.vertexSet().size()) + .average() + .orElse(0.0); - void printDSM(Graph<String, DefaultWeightedEdge> graph, List<String> sortedActivities) { - - System.out.println("Design Structure Matrix:"); - System.out.print(" "); - for (String col : sortedActivities) { - System.out.print(col + " "); - } - System.out.println(); - for (String row : sortedActivities) { - System.out.print(row + " "); - for (String col : sortedActivities) { - if (col.equals(row)) { - System.out.print("- "); - } else { - DefaultWeightedEdge edge = graph.getEdge(row, col); - if (edge != null) { - System.out.print((int) graph.getEdgeWeight(edge) + " "); - } else { - System.out.print("0 "); - } - } - } - System.out.println(); - } + // capture the what-if values + return new EdgeToRemoveInfo(edge, graph.getEdgeWeight(edge), cycleCount, averageCycleNodeCount); } } diff --git a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java index 4dbdfef1..d954057c 100644 --- a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java +++ b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java @@ -7,7 +7,6 @@ public class EdgeToRemoveInfo { private final DefaultWeightedEdge edge; private final double edgeWeight; - private final int newEdgeCount; - private final int newStronglyConnectedComponentCount; - private final double newAverageStronglyConnectedComponentSize; + private final int newCycleCount; + private final double averageCycleNodeCount; } diff --git a/circular-reference-detector/src/test/java/org/hjug/cycledetector/CircularReferenceCheckerTests.java b/dsm/src/test/java/org/hjug/dsm/CircularReferenceCheckerTests.java similarity index 50% rename from circular-reference-detector/src/test/java/org/hjug/cycledetector/CircularReferenceCheckerTests.java rename to dsm/src/test/java/org/hjug/dsm/CircularReferenceCheckerTests.java index d6c4accd..fb2e8439 100644 --- a/circular-reference-detector/src/test/java/org/hjug/cycledetector/CircularReferenceCheckerTests.java +++ b/dsm/src/test/java/org/hjug/dsm/CircularReferenceCheckerTests.java @@ -1,9 +1,7 @@ -package org.hjug.cycledetector; +package org.hjug.dsm; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.File; import java.util.Map; import org.jgrapht.Graph; import org.jgrapht.graph.AsSubgraph; @@ -18,7 +16,7 @@ class CircularReferenceCheckerTests { @DisplayName("Detect 3 cycles from given graph.") @Test - public void detectCyclesTest() { + void detectCyclesTest() { Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class); classReferencesGraph.addVertex("A"); classReferencesGraph.addVertex("B"); @@ -27,21 +25,7 @@ public void detectCyclesTest() { classReferencesGraph.addEdge("B", "C"); classReferencesGraph.addEdge("C", "A"); Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap = - sutCircularReferenceChecker.detectCycles(classReferencesGraph); - assertEquals(3, cyclesForEveryVertexMap.size()); - } - - @DisplayName("Create graph image in given outputDirectory") - @Test - public void createImageTest() { - Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class); - classReferencesGraph.addVertex("A"); - classReferencesGraph.addVertex("B"); - classReferencesGraph.addVertex("C"); - classReferencesGraph.addEdge("A", "B"); - classReferencesGraph.addEdge("B", "C"); - classReferencesGraph.addEdge("C", "A"); - File newGraphImage = new File("src/test/resources/testOutputDirectory/graphtestGraph.png"); - assertTrue(newGraphImage.exists() && !newGraphImage.isDirectory()); + sutCircularReferenceChecker.getCycles(classReferencesGraph); + assertEquals(1, cyclesForEveryVertexMap.size()); } } diff --git a/dsm/src/test/java/org/hjug/dsm/DSMTest.java b/dsm/src/test/java/org/hjug/dsm/DSMTest.java index c9c04917..043bebfe 100644 --- a/dsm/src/test/java/org/hjug/dsm/DSMTest.java +++ b/dsm/src/test/java/org/hjug/dsm/DSMTest.java @@ -22,9 +22,9 @@ void setUp() { dsm.addDependency("A", "B", 1); dsm.addDependency("B", "C", 2); dsm.addDependency("C", "D", 3); - dsm.addDependency("D", "A", 4); // Adding a cycle - dsm.addDependency("C", "A", 5); // Adding a cycle dsm.addDependency("B", "A", 6); // Adding a cycle + dsm.addDependency("C", "A", 5); // Adding a cycle + dsm.addDependency("D", "A", 4); // Adding a cycle /* D C B A @@ -40,8 +40,8 @@ void setUp() { dsm.addActivity("H"); dsm.addDependency("D", "C", 2); dsm.addDependency("A", "H", 7); - dsm.addDependency("E", "H", 2); dsm.addDependency("E", "C", 9); + dsm.addDependency("E", "H", 2); dsm.addDependency("G", "E", 2); dsm.addDependency("H", "D", 9); dsm.addDependency("H", "G", 5); @@ -96,12 +96,39 @@ void edgesAboveDiagonal() { @Test void getImpactOfEdgesAboveDiagonalIfRemoved() { + dsm = new DSM(); + dsm.addActivity("A"); + dsm.addActivity("B"); + dsm.addActivity("C"); + dsm.addActivity("D"); + + // Cycle 1 + dsm.addDependency("A", "B", 1); + dsm.addDependency("B", "C", 2); + dsm.addDependency("C", "D", 3); + dsm.addDependency("B", "A", 6); // Adding a cycle + dsm.addDependency("C", "A", 5); // Adding a cycle + dsm.addDependency("D", "A", 4); // Adding a cycle + + // Cycle 2 + dsm.addActivity("E"); + dsm.addActivity("F"); + dsm.addActivity("G"); + dsm.addActivity("H"); + dsm.addDependency("E", "F", 2); + dsm.addDependency("F", "G", 7); + dsm.addDependency("G", "H", 9); + dsm.addDependency("H", "E", 9); // create cycle + List<EdgeToRemoveInfo> infos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(); - assertEquals(5, infos.size()); + assertEquals(4, infos.size()); + + assertEquals("(D : A)", infos.get(0).getEdge().toString()); + assertEquals(2, infos.get(0).getNewCycleCount()); + assertEquals(3.5, infos.get(0).getAverageCycleNodeCount()); - assertEquals("(D : C)", infos.get(0).getEdge().toString()); - assertEquals(4, infos.get(0).getNewEdgeCount()); - assertEquals(7, infos.get(0).getNewStronglyConnectedComponentCount()); - assertEquals(3.7142857142857144, infos.get(0).getNewAverageStronglyConnectedComponentSize()); + assertEquals("(H : E)", infos.get(3).getEdge().toString()); + assertEquals(1, infos.get(3).getNewCycleCount()); + assertEquals(4.0, infos.get(3).getAverageCycleNodeCount()); } } diff --git a/pom.xml b/pom.xml index aaff0466..8eaa7b90 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,6 @@ <modules> <module>test-resources</module> <module>codebase-graph-builder</module> - <module>circular-reference-detector</module> <module>dsm</module> <module>change-proneness-ranker</module> <module>effort-ranker</module> @@ -103,8 +102,8 @@ </dependency> <dependency> - <groupId>org.hjug.refactorfirst.circularreferencedetector</groupId> - <artifactId>circular-reference-detector</artifactId> + <groupId>org.hjug.refactorfirst.dsm</groupId> + <artifactId>dsm</artifactId> <version>${project.version}</version> </dependency> From 704dd8494a6443387dc2cbb7db2be58afd232f75 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Sat, 22 Feb 2025 20:07:36 -0600 Subject: [PATCH 38/78] #111 Handling getMethodType() == null - Handling case where J.MethodInvocation.getMethodType() is null, though I'm not sure how this can happen. - Added assertion to JavaVisitorTest - Updated version of rewrite-recipe-bom --- codebase-graph-builder/pom.xml | 2 +- .../visitor/JavaMethodInvocationVisitor.java | 7 ++++++- .../hjug/graphbuilder/visitor/JavaVisitorTest.java | 11 ++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/codebase-graph-builder/pom.xml b/codebase-graph-builder/pom.xml index f1c67690..3fff4e54 100644 --- a/codebase-graph-builder/pom.xml +++ b/codebase-graph-builder/pom.xml @@ -19,7 +19,7 @@ <dependency> <groupId>org.openrewrite.recipe</groupId> <artifactId>rewrite-recipe-bom</artifactId> - <version>3.1.0</version> + <version>3.3.0</version> <type>pom</type> <scope>import</scope> </dependency> diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitor.java index 45e15e6c..70f81833 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodInvocationVisitor.java @@ -6,6 +6,7 @@ import org.jgrapht.graph.DefaultWeightedEdge; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; // See RemoveMethodInvocationsVisitor for other visitor methods to override // Custom visitor - not extending IsoVisitor on purpose since it does not provide caller information @@ -18,7 +19,11 @@ public class JavaMethodInvocationVisitor implements TypeProcessor { public J.MethodInvocation visitMethodInvocation(String invokingFqn, J.MethodInvocation methodInvocation) { // getDeclaringType() returns the type that declared the method being invoked - processType(invokingFqn, methodInvocation.getMethodType().getDeclaringType()); + JavaType.Method methodType = methodInvocation.getMethodType(); + // sometimes methodType is null - not sure why + if (null != methodType && null != methodType.getDeclaringType()) { + processType(invokingFqn, methodType.getDeclaringType()); + } if (null != methodInvocation.getTypeParameters() && !methodInvocation.getTypeParameters().isEmpty()) { 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 3b41d13b..b1260b29 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 @@ -1,23 +1,22 @@ package org.hjug.graphbuilder.visitor; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; -@Disabled class JavaVisitorTest { @Test @@ -38,14 +37,12 @@ void visitClasses() throws IOException { final JavaVisitor<ExecutionContext> javaVisitor = new JavaVisitor<>(classReferencesGraph, packageReferencesGraph); - List<String> fqdns = new ArrayList<>(); - List<Path> list = Files.walk(Paths.get(srcDirectory.getAbsolutePath())).collect(Collectors.toList()); - javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach((cu) -> { + javaParser.parse(list, Paths.get(srcDirectory.getAbsolutePath()), ctx).forEach(cu -> { System.out.println(cu.getSourcePath()); javaVisitor.visit(cu, ctx); }); - fqdns.addAll(javaVisitor.getPackagesInCodebase()); + assertEquals(3, javaVisitor.getPackagesInCodebase().size()); } } From d3fe5e62cce0a63b16f623dec7237b4eada71590 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt <jimbethancourt@gmail.com> Date: Sat, 22 Feb 2025 20:12:14 -0600 Subject: [PATCH 39/78] Pulled cycle ranking functionality into CycleRanker Pulled cycle ranking functionality out of CostBenefitCalculator and moved into its own class, CycleRanker, and updated users. - Removed dead code that was incorporating churn metrics into cycle ranking --- .../org/hjug/cbc/CostBenefitCalculator.java | 163 +-------------- .../main/java/org/hjug/cbc/CycleRanker.java | 129 ++++++++++++ .../hjug/dsm/CircularReferenceChecker.java | 1 - .../mavenreport/RefactorFirstMavenReport.java | 11 +- .../hjug/refactorfirst/report/HtmlReport.java | 69 ++++--- .../report/SimpleHtmlReport.java | 192 ++++++++---------- 6 files changed, 253 insertions(+), 312 deletions(-) create mode 100644 cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java 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 db9c5b09..bd35a21d 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 @@ -11,36 +11,24 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.sourceforge.pmd.*; import net.sourceforge.pmd.lang.LanguageRegistry; import org.eclipse.jgit.api.errors.GitAPIException; -import org.hjug.dsm.CircularReferenceChecker; import org.hjug.git.ChangePronenessRanker; import org.hjug.git.GitLogReader; import org.hjug.git.ScmLogInfo; -import org.hjug.graphbuilder.JavaGraphBuilder; import org.hjug.metrics.*; import org.hjug.metrics.rules.CBORule; -import org.jgrapht.Graph; -import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree; -import org.jgrapht.graph.AsSubgraph; -import org.jgrapht.graph.AsUndirectedGraph; -import org.jgrapht.graph.DefaultWeightedEdge; @Slf4j public class CostBenefitCalculator implements AutoCloseable { private Report report; - private String repositoryPath; + private final String repositoryPath; private GitLogReader gitLogReader; private final ChangePronenessRanker changePronenessRanker; - private final JavaGraphBuilder javaGraphBuilder = new JavaGraphBuilder(); - - @Getter - private Graph<String, DefaultWeightedEdge> classReferencesGraph; public CostBenefitCalculator(String repositoryPath) { this.repositoryPath = repositoryPath; @@ -60,127 +48,6 @@ public void close() throws Exception { gitLogReader.close(); } - public List<RankedCycle> runCycleAnalysis() { - List<RankedCycle> rankedCycles = new ArrayList<>(); - try { - boolean calculateCycleChurn = false; - identifyRankedCycles(rankedCycles); - sortRankedCycles(rankedCycles, calculateCycleChurn); - setPriorities(rankedCycles); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return rankedCycles; - } - - public List<RankedCycle> runCycleAnalysisAndCalculateCycleChurn() { - List<RankedCycle> rankedCycles = new ArrayList<>(); - try { - boolean calculateCycleChurn = true; - identifyRankedCycles(rankedCycles); - sortRankedCycles(rankedCycles, calculateCycleChurn); - setPriorities(rankedCycles); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return rankedCycles; - } - - private void identifyRankedCycles(List<RankedCycle> rankedCycles) throws IOException { - classReferencesGraph = javaGraphBuilder.getClassReferences(repositoryPath); - CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); - Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles = - circularReferenceChecker.getCycles(classReferencesGraph); - Map<String, String> classNamesAndPaths = getClassNamesAndPaths(); - cycles.forEach((vertex, subGraph) -> { - Set<DefaultWeightedEdge> minCutEdges; - GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree = - new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); - double minCut = gusfieldGomoryHuCutTree.calculateMinCut(); - minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); - - List<CycleNode> cycleNodes = subGraph.vertexSet().stream() - .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) - // .peek(cycleNode -> log.info(cycleNode.toString())) - .collect(Collectors.toList()); - - rankedCycles.add(createRankedCycle(vertex, subGraph, cycleNodes, minCut, minCutEdges)); - }); - } - - private static void setPriorities(List<RankedCycle> rankedCycles) { - int priority = 1; - for (RankedCycle rankedCycle : rankedCycles) { - rankedCycle.setPriority(priority++); - } - } - - private static void sortRankedCycles(List<RankedCycle> rankedCycles, boolean calculateChurnForCycles) { - if (calculateChurnForCycles) { - rankedCycles.sort(Comparator.comparing(RankedCycle::getAverageChangeProneness)); - - int cpr = 1; - for (RankedCycle rankedCycle : rankedCycles) { - rankedCycle.setChangePronenessRank(cpr++); - } - } else { - rankedCycles.sort(Comparator.comparing(RankedCycle::getRawPriority).reversed()); - } - } - - private RankedCycle createRankedCycle( - String vertex, - AsSubgraph<String, DefaultWeightedEdge> subGraph, - List<CycleNode> cycleNodes, - double minCut, - Set<DefaultWeightedEdge> minCutEdges) { - - return new RankedCycle(vertex, subGraph.vertexSet(), subGraph.edgeSet(), minCut, minCutEdges, cycleNodes); - } - - private RankedCycle createRankedCycleWithChurn( - boolean calculateChurnForCycles, - String vertex, - AsSubgraph<String, DefaultWeightedEdge> subGraph, - List<CycleNode> cycleNodes, - double minCut, - Set<DefaultWeightedEdge> minCutEdges) { - RankedCycle rankedCycle; - if (calculateChurnForCycles) { - List<ScmLogInfo> changeRanks = getRankedChangeProneness(cycleNodes); - - Map<String, CycleNode> cycleNodeMap = new HashMap<>(); - - for (CycleNode cycleNode : cycleNodes) { - cycleNodeMap.put(cycleNode.getFileName(), cycleNode); - } - - for (ScmLogInfo changeRank : changeRanks) { - CycleNode cycleNode = cycleNodeMap.get(changeRank.getPath()); - cycleNode.setScmLogInfo(changeRank); - } - - // sum change proneness ranks - int changePronenessRankSum = changeRanks.stream() - .mapToInt(ScmLogInfo::getChangePronenessRank) - .sum(); - rankedCycle = new RankedCycle( - vertex, - changePronenessRankSum, - subGraph.vertexSet(), - subGraph.edgeSet(), - minCut, - minCutEdges, - cycleNodes); - } else { - rankedCycle = - new RankedCycle(vertex, subGraph.vertexSet(), subGraph.edgeSet(), minCut, minCutEdges, cycleNodes); - } - return rankedCycle; - } - // copied from PMD's PmdTaskImpl.java and modified public void runPmdAnalysis() throws IOException { PMDConfiguration configuration = new PMDConfiguration(); @@ -326,32 +193,4 @@ private String canonicaliseURIStringForRepoLookup(String uriString) { } return uriString.replace("file:///" + repositoryPath.replace("\\", "/") + "/", ""); } - - public Map<String, String> getClassNamesAndPaths() throws IOException { - - Map<String, String> fileNamePaths = new HashMap<>(); - - try (Stream<Path> walk = Files.walk(Paths.get(repositoryPath))) { - walk.forEach(path -> { - String filename = path.getFileName().toString(); - if (filename.endsWith(".java")) { - String uriString = path.toUri().toString(); - fileNamePaths.put(getClassName(filename), canonicaliseURIStringForRepoLookup(uriString)); - } - }); - } - - return fileNamePaths; - } - - /** - * Extract class name from java file name - * Example : MyJavaClass.java becomes MyJavaClass - * - * @param javaFileName - * @return - */ - private String getClassName(String javaFileName) { - return javaFileName.substring(0, javaFileName.indexOf('.')); - } } diff --git a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java new file mode 100644 index 00000000..2aedc76e --- /dev/null +++ b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java @@ -0,0 +1,129 @@ +package org.hjug.cbc; + +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; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.hjug.dsm.CircularReferenceChecker; +import org.hjug.graphbuilder.JavaGraphBuilder; +import org.jgrapht.Graph; +import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree; +import org.jgrapht.graph.AsSubgraph; +import org.jgrapht.graph.AsUndirectedGraph; +import org.jgrapht.graph.DefaultWeightedEdge; + +@RequiredArgsConstructor +public class CycleRanker { + + private final String repositoryPath; + private final JavaGraphBuilder javaGraphBuilder = new JavaGraphBuilder(); + + @Getter + private Graph<String, DefaultWeightedEdge> classReferencesGraph; + + public List<RankedCycle> runCycleAnalysis() { + List<RankedCycle> rankedCycles = new ArrayList<>(); + try { + boolean calculateCycleChurn = false; + identifyRankedCycles(rankedCycles); + sortRankedCycles(rankedCycles, calculateCycleChurn); + setPriorities(rankedCycles); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return rankedCycles; + } + + private void identifyRankedCycles(List<RankedCycle> rankedCycles) throws IOException { + classReferencesGraph = javaGraphBuilder.getClassReferences(repositoryPath); + CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); + Map<String, AsSubgraph<String, DefaultWeightedEdge>> cycles = + circularReferenceChecker.getCycles(classReferencesGraph); + Map<String, String> classNamesAndPaths = getClassNamesAndPaths(); + cycles.forEach((vertex, subGraph) -> { + Set<DefaultWeightedEdge> minCutEdges; + GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree = + new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); + double minCut = gusfieldGomoryHuCutTree.calculateMinCut(); + minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); + + List<CycleNode> cycleNodes = subGraph.vertexSet().stream() + .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) + // .peek(cycleNode -> log.info(cycleNode.toString())) + .collect(Collectors.toList()); + + rankedCycles.add(createRankedCycle(vertex, subGraph, cycleNodes, minCut, minCutEdges)); + }); + } + + private RankedCycle createRankedCycle( + String vertex, + AsSubgraph<String, DefaultWeightedEdge> subGraph, + List<CycleNode> cycleNodes, + double minCut, + Set<DefaultWeightedEdge> minCutEdges) { + + return new RankedCycle(vertex, subGraph.vertexSet(), subGraph.edgeSet(), minCut, minCutEdges, cycleNodes); + } + + private static void sortRankedCycles(List<RankedCycle> rankedCycles, boolean calculateChurnForCycles) { + if (calculateChurnForCycles) { + rankedCycles.sort(Comparator.comparing(RankedCycle::getAverageChangeProneness)); + + int cpr = 1; + for (RankedCycle rankedCycle : rankedCycles) { + rankedCycle.setChangePronenessRank(cpr++); + } + } else { + rankedCycles.sort(Comparator.comparing(RankedCycle::getRawPriority).reversed()); + } + } + + private static void setPriorities(List<RankedCycle> rankedCycles) { + int priority = 1; + for (RankedCycle rankedCycle : rankedCycles) { + rankedCycle.setPriority(priority++); + } + } + + public Map<String, String> getClassNamesAndPaths() throws IOException { + + Map<String, String> fileNamePaths = new HashMap<>(); + + try (Stream<Path> walk = Files.walk(Paths.get(repositoryPath))) { + walk.forEach(path -> { + String filename = path.getFileName().toString(); + if (filename.endsWith(".java")) { + String uriString = path.toUri().toString(); + fileNamePaths.put(getClassName(filename), canonicaliseURIStringForRepoLookup(uriString)); + } + }); + } + + return fileNamePaths; + } + + private String canonicaliseURIStringForRepoLookup(String uriString) { + if (repositoryPath.startsWith("/") || repositoryPath.startsWith("\\")) { + return uriString.replace("file://" + repositoryPath.replace("\\", "/") + "/", ""); + } + return uriString.replace("file:///" + repositoryPath.replace("\\", "/") + "/", ""); + } + + /** + * Extract class name from java file name + * Example : MyJavaClass.java becomes MyJavaClass + * + * @param javaFileName + * @return + */ + private String getClassName(String javaFileName) { + return javaFileName.substring(0, javaFileName.indexOf('.')); + } +} diff --git a/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java b/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java index 25622583..561267f8 100644 --- a/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java +++ b/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java @@ -20,7 +20,6 @@ public class CircularReferenceChecker { * @param graph * @return a Map of unique cycles in the graph */ - public Map<String, AsSubgraph<String, DefaultWeightedEdge>> getCycles(Graph<String, DefaultWeightedEdge> graph) { // use CycleDetector.findCycles()? diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index 9c03b906..809abd53 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -19,6 +19,7 @@ import org.apache.maven.reporting.AbstractMavenReport; import org.apache.maven.reporting.MavenReportException; import org.hjug.cbc.CostBenefitCalculator; +import org.hjug.cbc.CycleRanker; import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; import org.hjug.gdg.GraphDataGenerator; @@ -225,13 +226,15 @@ public void executeReport(Locale locale) throws MavenReportException { costBenefitCalculator.runPmdAnalysis(); rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - rankedCycles = runCycleAnalysis(costBenefitCalculator, outputDirectory.getPath()); - classGraph = costBenefitCalculator.getClassReferencesGraph(); } catch (Exception e) { log.error("Error running analysis."); throw new RuntimeException(e); } + CycleRanker cycleRanker = new CycleRanker(projectBaseDir); + rankedCycles = cycleRanker.runCycleAnalysis(); + classGraph = cycleRanker.getClassReferencesGraph(); + if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) { mainSink.text("Contratulations! " + projectName + " " + projectVersion + " has no God classes, highly coupled classes, or cycles!"); @@ -460,10 +463,6 @@ public void executeReport(Locale locale) throws MavenReportException { log.info("Done! View the report at target/site/{}", filename); } - public List<RankedCycle> runCycleAnalysis(CostBenefitCalculator costBenefitCalculator, String outputDirectory) { - return costBenefitCalculator.runCycleAnalysis(); - } - private void renderCycles( String outputDirectory, Sink mainSink, List<RankedCycle> rankedCycles, DateTimeFormatter formatter) { 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 5d70544c..86958ea9 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -212,8 +212,8 @@ public class HtmlReport extends SimpleHtmlReport { + " <br/>"; @Override - public void printHead() { - stringBuilder.append("<script async defer src=\"https://buttons.github.io/buttons.js\"></script>\n" + public String printHead() { + return "<script async defer src=\"https://buttons.github.io/buttons.js\"></script>\n" // google chart import + "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>\n" // d3 dot graph imports @@ -229,45 +229,33 @@ public void printHead() { + SUGIYAMA_SIGMA_GRAPH + POPUP_FUNCTIONS + POPUP_STYLE - + "</head>\n"); + + "</head>\n"; } @Override - public void printOpenBodyTag() { - stringBuilder.append(" <body class=\"composite\">\n"); - printOverlay(); + public String printOpenBodyTag() { + return " <body class=\"composite\">\n" + printOverlay(); } - private void printOverlay() { - stringBuilder.append("<div class=\"overlay\" id=\"overlay\" onclick=\"hidePopup()\"></div>"); + private String printOverlay() { + return "<div class=\"overlay\" id=\"overlay\" onclick=\"hidePopup()\"></div>"; } @Override - public void printTitle(String projectName, String projectVersion) { - stringBuilder - .append("<title>Refactor First Report for ") - .append(projectName) - .append(" ") - .append(projectVersion) - .append(" \n"); + public String printTitle(String projectName, String projectVersion) { + return "Refactor First Report for " + projectName + " " + projectVersion + " \n"; } @Override - void renderGithubButtons() { - stringBuilder.append("
\n"); - stringBuilder.append("Show RefactorFirst some ❤️\n"); - stringBuilder.append("
\n"); - stringBuilder.append( - "Star\n"); - stringBuilder.append( - "Fork\n"); - stringBuilder.append( - "Watch\n"); - stringBuilder.append( - "Issue\n"); - stringBuilder.append( - "Sponsor\n"); - stringBuilder.append("
"); + String renderGithubButtons() { + return "
\n" + "Show RefactorFirst some ❤️\n" + + "
\n" + + "Star\n" + + "Fork\n" + + "Watch\n" + + "Issue\n" + + "Sponsor\n" + + "
"; } @Override @@ -302,29 +290,37 @@ public String getDescription(Locale locale) { } @Override - void renderGodClassChart(List rankedGodClassDisharmonies, int maxGodClassPriority) { + String renderGodClassChart(List rankedGodClassDisharmonies, int maxGodClassPriority) { + StringBuilder stringBuilder = new StringBuilder(); + String godClassChart = writeGodClassGchartJs(rankedGodClassDisharmonies, maxGodClassPriority - 1); stringBuilder.append( "
\n"); - renderGithubButtons(); + stringBuilder.append(renderGithubButtons()); stringBuilder.append(GOD_CLASS_CHART_LEGEND); + + return stringBuilder.toString(); } @Override - void renderCBOChart(List rankedCBODisharmonies, int maxCboPriority) { + String renderCBOChart(List rankedCBODisharmonies, int maxCboPriority) { + StringBuilder stringBuilder = new StringBuilder(); + String cboChart = writeGCBOGchartJs(rankedCBODisharmonies, maxCboPriority - 1); stringBuilder.append( "
\n"); - renderGithubButtons(); + stringBuilder.append(renderGithubButtons()); stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND); + return stringBuilder.toString(); } @Override - public void renderCycleImage(RankedCycle cycle) { + public String renderCycleImage(RankedCycle cycle) { String dot = buildDot(classGraph, cycle); String cycleName = getClassName(cycle.getCycleName()).replace("$", "_"); + StringBuilder stringBuilder = new StringBuilder(); if (cycle.getCycleNodes().size() + cycle.getEdgeSet().size() < d3Threshold) { stringBuilder.append( "
\n"); @@ -338,6 +334,7 @@ public void renderCycleImage(RankedCycle cycle) { stringBuilder.append(".renderDot(" + cycleName + "_dot);\n"); stringBuilder.append("\n"); } else { + // revisit and add D3 popup button as well stringBuilder.append("\n"); @@ -351,6 +348,8 @@ public void renderCycleImage(RankedCycle cycle) { stringBuilder.append("
\n"); stringBuilder.append("
\n"); stringBuilder.append("
\n"); + + return stringBuilder.toString(); } String buildDot(Graph classGraph, RankedCycle cycle) { 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 57757830..19966702 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -13,6 +13,7 @@ import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.hjug.cbc.CostBenefitCalculator; +import org.hjug.cbc.CycleRanker; import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; import org.hjug.git.GitLogReader; @@ -71,8 +72,6 @@ public class SimpleHtmlReport { .withLocale(Locale.getDefault()) .withZone(ZoneId.systemDefault()); - StringBuilder stringBuilder = new StringBuilder(); - public void execute( boolean showDetails, String projectName, String projectVersion, String outputDirectory, File baseDir) { @@ -83,13 +82,14 @@ public void execute( log.info("Generating {} for {} - {}", filename, projectName, projectVersion); + StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(THE_BEGINNING); - printTitle(projectName, projectVersion); - printHead(); - printOpenBodyTag(); - printBreadcrumbs(); - printProjectHeader(projectName, projectVersion); + stringBuilder.append(printTitle(projectName, projectVersion)); + stringBuilder.append(printHead()); + stringBuilder.append(printOpenBodyTag()); + stringBuilder.append(printBreadcrumbs()); + stringBuilder.append(printProjectHeader(projectName, projectVersion)); GitLogReader gitLogReader = new GitLogReader(); String projectBaseDir; @@ -133,28 +133,22 @@ public void execute( return; } - List rankedGodClassDisharmonies; - List rankedCBODisharmonies; + List rankedGodClassDisharmonies = List.of(); + List rankedCBODisharmonies = List.of(); List rankedCycles; try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { costBenefitCalculator.runPmdAnalysis(); rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - - // will revisit later - /*if (showDetails) { - rankedCycles = costBenefitCalculator.runCycleAnalysisAndCalculateCycleChurn(); - } else { - rankedCycles = costBenefitCalculator.runCycleAnalysis(); - }*/ - - rankedCycles = costBenefitCalculator.runCycleAnalysis(); - classGraph = costBenefitCalculator.getClassReferencesGraph(); } catch (Exception e) { log.error("Error running analysis."); throw new RuntimeException(e); } + CycleRanker cycleRanker = new CycleRanker(projectBaseDir); + rankedCycles = cycleRanker.runCycleAnalysis(); + classGraph = cycleRanker.getClassReferencesGraph(); + if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) { stringBuilder .append("Congratulations! ") @@ -162,7 +156,7 @@ public void execute( .append(" ") .append(projectVersion) .append(" has no God classes, highly coupled classes, or cycles!"); - renderGithubButtons(); + stringBuilder.append(renderGithubButtons()); log.info("Done! No Disharmonies found!"); stringBuilder.append(THE_END); writeReportToDisk(outputDirectory, filename, stringBuilder); @@ -184,7 +178,7 @@ public void execute( } if (!rankedGodClassDisharmonies.isEmpty()) { - renderGodClassInfo(showDetails, rankedGodClassDisharmonies, godClassTableHeadings); + stringBuilder.append(renderGodClassInfo(showDetails, rankedGodClassDisharmonies, godClassTableHeadings)); } if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { @@ -192,7 +186,7 @@ public void execute( } if (!rankedCBODisharmonies.isEmpty()) { - renderHighlyCoupledClassInfo(rankedCBODisharmonies); + stringBuilder.append(renderHighlyCoupledClassInfo(rankedCBODisharmonies)); } if (!rankedCycles.isEmpty()) { @@ -203,7 +197,7 @@ public void execute( stringBuilder.append("
\n"); stringBuilder.append("
\n"); } - renderCycles(rankedCycles); + stringBuilder.append(renderCycles(rankedCycles)); } stringBuilder.append("\n"); @@ -216,15 +210,20 @@ public void execute( log.info("Done! View the report at target/site/{}", filename); } - private void renderCycles(List rankedCycles) { - renderClassCycleSummary(rankedCycles); + private String renderCycles(List rankedCycles) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(renderClassCycleSummary(rankedCycles)); for (RankedCycle rankedCycle : rankedCycles) { - renderSingleCycle(rankedCycle); + stringBuilder.append(renderSingleCycle(rankedCycle)); } + + return stringBuilder.toString(); } - private void renderClassCycleSummary(List rankedCycles) { + private String renderClassCycleSummary(List rankedCycles) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("\n"); stringBuilder.append( @@ -251,7 +250,7 @@ private void renderClassCycleSummary(List rankedCycles) { } for (String rowData : getRankedCycleSummaryData(rankedCycle, edgesToCut)) { - drawTableCell(rowData); + stringBuilder.append(drawTableCell(rowData)); } stringBuilder.append("\n"); @@ -259,6 +258,8 @@ private void renderClassCycleSummary(List rankedCycles) { stringBuilder.append("
\n"); stringBuilder.append("
\n"); + + return stringBuilder.toString(); } private String renderEdge(DefaultWeightedEdge edge) { @@ -275,18 +276,6 @@ private String renderEdge(DefaultWeightedEdge edge) { private String[] getCycleSummaryTableHeadings() { return new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; - // Will revisit later - /*String[] cycleTableHeadings; - if (showDetails) { - cycleTableHeadings = new String[] { - "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts" - }; - } else { - cycleTableHeadings = - new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; - } - return cycleTableHeadings; - */ } private String[] getRankedCycleSummaryData(RankedCycle rankedCycle, StringBuilder edgesToCut) { @@ -298,35 +287,10 @@ private String[] getRankedCycleSummaryData(RankedCycle rankedCycle, StringBuilde String.valueOf(rankedCycle.getEdgeSet().size()), edgesToCut.toString() }; - - // Will revisit later - /*String[] rankedCycleData; - if (showDetails) { - rankedCycleData = new String[] { - // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min - // Cuts" - getClassName(rankedCycle.getCycleName()), - rankedCycle.getPriority().toString(), - rankedCycle.getChangePronenessRank().toString(), - String.valueOf(rankedCycle.getCycleNodes().size()), - String.valueOf(rankedCycle.getEdgeSet().size()), - edgesToCut.toString() - }; - } else { - rankedCycleData = new String[] { - // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts" - getClassName(rankedCycle.getCycleName()), - rankedCycle.getPriority().toString(), - String.valueOf(rankedCycle.getCycleNodes().size()), - String.valueOf(rankedCycle.getEdgeSet().size()), - edgesToCut.toString() - }; - } - return rankedCycleData; - */ } - private void renderSingleCycle(RankedCycle cycle) { + private String renderSingleCycle(RankedCycle cycle) { + StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("
\n"); stringBuilder.append("
\n"); @@ -335,7 +299,7 @@ private void renderSingleCycle(RankedCycle cycle) { stringBuilder.append("
\n"); stringBuilder.append("

Class Cycle : " + getClassName(cycle.getCycleName()) + "

\n"); - renderCycleImage(cycle); + stringBuilder.append(renderCycleImage(cycle)); stringBuilder.append("
"); stringBuilder.append(""); @@ -356,7 +320,7 @@ private void renderSingleCycle(RankedCycle cycle) { for (String vertex : cycle.getVertexSet()) { stringBuilder.append(""); - drawTableCell(getClassName(vertex)); + stringBuilder.append(drawTableCell(getClassName(vertex))); StringBuilder edges = new StringBuilder(); for (DefaultWeightedEdge edge : cycle.getEdgeSet()) { if (edge.toString().startsWith("(" + vertex + " :")) { @@ -371,28 +335,31 @@ private void renderSingleCycle(RankedCycle cycle) { edges.append("
\n"); } } - drawTableCell(edges.toString()); + stringBuilder.append(drawTableCell(edges.toString())); stringBuilder.append("\n"); } stringBuilder.append("\n"); stringBuilder.append("\n"); + + return stringBuilder.toString(); } - public void renderCycleImage(RankedCycle cycle) { - // empty on purpose + public String renderCycleImage(RankedCycle cycle) { + return ""; // empty on purpose } - private void renderGodClassInfo( + private String renderGodClassInfo( boolean showDetails, List rankedGodClassDisharmonies, String[] godClassTableHeadings) { int maxGodClassPriority = rankedGodClassDisharmonies .get(rankedGodClassDisharmonies.size() - 1) .getPriority(); + StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("\n"); - renderGodClassChart(rankedGodClassDisharmonies, maxGodClassPriority); + stringBuilder.append(renderGodClassChart(rankedGodClassDisharmonies, maxGodClassPriority)); stringBuilder.append( "

God classes by the numbers: (Refactor Starting with Priority 1)

\n"); @@ -441,7 +408,7 @@ private void renderGodClassInfo( showDetails ? detailedRankedGodClassDisharmonyData : simpleRankedGodClassDisharmonyData; for (String rowData : rankedDisharmonyData) { - drawTableCell(rowData); + stringBuilder.append(drawTableCell(rowData)); } stringBuilder.append("\n"); @@ -449,9 +416,12 @@ private void renderGodClassInfo( stringBuilder.append("\n"); stringBuilder.append("\n"); + + return stringBuilder.toString(); } - private void renderHighlyCoupledClassInfo(List rankedCBODisharmonies) { + private String renderHighlyCoupledClassInfo(List rankedCBODisharmonies) { + StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append( ""); @@ -485,7 +455,7 @@ private void renderHighlyCoupledClassInfo(List rankedCBODishar }; for (String rowData : rankedCboClassDisharmonyData) { - drawTableCell(rowData); + stringBuilder.append(drawTableCell(rowData)); } stringBuilder.append(""); @@ -493,13 +463,23 @@ private void renderHighlyCoupledClassInfo(List rankedCBODishar stringBuilder.append(""); stringBuilder.append(""); + + return stringBuilder.toString(); } - void drawTableCell(String rowData) { + String drawTableCell(String rowData) { if (isNumber(rowData) || isDateTime(rowData)) { - stringBuilder.append("").append(rowData).append("\n"); + return new StringBuilder() + .append("") + .append(rowData) + .append("\n") + .toString(); } else { - stringBuilder.append("").append(rowData).append("\n"); + return new StringBuilder() + .append("") + .append(rowData) + .append("\n") + .toString(); } } @@ -511,40 +491,36 @@ boolean isDateTime(String rowData) { return rowData.contains(", "); } - public void printTitle(String projectName, String projectVersion) { - // empty on purpose + public String printTitle(String projectName, String projectVersion) { + return ""; // empty on purpose } - public void printHead() { - // empty on purpose + public String printHead() { + return ""; // empty on purpose } - public void printOpenBodyTag() { - stringBuilder.append(" \n"); + public String printOpenBodyTag() { + return " \n"; } - public void printBreadcrumbs() { - stringBuilder.append("
\n" + public String printBreadcrumbs() { + return "
\n" + "
\n" + "
\n" - + "
\n"); + + "
\n"; } - public void printProjectHeader(String projectName, String projectVersion) { - - stringBuilder.append("
\n" + "
\n" + public String printProjectHeader(String projectName, String projectVersion) { + return "
\n" + "
\n" + "
\n" + "
\n" - + "
\n"); - - stringBuilder - .append( - "
\n" - + "

RefactorFirst Report for ") - .append(projectName) - .append(" ") - .append(projectVersion) - .append("

\n"); + + "
\n" + "
\n" + + "

RefactorFirst Report for " + + projectName + + " " + + projectVersion + + "

\n"; } public void printProjectFooter(StringBuilder stringBuilder) { @@ -556,8 +532,8 @@ public void printProjectFooter(StringBuilder stringBuilder) { .append(""); } - void renderGithubButtons() { - // empty on purpose + String renderGithubButtons() { + return ""; // empty on purpose } String getOutputName() { @@ -565,8 +541,8 @@ String getOutputName() { return "refactor-first-report"; } - void renderGodClassChart(List rankedGodClassDisharmonies, int maxGodClassPriority) { - // empty on purpose + String renderGodClassChart(List rankedGodClassDisharmonies, int maxGodClassPriority) { + return ""; // empty on purpose } String writeGodClassGchartJs(List rankedDisharmonies, int maxPriority) { @@ -579,8 +555,8 @@ String writeGCBOGchartJs(List rankedDisharmonies, int maxPrior return ""; } - void renderCBOChart(List rankedCBODisharmonies, int maxCboPriority) { - // empty on purpose + String renderCBOChart(List rankedCBODisharmonies, int maxCboPriority) { + return ""; // empty on purpose } String getClassName(String fqn) { From ca3b6fa387162229d17bd233d20942accbb31cf7 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 23 Feb 2025 20:26:46 -0600 Subject: [PATCH 40/78] #132 RefactorFirstMavenReport is now using HtmlReport RefactorFirstMavenReport is now using HtmlReport to generate the body of the report. Fine tuning is still needed, but it works --- .../mavenreport/RefactorFirstMavenReport.java | 775 +----------------- .../hjug/refactorfirst/report/HtmlReport.java | 11 + .../report/SimpleHtmlReport.java | 69 +- 3 files changed, 59 insertions(+), 796 deletions(-) diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index 809abd53..1e4e6ba3 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -24,6 +24,8 @@ import org.hjug.cbc.RankedDisharmony; import org.hjug.gdg.GraphDataGenerator; import org.hjug.git.GitLogReader; +import org.hjug.refactorfirst.report.HtmlReport; +import org.jetbrains.annotations.NotNull; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; @@ -46,10 +48,6 @@ public class RefactorFirstMavenReport extends AbstractMavenReport { @Parameter(defaultValue = "${project.version}") private String projectVersion; - private DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) - .withLocale(Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - public String getOutputName() { // This report will generate simple-report.html when invoked in a project with `mvn site` return "refactor-first-report"; @@ -66,63 +64,21 @@ public String getDescription(Locale locale) { + " have the highest priority values."; } - public final String[] classCycleTableHeadings = {"Classes", "Relationships"}; - - private Graph classGraph; - @Override - public void executeReport(Locale locale) throws MavenReportException { - - final String[] godClassSimpleTableHeadings = { - "Class", - "Priority", - "Change Proneness Rank", - "Effort Rank", - "Method Count", - "Most Recent Commit Date", - "Commit Count" - }; - - final String[] godClassDetailedTableHeadings = { - "Class", - "Priority", - "Raw Priority", - "Change Proneness Rank", - "Effort Rank", - "WMC", - "WMC Rank", - "ATFD", - "ATFD Rank", - "TCC", - "TCC Rank", - "Date of First Commit", - "Most Recent Commit Date", - "Commit Count", - "Full Path" - }; + public void executeReport(Locale locale) { + HtmlReport htmlReport = new HtmlReport(); - final String[] cboTableHeadings = { - "Class", "Priority", "Change Proneness Rank", "Coupling Count", "Most Recent Commit Date", "Commit Count" - }; - - final String[] godClassTableHeadings = - showDetails ? godClassDetailedTableHeadings : godClassSimpleTableHeadings; - - if (Objects.equals(project.getName(), "Maven Stub Project (No POM)")) { - projectName = new File(Paths.get("").toAbsolutePath().toString()).getName(); - } - - String filename = getOutputName() + ".html"; - log.info("Generating {} for {} - {}", filename, projectName, projectVersion); - - // Get the Maven Doxia Sink, which will be used to generate the - // various elements of the document Sink mainSink = getSink(); - if (mainSink == null) { - throw new MavenReportException("Could not get the Doxia sink"); - } + printHead(mainSink); + // TODO: pass in the screen width to have d3.js render SVGs within the bounds of the screen + String report = htmlReport + .generateReport(showDetails, projectName, projectVersion, project.getBasedir()) + .toString(); - // Page head + mainSink.rawText(report); + } + + private void printHead(Sink mainSink) { mainSink.head(); mainSink.title(); mainSink.text("Refactor First Report for " + projectName + " " + projectVersion); @@ -167,710 +123,5 @@ public void executeReport(Locale locale) throws MavenReportException { mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); mainSink.head_(); - - mainSink.body(); - - // Heading 1 - mainSink.section1(); - mainSink.sectionTitle1(); - mainSink.text("RefactorFirst Report for " + projectName + " " + projectVersion); - mainSink.sectionTitle1_(); - - GitLogReader gitLogReader = new GitLogReader(); - String projectBaseDir; - Optional optionalGitDir; - - File baseDir = project.getBasedir(); - if (baseDir != null) { - projectBaseDir = baseDir.getPath(); - optionalGitDir = Optional.ofNullable(gitLogReader.getGitDir(baseDir)); - } else { - projectBaseDir = Paths.get("").toAbsolutePath().toString(); - optionalGitDir = Optional.ofNullable(gitLogReader.getGitDir(new File(projectBaseDir))); - } - - File gitDir; - if (optionalGitDir.isPresent()) { - gitDir = optionalGitDir.get(); - } else { - log.info( - "Done! No Git repository found! Please initialize a Git repository and perform an initial commit."); - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder - .append("No Git repository found in project ") - .append(projectName) - .append(" ") - .append(projectVersion) - .append(". "); - stringBuilder.append("Please initialize a Git repository and perform an initial commit."); - mainSink.text(stringBuilder.toString()); - return; - } - - String parentOfGitDir = gitDir.getParentFile().getPath(); - log.info("Project Base Dir: {} ", projectBaseDir); - log.info("Parent of Git Dir: {}", parentOfGitDir); - - if (!projectBaseDir.equals(parentOfGitDir)) { - log.warn("Project Base Directory does not match Git Parent Directory"); - mainSink.text("Project Base Directory does not match Git Parent Directory. " - + "Please refer to the report at the root of the site directory."); - return; - } - - List rankedGodClassDisharmonies; - List rankedCBODisharmonies; - List rankedCycles; - try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { - costBenefitCalculator.runPmdAnalysis(); - rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); - rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - } catch (Exception e) { - log.error("Error running analysis."); - throw new RuntimeException(e); - } - - CycleRanker cycleRanker = new CycleRanker(projectBaseDir); - rankedCycles = cycleRanker.runCycleAnalysis(); - classGraph = cycleRanker.getClassReferencesGraph(); - - if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) { - mainSink.text("Contratulations! " + projectName + " " + projectVersion - + " has no God classes, highly coupled classes, or cycles!"); - mainSink.section1_(); - renderGitHubButtons(mainSink); - mainSink.body_(); - log.info("Done! No Disharmonies found!"); - return; - } - - /* if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { - SinkEventAttributeSet godClassesLink = new SinkEventAttributeSet(); - godClassesLink.addAttribute(SinkEventAttributes.SRC, "#GOD"); - mainSink.anchor("God Classes", godClassesLink); - mainSink.anchor_(); - - mainSink.lineBreak(); - - SinkEventAttributeSet cboClassesLink = new SinkEventAttributeSet(); - godClassesLink.addAttribute(SinkEventAttributes.SRC, "#CBO"); - mainSink.anchor("Highly Coupled Classes", cboClassesLink); - mainSink.anchor_(); - }*/ - - if (!rankedGodClassDisharmonies.isEmpty()) { - int maxGodClassPriority = rankedGodClassDisharmonies - .get(rankedGodClassDisharmonies.size() - 1) - .getPriority(); - - SinkEventAttributeSet alignCenter = new SinkEventAttributeSet(); - alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center"); - - mainSink.division(alignCenter); - mainSink.section2(); - mainSink.sectionTitle2(); - mainSink.text("God Classes"); - mainSink.sectionTitle2_(); - mainSink.section2_(); - mainSink.division_(); - - String godClassScript = writeGodClassGchartJs(rankedGodClassDisharmonies, maxGodClassPriority - 1); - SinkEventAttributeSet seriesChartDiv = new SinkEventAttributeSet(); - seriesChartDiv.addAttribute(SinkEventAttributes.ID, "series_chart_div"); - seriesChartDiv.addAttribute(SinkEventAttributes.ALIGN, "center"); - mainSink.division(seriesChartDiv); - mainSink.division_(); - - SinkEventAttributeSet godClassJavascript = new SinkEventAttributeSet(); - godClassJavascript.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, godClassJavascript); - mainSink.rawText(godClassScript); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); - - renderGitHubButtons(mainSink); - - String legendHeading = "God Class Chart Legend:"; - String xAxis = "Effort to refactor to a non-God class"; - renderLegend(mainSink, legendHeading, xAxis); - - /* - *God Class table - */ - mainSink.lineBreak(); - mainSink.lineBreak(); - mainSink.division(alignCenter); - mainSink.section3(); - mainSink.sectionTitle3(); - mainSink.text("God classes by the numbers: (Refactor Starting with Priority 1)"); - mainSink.sectionTitle3_(); - mainSink.section3_(); - mainSink.division_(); - - mainSink.table(); - mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true); - - // header row - mainSink.tableRow(); - for (String heading : godClassTableHeadings) { - drawTableHeaderCell(heading, mainSink); - } - mainSink.tableRow_(); - - for (RankedDisharmony rankedGodClassDisharmony : rankedGodClassDisharmonies) { - mainSink.tableRow(); - - Object[] simpleRankedGodClassDisharmonyData = { - rankedGodClassDisharmony.getFileName(), - rankedGodClassDisharmony.getPriority(), - rankedGodClassDisharmony.getChangePronenessRank(), - rankedGodClassDisharmony.getEffortRank(), - rankedGodClassDisharmony.getWmc(), - rankedGodClassDisharmony.getMostRecentCommitTime(), - rankedGodClassDisharmony.getCommitCount() - }; - - Object[] detailedRankedGodClassDisharmonyData = { - rankedGodClassDisharmony.getFileName(), - rankedGodClassDisharmony.getPriority(), - rankedGodClassDisharmony.getRawPriority(), - rankedGodClassDisharmony.getChangePronenessRank(), - rankedGodClassDisharmony.getEffortRank(), - rankedGodClassDisharmony.getWmc(), - rankedGodClassDisharmony.getWmcRank(), - rankedGodClassDisharmony.getAtfd(), - rankedGodClassDisharmony.getAtfdRank(), - rankedGodClassDisharmony.getTcc(), - rankedGodClassDisharmony.getTccRank(), - rankedGodClassDisharmony.getFirstCommitTime(), - rankedGodClassDisharmony.getMostRecentCommitTime(), - rankedGodClassDisharmony.getCommitCount(), - rankedGodClassDisharmony.getPath() - }; - - final Object[] rankedDisharmonyData = - showDetails ? detailedRankedGodClassDisharmonyData : simpleRankedGodClassDisharmonyData; - - for (Object rowData : rankedDisharmonyData) { - drawTableCell(rowData, mainSink); - } - - mainSink.tableRow_(); - } - - mainSink.tableRows_(); - mainSink.table_(); - } - - if (!rankedCBODisharmonies.isEmpty()) { - if (!rankedGodClassDisharmonies.isEmpty()) { - mainSink.lineBreak(); - mainSink.lineBreak(); - mainSink.lineBreak(); - mainSink.horizontalRule(); - mainSink.lineBreak(); - mainSink.lineBreak(); - } - - int maxCboPriority = - rankedCBODisharmonies.get(rankedCBODisharmonies.size() - 1).getPriority(); - - SinkEventAttributeSet alignCenter = new SinkEventAttributeSet(); - alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center"); - - mainSink.division(alignCenter); - mainSink.section2(); - mainSink.sectionTitle2(); - mainSink.text("Highly Coupled Classes"); - mainSink.sectionTitle2_(); - mainSink.section2_(); - mainSink.division_(); - - SinkEventAttributeSet seriesChartDiv = new SinkEventAttributeSet(); - seriesChartDiv.addAttribute(SinkEventAttributes.ID, "series_chart_div_2"); - seriesChartDiv.addAttribute(SinkEventAttributes.ALIGN, "center"); - mainSink.division(seriesChartDiv); - mainSink.division_(); - - String cboScript = writeGCBOGchartJs(rankedCBODisharmonies, maxCboPriority - 1); - - SinkEventAttributeSet cboJavascript = new SinkEventAttributeSet(); - cboJavascript.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, cboJavascript); - mainSink.rawText(cboScript); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); - - renderGitHubButtons(mainSink); - - String legendHeading = "Highly Coupled Classes Chart Legend:"; - String xAxis = "Number of objects the class is coupled to"; - renderLegend(mainSink, legendHeading, xAxis); - - mainSink.division(alignCenter); - mainSink.section3(); - mainSink.sectionTitle3(); - mainSink.text("Highly Coupled classes by the numbers: (Refactor Starting with Priority 1)"); - mainSink.sectionTitle3_(); - mainSink.section3_(); - mainSink.division_(); - - SinkEventAttributeSet disharmonyTable = new SinkEventAttributeSet(); - mainSink.table(disharmonyTable); - mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true); - mainSink.tableRow(); - // Content - for (String heading : cboTableHeadings) { - drawTableHeaderCell(heading, mainSink); - } - mainSink.tableRow_(); - - for (RankedDisharmony rankedCboClassDisharmony : rankedCBODisharmonies) { - mainSink.tableRow(); - - String[] rankedCboClassDisharmonyData = { - rankedCboClassDisharmony.getFileName(), - rankedCboClassDisharmony.getPriority().toString(), - rankedCboClassDisharmony.getChangePronenessRank().toString(), - rankedCboClassDisharmony.getEffortRank().toString(), - formatter.format(rankedCboClassDisharmony.getMostRecentCommitTime()), - rankedCboClassDisharmony.getCommitCount().toString() - }; - - for (String rowData : rankedCboClassDisharmonyData) { - drawTableCell(rowData, mainSink); - } - - mainSink.tableRow_(); - } - } - mainSink.tableRows_(); - mainSink.table_(); - - if (!rankedCycles.isEmpty()) { - mainSink.lineBreak(); - mainSink.lineBreak(); - mainSink.horizontalRule(); - mainSink.lineBreak(); - mainSink.lineBreak(); - - renderCycles(outputDirectory.getPath(), mainSink, rankedCycles, formatter); - } - - // Close - mainSink.section1_(); - mainSink.body_(); - - log.info("Done! View the report at target/site/{}", filename); - } - - private void renderCycles( - String outputDirectory, Sink mainSink, List rankedCycles, DateTimeFormatter formatter) { - - SinkEventAttributeSet alignCenter = new SinkEventAttributeSet(); - alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center"); - - mainSink.division(alignCenter); - mainSink.section1(); - mainSink.sectionTitle1(); - mainSink.text("Class Cycles"); - mainSink.sectionTitle1_(); - mainSink.section1_(); - mainSink.division_(); - - mainSink.division(alignCenter); - mainSink.section2(); - mainSink.sectionTitle2(); - mainSink.text("Class Cycles by the numbers: (Refactor starting with Priority 1)"); - mainSink.sectionTitle2_(); - mainSink.section2_(); - mainSink.division_(); - - mainSink.paragraph(alignCenter); - mainSink.text("Note: often only one minimum cut relationship needs to be removed"); - mainSink.paragraph_(); - - mainSink.table(); - mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true); - - // Content - // header row - - String[] cycleTableHeadings; - if (showDetails) { - cycleTableHeadings = new String[] { - "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Minimum Cuts" - }; - } else { - cycleTableHeadings = - new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; - } - - mainSink.tableRow(); - for (String heading : cycleTableHeadings) { - drawTableHeaderCell(heading, mainSink); - } - mainSink.tableRow_(); - - for (RankedCycle rankedCycle : rankedCycles) { - mainSink.tableRow(); - - StringBuilder edgesToCut = new StringBuilder(); - for (DefaultWeightedEdge minCutEdge : rankedCycle.getMinCutEdges()) { - edgesToCut.append(minCutEdge + ":" + (int) classGraph.getEdgeWeight(minCutEdge)); - } - - String[] rankedCycleData; - if (showDetails) { - // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min Cuts" - rankedCycleData = new String[] { - rankedCycle.getCycleName(), - rankedCycle.getPriority().toString(), - rankedCycle.getChangePronenessRank().toString(), - String.valueOf(rankedCycle.getCycleNodes().size()), - String.valueOf(rankedCycle.getEdgeSet().size()), - edgesToCut.toString() - }; - } else { - // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts" - rankedCycleData = new String[] { - rankedCycle.getCycleName(), - rankedCycle.getPriority().toString(), - String.valueOf(rankedCycle.getCycleNodes().size()), - String.valueOf(rankedCycle.getEdgeSet().size()), - edgesToCut.toString() - }; - } - - for (String rowData : rankedCycleData) { - drawCycleTableCell(rowData, mainSink); - } - - mainSink.tableRow_(); - } - mainSink.tableRows_(); - - mainSink.table_(); - - for (RankedCycle rankedCycle : rankedCycles) { - renderCycle(outputDirectory, mainSink, rankedCycle, formatter); - } - } - - private void renderCycle(String outputDirectory, Sink mainSink, RankedCycle cycle, DateTimeFormatter formatter) { - - mainSink.lineBreak(); - mainSink.lineBreak(); - mainSink.lineBreak(); - mainSink.lineBreak(); - mainSink.lineBreak(); - - SinkEventAttributeSet alignCenter = new SinkEventAttributeSet(); - alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center"); - - mainSink.division(alignCenter); - mainSink.section2(); - mainSink.sectionTitle2(); - mainSink.text("Class Cycle : " + cycle.getCycleName()); - mainSink.sectionTitle2_(); - mainSink.section2_(); - mainSink.division_(); - - renderCycleImage(classGraph, cycle, mainSink); - - mainSink.division(alignCenter); - mainSink.bold(); - mainSink.text("\"*\" indicates relationship(s) to remove to decompose cycle"); - mainSink.bold_(); - mainSink.division_(); - - mainSink.table(); - mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true); - - // Content - mainSink.tableRow(); - for (String heading : classCycleTableHeadings) { - drawTableHeaderCell(heading, mainSink); - } - mainSink.tableRow_(); - - for (String vertex : cycle.getVertexSet()) { - mainSink.tableRow(); - drawTableCell(vertex, mainSink); - StringBuilder edges = new StringBuilder(); - for (org.jgrapht.graph.DefaultWeightedEdge edge : cycle.getEdgeSet()) { - if (edge.toString().startsWith("(" + vertex + " :")) { - if (cycle.getMinCutEdges().contains(edge)) { - edges.append(edge); - edges.append(":") - .append((int) classGraph.getEdgeWeight(edge)) - .append("*"); - } else { - edges.append(edge); - edges.append(":").append((int) classGraph.getEdgeWeight(edge)); - } - } - } - drawCycleTableCell(edges.toString(), mainSink); - mainSink.tableRow_(); - } - - mainSink.tableRows_(); - mainSink.table_(); - } - - private void renderLegend(Sink mainSink, String legendHeading, String xAxis) { - SinkEventAttributeSet width = new SinkEventAttributeSet(); - width.addAttribute(SinkEventAttributes.STYLE, "width:350px"); - mainSink.division(width); - mainSink.table(); - mainSink.tableRows(new int[] {Sink.JUSTIFY_LEFT}, true); - mainSink.tableRow(width); - drawTableHeaderCell(legendHeading, mainSink); - mainSink.tableRow_(); - legendRow(mainSink, "X-Axis:", xAxis); - legendRow(mainSink, "Y-Axis:", "Relative Churn"); - legendRow(mainSink, "Color:", "Priority of what to fix first"); - legendRow(mainSink, "Circle Size:", "Priority (Visual) of what to fix first"); - mainSink.tableRows_(); - mainSink.table_(); - mainSink.division_(); - } - - private static void legendRow(Sink mainSink, String boldText, String explanation) { - mainSink.tableRow(); - mainSink.tableCell(); - mainSink.bold(); - mainSink.text(boldText); - mainSink.bold_(); - mainSink.text(explanation); - mainSink.tableCell_(); - mainSink.tableRow_(); - } - - void drawTableHeaderCell(String cellText, Sink mainSink) { - mainSink.tableHeaderCell(); - mainSink.text(cellText); - mainSink.tableHeaderCell_(); - } - - void drawTableCell(Object cellText, Sink mainSink) { - SinkEventAttributeSet align = new SinkEventAttributeSet(); - if (cellText instanceof Integer || cellText instanceof Instant) { - align.addAttribute(SinkEventAttributes.ALIGN, "right"); - } else { - align.addAttribute(SinkEventAttributes.ALIGN, "left"); - } - - mainSink.tableCell(align); - - if (cellText instanceof Instant) { - mainSink.text(formatter.format((Instant) cellText)); - } else { - mainSink.text(cellText.toString()); - } - - mainSink.tableCell_(); - } - - void drawCycleTableCell(String cellText, Sink mainSink) { - SinkEventAttributeSet align = new SinkEventAttributeSet(); - align.addAttribute(SinkEventAttributes.ALIGN, "left"); - - mainSink.tableCell(align); - - for (String string : cellText.split("\\(")) { - if (string.contains("*")) { - mainSink.bold(); - mainSink.text("(" + string); - mainSink.bold_(); - } else { - if (string.contains(")")) { - mainSink.text("(" + string); - } else { - mainSink.text(string); - } - } - - mainSink.lineBreak(); - } - - mainSink.tableCell_(); - } - - /* - Star - Fork - Watch - Issue - Sponsor - */ - void renderGitHubButtons(Sink mainSink) { - SinkEventAttributeSet alignCenter = new SinkEventAttributeSet(); - alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center"); - - mainSink.division(alignCenter); - mainSink.text("Show RefactorFirst some ❤️"); - mainSink.lineBreak(); - - renderGitHubButton( - mainSink, - "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/refactorfirst/refactorfirst", - "octicon-star", - "true", - "Star refactorfirst/refactorfirst on GitHub", - "Star"); - renderGitHubButton( - mainSink, - "/refactorfirst/refactorfirst/fork", - "octicon-repo-forked", - "true", - "Fork refactorfirst/refactorfirst on GitHub", - "Fork"); - renderGitHubButton( - mainSink, - "/refactorfirst/refactorfirst/subscription", - "octicon-eye", - "true", - "Watch refactorfirst/refactorfirst on GitHub", - "Watch"); - renderGitHubButton( - mainSink, - "/refactorfirst/refactorfirst/issue", - "octicon-issue-opened", - "false", - "Issue refactorfirst/refactorfirst on GitHub", - "Issue"); - renderGitHubButton( - mainSink, - "/jimbethancourt/refactorfirst/issue", - "octicon-heart", - "false", - "Sponsor @jimbethancourt on GitHub", - "Sponsor"); - - mainSink.division_(); - } - - private static void renderGitHubButton( - Sink mainSink, - String url, - String dataIconValue, - String dataShowCount, - String ariaLabel, - String anchorText) { - SinkEventAttributeSet starButton = new SinkEventAttributeSet(); - starButton.addAttribute(SinkEventAttributes.HREF, url); - starButton.addAttribute("class", "github-button"); - starButton.addAttribute("data-icon", dataIconValue); - starButton.addAttribute("data-size", "large"); - starButton.addAttribute("data-show-count", dataShowCount); - starButton.addAttribute("aria-label", ariaLabel); - mainSink.unknown("a", new Object[] {HtmlMarkup.TAG_TYPE_START}, starButton); - mainSink.text(anchorText); - mainSink.unknown("a", new Object[] {HtmlMarkup.TAG_TYPE_END}, null); - } - - String writeGodClassGchartJs(List rankedDisharmonies, int maxPriority) { - GraphDataGenerator graphDataGenerator = new GraphDataGenerator(); - String scriptStart = graphDataGenerator.getGodClassScriptStart(); - String bubbleChartData = graphDataGenerator.generateGodClassBubbleChartData(rankedDisharmonies, maxPriority); - String scriptEnd = graphDataGenerator.getGodClassScriptEnd(); - - return scriptStart + bubbleChartData + scriptEnd; - } - - String writeGCBOGchartJs(List rankedDisharmonies, int maxPriority) { - GraphDataGenerator graphDataGenerator = new GraphDataGenerator(); - String scriptStart = graphDataGenerator.getCBOScriptStart(); - String bubbleChartData = graphDataGenerator.generateCBOBubbleChartData(rankedDisharmonies, maxPriority); - String scriptEnd = graphDataGenerator.getCBOScriptEnd(); - - return scriptStart + bubbleChartData + scriptEnd; - } - - void renderCycleImage(Graph classGraph, RankedCycle cycle, Sink mainSink) { - - SinkEventAttributeSet graphDivAttrs = new SinkEventAttributeSet(); - graphDivAttrs.addAttribute(SinkEventAttributes.ALIGN, "center"); - graphDivAttrs.addAttribute(SinkEventAttributes.ID, cycle.getCycleName()); - graphDivAttrs.addAttribute(SinkEventAttributes.STYLE, "border: thin solid black"); - - mainSink.division(graphDivAttrs); - mainSink.division_(); - - String dot = buildDot(classGraph, cycle); - - StringBuilder d3chart = new StringBuilder(); - d3chart.append("d3.select(\"#" + cycle.getCycleName() + "\")\n"); - d3chart.append(".graphviz()\n"); - d3chart.append(".width(screen.width - 300)\n"); - d3chart.append(".height(screen.height)\n"); - d3chart.append(".fit(true)\n"); - d3chart.append(".renderDot(" + dot + ");\n"); - - SinkEventAttributeSet dotChartScript = new SinkEventAttributeSet(); - dotChartScript.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); - - String script = "script"; - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, dotChartScript); - - mainSink.rawText(d3chart.toString()); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); - - SinkEventAttributeSet alignCenter = new SinkEventAttributeSet(); - alignCenter.addAttribute(SinkEventAttributes.ALIGN, "center"); - - mainSink.paragraph(alignCenter); - mainSink.text("Red arrows represent relationship(s) to remove to decompose cycle"); - mainSink.paragraph_(); - - mainSink.lineBreak(); - mainSink.lineBreak(); - } - - String buildDot(Graph classGraph, RankedCycle cycle) { - StringBuilder dot = new StringBuilder(); - - dot.append("'strict digraph G {\\n' +\n"); - - // render vertices - // e.g DownloadManager; - for (String vertex : cycle.getVertexSet()) { - dot.append("'"); - dot.append(vertex); - dot.append(";\\n' +\n"); - } - - for (DefaultWeightedEdge edge : cycle.getEdgeSet()) { - // 'DownloadManager -> Download [ label="1" color="red" ];' - - // render edge - String[] vertexes = - edge.toString().replace("(", "").replace(")", "").split(":"); - - String start = vertexes[0].trim(); - String end = vertexes[1].trim(); - - dot.append("'"); - dot.append(start); - dot.append(" -> "); - dot.append(end); - - // render edge attributes - dot.append(" [ "); - dot.append("label = \""); - dot.append((int) classGraph.getEdgeWeight(edge)); - dot.append("\""); - - if (cycle.getMinCutEdges().contains(edge)) { - dot.append(" color = \"red\""); - } - - dot.append(" ];\\n' +\n"); - } - - dot.append("'}'"); - - return dot.toString(); } } 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 86958ea9..5fc4435a 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -352,6 +352,17 @@ public String renderCycleImage(RankedCycle cycle) { return stringBuilder.toString(); } + String buildDot(Graph classGraph) { + + // render vertices + classGraph.vertexSet(); + + // render edges + classGraph.edgeSet(); + + return ""; + } + String buildDot(Graph classGraph, RankedCycle cycle) { StringBuilder dot = new StringBuilder(); 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 19966702..dd042ac1 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -12,7 +12,6 @@ import java.util.Locale; import java.util.Optional; import lombok.extern.slf4j.Slf4j; -import org.hjug.cbc.CostBenefitCalculator; import org.hjug.cbc.CycleRanker; import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; @@ -28,7 +27,7 @@ public class SimpleHtmlReport { public static final String THE_BEGINNING = - "\n" + " \n"; + "\n" + "\n"; public static final String THE_END = "
\n" + "
\n" + " \n" + "\n"; @@ -75,18 +74,27 @@ public class SimpleHtmlReport { public void execute( boolean showDetails, String projectName, String projectVersion, String outputDirectory, File baseDir) { - final String[] godClassTableHeadings = - showDetails ? godClassDetailedTableHeadings : godClassSimpleTableHeadings; - String filename = getOutputName() + ".html"; - log.info("Generating {} for {} - {}", filename, projectName, projectVersion); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(THE_BEGINNING); + stringBuilder.append(""); stringBuilder.append(printTitle(projectName, projectVersion)); stringBuilder.append(printHead()); + stringBuilder.append(""); + stringBuilder.append(generateReport(showDetails, projectName, projectVersion, baseDir)); + + stringBuilder.append(printProjectFooter()); + stringBuilder.append(THE_END); + + writeReportToDisk(outputDirectory, filename, stringBuilder); + log.info("Done! View the report at target/site/{}", filename); + } + + public StringBuilder generateReport(boolean showDetails, String projectName, String projectVersion, File baseDir) { + StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(printOpenBodyTag()); stringBuilder.append(printBreadcrumbs()); stringBuilder.append(printProjectHeader(projectName, projectVersion)); @@ -116,9 +124,8 @@ public void execute( .append(projectVersion) .append(". "); stringBuilder.append("Please initialize a Git repository and perform an initial commit."); - stringBuilder.append(THE_END); - writeReportToDisk(outputDirectory, filename, stringBuilder); - return; + + return stringBuilder; } String parentOfGitDir = gitDir.getParentFile().getPath(); @@ -129,21 +136,20 @@ public void execute( log.warn("Project Base Directory does not match Git Parent Directory"); stringBuilder.append("Project Base Directory does not match Git Parent Directory. " + "Please refer to the report at the root of the site directory."); - stringBuilder.append(THE_END); - return; + return stringBuilder; } List rankedGodClassDisharmonies = List.of(); List rankedCBODisharmonies = List.of(); List rankedCycles; - try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { - costBenefitCalculator.runPmdAnalysis(); - rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); - rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - } catch (Exception e) { - log.error("Error running analysis."); - throw new RuntimeException(e); - } + // try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { + // costBenefitCalculator.runPmdAnalysis(); + // rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); + // rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); + // } catch (Exception e) { + // log.error("Error running analysis."); + // throw new RuntimeException(e); + // } CycleRanker cycleRanker = new CycleRanker(projectBaseDir); rankedCycles = cycleRanker.runCycleAnalysis(); @@ -158,9 +164,7 @@ public void execute( .append(" has no God classes, highly coupled classes, or cycles!"); stringBuilder.append(renderGithubButtons()); log.info("Done! No Disharmonies found!"); - stringBuilder.append(THE_END); - writeReportToDisk(outputDirectory, filename, stringBuilder); - return; + return stringBuilder; } if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { @@ -178,6 +182,8 @@ public void execute( } if (!rankedGodClassDisharmonies.isEmpty()) { + final String[] godClassTableHeadings = + showDetails ? godClassDetailedTableHeadings : godClassSimpleTableHeadings; stringBuilder.append(renderGodClassInfo(showDetails, rankedGodClassDisharmonies, godClassTableHeadings)); } @@ -201,13 +207,9 @@ public void execute( } stringBuilder.append("\n"); - printProjectFooter(stringBuilder); - stringBuilder.append(THE_END); log.debug(stringBuilder.toString()); - - writeReportToDisk(outputDirectory, filename, stringBuilder); - log.info("Done! View the report at target/site/{}", filename); + return stringBuilder; } private String renderCycles(List rankedCycles) { @@ -523,13 +525,12 @@ public String printProjectHeader(String projectName, String projectVersion) { + "\n"; } - public void printProjectFooter(StringBuilder stringBuilder) { - stringBuilder - .append("
\n" + "
\n" + "
\n") - .append("Last Published: ") - .append(formatter.format(Instant.now())) - .append("
\n" + "
\n" + "
\n") - .append("
"); + public String printProjectFooter() { + return "
\n" + "
\n" + "
\n" + + "Last Published: " + + formatter.format(Instant.now()) + + "
\n" + + "
\n" + "
\n" + "
"; } String renderGithubButtons() { From 285331a5f90e94eea9fca6b12437de24407ebd8a Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 23 Feb 2025 20:29:00 -0600 Subject: [PATCH 41/78] Forgot to uncomment CostBenefitCalculator processing after debugging --- .../refactorfirst/report/SimpleHtmlReport.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 dd042ac1..7f528056 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -12,6 +12,7 @@ import java.util.Locale; import java.util.Optional; import lombok.extern.slf4j.Slf4j; +import org.hjug.cbc.CostBenefitCalculator; import org.hjug.cbc.CycleRanker; import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; @@ -142,14 +143,14 @@ public StringBuilder generateReport(boolean showDetails, String projectName, Str List rankedGodClassDisharmonies = List.of(); List rankedCBODisharmonies = List.of(); List rankedCycles; - // try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { - // costBenefitCalculator.runPmdAnalysis(); - // rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); - // rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - // } catch (Exception e) { - // log.error("Error running analysis."); - // throw new RuntimeException(e); - // } + try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { + costBenefitCalculator.runPmdAnalysis(); + rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); + rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); + } catch (Exception e) { + log.error("Error running analysis."); + throw new RuntimeException(e); + } CycleRanker cycleRanker = new CycleRanker(projectBaseDir); rankedCycles = cycleRanker.runCycleAnalysis(); From 625f8219c1c1d4979653d808f326d627d7b03500 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Mon, 24 Feb 2025 20:25:51 -0600 Subject: [PATCH 42/78] #130 #132 Added Javascript to headers in RefactorFirstMavenReport & printing backward edges - Added Javascript to headers in RefactorFirstMavenReport - Updated README.md - Added count of cycles a backward edge is in - Printing backward edges in SimpleHtmlReport --- README.md | 5 +- dsm/src/main/java/org/hjug/dsm/DSM.java | 22 ++++- .../java/org/hjug/dsm/EdgeToRemoveInfo.java | 1 + pom.xml | 2 - .../mavenreport/RefactorFirstMavenReport.java | 85 ++++++++----------- .../hjug/refactorfirst/report/HtmlReport.java | 74 ++++++++-------- .../report/SimpleHtmlReport.java | 84 ++++++++++++++++-- 7 files changed, 172 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 5ab686ff..c7efb6f2 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,11 @@ The graphs generated in the report will look similar to this one: ![image info](./RefactorFirst_Sample_Report.png) ## Please Note: Java 11 (or newer) required to run RefactorFirst -The change to require Java 11 is needed to address vulnerability CVE-2023-4759 in JGit **Java 21 codebase analysis is supported!** +The change to require Java 11 is needed to address vulnerability CVE-2023-4759 in JGit +Please use a recent JDK release of the Java version you are using. +If you use an old JDK release of your chosen Java version, you may encounter issues during analysis. + ## There are several ways to run the analysis on your codebase: diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index d8ed7f81..b13b794b 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -232,6 +232,9 @@ public List getImpactOfEdgesAboveDiagonalIfRemoved() { // get edges above diagonal for DSM graph List edgesAboveDiagonal = getEdgesAboveDiagonal(); + Map> allCycles = + new CircularReferenceChecker().getCycles(graph); + // build the cloned graph Graph clonedGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.vertexSet().forEach(clonedGraph::addVertex); @@ -242,11 +245,19 @@ public List getImpactOfEdgesAboveDiagonalIfRemoved() { List edgesToRemove = new ArrayList<>(); // capture impact of each edge on graph when removed for (DefaultWeightedEdge edge : edgesAboveDiagonal) { + int edgeInCyclesCount = 0; + for (AsSubgraph cycle : allCycles.values()) { + if (cycle.containsEdge(edge)) { + edgeInCyclesCount++; + } + } + // remove the edge clonedGraph.removeEdge(edge); // identify updated cycles and calculate updated graph information - edgesToRemove.add(getEdgeToRemoveInfo(edge, new CircularReferenceChecker().getCycles(clonedGraph))); + edgesToRemove.add(getEdgeToRemoveInfo( + edge, edgeInCyclesCount, new CircularReferenceChecker().getCycles(clonedGraph))); // add the edge back for next iteration clonedGraph.addEdge(graph.getEdgeSource(edge), graph.getEdgeTarget(edge), edge); @@ -257,9 +268,11 @@ public List getImpactOfEdgesAboveDiagonalIfRemoved() { } private EdgeToRemoveInfo getEdgeToRemoveInfo( - DefaultWeightedEdge edge, Map> cycles) { + DefaultWeightedEdge edge, + int edgeInCyclesCount, + Map> cycles) { // get the new number of cycles - int cycleCount = cycles.size(); + int newCycleCount = cycles.size(); // calculate the average cycle node count double averageCycleNodeCount = cycles.values().stream() @@ -268,6 +281,7 @@ private EdgeToRemoveInfo getEdgeToRemoveInfo( .orElse(0.0); // capture the what-if values - return new EdgeToRemoveInfo(edge, graph.getEdgeWeight(edge), cycleCount, averageCycleNodeCount); + return new EdgeToRemoveInfo( + edge, graph.getEdgeWeight(edge), edgeInCyclesCount, newCycleCount, averageCycleNodeCount); } } diff --git a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java index d954057c..54a8a9a6 100644 --- a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java +++ b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java @@ -7,6 +7,7 @@ public class EdgeToRemoveInfo { private final DefaultWeightedEdge edge; private final double edgeWeight; + private final int edgeInCycleCount; private final int newCycleCount; private final double averageCycleNodeCount; } diff --git a/pom.xml b/pom.xml index 8eaa7b90..26e1285e 100644 --- a/pom.xml +++ b/pom.xml @@ -62,8 +62,6 @@ 4.0.3 1.10.1 - - jimbethancourt_RefactorFirst ${project.artifactId} jimbethancourt-github diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index 1e4e6ba3..ef71528d 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -1,11 +1,5 @@ package org.hjug.mavenreport; -import java.io.File; -import java.nio.file.Paths; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; import java.util.*; import lombok.extern.slf4j.Slf4j; import org.apache.maven.doxia.markup.HtmlMarkup; @@ -17,17 +11,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.reporting.AbstractMavenReport; -import org.apache.maven.reporting.MavenReportException; -import org.hjug.cbc.CostBenefitCalculator; -import org.hjug.cbc.CycleRanker; -import org.hjug.cbc.RankedCycle; -import org.hjug.cbc.RankedDisharmony; -import org.hjug.gdg.GraphDataGenerator; -import org.hjug.git.GitLogReader; import org.hjug.refactorfirst.report.HtmlReport; -import org.jetbrains.annotations.NotNull; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultWeightedEdge; @Slf4j @Mojo( @@ -72,7 +56,7 @@ public void executeReport(Locale locale) { printHead(mainSink); // TODO: pass in the screen width to have d3.js render SVGs within the bounds of the screen String report = htmlReport - .generateReport(showDetails, projectName, projectVersion, project.getBasedir()) + .generateReport(showDetails, projectName, projectVersion, project.getBasedir(), 300) .toString(); mainSink.rawText(report); @@ -84,44 +68,45 @@ private void printHead(Sink mainSink) { mainSink.text("Refactor First Report for " + projectName + " " + projectVersion); mainSink.title_(); - /** - * @See https://maven.apache.org/doxia/developers/sink.html#How_to_inject_javascript_code_into_HTML - */ - SinkEventAttributeSet githubButtonJS = new SinkEventAttributeSet(); - githubButtonJS.addAttribute(SinkEventAttributes.SRC, "https://buttons.github.io/buttons.js"); - - String script = "script"; - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, githubButtonJS); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); - - SinkEventAttributeSet googleChartImport = new SinkEventAttributeSet(); - googleChartImport.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); - googleChartImport.addAttribute(SinkEventAttributes.SRC, "https://www.gstatic.com/charts/loader.js"); + // GH Buttons import + renderJsDeclaration(mainSink, "https://buttons.github.io/buttons.js"); + // google chart import + renderJsDeclaration(mainSink, "https://www.gstatic.com/charts/loader.js"); + // d3 dot graph imports + renderJsDeclaration(mainSink, "https://d3js.org/d3.v5.min.js"); + renderJsDeclaration(mainSink, "https://unpkg.com/d3-graphviz@3.0.5/build/d3-graphviz.min.js"); + renderJsDeclaration(mainSink, "https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js"); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, googleChartImport); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); + // sigma graph imports - sigma, graphology, graphlib, and graphlib-dot + renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js"); + renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js"); + // may only need graphlib-dot + renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/graphlib/2.1.8/graphlib.min.js"); + renderJsDeclaration(mainSink, "https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.4/dist/graphlib-dot.min.js"); - SinkEventAttributeSet d3js = new SinkEventAttributeSet(); - d3js.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); - d3js.addAttribute(SinkEventAttributes.SRC, "https://d3js.org/d3.v5.min.js"); + renderJsDeclaration(mainSink, HtmlReport.SUGIYAMA_SIGMA_GRAPH); + renderJsDeclaration(mainSink, HtmlReport.POPUP_FUNCTIONS); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, d3js); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); + renderStyle(mainSink); - SinkEventAttributeSet graphViz = new SinkEventAttributeSet(); - graphViz.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); - graphViz.addAttribute(SinkEventAttributes.SRC, "https://unpkg.com/d3-graphviz@3.0.5/build/d3-graphviz.min.js"); - - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, graphViz); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); - - SinkEventAttributeSet wasm = new SinkEventAttributeSet(); - wasm.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); - wasm.addAttribute(SinkEventAttributes.SRC, "https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js"); + mainSink.head_(); + } - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_START}, wasm); - mainSink.unknown(script, new Object[] {HtmlMarkup.TAG_TYPE_END}, null); + /** + * @See https://maven.apache.org/doxia/developers/sink.html#How_to_inject_javascript_code_into_HTML + */ + private void renderJsDeclaration(Sink mainSink, String scriptUrl) { + SinkEventAttributeSet githubButtonJS = new SinkEventAttributeSet(); + githubButtonJS.addAttribute(SinkEventAttributes.TYPE, "text/javascript"); + githubButtonJS.addAttribute(SinkEventAttributes.SRC, scriptUrl); + mainSink.unknown("script", new Object[] {HtmlMarkup.TAG_TYPE_START}, githubButtonJS); + mainSink.unknown("script", new Object[] {HtmlMarkup.TAG_TYPE_END}, null); + } - mainSink.head_(); + private void renderStyle(Sink mainSink) { + SinkEventAttributeSet githubButtonJS = new SinkEventAttributeSet(); + githubButtonJS.addAttribute(SinkEventAttributes.SRC, HtmlReport.POPUP_STYLE); + mainSink.unknown("script", new Object[] {HtmlMarkup.TAG_TYPE_START}, githubButtonJS); + mainSink.unknown("script", new Object[] {HtmlMarkup.TAG_TYPE_END}, null); } } 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 5fc4435a..97042a59 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -16,7 +16,7 @@ public class HtmlReport extends SimpleHtmlReport { // use Files.readString(Path.of(file)) // Created by generative AI and modified slightly - static final String SUGIYAMA_SIGMA_GRAPH = ""; // Created by generative AI and modified - static final String POPUP_STYLE = ""; // Created by generative AI and modified - static final String POPUP_FUNCTIONS = ""; - - public static final String GOD_CLASS_CHART_LEGEND = + public static final String POPUP_FUNCTIONS = + ""; + + private static final String GOD_CLASS_CHART_LEGEND = "

God Class Chart Legend:

" + " \n" + " \n" + " \n" @@ -200,7 +201,7 @@ public class HtmlReport extends SimpleHtmlReport { + "
X-Axis: Effort to refactor to a non-God class
" + "
"; - public static final String COUPLING_BETWEEN_OBJECT_CHART_LEGEND = + private static final String COUPLING_BETWEEN_OBJECT_CHART_LEGEND = "

Coupling Between Objects Chart Legend:

" + " \n" + " \n" + " \n" @@ -213,7 +214,9 @@ public class HtmlReport extends SimpleHtmlReport { @Override public String printHead() { - return "\n" + // !Remember to update RefactorFirstMavenReport if this is modified + return // GH Buttons import + "\n" // google chart import + "\n" // d3 dot graph imports @@ -228,8 +231,7 @@ public String printHead() { + "\n" + SUGIYAMA_SIGMA_GRAPH + POPUP_FUNCTIONS - + POPUP_STYLE - + "\n"; + + POPUP_STYLE; } @Override @@ -328,7 +330,7 @@ public String renderCycleImage(RankedCycle cycle) { stringBuilder.append("const " + cycleName + "_dot = " + dot + "\n"); stringBuilder.append("d3.select(\"#" + cycleName + "\")\n"); stringBuilder.append(".graphviz()\n"); - stringBuilder.append(".width(screen.width - 200)\n"); + stringBuilder.append(".width(screen.width - " + pixels + ")\n"); stringBuilder.append(".height(screen.height)\n"); stringBuilder.append(".fit(true)\n"); stringBuilder.append(".renderDot(" + cycleName + "_dot);\n"); 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 7f528056..d3ccb839 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -16,6 +16,8 @@ import org.hjug.cbc.CycleRanker; import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; +import org.hjug.dsm.DSM; +import org.hjug.dsm.EdgeToRemoveInfo; import org.hjug.git.GitLogReader; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; @@ -67,6 +69,10 @@ public class SimpleHtmlReport { public final String[] classCycleTableHeadings = {"Classes", "Relationships"}; Graph classGraph; + DSM dsm; + List edgesAboveDiagonal; + + int pixels; DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) .withLocale(Locale.getDefault()) @@ -95,6 +101,13 @@ public void execute( } public StringBuilder generateReport(boolean showDetails, String projectName, String projectVersion, File baseDir) { + return generateReport(showDetails, projectName, projectVersion, baseDir, 200); + } + + // pixels param is for SVG image pixel padding + public StringBuilder generateReport( + boolean showDetails, String projectName, String projectVersion, File baseDir, int pixels) { + this.pixels = pixels; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(printOpenBodyTag()); stringBuilder.append(printBreadcrumbs()); @@ -143,18 +156,20 @@ public StringBuilder generateReport(boolean showDetails, String projectName, Str List rankedGodClassDisharmonies = List.of(); List rankedCBODisharmonies = List.of(); List rankedCycles; - try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { - costBenefitCalculator.runPmdAnalysis(); - rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); - rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); - } catch (Exception e) { - log.error("Error running analysis."); - throw new RuntimeException(e); - } + try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { + costBenefitCalculator.runPmdAnalysis(); + rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); + rankedCBODisharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(); + } catch (Exception e) { + log.error("Error running analysis."); + throw new RuntimeException(e); + } CycleRanker cycleRanker = new CycleRanker(projectBaseDir); rankedCycles = cycleRanker.runCycleAnalysis(); classGraph = cycleRanker.getClassReferencesGraph(); + dsm = new DSM(classGraph); + edgesAboveDiagonal = dsm.getEdgesAboveDiagonal(); if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) { stringBuilder @@ -217,6 +232,12 @@ private String renderCycles(List rankedCycles) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(renderClassCycleSummary(rankedCycles)); + stringBuilder.append("
\n"); + + List edgeToRemoveInfos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(); + stringBuilder.append("
\n"); + stringBuilder.append(renderEdgeToRemoveInfos(edgeToRemoveInfos)); + for (RankedCycle rankedCycle : rankedCycles) { stringBuilder.append(renderSingleCycle(rankedCycle)); } @@ -224,6 +245,38 @@ private String renderCycles(List rankedCycles) { return stringBuilder.toString(); } + private String renderEdgeToRemoveInfos(List edges) { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.append("

Backward Edge Removal Impact

\n"); + + stringBuilder.append("

Hint: Remove first lowest edge weight

\n"); + stringBuilder.append("
X-Axis: Number of objects the class is coupled to
\n"); + + // Content + stringBuilder.append("\n\n"); + for (String heading : getEdgesToRemoveInfoTableHeadings()) { + stringBuilder.append("\n"); + } + stringBuilder.append("\n"); + + stringBuilder.append("\n"); + for (EdgeToRemoveInfo edge : edges) { + stringBuilder.append("\n"); + + for (String rowData : getEdgeToRemoveInfos(edge)) { + stringBuilder.append(drawTableCell(rowData)); + } + + stringBuilder.append("\n"); + } + + stringBuilder.append("\n"); + stringBuilder.append("
").append(heading).append("
\n"); + + return stringBuilder.toString(); + } + private String renderClassCycleSummary(List rankedCycles) { StringBuilder stringBuilder = new StringBuilder(); @@ -281,6 +334,21 @@ private String[] getCycleSummaryTableHeadings() { return new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; } + private String[] getEdgesToRemoveInfoTableHeadings() { + return new String[] {"Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "Average Cycle Node Count"}; + } + + private String[] getEdgeToRemoveInfos(EdgeToRemoveInfo edgeToRemoveInfo) { + return new String[] { + // "Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "Average Cycle Node Count" + renderEdge(edgeToRemoveInfo.getEdge()), + String.valueOf((int) edgeToRemoveInfo.getEdgeWeight()), + String.valueOf(edgeToRemoveInfo.getEdgeInCycleCount()), + String.valueOf(edgeToRemoveInfo.getNewCycleCount()), + String.valueOf(edgeToRemoveInfo.getAverageCycleNodeCount()) + }; + } + private String[] getRankedCycleSummaryData(RankedCycle rankedCycle, StringBuilder edgesToCut) { return new String[] { // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts" From 8f8364503b160c1f0d383f65c538d99266c58c16 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Tue, 25 Feb 2025 18:35:15 -0600 Subject: [PATCH 43/78] Now rendering CBO Chart --- .../java/org/hjug/refactorfirst/report/SimpleHtmlReport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d3ccb839..f4b5e46e 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -499,7 +499,7 @@ private String renderHighlyCoupledClassInfo(List rankedCBODish int maxCboPriority = rankedCBODisharmonies.get(rankedCBODisharmonies.size() - 1).getPriority(); - renderCBOChart(rankedCBODisharmonies, maxCboPriority); + stringBuilder.append(renderCBOChart(rankedCBODisharmonies, maxCboPriority)); stringBuilder.append( "

Highly Coupled classes by the numbers: (Refactor starting with Priority 1)

"); From e96ac771e3f91616e0c2f0b8c18098c8635c028d Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sat, 1 Mar 2025 19:22:40 -0600 Subject: [PATCH 44/78] Handle NPE --- .../visitor/JavaMethodDeclarationVisitor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitor.java b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitor.java index 31847331..1775473b 100644 --- a/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitor.java +++ b/codebase-graph-builder/src/main/java/org/hjug/graphbuilder/visitor/JavaMethodDeclarationVisitor.java @@ -34,7 +34,13 @@ public JavaMethodDeclarationVisitor( public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P p) { J.MethodDeclaration methodDeclaration = super.visitMethodDeclaration(method, p); - String owner = methodDeclaration.getMethodType().getDeclaringType().getFullyQualifiedName(); + JavaType.Method methodType = methodDeclaration.getMethodType(); + if (null == methodType) { + // sometimes methodType is null, not sure why... + return methodDeclaration; + } + + String owner = methodType.getDeclaringType().getFullyQualifiedName(); // if returnTypeExpression is null, a constructor declaration is being processed TypeTree returnTypeExpression = methodDeclaration.getReturnTypeExpression(); From d47ab84f87cdfe8bd466faa683fa52fb67c8b054 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Thu, 6 Mar 2025 20:07:34 -0600 Subject: [PATCH 45/78] Returning subgraphs immediately if they have already been calculated --- dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java b/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java index 561267f8..54700e0e 100644 --- a/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java +++ b/dsm/src/main/java/org/hjug/dsm/CircularReferenceChecker.java @@ -22,6 +22,10 @@ public class CircularReferenceChecker { */ public Map> getCycles(Graph graph) { + if (!uniqueSubGraphs.isEmpty()) { + return uniqueSubGraphs; + } + // use CycleDetector.findCycles()? Map> cycles = detectCycles(graph); From 9c858e4ed740c0bad07b14fcd18c0da578432158 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Thu, 6 Mar 2025 20:38:34 -0600 Subject: [PATCH 46/78] #129 Optimizing DSM - Now Using Kosaraju's Algorithm to identify strongly connected components (SCCs) - Using Sparse graphs to identify SCCs more efficiently - Calculating edge removal priority based on impact / effort --- dsm/pom.xml | 4 + dsm/src/main/java/org/hjug/dsm/DSM.java | 133 ++++++++++++------ .../java/org/hjug/dsm/EdgeToRemoveInfo.java | 1 + dsm/src/test/java/org/hjug/dsm/DSMTest.java | 18 +-- 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/dsm/pom.xml b/dsm/pom.xml index a16c0e24..6061ec02 100644 --- a/dsm/pom.xml +++ b/dsm/pom.xml @@ -24,6 +24,10 @@ org.jgrapht jgrapht-core + + org.jgrapht + jgrapht-opt + org.slf4j slf4j-api diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index b13b794b..a2e5c745 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -1,13 +1,16 @@ package org.hjug.dsm; import java.util.*; +import java.util.stream.Collectors; import lombok.Getter; import org.jgrapht.Graph; import org.jgrapht.Graphs; -import org.jgrapht.alg.cycle.TarjanSimpleCycles; +import org.jgrapht.alg.connectivity.KosarajuStrongConnectivityInspector; +import org.jgrapht.alg.util.Triple; import org.jgrapht.graph.AsSubgraph; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; +import org.jgrapht.opt.graph.sparse.SparseIntDirectedWeightedGraph; /* Generated with Generative AI using a prompt similar to the following and iterated on: @@ -35,20 +38,22 @@ public class DSM { private List sortedActivities; boolean activitiesSorted = false; - @Getter - int stronglyConnectedComponentCount = 0; + Map vertexToInt = new HashMap<>(); + Map intToVertex = new HashMap<>(); + List> sparseEdges = new ArrayList<>(); + int vertexCount = 0; @Getter - double averageStronglyConnectedComponentSize = 0.0; + Map> cycles; public DSM() { - graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); - sortedActivities = new ArrayList<>(); + this(new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class)); } public DSM(Graph graph) { this.graph = graph; sortedActivities = new ArrayList<>(); + cycles = new CircularReferenceChecker().getCycles(graph); } public void addActivity(String activity) { @@ -63,16 +68,9 @@ public void addDependency(String from, String to, int weight) { } private void orderVertices() { - List> sccs = findStronglyConnectedComponents(graph); - - // capture the number of cycles - stronglyConnectedComponentCount = sccs.size(); - - // capture the average size of the cycles - averageStronglyConnectedComponentSize = - sccs.stream().mapToInt(Set::size).average().orElse(0.0); - - sortedActivities = topologicalSort(sccs, graph); + SparseIntDirectedWeightedGraph sparseGraph = getSparseIntDirectedWeightedGraph(); + List> sccs = findStronglyConnectedComponents(sparseGraph); + sortedActivities = convertIntToStringVertices(topologicalSort(sccs, sparseGraph)); // reversing corrects rendering of the DSM // with sources as rows and targets as columns // was needed after AI solution was generated and iterated @@ -80,22 +78,47 @@ private void orderVertices() { activitiesSorted = true; } - private List> findStronglyConnectedComponents(Graph graph) { - TarjanSimpleCycles tarjan = new TarjanSimpleCycles<>(graph); - List> cycles = tarjan.findSimpleCycles(); - List> sccs = new ArrayList<>(); - for (List cycle : cycles) { - sccs.add(new HashSet<>(cycle)); + private SparseIntDirectedWeightedGraph getSparseIntDirectedWeightedGraph() { + for (String vertex : graph.vertexSet()) { + vertexToInt.put(vertex, vertexCount); + intToVertex.put(vertexCount, vertex); + vertexCount++; + } + + // Create the list of sparseEdges for the SparseIntDirectedWeightedGraph + for (DefaultWeightedEdge edge : graph.edgeSet()) { + int source = vertexToInt.get(graph.getEdgeSource(edge)); + int target = vertexToInt.get(graph.getEdgeTarget(edge)); + double weight = graph.getEdgeWeight(edge); + sparseEdges.add(Triple.of(source, target, weight)); } - return sccs; + + // Create the SparseIntDirectedWeightedGraph + return new SparseIntDirectedWeightedGraph(vertexCount, sparseEdges); + } + + List convertIntToStringVertices(List intVertices) { + return intVertices.stream().map(intToVertex::get).collect(Collectors.toList()); + } + + /** + * Kosaraju SCC detector avoids stack overflow. + * It is used by JGraphT's CycleDetector, and makes sense to use it here as well for consistency + * @param graph + * @return + */ + private List> findStronglyConnectedComponents(Graph graph) { + KosarajuStrongConnectivityInspector kosaraju = + new KosarajuStrongConnectivityInspector<>(graph); + return kosaraju.stronglyConnectedSets(); } - private List topologicalSort(List> sccs, Graph graph) { - List sortedActivities = new ArrayList<>(); - Set visited = new HashSet<>(); + private List topologicalSort(List> sccs, Graph graph) { + List sortedActivities = new ArrayList<>(); + Set visited = new HashSet<>(); - for (Set scc : sccs) { - for (String activity : scc) { + for (Set scc : sccs) { + for (Integer activity : scc) { if (!visited.contains(activity)) { topologicalSortUtil(activity, visited, sortedActivities, graph); } @@ -107,13 +130,10 @@ private List topologicalSort(List> sccs, Graph visited, - List sortedActivities, - Graph graph) { + Integer activity, Set visited, List sortedActivities, Graph graph) { visited.add(activity); - for (String neighbor : Graphs.successorListOf(graph, activity)) { + for (Integer neighbor : Graphs.successorListOf(graph, activity)) { if (!visited.contains(neighbor)) { topologicalSortUtil(neighbor, visited, sortedActivities, graph); } @@ -225,15 +245,25 @@ void printDSM(Graph graph, List sortedActiv /** * Captures the impact of the removal of each edge above the diagonal. + * Limited to 50 by default * * @return List Impact of each edge if removed. */ - public List getImpactOfEdgesAboveDiagonalIfRemoved() { + public List getImpactOfEdgesAboveDiagonalIfRemoved(int limit) { // get edges above diagonal for DSM graph - List edgesAboveDiagonal = getEdgesAboveDiagonal(); + List edgesAboveDiagonal; + List allEdgesAboveDiagonal = getEdgesAboveDiagonal(); + + if (limit == 0 || allEdgesAboveDiagonal.size() <= 50) { + edgesAboveDiagonal = allEdgesAboveDiagonal; + } else { + // get first 50 values of min weight + List minimumWeightEdgesAboveDiagonal = getMinimumWeightEdgesAboveDiagonal(); + int max = Math.min(minimumWeightEdgesAboveDiagonal.size(), 50); + edgesAboveDiagonal = minimumWeightEdgesAboveDiagonal.subList(0, max); + } - Map> allCycles = - new CircularReferenceChecker().getCycles(graph); + double avgCycleNodeCount = getAverageCycleNodeCount(); // build the cloned graph Graph clonedGraph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); @@ -246,7 +276,7 @@ public List getImpactOfEdgesAboveDiagonalIfRemoved() { // capture impact of each edge on graph when removed for (DefaultWeightedEdge edge : edgesAboveDiagonal) { int edgeInCyclesCount = 0; - for (AsSubgraph cycle : allCycles.values()) { + for (AsSubgraph cycle : cycles.values()) { if (cycle.containsEdge(edge)) { edgeInCyclesCount++; } @@ -257,31 +287,48 @@ public List getImpactOfEdgesAboveDiagonalIfRemoved() { // identify updated cycles and calculate updated graph information edgesToRemove.add(getEdgeToRemoveInfo( - edge, edgeInCyclesCount, new CircularReferenceChecker().getCycles(clonedGraph))); + edge, edgeInCyclesCount, avgCycleNodeCount, new CircularReferenceChecker().getCycles(clonedGraph))); // add the edge back for next iteration clonedGraph.addEdge(graph.getEdgeSource(edge), graph.getEdgeTarget(edge), edge); clonedGraph.setEdgeWeight(edge, graph.getEdgeWeight(edge)); } + edgesToRemove.sort(Comparator.comparing(EdgeToRemoveInfo::getPayoff)); + Collections.reverse(edgesToRemove); return edgesToRemove; } private EdgeToRemoveInfo getEdgeToRemoveInfo( DefaultWeightedEdge edge, int edgeInCyclesCount, + double currentAvgCycleNodeCount, Map> cycles) { // get the new number of cycles int newCycleCount = cycles.size(); // calculate the average cycle node count - double averageCycleNodeCount = cycles.values().stream() + double newAverageCycleNodeCount = getAverageCycleNodeCount(cycles); + + // capture the what-if values + double edgeWeight = graph.getEdgeWeight(edge); + + double impact = (currentAvgCycleNodeCount - newAverageCycleNodeCount) / edgeWeight; + return new EdgeToRemoveInfo( + edge, edgeWeight, edgeInCyclesCount, newCycleCount, newAverageCycleNodeCount, impact); + } + + public static double getAverageCycleNodeCount(Map> cycles) { + return cycles.values().stream() .mapToInt(cycle -> cycle.vertexSet().size()) .average() .orElse(0.0); + } - // capture the what-if values - return new EdgeToRemoveInfo( - edge, graph.getEdgeWeight(edge), edgeInCyclesCount, newCycleCount, averageCycleNodeCount); + public double getAverageCycleNodeCount() { + return cycles.values().stream() + .mapToInt(cycle -> cycle.vertexSet().size()) + .average() + .orElse(0.0); } } diff --git a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java index 54a8a9a6..c16e0967 100644 --- a/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java +++ b/dsm/src/main/java/org/hjug/dsm/EdgeToRemoveInfo.java @@ -10,4 +10,5 @@ public class EdgeToRemoveInfo { private final int edgeInCycleCount; private final int newCycleCount; private final double averageCycleNodeCount; + private final double payoff; // impact / effort } diff --git a/dsm/src/test/java/org/hjug/dsm/DSMTest.java b/dsm/src/test/java/org/hjug/dsm/DSMTest.java index 043bebfe..4e871eb1 100644 --- a/dsm/src/test/java/org/hjug/dsm/DSMTest.java +++ b/dsm/src/test/java/org/hjug/dsm/DSMTest.java @@ -4,6 +4,7 @@ import java.util.List; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -96,7 +97,7 @@ void edgesAboveDiagonal() { @Test void getImpactOfEdgesAboveDiagonalIfRemoved() { - dsm = new DSM(); + dsm = new DSM(new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class)); dsm.addActivity("A"); dsm.addActivity("B"); dsm.addActivity("C"); @@ -120,15 +121,14 @@ void getImpactOfEdgesAboveDiagonalIfRemoved() { dsm.addDependency("G", "H", 9); dsm.addDependency("H", "E", 9); // create cycle - List infos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(); - assertEquals(4, infos.size()); + dsm.addDependency("A", "E", 9); + dsm.addDependency("E", "A", 3); // create cycle between cycles - assertEquals("(D : A)", infos.get(0).getEdge().toString()); - assertEquals(2, infos.get(0).getNewCycleCount()); - assertEquals(3.5, infos.get(0).getAverageCycleNodeCount()); + List infos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(50); + assertEquals(5, infos.size()); - assertEquals("(H : E)", infos.get(3).getEdge().toString()); - assertEquals(1, infos.get(3).getNewCycleCount()); - assertEquals(4.0, infos.get(3).getAverageCycleNodeCount()); + assertEquals("(H : E)", infos.get(0).getEdge().toString()); + assertEquals(2, infos.get(0).getNewCycleCount()); + assertEquals(4.5, infos.get(0).getAverageCycleNodeCount()); } } From 4fe152670a7b216e08c2a55a011f1b28ce964a49 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Thu, 6 Mar 2025 20:39:17 -0600 Subject: [PATCH 47/78] No longer returning min cut data since it has a runtime of O(V^4) --- .../src/main/java/org/hjug/cbc/CycleRanker.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java index 2aedc76e..3470930d 100644 --- a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java +++ b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java @@ -12,9 +12,7 @@ import org.hjug.dsm.CircularReferenceChecker; import org.hjug.graphbuilder.JavaGraphBuilder; import org.jgrapht.Graph; -import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree; import org.jgrapht.graph.AsSubgraph; -import org.jgrapht.graph.AsUndirectedGraph; import org.jgrapht.graph.DefaultWeightedEdge; @RequiredArgsConstructor @@ -47,18 +45,19 @@ private void identifyRankedCycles(List rankedCycles) throws IOExcep circularReferenceChecker.getCycles(classReferencesGraph); Map classNamesAndPaths = getClassNamesAndPaths(); cycles.forEach((vertex, subGraph) -> { - Set minCutEdges; + // TODO: Calculate min cuts for smaller graphs - has a runtime of O(V^4) for a graph + /*Set minCutEdges; GusfieldGomoryHuCutTree gusfieldGomoryHuCutTree = new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph)); double minCut = gusfieldGomoryHuCutTree.calculateMinCut(); - minCutEdges = gusfieldGomoryHuCutTree.getCutEdges(); + minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();*/ List cycleNodes = subGraph.vertexSet().stream() .map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle))) // .peek(cycleNode -> log.info(cycleNode.toString())) .collect(Collectors.toList()); - rankedCycles.add(createRankedCycle(vertex, subGraph, cycleNodes, minCut, minCutEdges)); + rankedCycles.add(createRankedCycle(vertex, subGraph, cycleNodes, 0.0, new HashSet<>())); }); } From bb2cecd9d1c11aa6d493ab68ab2347171eec07f1 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Thu, 6 Mar 2025 20:40:01 -0600 Subject: [PATCH 48/78] #129 Added jgrapht-opt as a dependency --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 26e1285e..e34961c3 100644 --- a/pom.xml +++ b/pom.xml @@ -154,6 +154,11 @@ jgrapht-core 1.5.2 + + org.jgrapht + jgrapht-opt + 1.5.2 + net.sourceforge.pmd From 8e6bacbd3860b6dd748ac39dde6d6c18db2f51ca Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Thu, 6 Mar 2025 20:45:46 -0600 Subject: [PATCH 49/78] Printing out class map & edges to remove at top -Printing out class map & edges to remove at top - Removed classes with no incoming / outgoing edges from class map. --- .../org/hjug/refactorfirst/ReportCommand.java | 4 +- .../mavenreport/RefactorFirstHtmlReport.java | 3 +- .../mavenreport/RefactorFirstMavenReport.java | 3 +- .../RefactorFirstSimpleHtmlReport.java | 3 +- .../hjug/refactorfirst/report/HtmlReport.java | 121 +++++++++++++++--- .../report/SimpleHtmlReport.java | 108 +++++++++++----- .../refactorfirst/report/HtmlReportTest.java | 8 +- 7 files changed, 191 insertions(+), 59 deletions(-) diff --git a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java index ec448110..047a97cc 100644 --- a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java +++ b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java @@ -61,11 +61,11 @@ public Integer call() { switch (reportType) { case SIMPLE_HTML: SimpleHtmlReport simpleHtmlReport = new SimpleHtmlReport(); - simpleHtmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir); + simpleHtmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir, 50); return 0; case HTML: HtmlReport htmlReport = new HtmlReport(); - htmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir); + htmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir, 50); return 0; case JSON: JsonReportExecutor jsonReportExecutor = new JsonReportExecutor(); diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java index b7b378f9..907f72d9 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java @@ -48,6 +48,7 @@ public void execute() { .getReporting() .getOutputDirectory() .replace("${project.basedir}" + File.separator, ""), - project.getBasedir()); + project.getBasedir(), + 50); } } diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index ef71528d..6a9c050e 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -54,9 +54,8 @@ public void executeReport(Locale locale) { Sink mainSink = getSink(); printHead(mainSink); - // TODO: pass in the screen width to have d3.js render SVGs within the bounds of the screen String report = htmlReport - .generateReport(showDetails, projectName, projectVersion, project.getBasedir(), 300) + .generateReport(showDetails, projectName, projectVersion, project.getBasedir(), 300, 50) .toString(); mainSink.rawText(report); diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java index 40cefb2d..83425b1f 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java @@ -48,6 +48,7 @@ public void execute() { .getReporting() .getOutputDirectory() .replace("${project.basedir}" + File.separator, ""), - project.getBasedir()); + project.getBasedir(), + 50); } } 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 97042a59..b7dfe738 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -1,7 +1,9 @@ package org.hjug.refactorfirst.report; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; import lombok.extern.slf4j.Slf4j; import org.hjug.cbc.RankedCycle; import org.hjug.cbc.RankedDisharmony; @@ -317,8 +319,96 @@ String renderCBOChart(List rankedCBODisharmonies, int maxCboPr } @Override - public String renderCycleImage(RankedCycle cycle) { - String dot = buildDot(classGraph, cycle); + public String renderClassGraphDotImage() { + String dot = buildClassGraphDot(classGraph); + + String classGraphName = "classGraph"; + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("

Class Map

"); + if (classGraph.vertexSet().size() + classGraph.edgeSet().size() < d3Threshold) { + stringBuilder.append( + "
\n"); + stringBuilder.append("\n"); + } else { + // revisit and add D3 popup button as well + stringBuilder.append("\n"); + stringBuilder.append(generatePopup(classGraphName)); + } + + stringBuilder.append("
\n"); + stringBuilder.append( + "

Red lines represent back edges to remove. Remove one to start decomposing the cycle.

\n"); + stringBuilder.append("

Zoom in / out with your mouse wheel and click/move to drag the image.

\n"); + stringBuilder.append("
\n"); + stringBuilder.append("
\n"); + stringBuilder.append("
\n"); + + return stringBuilder.toString(); + } + + String buildClassGraphDot(Graph classGraph) { + StringBuilder dot = new StringBuilder(); + dot.append("`strict digraph G {\n"); + + Set vertexesToRender = new HashSet<>(); + for (DefaultWeightedEdge edge : classGraph.edgeSet()) { + // DownloadManager -> Download [ label="1" color="red" ]; + + // render edge + String[] vertexes = extractVertexes(edge); + String start = getClassName(vertexes[0].trim()).replace("$", "_"); + String end = getClassName(vertexes[1].trim()).replace("$", "_"); + + vertexesToRender.add(vertexes[0].trim()); + vertexesToRender.add(vertexes[1].trim()); + + dot.append(start); + dot.append(" -> "); + dot.append(end); + + // render edge attributes + int edgeWeight = (int) classGraph.getEdgeWeight(edge); + dot.append(" [ "); + dot.append("label = \""); + dot.append(edgeWeight); + dot.append("\" "); + dot.append("weight = \""); + dot.append(edgeWeight); + dot.append("\""); + + if (edgesAboveDiagonal.contains(edge)) { + dot.append(" color = \"red\""); + } + + dot.append(" ];\n"); + } + + // render vertices + // e.g DownloadManager; + // for (String vertex : classGraph.vertexSet()) { + for (String vertex : vertexesToRender) { + dot.append(getClassName(vertex).replace("$", "_")); + dot.append(";\n"); + } + + dot.append("}`;"); + return dot.toString(); + } + + @Override + public String renderCycleDotImage(RankedCycle cycle) { + String dot = buildCycleDot(classGraph, cycle); String cycleName = getClassName(cycle.getCycleName()).replace("$", "_"); @@ -345,7 +435,7 @@ public String renderCycleImage(RankedCycle cycle) { stringBuilder.append("
\n"); stringBuilder.append( - "

Red lines represent relationship(s) to remove. Remove one to start decomposing the cycle.

\n"); + "

Red lines represent back edges to remove. Remove one to start decomposing the cycle.

\n"); stringBuilder.append("

Zoom in / out with your mouse wheel and click/move to drag the image.

\n"); stringBuilder.append("
\n"); stringBuilder.append("
\n"); @@ -354,18 +444,7 @@ public String renderCycleImage(RankedCycle cycle) { return stringBuilder.toString(); } - String buildDot(Graph classGraph) { - - // render vertices - classGraph.vertexSet(); - - // render edges - classGraph.edgeSet(); - - return ""; - } - - String buildDot(Graph classGraph, RankedCycle cycle) { + String buildCycleDot(Graph classGraph, RankedCycle cycle) { StringBuilder dot = new StringBuilder(); dot.append("`strict digraph G {\n"); @@ -373,7 +452,7 @@ String buildDot(Graph classGraph, RankedCycle cycle // render vertices // e.g DownloadManager; for (String vertex : cycle.getVertexSet()) { - dot.append(getClassName(vertex)); + dot.append(getClassName(vertex).replace("$", "_")); dot.append(";\n"); } @@ -382,8 +461,8 @@ String buildDot(Graph classGraph, RankedCycle cycle // render edge String[] vertexes = extractVertexes(edge); - String start = getClassName(vertexes[0].trim()); - String end = getClassName(vertexes[1].trim()); + String start = getClassName(vertexes[0].trim()).replace("$", "_"); + String end = getClassName(vertexes[1].trim()).replace("$", "_"); dot.append(start); dot.append(" -> "); @@ -399,10 +478,14 @@ String buildDot(Graph classGraph, RankedCycle cycle dot.append(edgeWeight); dot.append("\""); - if (cycle.getMinCutEdges().contains(edge)) { + if (edgesAboveDiagonal.contains(edge)) { dot.append(" color = \"red\""); } + if (cycle.getMinCutEdges().contains(edge) && !edgesAboveDiagonal.contains(edge)) { + dot.append(" color = \"blue\""); + } + dot.append(" ];\n"); } 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 f4b5e46e..0f84d3d6 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -70,7 +70,7 @@ public class SimpleHtmlReport { Graph classGraph; DSM dsm; - List edgesAboveDiagonal; + List edgesAboveDiagonal = List.of(); // initialize for unit tests int pixels; @@ -79,7 +79,12 @@ public class SimpleHtmlReport { .withZone(ZoneId.systemDefault()); public void execute( - boolean showDetails, String projectName, String projectVersion, String outputDirectory, File baseDir) { + boolean showDetails, + String projectName, + String projectVersion, + String outputDirectory, + File baseDir, + int edgeAnalysisCount) { String filename = getOutputName() + ".html"; log.info("Generating {} for {} - {}", filename, projectName, projectVersion); @@ -91,7 +96,7 @@ public void execute( stringBuilder.append(printTitle(projectName, projectVersion)); stringBuilder.append(printHead()); stringBuilder.append(""); - stringBuilder.append(generateReport(showDetails, projectName, projectVersion, baseDir)); + stringBuilder.append(generateReport(showDetails, projectName, projectVersion, baseDir, edgeAnalysisCount)); stringBuilder.append(printProjectFooter()); stringBuilder.append(THE_END); @@ -100,13 +105,19 @@ public void execute( log.info("Done! View the report at target/site/{}", filename); } - public StringBuilder generateReport(boolean showDetails, String projectName, String projectVersion, File baseDir) { - return generateReport(showDetails, projectName, projectVersion, baseDir, 200); + public StringBuilder generateReport( + boolean showDetails, String projectName, String projectVersion, File baseDir, int edgeAnalysisCount) { + return generateReport(showDetails, projectName, projectVersion, baseDir, 200, edgeAnalysisCount); } // pixels param is for SVG image pixel padding public StringBuilder generateReport( - boolean showDetails, String projectName, String projectVersion, File baseDir, int pixels) { + boolean showDetails, + String projectName, + String projectVersion, + File baseDir, + int pixels, + int edgeAnalysisCount) { this.pixels = pixels; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(printOpenBodyTag()); @@ -155,7 +166,6 @@ public StringBuilder generateReport( List rankedGodClassDisharmonies = List.of(); List rankedCBODisharmonies = List.of(); - List rankedCycles; try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { costBenefitCalculator.runPmdAnalysis(); rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); @@ -166,11 +176,13 @@ public StringBuilder generateReport( } CycleRanker cycleRanker = new CycleRanker(projectBaseDir); - rankedCycles = cycleRanker.runCycleAnalysis(); + List rankedCycles = cycleRanker.runCycleAnalysis(); classGraph = cycleRanker.getClassReferencesGraph(); dsm = new DSM(classGraph); edgesAboveDiagonal = dsm.getEdgesAboveDiagonal(); + stringBuilder.append(renderClassGraphDotImage()); + if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) { stringBuilder .append("Congratulations! ") @@ -183,6 +195,11 @@ public StringBuilder generateReport( return stringBuilder; } + // Display impact of each edge if removed + stringBuilder.append("
\n"); + List edgeToRemoveInfos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(edgeAnalysisCount); + stringBuilder.append(renderEdgeToRemoveInfos(edgeToRemoveInfos)); + if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { stringBuilder.append("God Classes\n"); stringBuilder.append("
\n"); @@ -234,10 +251,6 @@ private String renderCycles(List rankedCycles) { stringBuilder.append("
\n"); - List edgeToRemoveInfos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(); - stringBuilder.append("
\n"); - stringBuilder.append(renderEdgeToRemoveInfos(edgeToRemoveInfos)); - for (RankedCycle rankedCycle : rankedCycles) { stringBuilder.append(renderSingleCycle(rankedCycle)); } @@ -249,8 +262,26 @@ private String renderEdgeToRemoveInfos(List edges) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("

Backward Edge Removal Impact

\n"); + stringBuilder.append("
\n"); + + stringBuilder + .append("Current Cycle Count: ") + .append(dsm.getCycles().size()) + .append("
\n"); + stringBuilder + .append("Current Average Cycle Node Count: ") + .append(dsm.getAverageCycleNodeCount()) + .append("
\n"); + stringBuilder + .append("Current Total Back Edge Count: ") + .append(dsm.getEdgesAboveDiagonal().size()) + .append("
\n"); + stringBuilder + .append("Current Total Min Weight Back Edge Count: ") + .append(dsm.getMinimumWeightEdgesAboveDiagonal().size()) + .append("
\n"); + stringBuilder.append("
\n"); - stringBuilder.append("

Hint: Remove first lowest edge weight

\n"); stringBuilder.append("\n"); // Content @@ -282,10 +313,8 @@ private String renderClassCycleSummary(List rankedCycles) { stringBuilder.append("\n"); - stringBuilder.append( - "

Class Cycles by the numbers: (Refactor starting with Priority 1)

\n"); - stringBuilder.append( - "

Note: often only one minimum cut relationship needs to be removed

"); + stringBuilder.append("

Class Cycles by the numbers:

\n"); + stringBuilder.append("

Bold edges are backward edges causing cycles

"); stringBuilder.append("
\n"); // Content @@ -296,16 +325,23 @@ private String renderClassCycleSummary(List rankedCycles) { stringBuilder.append("\n"); stringBuilder.append("\n"); - for (RankedCycle rankedCycle : rankedCycles) { + for (RankedCycle cycle : rankedCycles) { stringBuilder.append("\n"); - StringBuilder edgesToCut = new StringBuilder(); - for (DefaultWeightedEdge minCutEdge : rankedCycle.getMinCutEdges()) { - edgesToCut.append(renderEdge(minCutEdge)); - edgesToCut.append("
\n"); + StringBuilder edges = new StringBuilder(); + for (DefaultWeightedEdge edge : cycle.getMinCutEdges()) { + + if (edgesAboveDiagonal.contains(edge)) { + stringBuilder.append(""); + edges.append(renderEdge(edge)); + stringBuilder.append(""); + } else { + edges.append(renderEdge(edge)); + } + edges.append("
\n"); } - for (String rowData : getRankedCycleSummaryData(rankedCycle, edgesToCut)) { + for (String rowData : getRankedCycleSummaryData(cycle, edges)) { stringBuilder.append(drawTableCell(rowData)); } @@ -335,17 +371,20 @@ private String[] getCycleSummaryTableHeadings() { } private String[] getEdgesToRemoveInfoTableHeadings() { - return new String[] {"Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "Average Cycle Node Count"}; + return new String[] { + "Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "New Avg Cycle Node Count", "Impact/Edge Weight" + }; } private String[] getEdgeToRemoveInfos(EdgeToRemoveInfo edgeToRemoveInfo) { return new String[] { - // "Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "Average Cycle Node Count" + // "Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "New Avg Cycle Node Count" renderEdge(edgeToRemoveInfo.getEdge()), String.valueOf((int) edgeToRemoveInfo.getEdgeWeight()), String.valueOf(edgeToRemoveInfo.getEdgeInCycleCount()), String.valueOf(edgeToRemoveInfo.getNewCycleCount()), - String.valueOf(edgeToRemoveInfo.getAverageCycleNodeCount()) + String.valueOf(edgeToRemoveInfo.getAverageCycleNodeCount()), + String.valueOf(edgeToRemoveInfo.getPayoff()) }; } @@ -370,11 +409,12 @@ private String renderSingleCycle(RankedCycle cycle) { stringBuilder.append("
\n"); stringBuilder.append("

Class Cycle : " + getClassName(cycle.getCycleName()) + "

\n"); - stringBuilder.append(renderCycleImage(cycle)); + stringBuilder.append(renderCycleDotImage(cycle)); stringBuilder.append("
"); stringBuilder.append(""); - stringBuilder.append("\"*\" indicates relationship(s) to remove to decompose cycle"); + stringBuilder.append("Bold text indicates back edge to remove to decompose cycle"); + stringBuilder.append("\"*\" indicates that edge is also a minimum cut edge in the cycle"); stringBuilder.append(""); stringBuilder.append("
\n"); @@ -395,9 +435,13 @@ private String renderSingleCycle(RankedCycle cycle) { StringBuilder edges = new StringBuilder(); for (DefaultWeightedEdge edge : cycle.getEdgeSet()) { if (edge.toString().startsWith("(" + vertex + " :")) { - if (cycle.getMinCutEdges().contains(edge)) { + + if (edgesAboveDiagonal.contains(edge)) { edges.append(""); edges.append(renderEdge(edge)); + if (cycle.getMinCutEdges().contains(edge)) { + edges.append("*"); + } edges.append(""); } else { edges.append(renderEdge(edge)); @@ -417,7 +461,11 @@ private String renderSingleCycle(RankedCycle cycle) { return stringBuilder.toString(); } - public String renderCycleImage(RankedCycle cycle) { + public String renderClassGraphDotImage() { + return ""; // empty on purpose + } + + public String renderCycleDotImage(RankedCycle cycle) { return ""; // empty on purpose } diff --git a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java index 27393bd9..734c6507 100644 --- a/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java +++ b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java @@ -38,7 +38,7 @@ void getDescription() { } @Test - void buildDot() { + void buildCycleDot() { Graph classGraph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); classGraph.addVertex("A"); classGraph.addVertex("B"); @@ -59,7 +59,7 @@ void buildDot() { cycleName, 0, classGraph.vertexSet(), classGraph.edgeSet(), minCutCount, minCutEdges, cycleNodes); HtmlReport htmlReport = new HtmlReport(); - String dot = htmlReport.buildDot(classGraph, rankedCycle); + String dot = htmlReport.buildCycleDot(classGraph, rankedCycle); StringBuilder expectedDot = new StringBuilder(); expectedDot.append("`strict digraph G {\n" @@ -67,8 +67,8 @@ void buildDot() { + "B;\n" + "C;\n" + "A -> B [ label = \"2\" weight = \"2\" ];\n" - + "B -> C [ label = \"1\" weight = \"1\" color = \"red\" ];\n" - + "C -> A [ label = \"1\" weight = \"1\" color = \"red\" ];\n" + + "B -> C [ label = \"1\" weight = \"1\" color = \"blue\" ];\n" + + "C -> A [ label = \"1\" weight = \"1\" color = \"blue\" ];\n" + "}`;"); assertEquals(expectedDot.toString(), dot); From e6cb30f530135059ca36ccf7334bfc8538a54c61 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Fri, 7 Mar 2025 18:55:38 -0600 Subject: [PATCH 50/78] Parameterized cycle analysis limit - Parameterized cycle analysis limit - Tidied up SimpleHtmlReport and HtmlReport output --- dsm/src/main/java/org/hjug/dsm/DSM.java | 6 +-- .../mavenreport/RefactorFirstHtmlReport.java | 5 ++- .../mavenreport/RefactorFirstMavenReport.java | 6 ++- .../RefactorFirstSimpleHtmlReport.java | 5 ++- .../hjug/refactorfirst/report/HtmlReport.java | 3 +- .../report/SimpleHtmlReport.java | 38 +++++++++++++------ 6 files changed, 45 insertions(+), 18 deletions(-) diff --git a/dsm/src/main/java/org/hjug/dsm/DSM.java b/dsm/src/main/java/org/hjug/dsm/DSM.java index a2e5c745..93190380 100644 --- a/dsm/src/main/java/org/hjug/dsm/DSM.java +++ b/dsm/src/main/java/org/hjug/dsm/DSM.java @@ -245,8 +245,8 @@ void printDSM(Graph graph, List sortedActiv /** * Captures the impact of the removal of each edge above the diagonal. - * Limited to 50 by default * + * @param limit the number of back edges to analyze * @return List Impact of each edge if removed. */ public List getImpactOfEdgesAboveDiagonalIfRemoved(int limit) { @@ -254,12 +254,12 @@ public List getImpactOfEdgesAboveDiagonalIfRemoved(int limit) List edgesAboveDiagonal; List allEdgesAboveDiagonal = getEdgesAboveDiagonal(); - if (limit == 0 || allEdgesAboveDiagonal.size() <= 50) { + if (limit == 0 || allEdgesAboveDiagonal.size() <= limit) { edgesAboveDiagonal = allEdgesAboveDiagonal; } else { // get first 50 values of min weight List minimumWeightEdgesAboveDiagonal = getMinimumWeightEdgesAboveDiagonal(); - int max = Math.min(minimumWeightEdgesAboveDiagonal.size(), 50); + int max = Math.min(minimumWeightEdgesAboveDiagonal.size(), limit); edgesAboveDiagonal = minimumWeightEdgesAboveDiagonal.subList(0, max); } diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java index 907f72d9..74ab986c 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java @@ -23,6 +23,9 @@ public class RefactorFirstHtmlReport extends AbstractMojo { @Parameter(property = "showDetails") private boolean showDetails = false; + @Parameter(property = "backEdgeAnalysisCount") + protected int backEdgeAnalysisCount = 50; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -49,6 +52,6 @@ public void execute() { .getOutputDirectory() .replace("${project.basedir}" + File.separator, ""), project.getBasedir(), - 50); + backEdgeAnalysisCount); } } diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index 6a9c050e..db810945 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -26,6 +26,9 @@ public class RefactorFirstMavenReport extends AbstractMavenReport { @Parameter(property = "showDetails") private boolean showDetails = false; + @Parameter(property = "backEdgeAnalysisCount") + protected int backEdgeAnalysisCount = 50; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -55,7 +58,8 @@ public void executeReport(Locale locale) { Sink mainSink = getSink(); printHead(mainSink); String report = htmlReport - .generateReport(showDetails, projectName, projectVersion, project.getBasedir(), 300, 50) + .generateReport( + showDetails, projectName, projectVersion, project.getBasedir(), 300, backEdgeAnalysisCount) .toString(); mainSink.rawText(report); diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java index 83425b1f..861d3001 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java @@ -23,6 +23,9 @@ public class RefactorFirstSimpleHtmlReport extends AbstractMojo { @Parameter(property = "showDetails") private boolean showDetails = false; + @Parameter(property = "backEdgeAnalysisCount") + private int backEdgeAnalysisCount = 50; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -49,6 +52,6 @@ public void execute() { .getOutputDirectory() .replace("${project.basedir}" + File.separator, ""), project.getBasedir(), - 50); + backEdgeAnalysisCount); } } 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 b7dfe738..4d8f8194 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -325,7 +325,8 @@ public String renderClassGraphDotImage() { String classGraphName = "classGraph"; StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("

Class Map

"); + stringBuilder.append("

Class Map

"); + stringBuilder.append("
Excludes classes that have no incoming and outgoing edges
"); if (classGraph.vertexSet().size() + classGraph.edgeSet().size() < d3Threshold) { stringBuilder.append( "
\n"); 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 0f84d3d6..149214ad 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -181,26 +181,29 @@ public StringBuilder generateReport( dsm = new DSM(classGraph); edgesAboveDiagonal = dsm.getEdgesAboveDiagonal(); - stringBuilder.append(renderClassGraphDotImage()); + List edgeToRemoveInfos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(edgeAnalysisCount); - if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty() && rankedCycles.isEmpty()) { + if (edgeToRemoveInfos.isEmpty() + && rankedGodClassDisharmonies.isEmpty() + && rankedCBODisharmonies.isEmpty() + && rankedCycles.isEmpty()) { stringBuilder .append("Congratulations! ") .append(projectName) .append(" ") .append(projectVersion) - .append(" has no God classes, highly coupled classes, or cycles!"); + .append(" has no Back Edges, God classes, Highly Coupled Classes, or Cycles!"); stringBuilder.append(renderGithubButtons()); log.info("Done! No Disharmonies found!"); return stringBuilder; } - // Display impact of each edge if removed - stringBuilder.append("
\n"); - List edgeToRemoveInfos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(edgeAnalysisCount); - stringBuilder.append(renderEdgeToRemoveInfos(edgeToRemoveInfos)); + if (!edgeToRemoveInfos.isEmpty()) { + stringBuilder.append("Back Edges\n"); + stringBuilder.append("
\n"); + } - if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { + if (!rankedGodClassDisharmonies.isEmpty()) { stringBuilder.append("God Classes\n"); stringBuilder.append("
\n"); } @@ -214,6 +217,12 @@ public StringBuilder generateReport( stringBuilder.append("Class Cycles\n"); } + stringBuilder.append(renderClassGraphDotImage()); + + // Display impact of each edge if removed + stringBuilder.append("
\n"); + stringBuilder.append(renderEdgeToRemoveInfos(edgeToRemoveInfos)); + if (!rankedGodClassDisharmonies.isEmpty()) { final String[] godClassTableHeadings = showDetails ? godClassDetailedTableHeadings : godClassSimpleTableHeadings; @@ -261,7 +270,8 @@ private String renderCycles(List rankedCycles) { private String renderEdgeToRemoveInfos(List edges) { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("

Backward Edge Removal Impact

\n"); + stringBuilder.append( + "\n"); stringBuilder.append("
\n"); stringBuilder @@ -372,13 +382,19 @@ private String[] getCycleSummaryTableHeadings() { private String[] getEdgesToRemoveInfoTableHeadings() { return new String[] { - "Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "New Avg Cycle Node Count", "Impact/Edge Weight" + "Edge", + "Edge Weight", + "In # of Cycles", + "New Cycle Count", + "New Avg Cycle Node Count", + "Avg Node Δ ÷ Effort" }; } private String[] getEdgeToRemoveInfos(EdgeToRemoveInfo edgeToRemoveInfo) { return new String[] { - // "Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "New Avg Cycle Node Count" + // "Edge", "Edge Weight", "In # of Cycles", "New Cycle Count", "New Avg Cycle Node Count", "Avg Node Count / + // Effort" renderEdge(edgeToRemoveInfo.getEdge()), String.valueOf((int) edgeToRemoveInfo.getEdgeWeight()), String.valueOf(edgeToRemoveInfo.getEdgeInCycleCount()), From 0b7c65206a724971e2faf42c310b79446c0d817a Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Fri, 7 Mar 2025 19:11:04 -0600 Subject: [PATCH 51/78] Improved Class Map usage instructions --- .../org/hjug/refactorfirst/report/HtmlReport.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 4d8f8194..ff98af51 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -326,7 +326,11 @@ public String renderClassGraphDotImage() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("

Class Map

"); - stringBuilder.append("
Excludes classes that have no incoming and outgoing edges
"); + stringBuilder.append("
Excludes classes that have no incoming and outgoing edges
"); + stringBuilder.append("Red lines represent back edges to remove.
\n"); + stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); + stringBuilder.append("
\n"); + if (classGraph.vertexSet().size() + classGraph.edgeSet().size() < d3Threshold) { stringBuilder.append( "
\n"); @@ -347,11 +351,6 @@ public String renderClassGraphDotImage() { stringBuilder.append(generatePopup(classGraphName)); } - stringBuilder.append("
\n"); - stringBuilder.append( - "

Red lines represent back edges to remove. Remove one to start decomposing the cycle.

\n"); - stringBuilder.append("

Zoom in / out with your mouse wheel and click/move to drag the image.

\n"); - stringBuilder.append("
\n"); stringBuilder.append("
\n"); stringBuilder.append("
\n"); From 39d2e233a0ea6f4734b70dc36e89dfe4e5ce4c44 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sat, 8 Mar 2025 16:52:31 -0600 Subject: [PATCH 52/78] Adding analyzeCycles parameter Adding analyzeCycles parameter to allow suppression of cycle analysis and cycle analysis output --- .../org/hjug/refactorfirst/ReportCommand.java | 16 +++++++++-- .../main/java/org/hjug/cbc/CycleRanker.java | 12 +++++++-- .../mavenreport/RefactorFirstHtmlReport.java | 8 ++++-- .../mavenreport/RefactorFirstMavenReport.java | 11 +++++++- .../RefactorFirstSimpleHtmlReport.java | 8 ++++-- .../hjug/refactorfirst/report/HtmlReport.java | 2 +- .../report/SimpleHtmlReport.java | 27 ++++++++++++++----- 7 files changed, 67 insertions(+), 17 deletions(-) diff --git a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java index 047a97cc..c0d8c862 100644 --- a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java +++ b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java @@ -24,6 +24,16 @@ public class ReportCommand implements Callable { description = "Show detailed report") private boolean showDetails; + @Option( + names = {"-eac", "--edge-analysis-count"}, + description = "Back Edge Analysis Count") + protected int backEdgeAnalysisCount = 50; + + @Option( + names = {"-c", "--analyze-cycles"}, + description = "Analyze Cycles") + private boolean analyzeCycles = true; + @Option( names = {"-p", "--project"}, description = "Project name") @@ -52,6 +62,8 @@ public class ReportCommand implements Callable { defaultValue = "HTML") private ReportType reportType; + + @Override public Integer call() { @@ -61,11 +73,11 @@ public Integer call() { switch (reportType) { case SIMPLE_HTML: SimpleHtmlReport simpleHtmlReport = new SimpleHtmlReport(); - simpleHtmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir, 50); + simpleHtmlReport.execute(backEdgeAnalysisCount, analyzeCycles, showDetails, projectName, projectVersion, outputDirectory, baseDir); return 0; case HTML: HtmlReport htmlReport = new HtmlReport(); - htmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir, 50); + htmlReport.execute(backEdgeAnalysisCount, analyzeCycles, showDetails, projectName, projectVersion, outputDirectory, baseDir); return 0; case JSON: JsonReportExecutor jsonReportExecutor = new JsonReportExecutor(); diff --git a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java index 3470930d..4807b601 100644 --- a/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java +++ b/cost-benefit-calculator/src/main/java/org/hjug/cbc/CycleRanker.java @@ -24,10 +24,19 @@ public class CycleRanker { @Getter private Graph classReferencesGraph; - public List runCycleAnalysis() { + public void generateClassReferencesGraph() { + try { + classReferencesGraph = javaGraphBuilder.getClassReferences(repositoryPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public List performCycleAnalysis() { List rankedCycles = new ArrayList<>(); try { boolean calculateCycleChurn = false; + generateClassReferencesGraph(); identifyRankedCycles(rankedCycles); sortRankedCycles(rankedCycles, calculateCycleChurn); setPriorities(rankedCycles); @@ -39,7 +48,6 @@ public List runCycleAnalysis() { } private void identifyRankedCycles(List rankedCycles) throws IOException { - classReferencesGraph = javaGraphBuilder.getClassReferences(repositoryPath); CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker(); Map> cycles = circularReferenceChecker.getCycles(classReferencesGraph); diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java index 74ab986c..feabd586 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java @@ -26,6 +26,9 @@ public class RefactorFirstHtmlReport extends AbstractMojo { @Parameter(property = "backEdgeAnalysisCount") protected int backEdgeAnalysisCount = 50; + @Parameter(property = "analyzeCycles") + private boolean analyzeCycles = true; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -44,6 +47,8 @@ public void execute() { log.info(outputDirectory.getPath()); HtmlReport htmlReport = new HtmlReport(); htmlReport.execute( + backEdgeAnalysisCount, + analyzeCycles, showDetails, projectName, projectVersion, @@ -51,7 +56,6 @@ public void execute() { .getReporting() .getOutputDirectory() .replace("${project.basedir}" + File.separator, ""), - project.getBasedir(), - backEdgeAnalysisCount); + project.getBasedir()); } } diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index db810945..6a7d5dc3 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -29,6 +29,9 @@ public class RefactorFirstMavenReport extends AbstractMavenReport { @Parameter(property = "backEdgeAnalysisCount") protected int backEdgeAnalysisCount = 50; + @Parameter(property = "analyzeCycles") + private boolean analyzeCycles = true; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -59,7 +62,13 @@ public void executeReport(Locale locale) { printHead(mainSink); String report = htmlReport .generateReport( - showDetails, projectName, projectVersion, project.getBasedir(), 300, backEdgeAnalysisCount) + showDetails, + projectName, + projectVersion, + project.getBasedir(), + 300, + backEdgeAnalysisCount, + analyzeCycles) .toString(); mainSink.rawText(report); diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java index 861d3001..df595e68 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java @@ -26,6 +26,9 @@ public class RefactorFirstSimpleHtmlReport extends AbstractMojo { @Parameter(property = "backEdgeAnalysisCount") private int backEdgeAnalysisCount = 50; + @Parameter(property = "analyzeCycles") + private boolean analyzeCycles = true; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -44,6 +47,8 @@ public void execute() { log.info(outputDirectory.getPath()); SimpleHtmlReport htmlReport = new SimpleHtmlReport(); htmlReport.execute( + backEdgeAnalysisCount, + analyzeCycles, showDetails, projectName, projectVersion, @@ -51,7 +56,6 @@ public void execute() { .getReporting() .getOutputDirectory() .replace("${project.basedir}" + File.separator, ""), - project.getBasedir(), - backEdgeAnalysisCount); + project.getBasedir()); } } 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 ff98af51..51f01c26 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -500,7 +500,7 @@ String generatePopup(String cycleName) { + "', 'graph-container-" + cycleName + "'," + cycleName + "_dot )\">Show " + cycleName + " Cycle Popup\n" + "\n" + "
\n" + " ×\n" + + cycleName + "\">\n" + " X\n" + "
\n" + "
"; } 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 149214ad..bac7fd18 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -79,12 +79,13 @@ public class SimpleHtmlReport { .withZone(ZoneId.systemDefault()); public void execute( + int edgeAnalysisCount, + boolean analyzeCycles, boolean showDetails, String projectName, String projectVersion, String outputDirectory, - File baseDir, - int edgeAnalysisCount) { + File baseDir) { String filename = getOutputName() + ".html"; log.info("Generating {} for {} - {}", filename, projectName, projectVersion); @@ -96,7 +97,8 @@ public void execute( stringBuilder.append(printTitle(projectName, projectVersion)); stringBuilder.append(printHead()); stringBuilder.append(""); - stringBuilder.append(generateReport(showDetails, projectName, projectVersion, baseDir, edgeAnalysisCount)); + stringBuilder.append( + generateReport(showDetails, projectName, projectVersion, baseDir, edgeAnalysisCount, analyzeCycles)); stringBuilder.append(printProjectFooter()); stringBuilder.append(THE_END); @@ -106,8 +108,13 @@ public void execute( } public StringBuilder generateReport( - boolean showDetails, String projectName, String projectVersion, File baseDir, int edgeAnalysisCount) { - return generateReport(showDetails, projectName, projectVersion, baseDir, 200, edgeAnalysisCount); + boolean showDetails, + String projectName, + String projectVersion, + File baseDir, + int edgeAnalysisCount, + boolean analyzeCycles) { + return generateReport(showDetails, projectName, projectVersion, baseDir, 200, edgeAnalysisCount, analyzeCycles); } // pixels param is for SVG image pixel padding @@ -117,7 +124,8 @@ public StringBuilder generateReport( String projectVersion, File baseDir, int pixels, - int edgeAnalysisCount) { + int edgeAnalysisCount, + boolean analyzeCycles) { this.pixels = pixels; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(printOpenBodyTag()); @@ -176,7 +184,12 @@ public StringBuilder generateReport( } CycleRanker cycleRanker = new CycleRanker(projectBaseDir); - List rankedCycles = cycleRanker.runCycleAnalysis(); + List rankedCycles = List.of(); + if (analyzeCycles) { + rankedCycles = cycleRanker.performCycleAnalysis(); + } else { + cycleRanker.generateClassReferencesGraph(); + } classGraph = cycleRanker.getClassReferencesGraph(); dsm = new DSM(classGraph); edgesAboveDiagonal = dsm.getEdgesAboveDiagonal(); From d4dfebba25bf35126dc5e7914193b30574cc4709 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 9 Mar 2025 15:05:24 -0500 Subject: [PATCH 53/78] #128 Adding 3D Force Graph to playground --- .../src/test/resources/sigmaPlayground.html | 2177 +++-------------- 1 file changed, 364 insertions(+), 1813 deletions(-) diff --git a/report/src/test/resources/sigmaPlayground.html b/report/src/test/resources/sigmaPlayground.html index bbcb4727..2b7a73a2 100644 --- a/report/src/test/resources/sigmaPlayground.html +++ b/report/src/test/resources/sigmaPlayground.html @@ -8,12 +8,13 @@ - + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/report/src/test/resources/spriteText.html b/report/src/test/resources/spriteText.html new file mode 100644 index 00000000..dff5dde8 --- /dev/null +++ b/report/src/test/resources/spriteText.html @@ -0,0 +1,62 @@ + + + + + + + + + +
+ + + \ No newline at end of file From 36a544609ab31d73855d29a0d8f3e05c602bc87a Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Mon, 10 Mar 2025 19:42:53 -0500 Subject: [PATCH 60/78] commented out highlight code --- report/src/test/resources/sigmaPlayground.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/report/src/test/resources/sigmaPlayground.html b/report/src/test/resources/sigmaPlayground.html index 782b87e3..481bd58c 100644 --- a/report/src/test/resources/sigmaPlayground.html +++ b/report/src/test/resources/sigmaPlayground.html @@ -256,9 +256,10 @@ const Graph = new ForceGraph3D(container) .graphData(gData) .nodeLabel('id') + // uncomment when adding highlight control GUI // .nodeColor(node => highlightNodes.has(node) ? node === hoverNode ? 'rgb(255,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.6)') - .linkWidth(link => highlightLinks.has(link) ? 4 : 1) - .linkDirectionalParticles(link => highlightLinks.has(link) ? 4 : 0) + // .linkWidth(link => highlightLinks.has(link) ? 4 : 1) + // .linkDirectionalParticles(link => highlightLinks.has(link) ? 4 : 0) .linkDirectionalParticleWidth(4) .width(container.clientWidth) .height(container.clientHeight) From 86fc7e381c5db82f91cbe888c693b199bde3db2e Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Mon, 10 Mar 2025 20:20:50 -0500 Subject: [PATCH 61/78] #128 Adding Force 3D Graph --- .../org/hjug/refactorfirst/ReportCommand.java | 20 +- .../mavenreport/RefactorFirstMavenReport.java | 2 + .../hjug/refactorfirst/report/HtmlReport.java | 272 +++++++++++++++--- 3 files changed, 249 insertions(+), 45 deletions(-) diff --git a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java index c0d8c862..0ad8ed9e 100644 --- a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java +++ b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java @@ -62,8 +62,6 @@ public class ReportCommand implements Callable { defaultValue = "HTML") private ReportType reportType; - - @Override public Integer call() { @@ -73,11 +71,25 @@ public Integer call() { switch (reportType) { case SIMPLE_HTML: SimpleHtmlReport simpleHtmlReport = new SimpleHtmlReport(); - simpleHtmlReport.execute(backEdgeAnalysisCount, analyzeCycles, showDetails, projectName, projectVersion, outputDirectory, baseDir); + simpleHtmlReport.execute( + backEdgeAnalysisCount, + analyzeCycles, + showDetails, + projectName, + projectVersion, + outputDirectory, + baseDir); return 0; case HTML: HtmlReport htmlReport = new HtmlReport(); - htmlReport.execute(backEdgeAnalysisCount, analyzeCycles, showDetails, projectName, projectVersion, outputDirectory, baseDir); + htmlReport.execute( + backEdgeAnalysisCount, + analyzeCycles, + showDetails, + projectName, + projectVersion, + outputDirectory, + baseDir); return 0; case JSON: JsonReportExecutor jsonReportExecutor = new JsonReportExecutor(); diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index 6e8a0830..c7c15f18 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -95,8 +95,10 @@ private void printHead(Sink mainSink) { // may only need graphlib-dot renderJsDeclaration(mainSink, "https://cdnjs.cloudflare.com/ajax/libs/graphlib/2.1.8/graphlib.min.js"); renderJsDeclaration(mainSink, "https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.4/dist/graphlib-dot.min.js"); + renderJsDeclaration(mainSink, "https://unpkg.com/3d-force-graph"); renderJsDeclaration(mainSink, HtmlReport.SUGIYAMA_SIGMA_GRAPH); + renderJsDeclaration(mainSink, HtmlReport.FORCE_3D_GRAPH); renderJsDeclaration(mainSink, HtmlReport.POPUP_FUNCTIONS); renderStyle(mainSink); 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 1cbf06a3..b2cee199 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -123,6 +123,165 @@ public class HtmlReport extends SimpleHtmlReport { + "}\n" + ""; + public static final String FORCE_3D_GRAPH = + ""; + // Created by generative AI and modified public static final String POPUP_STYLE = ""; // Created by generative AI and modified - public static final String POPUP_FUNCTIONS = - ""; + public static final String POPUP_FUNCTIONS = ""; private static final String GOD_CLASS_CHART_LEGEND = "

God Class Chart Legend:

" + "
\n" @@ -231,7 +403,9 @@ public String printHead() { // may only need graphlib-dot + "\n" + "\n" + + "\n" + SUGIYAMA_SIGMA_GRAPH + + FORCE_3D_GRAPH + POPUP_FUNCTIONS + POPUP_STYLE; } @@ -327,6 +501,11 @@ public String renderClassGraphDotImage() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("

Class Map

"); stringBuilder.append("
Excludes classes that have no incoming and outgoing edges
"); + stringBuilder.append("\n"); + stringBuilder.append(generateForce3DPopup(classGraphName)); + stringBuilder.append("Red lines represent back edges to remove.
\n"); stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); stringBuilder.append("
\n"); @@ -335,7 +514,7 @@ public String renderClassGraphDotImage() { stringBuilder.append( "
\n"); stringBuilder.append("\n"); } else { // revisit and add D3 popup button as well - stringBuilder.append("\n"); - stringBuilder.append(generatePopup(classGraphName)); + stringBuilder.append(generate2DPopup(classGraphName)); } stringBuilder.append("
\n"); @@ -413,11 +589,16 @@ public String renderCycleDotImage(RankedCycle cycle) { String cycleName = getClassName(cycle.getCycleName()).replace("$", "_"); StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("\n"); + stringBuilder.append(generateForce3DPopup(cycleName)); + if (cycle.getCycleNodes().size() + cycle.getEdgeSet().size() < d3Threshold) { stringBuilder.append( "
\n"); stringBuilder.append("\n"); } else { // revisit and add D3 popup button as well - stringBuilder.append("\n"); - stringBuilder.append(generatePopup(cycleName)); + + stringBuilder.append(generate2DPopup(cycleName)); } stringBuilder.append("
\n"); @@ -494,13 +673,24 @@ String buildCycleDot(Graph classGraph, RankedCycle return dot.toString().replace("$", "_"); } - String generatePopup(String cycleName) { + String generate2DPopup(String cycleName) { // Created by generative AI and modified return "\n" + "\n" + + " 2D Popup\n" + "\n" + + "
\n" + " ×\n" + + "
\n" + "
"; + } + + String generateForce3DPopup(String cycleName) { + // Created by generative AI and modified + return "\n" + "\n" + "
\n" + " X\n" + + cycleName + "\">\n" + " ×\n" + "
\n" + "
"; } From 438990d6ca484a1ed783de2ed4250221ff07fe18 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Tue, 11 Mar 2025 18:53:45 -0500 Subject: [PATCH 62/78] #131 Minifying HTML output by default Minifying HTML / JS / CSS by default when report is generated by HTML Report, Simple HTML Report, and CLI, with option to not minify the report. --- .../org/hjug/refactorfirst/ReportCommand.java | 15 +++++++++---- pom.xml | 6 +++++ .../mavenreport/RefactorFirstHtmlReport.java | 8 +++++-- .../RefactorFirstSimpleHtmlReport.java | 8 +++++-- report/pom.xml | 5 +++++ .../hjug/refactorfirst/report/CsvReport.java | 6 ++--- .../refactorfirst/report/ReportWriter.java | 4 ++-- .../report/SimpleHtmlReport.java | 22 ++++++++++++++++--- .../report/json/JsonReportExecutor.java | 5 +++-- 9 files changed, 61 insertions(+), 18 deletions(-) diff --git a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java index 0ad8ed9e..42bd1b9d 100644 --- a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java +++ b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java @@ -34,6 +34,11 @@ public class ReportCommand implements Callable { description = "Analyze Cycles") private boolean analyzeCycles = true; + @Option( + names = {"-m", "--minify-html"}, + description = "Minify HTML output") + private boolean minifiyHtml = true; + @Option( names = {"-p", "--project"}, description = "Project name") @@ -75,10 +80,11 @@ public Integer call() { backEdgeAnalysisCount, analyzeCycles, showDetails, + minifiyHtml, projectName, projectVersion, - outputDirectory, - baseDir); + baseDir, + outputDirectory); return 0; case HTML: HtmlReport htmlReport = new HtmlReport(); @@ -86,10 +92,11 @@ public Integer call() { backEdgeAnalysisCount, analyzeCycles, showDetails, + minifiyHtml, projectName, projectVersion, - outputDirectory, - baseDir); + baseDir, + outputDirectory); return 0; case JSON: JsonReportExecutor jsonReportExecutor = new JsonReportExecutor(); diff --git a/pom.xml b/pom.xml index e34961c3..963f39b9 100644 --- a/pom.xml +++ b/pom.xml @@ -160,6 +160,12 @@ 1.5.2 + + in.wilsonl.minifyhtml + minify-html + 0.15.0 + + net.sourceforge.pmd pmd-java diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java index feabd586..f7337701 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java @@ -29,6 +29,9 @@ public class RefactorFirstHtmlReport extends AbstractMojo { @Parameter(property = "analyzeCycles") private boolean analyzeCycles = true; + @Parameter(property = "minifyHtml") + private boolean minifyHtml = true; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -50,12 +53,13 @@ public void execute() { backEdgeAnalysisCount, analyzeCycles, showDetails, + minifyHtml, projectName, projectVersion, + project.getBasedir(), project.getModel() .getReporting() .getOutputDirectory() - .replace("${project.basedir}" + File.separator, ""), - project.getBasedir()); + .replace("${project.basedir}" + File.separator, "")); } } diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java index df595e68..c5b7c621 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java @@ -29,6 +29,9 @@ public class RefactorFirstSimpleHtmlReport extends AbstractMojo { @Parameter(property = "analyzeCycles") private boolean analyzeCycles = true; + @Parameter(property = "minifyHtml") + private boolean minifyHtml = true; + @Parameter(defaultValue = "${project.name}") private String projectName; @@ -50,12 +53,13 @@ public void execute() { backEdgeAnalysisCount, analyzeCycles, showDetails, + minifyHtml, projectName, projectVersion, + project.getBasedir(), project.getModel() .getReporting() .getOutputDirectory() - .replace("${project.basedir}" + File.separator, ""), - project.getBasedir()); + .replace("${project.basedir}" + File.separator, "")); } } diff --git a/report/pom.xml b/report/pom.xml index 6f79b22e..b1e37f51 100644 --- a/report/pom.xml +++ b/report/pom.xml @@ -19,6 +19,11 @@ com.fasterxml.jackson.core jackson-databind + + + in.wilsonl.minifyhtml + minify-html + \ No newline at end of file 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 30ee3455..e63a2650 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/CsvReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/CsvReport.java @@ -66,7 +66,7 @@ public void execute( .append(projectVersion) .append(". "); contentBuilder.append("Please initialize a Git repository and perform an initial commit."); - writeReportToDisk(outputDirectory, filename, contentBuilder); + writeReportToDisk(outputDirectory, filename, contentBuilder.toString()); return; } @@ -103,7 +103,7 @@ public void execute( .append(" has no God classes!"); log.info("Done! No God classes found!"); - writeReportToDisk(outputDirectory, filename, contentBuilder); + writeReportToDisk(outputDirectory, filename, contentBuilder.toString()); return; } @@ -123,7 +123,7 @@ public void execute( log.info(contentBuilder.toString()); - writeReportToDisk(outputDirectory, filename, contentBuilder); + writeReportToDisk(outputDirectory, filename, contentBuilder.toString()); } private DateTimeFormatter createFileDateTimeFormatter() { diff --git a/report/src/main/java/org/hjug/refactorfirst/report/ReportWriter.java b/report/src/main/java/org/hjug/refactorfirst/report/ReportWriter.java index bd186fbb..421c3955 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/ReportWriter.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/ReportWriter.java @@ -11,7 +11,7 @@ public class ReportWriter { public static void writeReportToDisk( - final String reportOutputDirectory, final String filename, final StringBuilder stringBuilder) { + final String reportOutputDirectory, final String filename, final String string) { final File reportOutputDir = new File(reportOutputDirectory); if (!reportOutputDir.exists()) { @@ -29,7 +29,7 @@ public static void writeReportToDisk( } try (BufferedWriter writer = Files.newBufferedWriter(reportFile.toPath(), Charset.defaultCharset())) { - writer.write(stringBuilder.toString()); + writer.write(string); } catch (IOException e) { log.error("Error writing chart script file", e); } 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 bac7fd18..03b715de 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -2,6 +2,8 @@ import static org.hjug.refactorfirst.report.ReportWriter.writeReportToDisk; +import in.wilsonl.minifyhtml.Configuration; +import in.wilsonl.minifyhtml.MinifyHtml; import java.io.File; import java.nio.file.Paths; import java.time.Instant; @@ -78,14 +80,22 @@ public class SimpleHtmlReport { .withLocale(Locale.getDefault()) .withZone(ZoneId.systemDefault()); + private final Configuration htmlMinifierConfig = new Configuration.Builder() + .setKeepHtmlAndHeadOpeningTags(true) + .setKeepComments(false) + .setMinifyJs(true) + .setMinifyCss(true) + .build(); + public void execute( int edgeAnalysisCount, boolean analyzeCycles, boolean showDetails, + boolean minifyHtml, String projectName, String projectVersion, - String outputDirectory, - File baseDir) { + File baseDir, + String outputDirectory) { String filename = getOutputName() + ".html"; log.info("Generating {} for {} - {}", filename, projectName, projectVersion); @@ -103,7 +113,13 @@ public void execute( stringBuilder.append(printProjectFooter()); stringBuilder.append(THE_END); - writeReportToDisk(outputDirectory, filename, stringBuilder); + String reportHtml; + if (minifyHtml) { + reportHtml = MinifyHtml.minify(stringBuilder.toString(), htmlMinifierConfig); + } else { + reportHtml = stringBuilder.toString(); + } + writeReportToDisk(outputDirectory, filename, reportHtml); log.info("Done! View the report at target/site/{}", filename); } diff --git a/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportExecutor.java b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportExecutor.java index cf552fc8..92b7efdb 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportExecutor.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportExecutor.java @@ -49,7 +49,7 @@ public void execute(File baseDir, String outputDirectory) { try { final String reportJson = MAPPER.writeValueAsString(report); - writeReportToDisk(outputDirectory, FILE_NAME, new StringBuilder(reportJson)); + writeReportToDisk(outputDirectory, FILE_NAME, new StringBuilder(reportJson).toString()); } catch (final JsonProcessingException jsonProcessingException) { final String errorMessage = "Could not generate a json report: " + jsonProcessingException; @@ -64,7 +64,8 @@ public void execute(File baseDir, String outputDirectory) { private void writeErrorReport(final JsonReport errorReport, String outputDirectory) { try { - writeReportToDisk(outputDirectory, FILE_NAME, new StringBuilder(MAPPER.writeValueAsString(errorReport))); + writeReportToDisk( + outputDirectory, FILE_NAME, new StringBuilder(MAPPER.writeValueAsString(errorReport)).toString()); } catch (final JsonProcessingException jsonProcessingException) { log.error("failed to write error report: ", jsonProcessingException); } From e2f22dc198a49e4d46edb1b80453a9e6d5891782 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Tue, 11 Mar 2025 19:01:26 -0500 Subject: [PATCH 63/78] Using defaultValue instead of setting fields directly --- .../java/org/hjug/refactorfirst/ReportCommand.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java index 42bd1b9d..94c6697e 100644 --- a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java +++ b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java @@ -21,23 +21,27 @@ public class ReportCommand implements Callable { @Option( names = {"-d", "--details"}, + defaultValue = "false", description = "Show detailed report") private boolean showDetails; @Option( names = {"-eac", "--edge-analysis-count"}, + defaultValue = "50", description = "Back Edge Analysis Count") - protected int backEdgeAnalysisCount = 50; + protected int backEdgeAnalysisCount; @Option( names = {"-c", "--analyze-cycles"}, + defaultValue = "true", description = "Analyze Cycles") - private boolean analyzeCycles = true; + private boolean analyzeCycles; @Option( names = {"-m", "--minify-html"}, + defaultValue = "true", description = "Minify HTML output") - private boolean minifiyHtml = true; + private boolean minifiyHtml; @Option( names = {"-p", "--project"}, From c7c7a9c464457a4f69b4ae913b9d1d5f8cd8d935 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Tue, 11 Mar 2025 19:26:48 -0500 Subject: [PATCH 64/78] Added logging statements and cleaned up HTML - Added logging statements - Cleaned up logic that generates spacing between report sections - Commented out Minimum Edges to Cut column --- .../hjug/refactorfirst/report/HtmlReport.java | 3 +- .../report/SimpleHtmlReport.java | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) 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 b2cee199..b188ae0d 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -527,8 +527,7 @@ public String renderClassGraphDotImage() { stringBuilder.append(generate2DPopup(classGraphName)); } - stringBuilder.append("
\n"); - stringBuilder.append("
\n"); + stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"); return stringBuilder.toString(); } 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 03b715de..2a1996b4 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -190,6 +190,7 @@ public StringBuilder generateReport( List rankedGodClassDisharmonies = List.of(); List rankedCBODisharmonies = List.of(); + log.info("Identifying Object Oriented Disharmonies"); try (CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator(projectBaseDir)) { costBenefitCalculator.runPmdAnalysis(); rankedGodClassDisharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(); @@ -202,14 +203,17 @@ public StringBuilder generateReport( CycleRanker cycleRanker = new CycleRanker(projectBaseDir); List rankedCycles = List.of(); if (analyzeCycles) { + log.info("Analyzing Cycles"); rankedCycles = cycleRanker.performCycleAnalysis(); } else { cycleRanker.generateClassReferencesGraph(); } + classGraph = cycleRanker.getClassReferencesGraph(); dsm = new DSM(classGraph); edgesAboveDiagonal = dsm.getEdgesAboveDiagonal(); + log.info("Performing edge removal what-if analysis"); List edgeToRemoveInfos = dsm.getImpactOfEdgesAboveDiagonalIfRemoved(edgeAnalysisCount); if (edgeToRemoveInfos.isEmpty() @@ -246,34 +250,32 @@ public StringBuilder generateReport( stringBuilder.append("Class Cycles\n"); } + log.info("Generating HTML Report"); + stringBuilder.append(renderClassGraphDotImage()); // Display impact of each edge if removed stringBuilder.append("
\n"); - stringBuilder.append(renderEdgeToRemoveInfos(edgeToRemoveInfos)); + String edgeInfos = renderEdgeToRemoveInfos(edgeToRemoveInfos); + + if (!edgeToRemoveInfos.isEmpty()) { + stringBuilder.append(edgeInfos); + stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"); + } if (!rankedGodClassDisharmonies.isEmpty()) { final String[] godClassTableHeadings = showDetails ? godClassDetailedTableHeadings : godClassSimpleTableHeadings; stringBuilder.append(renderGodClassInfo(showDetails, rankedGodClassDisharmonies, godClassTableHeadings)); - } - - if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) { stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"); } if (!rankedCBODisharmonies.isEmpty()) { stringBuilder.append(renderHighlyCoupledClassInfo(rankedCBODisharmonies)); + stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"); } if (!rankedCycles.isEmpty()) { - if (!rankedGodClassDisharmonies.isEmpty() || !rankedCBODisharmonies.isEmpty()) { - stringBuilder.append("
\n"); - stringBuilder.append("
\n"); - stringBuilder.append("
\n"); - stringBuilder.append("
\n"); - stringBuilder.append("
\n"); - } stringBuilder.append(renderCycles(rankedCycles)); } @@ -306,19 +308,19 @@ private String renderEdgeToRemoveInfos(List edges) { stringBuilder .append("Current Cycle Count: ") .append(dsm.getCycles().size()) - .append("
\n"); + .append("
\n"); stringBuilder .append("Current Average Cycle Node Count: ") .append(dsm.getAverageCycleNodeCount()) - .append("
\n"); + .append("
\n"); stringBuilder .append("Current Total Back Edge Count: ") .append(dsm.getEdgesAboveDiagonal().size()) - .append("
\n"); + .append("
\n"); stringBuilder .append("Current Total Min Weight Back Edge Count: ") .append(dsm.getMinimumWeightEdgesAboveDiagonal().size()) - .append("
\n"); + .append("
\n"); stringBuilder.append("
\n"); stringBuilder.append("
\n"); @@ -406,7 +408,7 @@ private String renderEdge(DefaultWeightedEdge edge) { } private String[] getCycleSummaryTableHeadings() { - return new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count", "Minimum Cuts"}; + return new String[] {"Cycle Name", "Priority", "Class Count", "Relationship Count" /*, "Minimum Cuts"*/}; } private String[] getEdgesToRemoveInfoTableHeadings() { @@ -439,8 +441,8 @@ private String[] getRankedCycleSummaryData(RankedCycle rankedCycle, StringBuilde getClassName(rankedCycle.getCycleName()), rankedCycle.getPriority().toString(), String.valueOf(rankedCycle.getCycleNodes().size()), - String.valueOf(rankedCycle.getEdgeSet().size()), - edgesToCut.toString() + String.valueOf(rankedCycle.getEdgeSet().size()) // , + // edgesToCut.toString() }; } @@ -459,7 +461,7 @@ private String renderSingleCycle(RankedCycle cycle) { stringBuilder.append("
"); stringBuilder.append(""); stringBuilder.append("Bold text indicates back edge to remove to decompose cycle"); - stringBuilder.append("\"*\" indicates that edge is also a minimum cut edge in the cycle"); + // stringBuilder.append("
\"*\" indicates that edge is also a minimum cut edge in the cycle"); stringBuilder.append("
"); stringBuilder.append("
\n"); From e9d8bcd2d446cee26e4a2db6da44f933cd3409bb Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Tue, 11 Mar 2025 19:36:22 -0500 Subject: [PATCH 65/78] Cleaning up more HTML --- .../java/org/hjug/refactorfirst/report/HtmlReport.java | 10 +++++----- .../hjug/refactorfirst/report/SimpleHtmlReport.java | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) 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 b188ae0d..45ea642e 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -593,6 +593,11 @@ public String renderCycleDotImage(RankedCycle cycle) { stringBuilder.append("\n"); stringBuilder.append(generateForce3DPopup(cycleName)); + stringBuilder.append("
\n"); + stringBuilder.append("Red lines represent back edges to remove.
\n"); + stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); + stringBuilder.append("
\n"); + if (cycle.getCycleNodes().size() + cycle.getEdgeSet().size() < d3Threshold) { stringBuilder.append( "
\n"); @@ -611,11 +616,6 @@ public String renderCycleDotImage(RankedCycle cycle) { stringBuilder.append(generate2DPopup(cycleName)); } - stringBuilder.append("
\n"); - stringBuilder.append( - "

Red lines represent back edges to remove. Remove one to start decomposing the cycle.

\n"); - stringBuilder.append("

Zoom in / out with your mouse wheel and click/move to drag the image.

\n"); - stringBuilder.append("
\n"); stringBuilder.append("
\n"); stringBuilder.append("
\n"); 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 2a1996b4..d807a01f 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -355,7 +355,8 @@ private String renderClassCycleSummary(List rankedCycles) { stringBuilder.append("\n"); stringBuilder.append("

Class Cycles by the numbers:

\n"); - stringBuilder.append("

Bold edges are backward edges causing cycles

"); + // stringBuilder.append("

Bold edges are backward edges causing + // cycles

"); stringBuilder.append("
\n"); // Content From a361e0c66515e976932993af2719435076a2c134 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Tue, 11 Mar 2025 19:45:40 -0500 Subject: [PATCH 66/78] Remove commented out code --- .../src/main/java/org/hjug/refactorfirst/report/HtmlReport.java | 2 -- 1 file changed, 2 deletions(-) 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 45ea642e..36e55459 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -514,7 +514,6 @@ public String renderClassGraphDotImage() { stringBuilder.append( "
\n"); stringBuilder.append("\n"); - stringBuilder.append(generateForce3DPopup(classGraphName)); + stringBuilder.append(generateForce3DPopup(classGraphName + "3D")); - stringBuilder.append("Red lines represent back edges to remove.
\n"); + stringBuilder.append("
\nRed lines represent back edges to remove.
\n"); stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); stringBuilder.append("
\n"); @@ -590,7 +590,7 @@ public String renderCycleDotImage(RankedCycle cycle) { stringBuilder.append("\n"); - stringBuilder.append(generateForce3DPopup(cycleName)); + stringBuilder.append(generateForce3DPopup(cycleName + "3D")); stringBuilder.append("
\n"); stringBuilder.append("Red lines represent back edges to remove.
\n"); From 8d42df2faac51a1cebcb6e3e82ee2cd8b8913014 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Fri, 14 Mar 2025 18:52:44 -0500 Subject: [PATCH 70/78] #131 Defaulting minifyHtml to false - Defaulting minifyHtml to false - Applied Spotless --- cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java | 5 +++-- .../main/java/org/hjug/graphbuilder/JavaGraphBuilder.java | 4 ++-- .../java/org/hjug/mavenreport/RefactorFirstHtmlReport.java | 2 +- .../org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java index c0a0876f..71a7cc4d 100644 --- a/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java +++ b/cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java @@ -39,7 +39,7 @@ public class ReportCommand implements Callable { @Option( names = {"-m", "--minify-html"}, - defaultValue = "true", + defaultValue = "false", description = "Minify HTML output") private boolean minifiyHtml; @@ -54,7 +54,8 @@ public class ReportCommand implements Callable { */ @Option( names = {"-tsd", "--output"}, - description = "Test source directory. Defaults to test/src or test\\src based on your OS") + description = + "Test source directory. Defaults to test/src or test\\src based on your OS. Default is intentionally generic.") private String testSourceDirectory; @Option( 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 f9eefbfa..102ae245 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 @@ -42,8 +42,8 @@ public Graph getClassReferences( return classReferencesGraph; } - private CodebaseGraphDTO processWithOpenRewrite( - String srcDir, boolean excludeTests, String testSourceDirectory) throws IOException { + private CodebaseGraphDTO processWithOpenRewrite(String srcDir, boolean excludeTests, String testSourceDirectory) + throws IOException { File srcDirectory = new File(srcDir); JavaParser javaParser = JavaParser.fromJavaVersion().build(); diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java index 89b92ffb..7d86e8f0 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstHtmlReport.java @@ -30,7 +30,7 @@ public class RefactorFirstHtmlReport extends AbstractMojo { private boolean analyzeCycles = true; @Parameter(property = "minifyHtml") - private boolean minifyHtml = true; + private boolean minifyHtml = false; @Parameter(property = "excludeTests") private boolean excludeTests = true; diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java index d9878fb5..f1fb9b55 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstSimpleHtmlReport.java @@ -30,7 +30,7 @@ public class RefactorFirstSimpleHtmlReport extends AbstractMojo { private boolean analyzeCycles = true; @Parameter(property = "minifyHtml") - private boolean minifyHtml = true; + private boolean minifyHtml = false; @Parameter(property = "excludeTests") private boolean excludeTests = true; From bb077e39d9e0235750c96a0c10c75d05adf3a685 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sat, 15 Mar 2025 15:44:11 -0500 Subject: [PATCH 71/78] Putting style and javascript declarations in body - Putting style and javascript declarations in HTML body so they will be rendered correctly in the Maven report - Pulled several dependency versions into parent pom --- cli/pom.xml | 8 +- codebase-graph-builder/pom.xml | 23 +--- cost-benefit-calculator/pom.xml | 1 - dsm/pom.xml | 1 - effort-ranker/pom.xml | 2 - pom.xml | 117 +++++++++++------- refactor-first-maven-plugin/pom.xml | 6 +- .../mavenreport/RefactorFirstMavenReport.java | 8 +- .../hjug/refactorfirst/report/HtmlReport.java | 21 ++-- .../report/SimpleHtmlReport.java | 8 ++ 10 files changed, 105 insertions(+), 90 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index f9967c6f..d839560c 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -31,13 +31,15 @@ org.slf4j slf4j-simple - 2.0.7 + + + + com.google.guava + guava org.apache.maven maven-core - 3.9.2 - compile diff --git a/codebase-graph-builder/pom.xml b/codebase-graph-builder/pom.xml index 3fff4e54..22d8e462 100644 --- a/codebase-graph-builder/pom.xml +++ b/codebase-graph-builder/pom.xml @@ -19,7 +19,7 @@ org.openrewrite.recipe rewrite-recipe-bom - 3.3.0 + 3.4.0 pom import @@ -27,15 +27,9 @@ - - org.projectlombok - lombok - ${lombok.version} - org.slf4j slf4j-api - 1.7.26 org.jgrapht @@ -62,24 +56,11 @@ io.quarkus.gizmo gizmo - 1.8.0 + 1.9.0 org.openrewrite rewrite-core - - - org.junit.jupiter - junit-jupiter-api - 5.9.0 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.9.0 - test - \ No newline at end of file diff --git a/cost-benefit-calculator/pom.xml b/cost-benefit-calculator/pom.xml index d9e7921f..d8b135d6 100644 --- a/cost-benefit-calculator/pom.xml +++ b/cost-benefit-calculator/pom.xml @@ -16,7 +16,6 @@ org.slf4j slf4j-api - 2.0.7 diff --git a/dsm/pom.xml b/dsm/pom.xml index 6061ec02..d4aa537f 100644 --- a/dsm/pom.xml +++ b/dsm/pom.xml @@ -31,7 +31,6 @@ org.slf4j slf4j-api - 1.7.26 diff --git a/effort-ranker/pom.xml b/effort-ranker/pom.xml index cd42fa18..d63b9dc5 100644 --- a/effort-ranker/pom.xml +++ b/effort-ranker/pom.xml @@ -26,8 +26,6 @@ org.slf4j slf4j-api - jar - 1.7.2 diff --git a/pom.xml b/pom.xml index 963f39b9..ef98200e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.hjug.refactorfirst @@ -19,7 +20,7 @@ Apache License 2.0 - http://www.apache.org/licenses/ + http://www.apache.org/licenses/ repo @@ -41,8 +42,8 @@ scm:git:https://github.com/refactorfirst/RefactorFirst scm:git:https://github.com/refactorfirst/RefactorFirst https://github.com/refactorfirst/RefactorFirst - HEAD - + HEAD + GitHub @@ -57,11 +58,6 @@ 1.18.30 - - 4.0.0 - 4.0.3 - 1.10.1 - jimbethancourt_RefactorFirst ${project.artifactId} jimbethancourt-github @@ -191,7 +187,27 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4.2 + 2.18.3 + + + + + + com.google.guava + guava + 33.4.0-jre + + + + org.apache.maven + maven-core + ${maven.core.version} + + + com.google.guava + guava + + + + + org.slf4j + slf4j-api + 2.0.17 + + + org.slf4j + slf4j-simple + 2.0.17 + @@ -232,7 +259,6 @@ ${lombok.version} true - @@ -241,24 +267,24 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - - -XDcompilePolicy=simple - - - - - org.projectlombok - lombok - ${lombok.version} - - - - 11 + + + -XDcompilePolicy=simple + + + + + org.projectlombok + lombok + ${lombok.version} + + + + 11 @@ -298,13 +324,13 @@ org.pitest pitest-maven 1.16.1 - - - org.pitest - pitest-junit5-plugin - 1.2.1 - - + + + org.pitest + pitest-junit5-plugin + 1.2.1 + + @@ -325,12 +351,12 @@ com.github.spotbugs spotbugs-maven-plugin - ${spotbugs.maven.plugin.version} + 4.9.2.0 com.github.spotbugs spotbugs - ${spotbugs.version} + 4.9.3 @@ -343,7 +369,7 @@ com.h3xstream.findsecbugs findsecbugs-plugin - ${findsecbugs.plugin.version} + 1.103.0 @@ -375,8 +401,8 @@ *.java - - + + true 4 @@ -384,7 +410,7 @@ - + @@ -408,7 +434,7 @@ org.owasp dependency-check-maven - 6.1.0 + 12.1.0 8.0 @@ -432,7 +458,8 @@ maven-release-plugin 2.5.3 - https://oss.sonatype.org/content/repositories/snapshots/ + https://oss.sonatype.org/content/repositories/snapshots/ + @@ -523,6 +550,4 @@ - - diff --git a/refactor-first-maven-plugin/pom.xml b/refactor-first-maven-plugin/pom.xml index 19f0ed6e..09332f84 100644 --- a/refactor-first-maven-plugin/pom.xml +++ b/refactor-first-maven-plugin/pom.xml @@ -24,10 +24,14 @@ + + + com.google.guava + guava + org.apache.maven maven-core - ${maven.core.version} diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java index 5ab4ba17..26c27930 100644 --- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java +++ b/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java @@ -108,11 +108,11 @@ private void printHead(Sink mainSink) { renderJsDeclaration(mainSink, "https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.4/dist/graphlib-dot.min.js"); renderJsDeclaration(mainSink, "https://unpkg.com/3d-force-graph"); - renderJsDeclaration(mainSink, HtmlReport.SUGIYAMA_SIGMA_GRAPH); - renderJsDeclaration(mainSink, HtmlReport.FORCE_3D_GRAPH); - renderJsDeclaration(mainSink, HtmlReport.POPUP_FUNCTIONS); + // renderJsDeclaration(mainSink, HtmlReport.SUGIYAMA_SIGMA_GRAPH); + // renderJsDeclaration(mainSink, HtmlReport.FORCE_3D_GRAPH); + // renderJsDeclaration(mainSink, HtmlReport.POPUP_FUNCTIONS); - renderStyle(mainSink); + // renderStyle(mainSink); mainSink.head_(); } 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 49994dc7..961f3f33 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -403,11 +403,11 @@ public String printHead() { // may only need graphlib-dot + "\n" + "\n" - + "\n" - + SUGIYAMA_SIGMA_GRAPH - + FORCE_3D_GRAPH - + POPUP_FUNCTIONS - + POPUP_STYLE; + + "\n"; + } + + String printScripts() { + return SUGIYAMA_SIGMA_GRAPH + FORCE_3D_GRAPH + POPUP_FUNCTIONS + POPUP_STYLE; } @Override @@ -506,10 +506,13 @@ public String renderClassGraphDotImage() { stringBuilder.append("\n"); stringBuilder.append(generateForce3DPopup(classGraphName + "3D")); + stringBuilder.append(generate2DPopup(classGraphName)); + stringBuilder.append("
\nRed lines represent back edges to remove.
\n"); stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); stringBuilder.append("
\n"); + // revisit and add D3 popup button as well if (classGraph.vertexSet().size() + classGraph.edgeSet().size() < d3Threshold) { stringBuilder.append( "
\n"); @@ -521,13 +524,8 @@ public String renderClassGraphDotImage() { stringBuilder.append(".fit(true)\n"); stringBuilder.append(".renderDot(" + classGraphName + "_dot);\n"); stringBuilder.append("\n"); - } else { - // revisit and add D3 popup button as well - stringBuilder.append(generate2DPopup(classGraphName)); } - stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"); - return stringBuilder.toString(); } @@ -683,8 +681,9 @@ String generate2DPopup(String cycleName) { String generateForce3DPopup(String cycleName) { // Created by generative AI and modified + String correctName = cycleName.replace("3D", ""); return "\n" + "\n" + "
\n" + " ×\n" 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 d7cdfd6d..6e58267f 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -172,6 +172,7 @@ public StringBuilder generateReport( this.pixels = pixels; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(printOpenBodyTag()); + stringBuilder.append(printScripts()); stringBuilder.append(printBreadcrumbs()); stringBuilder.append(printProjectHeader(projectName, projectVersion)); @@ -280,6 +281,8 @@ public StringBuilder generateReport( log.info("Generating HTML Report"); stringBuilder.append(renderClassGraphDotImage()); + stringBuilder.append("
\n"); + stringBuilder.append(renderGithubButtons()); // Display impact of each edge if removed stringBuilder.append("
\n"); @@ -287,6 +290,7 @@ public StringBuilder generateReport( if (!edgeToRemoveInfos.isEmpty()) { stringBuilder.append(edgeInfos); + stringBuilder.append(renderGithubButtons()); stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"); } @@ -693,6 +697,10 @@ public String printHead() { return ""; // empty on purpose } + String printScripts() { + return ""; // empty on purpose + } + public String printOpenBodyTag() { return " \n"; } From b506ac1609fdbeb74baef47518ab786c9da2458e Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sat, 15 Mar 2025 19:59:41 -0500 Subject: [PATCH 72/78] Finally fixed 2D popup off-center issue Finally fixed 2D popup off-center issue. A div that I thought was dangling, well, wasn't... --- .../org/hjug/refactorfirst/report/HtmlReport.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 961f3f33..079801fe 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -500,11 +500,12 @@ public String renderClassGraphDotImage() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("

Class Map

"); - stringBuilder.append("
Excludes classes that have no incoming and outgoing edges
"); + stringBuilder.append( + "
Excludes classes that have no incoming and outgoing edges
"); stringBuilder.append("\n"); - stringBuilder.append(generateForce3DPopup(classGraphName + "3D")); + stringBuilder.append(generateForce3DPopup(classGraphName)); stringBuilder.append(generate2DPopup(classGraphName)); @@ -588,7 +589,8 @@ public String renderCycleDotImage(RankedCycle cycle) { stringBuilder.append("\n"); - stringBuilder.append(generateForce3DPopup(cycleName + "3D")); + stringBuilder.append(generateForce3DPopup(cycleName)); + stringBuilder.append(generate2DPopup(cycleName)); stringBuilder.append("
\n"); stringBuilder.append("Red lines represent back edges to remove.
\n"); @@ -609,7 +611,6 @@ public String renderCycleDotImage(RankedCycle cycle) { } else { // revisit and add D3 popup button as well - stringBuilder.append(generate2DPopup(cycleName)); } stringBuilder.append("
\n"); @@ -681,9 +682,8 @@ String generate2DPopup(String cycleName) { String generateForce3DPopup(String cycleName) { // Created by generative AI and modified - String correctName = cycleName.replace("3D", ""); return "\n" + "\n" + "
\n" + " ×\n" From 5f0604f979a5ed5580b7b625a30e806ecee82005 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 16 Mar 2025 09:18:00 -0500 Subject: [PATCH 73/78] Removed duplicate popup container div Removed duplicate popup container div --- .../hjug/refactorfirst/report/HtmlReport.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) 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 079801fe..9c6c3f8e 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -506,8 +506,8 @@ public String renderClassGraphDotImage() { stringBuilder.append("const " + classGraphName + "_dot = " + dot + "\n"); stringBuilder.append("\n"); stringBuilder.append(generateForce3DPopup(classGraphName)); - stringBuilder.append(generate2DPopup(classGraphName)); + stringBuilder.append(generateHidePopup(classGraphName)); stringBuilder.append("
\nRed lines represent back edges to remove.
\n"); stringBuilder.append("Zoom in / out with your mouse wheel and click/move to drag the image.\n"); @@ -525,6 +525,9 @@ public String renderClassGraphDotImage() { stringBuilder.append(".fit(true)\n"); stringBuilder.append(".renderDot(" + classGraphName + "_dot);\n"); stringBuilder.append("\n"); + } else { + // revisit and add D3 SVG popup button + stringBuilder.append("
\nMap is too big to render SVG quickly
\n"); } return stringBuilder.toString(); @@ -591,6 +594,7 @@ public String renderCycleDotImage(RankedCycle cycle) { stringBuilder.append("\n"); stringBuilder.append(generateForce3DPopup(cycleName)); stringBuilder.append(generate2DPopup(cycleName)); + stringBuilder.append(generateHidePopup(cycleName)); stringBuilder.append("
\n"); stringBuilder.append("Red lines represent back edges to remove.
\n"); @@ -609,8 +613,8 @@ public String renderCycleDotImage(RankedCycle cycle) { stringBuilder.append(".renderDot(" + cycleName + "_dot);\n"); stringBuilder.append("\n"); } else { - // revisit and add D3 popup button as well - + // revisit and add D3 SVG popup button + stringBuilder.append("
\nCycle is too big to render SVG quickly
\n"); } stringBuilder.append("
\n"); @@ -672,22 +676,21 @@ String buildCycleDot(Graph classGraph, RankedCycle String generate2DPopup(String cycleName) { // Created by generative AI and modified return "\n" + "\n" - + "
\n" + " ×\n" - + "
\n" + "
"; + + "', 'graph-container-" + cycleName + "', " + cycleName + "_dot )\">Show " + cycleName + + " 2D Popup\n"; } String generateForce3DPopup(String cycleName) { // Created by generative AI and modified return "\n" + "\n" - + "
\n" + " ×\n" - + "
\n" + "
"; + + "', 'graph-container-" + cycleName + "', " + cycleName + "_dot )\">Show " + cycleName + + " 3D Popup\n"; + } + + String generateHidePopup(String cycleName) { + return "
\n" + + "×\n" + + "
" + + "\n
\n"; } } From ba594403316ebe9a2c9fa5dd6d1c81e269ea0b1c Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 16 Mar 2025 16:05:49 -0500 Subject: [PATCH 74/78] Logging scmLogInfo at debug level --- .../src/main/java/org/hjug/cbc/CostBenefitCalculator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 03aa9317..66b29002 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 @@ -152,7 +152,7 @@ List getRankedChangeProneness(List disharm ScmLogInfo scmLogInfo = null; try { scmLogInfo = gitLogReader.fileLog(path); - log.info("Successfully fetched scmLogInfo for {}", scmLogInfo.getPath()); + log.debug("Successfully fetched scmLogInfo for {}", scmLogInfo.getPath()); } catch (GitAPIException | IOException e) { log.error("Error reading Git repository contents.", e); } catch (NullPointerException e) { From 8dc5b38b10ddbd4540d849ecdff7d09cf7f93bc1 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 16 Mar 2025 19:17:36 -0500 Subject: [PATCH 75/78] Updated version of findsecbugs-plugin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ef98200e..90429c97 100644 --- a/pom.xml +++ b/pom.xml @@ -369,7 +369,7 @@ com.h3xstream.findsecbugs findsecbugs-plugin - 1.103.0 + 1.13.0 From 3de2c7ca453cc782f6e9a72651546c60db4aae75 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 16 Mar 2025 19:30:58 -0500 Subject: [PATCH 76/78] #128 Improving load times - Reverting to base 3D graph features if nodes + edges > 4000 - Only performing cycle analysis on 10 biggest cycles. Will make configurable later. --- .../hjug/refactorfirst/report/HtmlReport.java | 149 +++++++++--------- .../report/SimpleHtmlReport.java | 11 +- .../src/test/resources/sigmaPlayground.html | 142 ++++++++--------- 3 files changed, 150 insertions(+), 152 deletions(-) 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 9c6c3f8e..c09526d6 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java @@ -124,7 +124,7 @@ public class HtmlReport extends SimpleHtmlReport { + ""; public static final String FORCE_3D_GRAPH = - ""; // Created by generative AI and modified @@ -527,7 +523,7 @@ public String renderClassGraphDotImage() { stringBuilder.append("\n"); } else { // revisit and add D3 SVG popup button - stringBuilder.append("
\nMap is too big to render SVG quickly
\n"); + stringBuilder.append("
\nClass Map SVG is too big to render SVG quickly
\n"); } return stringBuilder.toString(); @@ -614,7 +610,8 @@ public String renderCycleDotImage(RankedCycle cycle) { stringBuilder.append("\n"); } else { // revisit and add D3 SVG popup button - stringBuilder.append("
\nCycle is too big to render SVG quickly
\n"); + stringBuilder.append( + "
\nCycle " + cycleName + " SVG is too big to render SVG quickly
\n"); } stringBuilder.append("
\n"); 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 6e58267f..a67a25f1 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -322,9 +322,10 @@ private String renderCycles(List rankedCycles) { stringBuilder.append("
\n"); - for (RankedCycle rankedCycle : rankedCycles) { - stringBuilder.append(renderSingleCycle(rankedCycle)); - } + rankedCycles.stream() + .limit(10) + .map(this::renderSingleCycle) + .forEach(stringBuilder::append); return stringBuilder.toString(); } @@ -384,6 +385,10 @@ private String renderClassCycleSummary(List rankedCycles) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("\n"); + if (rankedCycles.size() > 10) { + stringBuilder.append( + "
10 largest cycles are shown in the sections below
\n"); + } stringBuilder.append("

Class Cycles by the numbers:

\n"); // stringBuilder.append("

Bold edges are backward edges causing diff --git a/report/src/test/resources/sigmaPlayground.html b/report/src/test/resources/sigmaPlayground.html index 481bd58c..08eb2380 100644 --- a/report/src/test/resources/sigmaPlayground.html +++ b/report/src/test/resources/sigmaPlayground.html @@ -215,7 +215,7 @@ source: edge.v, target: edge.w, color: graphlibGraph.edge(edge).color || 'white', - weight: graphlibGraph.edge(edge).weight + weight: graphlibGraph.edge(edge).weight, }); }); @@ -239,90 +239,86 @@ b.links.push(link); }); - const highlightNodes = new Set(); - const highlightLinks = new Set(); - let hoverNode = null; - - // ForceGraph3D()(container) - // .graphData({ nodes: nodes, links: links }) - // .nodeLabel('id') - // .nodeAutoColorBy('color') - // .linkAutoColorBy('color') - // .linkDirectionalArrowLength(3.5) - // .linkDirectionalArrowRelPos(1) - // .width(container.clientWidth) - // .height(container.clientHeight); - const Graph = new ForceGraph3D(container) .graphData(gData) .nodeLabel('id') - // uncomment when adding highlight control GUI - // .nodeColor(node => highlightNodes.has(node) ? node === hoverNode ? 'rgb(255,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.6)') - // .linkWidth(link => highlightLinks.has(link) ? 4 : 1) - // .linkDirectionalParticles(link => highlightLinks.has(link) ? 4 : 0) - .linkDirectionalParticleWidth(4) .width(container.clientWidth) - .height(container.clientHeight) - .nodeThreeObject(node => { - // use node labels instead of spheres + .height(container.clientHeight); + + if(gData.links.length + gData.nodes.length < 4000) { + console.log(gData.links.length + gData.nodes.length); + + + // use node labels instead of spheres + Graph.nodeThreeObject(node => { const sprite = new SpriteText(node.id); sprite.material.depthWrite = false; // make sprite background transparent sprite.color = node.color; sprite.textHeight = 4; return sprite; - }) - // code to display weight as link text - may be too much for browsers to handle - .linkThreeObjectExtend(true) - .linkThreeObject(link => { - // extend link with text sprite - const sprite = new SpriteText(`${link.weight}`); - sprite.color = 'lightgrey'; - sprite.textHeight = 3; - return sprite; - }) - .linkPositionUpdate((sprite, { start, end }) => { - const middlePos = Object.assign(...['x', 'y', 'z'].map(c => ({ - [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point - }))); - - // Position sprite - Object.assign(sprite.position, middlePos); - }) + }); + + // code to display weight as link text + // may be too much for browsers to handle + // Graph + // .linkThreeObjectExtend(true) + // .linkThreeObject(link => { + // // extend link with text sprite + // const sprite = new SpriteText(`${link.weight}`); + // sprite.color = 'lightgrey'; + // sprite.textHeight = 3; + // return sprite; + // }) + // .linkPositionUpdate((sprite, {start, end}) => { + // const middlePos = Object.assign(...['x', 'y', 'z'].map(c => ({ + // [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point + // }))); + // + // // Position sprite + // Object.assign(sprite.position, middlePos); + // }); + + // code to highlight nodes & links // TODO: enable via control - see Manipulate Link Force Distance for example - // .onNodeHover(node => { - // // no state change - // if ((!node && !highlightNodes.size) || (node && hoverNode === node)) return; - // - // highlightNodes.clear(); - // highlightLinks.clear(); - // if (node) { - // highlightNodes.add(node); - // node.neighbors.forEach(neighbor => highlightNodes.add(neighbor)); - // node.links.forEach(link => highlightLinks.add(link)); - // } - // - // hoverNode = node || null; - // - // updateHighlight(Graph); - // }) - // .onLinkHover(link => { - // highlightNodes.clear(); - // highlightLinks.clear(); - // - // if (link) { - // highlightLinks.add(link); - // highlightNodes.add(link.source); - // highlightNodes.add(link.target); - // } - // - // updateHighlight(Graph); - // }) - ; + const highlightNodes = new Set(); + const highlightLinks = new Set(); + let hoverNode = null; + Graph + .nodeColor(node => highlightNodes.has(node) ? node === hoverNode ? 'rgb(255,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.6)') + .linkWidth(link => highlightLinks.has(link) ? 4 : 1) + .linkDirectionalParticles(link => highlightLinks.has(link) ? 4 : 0) + .linkDirectionalParticleWidth(4) + .onNodeHover(node => { + // no state change + if ((!node && !highlightNodes.size) || (node && hoverNode === node)) return; + + highlightNodes.clear(); + highlightLinks.clear(); + if (node) { + highlightNodes.add(node); + node.neighbors.forEach(neighbor => highlightNodes.add(neighbor)); + node.links.forEach(link => highlightLinks.add(link)); + } + + hoverNode = node || null; + + updateHighlight(Graph); + }) + .onLinkHover(link => { + highlightNodes.clear(); + highlightLinks.clear(); + + if (link) { + highlightLinks.add(link); + highlightNodes.add(link.source); + highlightNodes.add(link.target); + } + + updateHighlight(Graph); + }); - Graph - .d3Force('link') - .distance(link => (1/link.weight) * 200); + } } // used by highlighting functionality From 499e6054e1100e32aaeeec206a5c35b525a6eec9 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 16 Mar 2025 19:44:06 -0500 Subject: [PATCH 77/78] Updated Lombok version - Updated Lombok version - Applied Spotless to SimpleHtmlReport --- pom.xml | 2 +- .../java/org/hjug/refactorfirst/report/SimpleHtmlReport.java | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 90429c97..7f8fbf45 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ - 1.18.30 + 1.18.36 jimbethancourt_RefactorFirst ${project.artifactId} jimbethancourt-github 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 a67a25f1..0523fe08 100644 --- a/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java +++ b/report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java @@ -322,10 +322,7 @@ private String renderCycles(List rankedCycles) { stringBuilder.append("
\n"); - rankedCycles.stream() - .limit(10) - .map(this::renderSingleCycle) - .forEach(stringBuilder::append); + rankedCycles.stream().limit(10).map(this::renderSingleCycle).forEach(stringBuilder::append); return stringBuilder.toString(); } From 020b0b5b2020d21d3b2b6eacf3447195c938c221 Mon Sep 17 00:00:00 2001 From: Jim Bethancourt Date: Sun, 16 Mar 2025 20:11:36 -0500 Subject: [PATCH 78/78] Updated version to 0.7.0-SNAPSHOT - Updated version to 0.7.0-SNAPSHOT - Overriding version of snappy used by maven-core to suppress CVE-2024-36124 --- change-proneness-ranker/pom.xml | 2 +- cli/pom.xml | 2 +- codebase-graph-builder/pom.xml | 3 +-- cost-benefit-calculator/pom.xml | 2 +- coverage/pom.xml | 2 +- dsm/pom.xml | 3 +-- effort-ranker/pom.xml | 3 +-- graph-data-generator/pom.xml | 3 +-- pom.xml | 2 +- refactor-first-maven-plugin/pom.xml | 8 +++++++- report/pom.xml | 2 +- test-resources/pom.xml | 2 +- 12 files changed, 18 insertions(+), 16 deletions(-) diff --git a/change-proneness-ranker/pom.xml b/change-proneness-ranker/pom.xml index c2e2fc29..47d1b835 100644 --- a/change-proneness-ranker/pom.xml +++ b/change-proneness-ranker/pom.xml @@ -5,7 +5,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.changepronenessranker diff --git a/cli/pom.xml b/cli/pom.xml index d839560c..53fd7e2a 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -4,7 +4,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT jar diff --git a/codebase-graph-builder/pom.xml b/codebase-graph-builder/pom.xml index 22d8e462..e37089fe 100644 --- a/codebase-graph-builder/pom.xml +++ b/codebase-graph-builder/pom.xml @@ -7,12 +7,11 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.codebasegraphbuilder codebase-graph-builder - 0.6.3-SNAPSHOT diff --git a/cost-benefit-calculator/pom.xml b/cost-benefit-calculator/pom.xml index d8b135d6..f2c567a4 100644 --- a/cost-benefit-calculator/pom.xml +++ b/cost-benefit-calculator/pom.xml @@ -5,7 +5,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.costbenefitcalculator diff --git a/coverage/pom.xml b/coverage/pom.xml index 0f0ec3a3..666238d8 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -7,7 +7,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT coverage diff --git a/dsm/pom.xml b/dsm/pom.xml index d4aa537f..2aa3db70 100644 --- a/dsm/pom.xml +++ b/dsm/pom.xml @@ -7,12 +7,11 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.dsm dsm - 0.6.3-SNAPSHOT Implementation of a DSM that only has JGraphT-Core as a dependency. diff --git a/effort-ranker/pom.xml b/effort-ranker/pom.xml index d63b9dc5..0b70f025 100644 --- a/effort-ranker/pom.xml +++ b/effort-ranker/pom.xml @@ -5,7 +5,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.effortranker @@ -20,7 +20,6 @@ org.hjug.refactorfirst.testresources test-resources - 0.6.3-SNAPSHOT diff --git a/graph-data-generator/pom.xml b/graph-data-generator/pom.xml index 52cf45e2..f898362d 100644 --- a/graph-data-generator/pom.xml +++ b/graph-data-generator/pom.xml @@ -5,7 +5,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.graphdatagenerator @@ -15,7 +15,6 @@ org.hjug.refactorfirst.costbenefitcalculator cost-benefit-calculator - 0.6.3-SNAPSHOT diff --git a/pom.xml b/pom.xml index 7f8fbf45..32b13a76 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT pom https://github.com/refactorfirst/RefactorFirst diff --git a/refactor-first-maven-plugin/pom.xml b/refactor-first-maven-plugin/pom.xml index 09332f84..9b3a1ff0 100644 --- a/refactor-first-maven-plugin/pom.xml +++ b/refactor-first-maven-plugin/pom.xml @@ -5,7 +5,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.plugin @@ -29,6 +29,12 @@ com.google.guava guava + + + org.iq80.snappy + snappy + 0.5 + org.apache.maven maven-core diff --git a/report/pom.xml b/report/pom.xml index b1e37f51..17ea3699 100644 --- a/report/pom.xml +++ b/report/pom.xml @@ -4,7 +4,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.report diff --git a/test-resources/pom.xml b/test-resources/pom.xml index c4b6a17e..43c87165 100644 --- a/test-resources/pom.xml +++ b/test-resources/pom.xml @@ -5,7 +5,7 @@ org.hjug.refactorfirst refactor-first - 0.6.3-SNAPSHOT + 0.7.0-SNAPSHOT org.hjug.refactorfirst.testresources