OOMetrics.java
/*
* Copyright (c) 2001-2024, Jean Tessier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Jean Tessier nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jeantessier.dependencyfinder.cli;
import com.jeantessier.classreader.AggregatingClassfileLoader;
import com.jeantessier.classreader.ClassfileLoader;
import com.jeantessier.classreader.LoadListenerVisitorAdapter;
import com.jeantessier.classreader.TransientClassfileLoader;
import com.jeantessier.commandline.CommandLineException;
import com.jeantessier.metrics.*;
import org.apache.logging.log4j.*;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.*;
import java.util.*;
public class OOMetrics extends DirectoryExplorerCommand {
public static final String DEFAULT_PROJECT_NAME = "Project";
public static final String DEFAULT_SORT = "name";
protected void populateCommandLineSwitches() {
super.populateCommandLineSwitches();
populateCommandLineSwitchesForXMLOutput(XMLPrinter.DEFAULT_ENCODING, XMLPrinter.DEFAULT_DTD_PREFIX, XMLPrinter.DEFAULT_INDENT_TEXT);
getCommandLine().addSingleValueSwitch("project-name", DEFAULT_PROJECT_NAME);
getCommandLine().addSingleValueSwitch("default-configuration", true);
getCommandLine().addSingleValueSwitch("configuration");
getCommandLine().addToggleSwitch("csv");
getCommandLine().addToggleSwitch("json");
getCommandLine().addToggleSwitch("text");
getCommandLine().addToggleSwitch("txt");
getCommandLine().addToggleSwitch("xml");
getCommandLine().addToggleSwitch("yaml");
getCommandLine().addToggleSwitch("yml");
getCommandLine().addToggleSwitch("validate");
getCommandLine().addToggleSwitch("project");
getCommandLine().addToggleSwitch("groups");
getCommandLine().addToggleSwitch("classes");
getCommandLine().addToggleSwitch("methods");
getCommandLine().addMultipleValuesSwitch("scope-includes-list");
getCommandLine().addMultipleValuesSwitch("scope-excludes-list");
getCommandLine().addMultipleValuesSwitch("filter-includes-list");
getCommandLine().addMultipleValuesSwitch("filter-excludes-list");
getCommandLine().addToggleSwitch("show-all-metrics");
getCommandLine().addToggleSwitch("show-empty-metrics");
getCommandLine().addToggleSwitch("show-hidden-measurements");
getCommandLine().addSingleValueSwitch("sort", DEFAULT_SORT);
getCommandLine().addToggleSwitch("expand");
getCommandLine().addToggleSwitch("reverse");
getCommandLine().addToggleSwitch("enable-cross-class-measurements");
}
protected Collection<CommandLineException> parseCommandLine(String[] args) {
Collection<CommandLineException> exceptions = super.parseCommandLine(args);
if (!getCommandLine().isPresent("project") && !getCommandLine().isPresent("groups") && !getCommandLine().isPresent("classes") && !getCommandLine().isPresent("methods")) {
getCommandLine().getSwitch("project").setValue(true);
getCommandLine().getSwitch("groups").setValue(true);
getCommandLine().getSwitch("classes").setValue(true);
getCommandLine().getSwitch("methods").setValue(true);
}
int modeSwitch = 0;
if (getCommandLine().getToggleSwitch("csv")) {
modeSwitch++;
}
if (getCommandLine().getToggleSwitch("txt")) {
modeSwitch++;
}
if (getCommandLine().getToggleSwitch("text")) {
modeSwitch++;
}
if (getCommandLine().getToggleSwitch("xml")) {
modeSwitch++;
}
if (getCommandLine().getToggleSwitch("json")) {
modeSwitch++;
}
if (getCommandLine().getToggleSwitch("yaml")) {
modeSwitch++;
}
if (getCommandLine().getToggleSwitch("yml")) {
modeSwitch++;
}
if (modeSwitch != 1) {
exceptions.add(new CommandLineException("Must have one and only one of -csv, -json, -text, -txt, -xml, -yml, or -yaml"));
}
return exceptions;
}
protected void doProcessing() throws Exception {
LogManager.getLogger(OOMetrics.class).debug("Reading configuration ...");
getVerboseListener().print("Reading configuration ...");
String projectName = getCommandLine().getSingleSwitch("project-name");
MetricsFactory factory;
if (getCommandLine().isPresent("configuration")) {
factory = new MetricsFactory(projectName, new MetricsConfigurationLoader(getCommandLine().getToggleSwitch("validate")).load(getCommandLine().getSingleSwitch("configuration")));
} else {
factory = new MetricsFactory(projectName, new MetricsConfigurationLoader(getCommandLine().getToggleSwitch("validate")).load(getCommandLine().getSingleSwitch("default-configuration")));
}
MetricsGatherer gatherer = new MetricsGatherer(factory);
if (getCommandLine().isPresent("scope-includes-list") || getCommandLine().isPresent("scope-excludes-list")) {
gatherer.setScopeIncludes(createCollection(getCommandLine().getMultipleSwitch("scope-includes-list"), getCommandLine().getMultipleSwitch("scope-excludes-list")));
}
if (getCommandLine().isPresent("filter-includes-list") || getCommandLine().isPresent("filter-excludes-list")) {
gatherer.setFilterIncludes(createCollection(getCommandLine().getMultipleSwitch("filter-includes-list"), getCommandLine().getMultipleSwitch("filter-excludes-list")));
}
gatherer.addMetricsListener(getVerboseListener());
if (getCommandLine().isPresent("enable-cross-class-measurements")) {
LogManager.getLogger(OOMetrics.class).debug("Reading in all classes ...");
getVerboseListener().print("Reading in all classes ...");
ClassfileLoader loader = new AggregatingClassfileLoader();
loader.addLoadListener(getVerboseListener());
loader.load(getCommandLine().getParameters());
LogManager.getLogger(OOMetrics.class).debug("Computing metrics ...");
getVerboseListener().print("Computing metrics ...");
gatherer.visitClassfiles(loader.getAllClassfiles());
} else {
ClassfileLoader loader = new TransientClassfileLoader();
loader.addLoadListener(getVerboseListener());
loader.addLoadListener(new LoadListenerVisitorAdapter(gatherer));
LogManager.getLogger(OOMetrics.class).debug("Reading classes and computing metrics as we go ...");
getVerboseListener().print("Reading classes and computing metrics as we go ...");
loader.load(getCommandLine().getParameters());
}
if (getCommandLine().isPresent("show-all-metrics")) {
gatherer.getMetricsFactory().getAllClassMetrics().forEach(metrics -> gatherer.getMetricsFactory().includeClassMetrics(metrics));
gatherer.getMetricsFactory().getAllMethodMetrics().forEach(metrics -> gatherer.getMetricsFactory().includeMethodMetrics(metrics));
}
LogManager.getLogger(OOMetrics.class).debug("Printing results ...");
getVerboseListener().print("Printing results ...");
if (getCommandLine().isPresent("csv")) {
printCSVFiles(gatherer.getMetricsFactory());
} else if (getCommandLine().isPresent("json")) {
printJSONFile(gatherer.getMetricsFactory());
} else if (getCommandLine().isPresent("text") || getCommandLine().isPresent("txt")) {
printTextFile(gatherer.getMetricsFactory());
} else if (getCommandLine().isPresent("xml")) {
printXMLFile(gatherer.getMetricsFactory());
} else if (getCommandLine().isPresent("yaml") || getCommandLine().isPresent("yml")) {
printYAMLFile(gatherer.getMetricsFactory());
}
LogManager.getLogger(OOMetrics.class).debug("Done.");
}
private static Collection<String> createCollection(Collection<String> includes, Collection<String> excludes) throws IOException {
Collection<String> result = new HashSet<>();
for (String include : includes) {
result.addAll(Files.readAllLines(Paths.get(include)));
}
for (String exclude : excludes) {
result.removeAll(Files.readAllLines(Paths.get(exclude)));
}
return result;
}
private void printCSVFiles(MetricsFactory factory) throws IOException {
printCSVFile("project", "Project", factory.getConfiguration().getProjectMeasurements(), factory.getProjectMetrics());
printCSVFile("groups", "Groups", factory.getConfiguration().getGroupMeasurements(), factory.getGroupMetrics());
printCSVFile("classes", "Classes", factory.getConfiguration().getClassMeasurements(), factory.getClassMetrics());
printCSVFile("methods", "Methods", factory.getConfiguration().getMethodMeasurements(), factory.getMethodMetrics());
}
private void printCSVFile(String name, String label, List<MeasurementDescriptor> descriptors, Collection<Metrics> metrics) throws IOException {
if (getCommandLine().getToggleSwitch(name)) {
if (getCommandLine().isPresent("out")) {
setOut(new PrintWriter(new FileWriter(getCommandLine().getSingleSwitch("out") + "_" + name + ".csv")));
} else {
getOut().print(label);
getOut().println(":");
}
printMetrics(descriptors, metrics, CSVPrinter.class);
if (getCommandLine().isPresent("out")) {
getOut().close();
}
}
}
private void printJSONFile(MetricsFactory factory) throws IOException {
printMetrics(factory.getProjectMetrics(), new JSONPrinter(getOut(), factory.getConfiguration()));
getOut().close();
}
private void printTextFile(MetricsFactory factory) throws IOException {
printTextFile("project", "Project metrics", factory.getConfiguration().getProjectMeasurements(), factory.getProjectMetrics());
printTextFile("groups", "Group metrics", factory.getConfiguration().getGroupMeasurements(), factory.getGroupMetrics());
printTextFile("classes", "Class metrics", factory.getConfiguration().getClassMeasurements(), factory.getClassMetrics());
printTextFile("methods", "Method metrics", factory.getConfiguration().getMethodMeasurements(), factory.getMethodMetrics());
getOut().close();
}
private void printTextFile(String name, String label, List<MeasurementDescriptor> descriptors, Collection<Metrics> metrics) throws IOException {
if (getCommandLine().getToggleSwitch(name)) {
getOut().println(label);
getOut().println(label.replaceAll(".", "-"));
printMetrics(descriptors, metrics, TextPrinter.class);
getOut().println();
}
}
private void printXMLFile(MetricsFactory factory) throws IOException {
printMetrics(factory.getProjectMetrics(), new XMLPrinter(getOut(), factory.getConfiguration(), getCommandLine().getSingleSwitch("encoding"), getCommandLine().getSingleSwitch("dtd-prefix")));
getOut().close();
}
private void printYAMLFile(MetricsFactory factory) throws IOException {
printMetrics(factory.getProjectMetrics(), new YAMLPrinter(getOut(), factory.getConfiguration()));
getOut().close();
}
private void printMetrics(List<MeasurementDescriptor> descriptors, Collection<Metrics> metrics, Class<? extends Printer> clazz) throws IOException {
try {
printMetrics(metrics, clazz.getConstructor(PrintWriter.class, List.class).newInstance(getOut(), descriptors));
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
throw new RuntimeException(ex);
}
}
private void printMetrics(Collection<Metrics> metrics, Printer printer) {
printer.setExpandCollectionMeasurements(getCommandLine().getToggleSwitch("expand"));
printer.setShowEmptyMetrics(getCommandLine().isPresent("show-empty-metrics"));
printer.setShowHiddenMeasurements(getCommandLine().isPresent("show-hidden-measurements"));
if (getCommandLine().isPresent("indent-text")) {
printer.setIndentText(getCommandLine().getSingleSwitch("indent-text"));
}
var comparator = new MetricsComparator(getCommandLine().getSingleSwitch("sort"));
if (getCommandLine().getToggleSwitch("reverse")) {
comparator.reverse();
}
printer.setComparator(comparator);
printer.visitMetrics(metrics);
}
public static void main(String[] args) throws Exception {
new OOMetrics().run(args);
}
}