Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MethodInvocationCounter Metric to Track Method Invocations within Classes #120

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,11 @@ Then, just run:
java -jar ck-x.x.x-SNAPSHOT-jar-with-dependencies.jar \
<project dir> \
<use jars:true|false> \
<max files per partition:0=automatic selection> \
<variables and fields metrics?:true|false> \
<max files per partition, 0=automatic selection> \
<variables and fields metrics? True|False> \
<output dir> \
[ignored directories...]
[ignored directories...] \
<verbose flag for large metric outputs? true|false>
```

`Project dir` refers to the directory where CK can find all the source code to be parsed.
Expand All @@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo here, Additionally

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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verbose flag, flag shouldn't be highlighted


The tool will generate three csv files: class, method, and variable levels.

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/github/mauricioaniche/ck/CK.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public CK(Callable<List<ClassLevelMetric>> classLevelMetrics, Callable<List<Meth
this.maxAtOnce = 100;
}

public CK(boolean useJars, int maxAtOnce, boolean variablesAndFields) {
public CK(boolean useJars, int maxAtOnce, boolean variablesAndFields, boolean verbose) {
MetricsFinder finder = new MetricsFinder();
this.classLevelMetrics = () -> finder.allClassLevelMetrics();
this.classLevelMetrics = () -> finder.allClassLevelMetrics(verbose);
this.methodLevelMetrics = () -> finder.allMethodLevelMetrics(variablesAndFields);

this.useJars = useJars;
Expand All @@ -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) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/github/mauricioaniche/ck/CKClassResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

}
2 changes: 2 additions & 0 deletions src/main/java/com/github/mauricioaniche/ck/ResultWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class ResultWriter {
"abstractMethodsQty",
"finalMethodsQty",
"synchronizedMethodsQty",
"methodInvocations",

/* Field Counting */
"totalFieldsQty",
Expand Down Expand Up @@ -178,6 +179,7 @@ public void printResult(CKClassResult result) throws IOException {
result.getNumberOfAbstractMethods(),
result.getNumberOfFinalMethods(),
result.getNumberOfSynchronizedMethods(),
result.getMethodInvocations(),

/* Field Counting */
result.getNumberOfFields(),
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/github/mauricioaniche/ck/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand All @@ -47,7 +51,7 @@ public static void main(String[] args) throws IOException {

Map<String, CKClassResult> 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) {

Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, MethodCounter.MethodInformation> 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<String, Integer> 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<MethodCounter.MethodInformation> infos = new ArrayList<>(methodInvocations.values());
String formattedResult = MethodCounter.formatResult(infos);
result.setMethodInvocation(formattedResult);
}

@Override
public boolean isVerbose() {
return true;
}
}
77 changes: 77 additions & 0 deletions src/main/java/com/github/mauricioaniche/ck/util/MethodCounter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.github.mauricioaniche.ck.util;

import java.util.*;
import java.util.stream.Collectors;

public class MethodCounter {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string produced by this class can be really long. I'm not sure I'd like to mix the CSV, which is mostly numbers with this super large string. Also, given that this string can be quite long, just concatenating it to the CSV might make the CSV invalid. We'd need to use proper escaping here. An alternative would be to output this to a separate file. And make this feature disabled by default, so we don't start producing large data files to people that have been using CK so far (and do not expect it).

public class MethodInformation {
private String parentName;
private Map<String, Integer> methodInvocations = new HashMap<>();

public MethodInformation(String parentName) {
this.parentName = parentName;
}

public String getParentName() {
return parentName;
}

public Map<String, Integer> getMethodInvocations() {
return methodInvocations;
}

@Override
public String toString() {
return "MethodInformation{" +
"parentName='" + parentName + '\'' +
", methodInvocation=" + methodInvocations +
'}';
}

public String toFormattedString() {
return parentName + "[ " + formatMethods(sortMethods(methodInvocations)) + " ] ";
}

private Map<String, Integer> sortMethods(Map<String, Integer> methodInvocation) {
return methodInvocation.entrySet().stream()
.sorted((Map.Entry.<String, Integer>comparingByValue().reversed()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}

private String formatMethods(Map<String, Integer> methodInvocation) {
return methodInvocation.entrySet().stream()
.map(entry -> entry.getKey() + "()" + ":" + entry.getValue())
.collect(Collectors.joining(" "));
}

}

public static String formatResult(List<MethodInformation> methodInformations) {
return methodInformations.stream().map(MethodInformation::toFormattedString).collect(Collectors.joining());
}
public static List<MethodInformation> count(String methodList) {
List<MethodInformation> 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;
}
}
18 changes: 14 additions & 4 deletions src/main/java/com/github/mauricioaniche/ck/util/MetricsFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -39,15 +40,24 @@ public List<MethodLevelMetric> allMethodLevelMetrics(boolean variablesAndFields)
}
}

public List<ClassLevelMetric> allClassLevelMetrics() {
public List<ClassLevelMetric> allClassLevelMetrics(boolean verbose) {

if(classLevelClasses == null)
loadClassLevelClasses();

try {
ArrayList<ClassLevelMetric> metrics = new ArrayList<>();
for (Class<? extends ClassLevelMetric> 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;
Expand Down