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

LCOM3 metric realisation issue #520

Open
User123363 opened this issue Apr 22, 2021 · 0 comments
Open

LCOM3 metric realisation issue #520

User123363 opened this issue Apr 22, 2021 · 0 comments

Comments

@User123363
Copy link

User123363 commented Apr 22, 2021

We found the CodeMR plugin implemented in Intellij Idea, which can also calculate LCOM3 metrics and decided to compare with jpeek results.

Our observations described below were made on the result of analysis of the open-source project CK (https://github.com/mauricioaniche/ck).

Consider the CK class:

package com.github.mauricioaniche.ck;

import com.github.mauricioaniche.ck.metric.ClassLevelMetric;
import com.github.mauricioaniche.ck.metric.MethodLevelMetric;
import com.github.mauricioaniche.ck.util.FileUtils;
import com.github.mauricioaniche.ck.util.MetricsFinder;
import com.google.common.collect.Lists;
import org.apache.log4j.Logger;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CK {

	private final int maxAtOnce;
	private final boolean useJars;
	
	private static Logger log = Logger.getLogger(CK.class);

	Callable<List<ClassLevelMetric>> classLevelMetrics;
	Callable<List<MethodLevelMetric>> methodLevelMetrics;

	// mostly for testing purposes
	public CK(Callable<List<ClassLevelMetric>> classLevelMetrics, Callable<List<MethodLevelMetric>> methodLevelMetrics) {
		this.useJars = false;
		this.classLevelMetrics = classLevelMetrics;
		this.methodLevelMetrics = methodLevelMetrics;
		this.maxAtOnce = 100;
	}

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

		this.useJars = useJars;
		if(maxAtOnce == 0)
			this.maxAtOnce = getMaxPartitionBasedOnMemory();
		else
			this.maxAtOnce = maxAtOnce;
	}

	public CK() {
		this(false, 0, true);
	}

	public void calculate(String path, CKNotifier notifier) {
		String[] javaFiles = FileUtils.getAllJavaFiles(path);
		log.info("Found " + javaFiles.length + " java files");

		calculate(Paths.get(path), notifier,
		 	Stream.of(javaFiles)
				.map(Paths::get)
				.toArray(Path[]::new)
			);
	}

	/**
	 * Convenience method to call ck with a path rather than a string
	 * @param path The path which contain the java class files to analyse
	 * @param notifier Handle to process the results and handle errors
	 */
	public void calculate(Path path, CKNotifier notifier) {
		calculate(path.toString(), notifier);
	}

	/**
	 * Calculate metrics for the passed javaFilePaths. Uses path to set the environment
	 * @param path The environment to where the source code is located
	 * @param notifier Handle to process the results and handle errors
	 * @param javaFilePaths The files to collect metrics of.
	 */
	public void calculate(Path path, CKNotifier notifier, Path... javaFilePaths) {
		String[] srcDirs = FileUtils.getAllDirs(path.toString());
		log.info("Found " + srcDirs.length + " src dirs");

		String[] allDependencies = useJars ? FileUtils.getAllJars(path.toString()) : null;

		if(useJars)
			log.info("Found " + allDependencies.length + " jar dependencies");
		
		MetricsExecutor storage = new MetricsExecutor(classLevelMetrics, methodLevelMetrics, notifier);

		// Converts the paths to strings and makes the method support relative paths as well.
		List<String> strJavaFilePaths = Stream.of(javaFilePaths).map(file -> file.isAbsolute() ? file.toString() : path.resolve(file).toString()).collect(Collectors.toList());

		List<List<String>> partitions = Lists.partition(strJavaFilePaths, maxAtOnce);
		log.debug("Max partition size: " + maxAtOnce + ", total partitions=" + partitions.size());

		for(List<String> partition : partitions) {
			log.debug("Next partition");
			ASTParser parser = ASTParser.newParser(AST.JLS11);
			
			parser.setResolveBindings(true);
			parser.setBindingsRecovery(true);
			
			Map<String, String> options = JavaCore.getOptions();
			JavaCore.setComplianceOptions(JavaCore.VERSION_11, options);
			parser.setCompilerOptions(options);
			parser.setEnvironment(allDependencies, srcDirs, null, true);
			parser.createASTs(partition.toArray(new String[partition.size()]), null, new String[0], storage, null);
		}
		
		log.info("Finished parsing");
    }

	private int getMaxPartitionBasedOnMemory() {
		long maxMemory= Runtime.getRuntime().maxMemory() / (1 << 20); // in MiB

		if      (maxMemory >= 2000) return 400;
		else if (maxMemory >= 1500) return 300;
		else if (maxMemory >= 1000) return 200;
		else if (maxMemory >=  500) return 100;
		else                        return 25;
	}

}

