diff --git a/README.md b/README.md index 4c97f38d..00cef215 100644 --- a/README.md +++ b/README.md @@ -196,9 +196,10 @@ java -jar ck-x.x.x-SNAPSHOT-jar-with-dependencies.jar \ \ \ \ - \ + \ \ - [ignored directories...] + [ignored directories...] \ + ``` `Project dir` refers to the directory where CK can find all the source code to be parsed. @@ -208,10 +209,10 @@ in the directory and use them to better resolve types. `Max files per partition` of the batch to process. Let us decide that for you and start with 0; if problems happen (i.e., out of memory) you think of tuning it. `Variables and field metrics` indicates to CK whether you want metrics at variable- and field-levels too. They are highly fine-grained and produce a lot of output; -you should skip it if you only need metrics at class or method level. Finally, `output dir` refer to the +you should skip it if you only need metrics at class or method level. Aditionally, `output dir` refer to the directory where CK will export the csv file with metrics from the analyzed project. Optionally, you can specify any number ignored directories, separated by spaces (for example, `build/`). -By default, `.git` and all other hidden folders are ignored. +By default, `.git` and all other hidden folders are ignored. Finally, the `verbose flag` tells CK if it must process metrics tagged as large outputs. If you are not interested in the detailed output of the metrics, you can set it to false. The tool will generate three csv files: class, method, and variable levels. diff --git a/src/main/java/com/github/mauricioaniche/ck/CK.java b/src/main/java/com/github/mauricioaniche/ck/CK.java index 803e07cc..6bde74e4 100644 --- a/src/main/java/com/github/mauricioaniche/ck/CK.java +++ b/src/main/java/com/github/mauricioaniche/ck/CK.java @@ -36,9 +36,9 @@ public CK(Callable> classLevelMetrics, Callable finder.allClassLevelMetrics(); + this.classLevelMetrics = () -> finder.allClassLevelMetrics(verbose); this.methodLevelMetrics = () -> finder.allMethodLevelMetrics(variablesAndFields); this.useJars = useJars; @@ -49,7 +49,7 @@ public CK(boolean useJars, int maxAtOnce, boolean variablesAndFields) { } public CK() { - this(false, 0, true); + this(false, 0, true, false); } public void calculate(String path, CKNotifier notifier) { diff --git a/src/main/java/com/github/mauricioaniche/ck/CKClassResult.java b/src/main/java/com/github/mauricioaniche/ck/CKClassResult.java index 9d8b2714..024c6b75 100644 --- a/src/main/java/com/github/mauricioaniche/ck/CKClassResult.java +++ b/src/main/java/com/github/mauricioaniche/ck/CKClassResult.java @@ -66,6 +66,8 @@ public class CKClassResult { private float tightClassCohesion; private float looseClassCohesion; + private String methodInvocations; + public CKClassResult(String file, String className, String type, int modifiers) { this.file = file; this.className = className; @@ -539,4 +541,12 @@ public int hashCode() { return Objects.hash(file, className, type); } + public void setMethodInvocation(String methodInvocations) { + this.methodInvocations = methodInvocations; + } + + public String getMethodInvocations() { + return this.methodInvocations; + } + } diff --git a/src/main/java/com/github/mauricioaniche/ck/ResultWriter.java b/src/main/java/com/github/mauricioaniche/ck/ResultWriter.java index e294c530..a9bca398 100644 --- a/src/main/java/com/github/mauricioaniche/ck/ResultWriter.java +++ b/src/main/java/com/github/mauricioaniche/ck/ResultWriter.java @@ -39,6 +39,7 @@ public class ResultWriter { "abstractMethodsQty", "finalMethodsQty", "synchronizedMethodsQty", + "methodInvocations", /* Field Counting */ "totalFieldsQty", @@ -178,6 +179,7 @@ public void printResult(CKClassResult result) throws IOException { result.getNumberOfAbstractMethods(), result.getNumberOfFinalMethods(), result.getNumberOfSynchronizedMethods(), + result.getMethodInvocations(), /* Field Counting */ result.getNumberOfFields(), diff --git a/src/main/java/com/github/mauricioaniche/ck/Runner.java b/src/main/java/com/github/mauricioaniche/ck/Runner.java index e0cab409..4b63a2ca 100644 --- a/src/main/java/com/github/mauricioaniche/ck/Runner.java +++ b/src/main/java/com/github/mauricioaniche/ck/Runner.java @@ -31,12 +31,16 @@ public static void main(String[] args) throws IOException { boolean variablesAndFields = true; if(args.length >= 4) variablesAndFields = Boolean.parseBoolean(args[3]); - + // path where the output csv files will be exported String outputDir = ""; if(args.length >= 5) outputDir = args[4]; + boolean isVerbose = false; + if(args.length >= 6) + isVerbose = Boolean.parseBoolean(args[5]); + // load possible additional ignored directories //noinspection ManualArrayToCollectionCopy for (int i = 5; i < args.length; i++) { @@ -47,7 +51,7 @@ public static void main(String[] args) throws IOException { Map results = new HashMap<>(); - new CK(useJars, maxAtOnce, variablesAndFields).calculate(path, new CKNotifier() { + new CK(useJars, maxAtOnce, variablesAndFields, isVerbose).calculate(path, new CKNotifier() { @Override public void notify(CKClassResult result) { diff --git a/src/main/java/com/github/mauricioaniche/ck/metric/ClassLevelMetric.java b/src/main/java/com/github/mauricioaniche/ck/metric/ClassLevelMetric.java index 66d15a2e..34e5bdef 100644 --- a/src/main/java/com/github/mauricioaniche/ck/metric/ClassLevelMetric.java +++ b/src/main/java/com/github/mauricioaniche/ck/metric/ClassLevelMetric.java @@ -1,9 +1,11 @@ package com.github.mauricioaniche.ck.metric; import com.github.mauricioaniche.ck.CKClassResult; -import org.eclipse.jdt.core.dom.CompilationUnit; public interface ClassLevelMetric { + default boolean isVerbose() { + return false; + } void setResult(CKClassResult result); default void setClassName(String className) { diff --git a/src/main/java/com/github/mauricioaniche/ck/metric/MethodInvocationCounter.java b/src/main/java/com/github/mauricioaniche/ck/metric/MethodInvocationCounter.java new file mode 100644 index 00000000..3324c13c --- /dev/null +++ b/src/main/java/com/github/mauricioaniche/ck/metric/MethodInvocationCounter.java @@ -0,0 +1,43 @@ +package com.github.mauricioaniche.ck.metric; + +import com.github.mauricioaniche.ck.CKClassResult; +import com.github.mauricioaniche.ck.util.MethodCounter; +import org.eclipse.jdt.core.dom.*; + +import java.util.*; + +public class MethodInvocationCounter implements CKASTVisitor, ClassLevelMetric { + private Map methodInvocations = new HashMap<>(); + private String currentMethod = null; + + @Override + public void visit(MethodDeclaration node) { + // Set the current method context + this.currentMethod = node.getName().getIdentifier(); + methodInvocations.putIfAbsent(currentMethod, new MethodCounter().new MethodInformation(currentMethod)); + } + + @Override + public void visit(MethodInvocation node) { + if (currentMethod != null) { + // Retrieve or create the MethodInformation for the current method + MethodCounter.MethodInformation info = methodInvocations.get(currentMethod); + String methodName = node.getName().getIdentifier(); + Map counts = info.getMethodInvocations(); + counts.put(methodName, counts.getOrDefault(methodName, 0) + 1); + } + } + + @Override + public void setResult(CKClassResult result) { + // Process all collected method invocation information into the desired format + List infos = new ArrayList<>(methodInvocations.values()); + String formattedResult = MethodCounter.formatResult(infos); + result.setMethodInvocation(formattedResult); + } + + @Override + public boolean isVerbose() { + return true; + } +} diff --git a/src/main/java/com/github/mauricioaniche/ck/util/MethodCounter.java b/src/main/java/com/github/mauricioaniche/ck/util/MethodCounter.java new file mode 100644 index 00000000..d1af583e --- /dev/null +++ b/src/main/java/com/github/mauricioaniche/ck/util/MethodCounter.java @@ -0,0 +1,77 @@ +package com.github.mauricioaniche.ck.util; + +import java.util.*; +import java.util.stream.Collectors; + +public class MethodCounter { + public class MethodInformation { + private String parentName; + private Map methodInvocations = new HashMap<>(); + + public MethodInformation(String parentName) { + this.parentName = parentName; + } + + public String getParentName() { + return parentName; + } + + public Map getMethodInvocations() { + return methodInvocations; + } + + @Override + public String toString() { + return "MethodInformation{" + + "parentName='" + parentName + '\'' + + ", methodInvocation=" + methodInvocations + + '}'; + } + + public String toFormattedString() { + return parentName + "[ " + formatMethods(sortMethods(methodInvocations)) + " ] "; + } + + private Map sortMethods(Map methodInvocation) { + return methodInvocation.entrySet().stream() + .sorted((Map.Entry.comparingByValue().reversed())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + + private String formatMethods(Map methodInvocation) { + return methodInvocation.entrySet().stream() + .map(entry -> entry.getKey() + "()" + ":" + entry.getValue()) + .collect(Collectors.joining(" ")); + } + + } + + public static String formatResult(List methodInformations) { + return methodInformations.stream().map(MethodInformation::toFormattedString).collect(Collectors.joining()); + } + public static List count(String methodList) { + List methodInformationList = new ArrayList<>(); + + String[] methodNames = methodList.split(";"); + for (String name : methodNames) { + String[] parts = name.split("/"); + String methodName = parts[0]; + String parentName = parts[1]; + + MethodInformation methodInformation = new MethodCounter().new MethodInformation(parentName); + if (methodInformationList.contains(methodInformation)) { + methodInformation = methodInformationList.get(methodInformationList.indexOf(methodInformation)); + } else { + methodInformationList.add(methodInformation); + } + + if (methodInformation.getMethodInvocations().containsKey(methodName)) { + methodInformation.getMethodInvocations().put(methodName, methodInformation.getMethodInvocations().get(methodName) + 1); + } else { + methodInformation.getMethodInvocations().put(methodName, 1); + } + } + + return methodInformationList; + } +} diff --git a/src/main/java/com/github/mauricioaniche/ck/util/MetricsFinder.java b/src/main/java/com/github/mauricioaniche/ck/util/MetricsFinder.java index 7734e2f1..fb99920a 100644 --- a/src/main/java/com/github/mauricioaniche/ck/util/MetricsFinder.java +++ b/src/main/java/com/github/mauricioaniche/ck/util/MetricsFinder.java @@ -2,11 +2,12 @@ import com.github.mauricioaniche.ck.metric.ClassLevelMetric; import com.github.mauricioaniche.ck.metric.MethodLevelMetric; -import com.github.mauricioaniche.ck.metric.RunAfter; import com.github.mauricioaniche.ck.metric.VariableOrFieldMetric; import org.reflections.Reflections; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; public class MetricsFinder { @@ -39,7 +40,7 @@ public List allMethodLevelMetrics(boolean variablesAndFields) } } - public List allClassLevelMetrics() { + public List allClassLevelMetrics(boolean verbose) { if(classLevelClasses == null) loadClassLevelClasses(); @@ -47,7 +48,16 @@ public List allClassLevelMetrics() { try { ArrayList metrics = new ArrayList<>(); for (Class aClass : classLevelClasses) { - metrics.add(aClass.getDeclaredConstructor().newInstance()); + boolean isVerbose = aClass.getDeclaredConstructor().newInstance().isVerbose(); + + if (verbose) { + metrics.add(aClass.getDeclaredConstructor().newInstance()); + } + else { + if (!isVerbose) { + metrics.add(aClass.getDeclaredConstructor().newInstance()); + } + } } return metrics;