In this class, the result of LCOM3 in jpeek 1.1, and in CodeMR 0.4.

Let's take another example, the ResultWriter class:

package com.github.mauricioaniche.ck;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;

public class ResultWriter {

    private static final String[] CLASS_HEADER = {
            "file",
            "class",
            "type",

            /* OO Metrics */
            "cbo",
            "wmc",
            "dit",
            "rfc",
            "lcom",
            "tcc",
            "lcc",

            /* Method Counting */
            "totalMethodsQty",
            "staticMethodsQty",
            "publicMethodsQty",
            "privateMethodsQty",
            "protectedMethodsQty",
            "defaultMethodsQty",
            "visibleMethodsQty",
            "abstractMethodsQty",
            "finalMethodsQty",
            "synchronizedMethodsQty",

            /* Field Counting */
            "totalFieldsQty",
            "staticFieldsQty",
            "publicFieldsQty",
            "privateFieldsQty",
            "protectedFieldsQty",
            "defaultFieldsQty",
            "finalFieldsQty",
            "synchronizedFieldsQty",

            /* Others */
            "nosi",
            "loc",
            "returnQty",
            "loopQty",
            "comparisonsQty",
            "tryCatchQty",
            "parenthesizedExpsQty",
            "stringLiteralsQty",
            "numbersQty",
            "assignmentsQty",
            "mathOperationsQty",
            "variablesQty",
            "maxNestedBlocksQty",
            "anonymousClassesQty",
            "innerClassesQty",
            "lambdasQty",
            "uniqueWordsQty",
            "modifiers",
            "logStatementsQty"};
    private static final String[] METHOD_HEADER = { "file", "class", "method", "constructor", "line", "cbo", "wmc", "rfc", "loc",
            "returnsQty", "variablesQty", "parametersQty", "methodsInvokedQty", "methodsInvokedLocalQty", "methodsInvokedIndirectLocalQty", "loopQty", "comparisonsQty", "tryCatchQty",
            "parenthesizedExpsQty", "stringLiteralsQty", "numbersQty", "assignmentsQty", "mathOperationsQty",
            "maxNestedBlocksQty", "anonymousClassesQty", "innerClassesQty", "lambdasQty", "uniqueWordsQty", "modifiers", "logStatementsQty", "hasJavaDoc" };
    private static final String[] VAR_FIELD_HEADER = { "file", "class", "method", "variable", "usage" };
    private final boolean variablesAndFields;

    private CSVPrinter classPrinter;
    private CSVPrinter methodPrinter;
    private CSVPrinter variablePrinter;
    private CSVPrinter fieldPrinter;

    /**
     * Initialise a new ResultWriter that writes to the specified files. Begins by
     * writing CSV headers to each file.
     * 
     * @param classFile    Output file for class metrics
     * @param methodFile   Output file for method metrics
     * @param variableFile Output file for variable metrics
     * @param fieldFile    Output file for field metrics
     * @throws IOException If headers cannot be written
     */
    public ResultWriter(String classFile, String methodFile, String variableFile, String fieldFile, boolean variablesAndFields) throws IOException {
        FileWriter classOut = new FileWriter(classFile);
        this.classPrinter = new CSVPrinter(classOut, CSVFormat.DEFAULT.withHeader(CLASS_HEADER));
        FileWriter methodOut = new FileWriter(methodFile);
        this.methodPrinter = new CSVPrinter(methodOut, CSVFormat.DEFAULT.withHeader(METHOD_HEADER));

        this.variablesAndFields = variablesAndFields;
        if(variablesAndFields) {
            FileWriter variableOut = new FileWriter(variableFile);
            this.variablePrinter = new CSVPrinter(variableOut, CSVFormat.DEFAULT.withHeader(VAR_FIELD_HEADER));
            FileWriter fieldOut = new FileWriter(fieldFile);
            this.fieldPrinter = new CSVPrinter(fieldOut, CSVFormat.DEFAULT.withHeader(VAR_FIELD_HEADER));
        }
    }

    /**
     * Print results for a single class and its methods and fields to the
     * appropriate CSVPrinters.
     * 
     * @param result The CKClassResult
     * @throws IOException If output files cannot be written to
     */
    public void printResult(CKClassResult result) throws IOException {
        this.classPrinter.printRecord(
                result.getFile(),
                result.getClassName(),
                result.getType(),

                /* OO Metrics */
                result.getCbo(),
                result.getWmc(),
                result.getDit(),
                result.getRfc(),
                result.getLcom(),
                result.getTightClassCohesion(),
                result.getLooseClassCohesion(),

                /* Method Counting */
                result.getNumberOfMethods(),
                result.getNumberOfStaticMethods(),
                result.getNumberOfPublicMethods(),
                result.getNumberOfPrivateMethods(),
                result.getNumberOfProtectedMethods(),
                result.getNumberOfDefaultMethods(),
                result.getVisibleMethods().size(),
                result.getNumberOfAbstractMethods(),
                result.getNumberOfFinalMethods(),
                result.getNumberOfSynchronizedMethods(),

                /* Field Counting */
                result.getNumberOfFields(),
                result.getNumberOfStaticFields(),
                result.getNumberOfPublicFields(),
                result.getNumberOfPrivateFields(),
                result.getNumberOfProtectedFields(),
                result.getNumberOfDefaultFields(),
                result.getNumberOfFinalFields(),
                result.getNumberOfSynchronizedFields(),

                /* Others */
                result.getNosi(),
                result.getLoc(),
                result.getReturnQty(),
                result.getLoopQty(),
                result.getComparisonsQty(),
                result.getTryCatchQty(),
                result.getParenthesizedExpsQty(),
                result.getStringLiteralsQty(),
                result.getNumbersQty(),
                result.getAssignmentsQty(),
                result.getMathOperationsQty(),
                result.getVariablesQty(),
                result.getMaxNestedBlocks(),
                result.getAnonymousClassesQty(),
                result.getInnerClassesQty(),
                result.getLambdasQty(),
                result.getUniqueWordsQty(),
                result.getModifiers(),
                result.getNumberOfLogStatements());

        for (CKMethodResult method : result.getMethods()) {
            this.methodPrinter.printRecord(result.getFile(), result.getClassName(), method.getMethodName(),
                    method.isConstructor(),
                    method.getStartLine(), method.getCbo(), method.getWmc(), method.getRfc(), method.getLoc(),
                    method.getReturnQty(), method.getVariablesQty(), method.getParametersQty(),
                    method.getMethodInvocations().size(), method.getMethodInvocationsLocal().size(), method.getMethodInvocationsIndirectLocal().size(),
                    method.getLoopQty(), method.getComparisonsQty(), method.getTryCatchQty(),
                    method.getParenthesizedExpsQty(), method.getStringLiteralsQty(), method.getNumbersQty(),
                    method.getAssignmentsQty(), method.getMathOperationsQty(), method.getMaxNestedBlocks(),
                    method.getAnonymousClassesQty(), method.getInnerClassesQty(), method.getLambdasQty(),
                    method.getUniqueWordsQty(), method.getModifiers(), method.getLogStatementsQty(), method.getHasJavadoc());

            if(variablesAndFields) {
                for (Map.Entry<String, Integer> entry : method.getVariablesUsage().entrySet()) {
                    this.variablePrinter.printRecord(result.getFile(), result.getClassName(), method.getMethodName(),
                            entry.getKey(), entry.getValue());
                }

                for (Map.Entry<String, Integer> entry : method.getFieldUsage().entrySet()) {
                    this.fieldPrinter.printRecord(result.getFile(), result.getClassName(), method.getMethodName(),
                            entry.getKey(), entry.getValue());
                }
            }
        }
    }

    /**
     * Flush and close resources that were opened to write results. This method
     * should be called after all CKClassResults have been calculated and printed.
     * 
     * @throws IOException If the resources cannot be closed
     */
    public void flushAndClose() throws IOException {
        this.classPrinter.flush();
        this.classPrinter.close();
        this.methodPrinter.flush();
        this.methodPrinter.close();
        if(variablesAndFields) {
            this.variablePrinter.flush();
            this.variablePrinter.close();
            this.fieldPrinter.flush();
            this.fieldPrinter.close();
        }
    }
}

In this class LCOM3 in jpeek shows 0.75 and in CodeMR 0.375.

This project is only a part of our overall comparison, however its classes are quite variative to show the main issues.

michaelwelly added a commit to michaelwelly/jpeek that referenced this issue Jun 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants