OOMetrics.java

  1. /*
  2.  *  Copyright (c) 2001-2024, Jean Tessier
  3.  *  All rights reserved.
  4.  *  
  5.  *  Redistribution and use in source and binary forms, with or without
  6.  *  modification, are permitted provided that the following conditions
  7.  *  are met:
  8.  *  
  9.  *      * Redistributions of source code must retain the above copyright
  10.  *        notice, this list of conditions and the following disclaimer.
  11.  *  
  12.  *      * Redistributions in binary form must reproduce the above copyright
  13.  *        notice, this list of conditions and the following disclaimer in the
  14.  *        documentation and/or other materials provided with the distribution.
  15.  *  
  16.  *      * Neither the name of Jean Tessier nor the names of his contributors
  17.  *        may be used to endorse or promote products derived from this software
  18.  *        without specific prior written permission.
  19.  *  
  20.  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21.  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22.  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23.  *  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
  24.  *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25.  *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26.  *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27.  *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28.  *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29.  *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30.  *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31.  */

  32. package com.jeantessier.dependencyfinder.cli;

  33. import com.jeantessier.classreader.AggregatingClassfileLoader;
  34. import com.jeantessier.classreader.ClassfileLoader;
  35. import com.jeantessier.classreader.LoadListenerVisitorAdapter;
  36. import com.jeantessier.classreader.TransientClassfileLoader;
  37. import com.jeantessier.commandline.CommandLineException;
  38. import com.jeantessier.metrics.*;
  39. import org.apache.logging.log4j.*;

  40. import java.io.*;
  41. import java.lang.reflect.*;
  42. import java.nio.file.*;
  43. import java.util.*;

  44. public class OOMetrics extends DirectoryExplorerCommand {
  45.     public static final String DEFAULT_PROJECT_NAME = "Project";
  46.     public static final String DEFAULT_SORT = "name";

  47.     protected void populateCommandLineSwitches() {
  48.         super.populateCommandLineSwitches();
  49.         populateCommandLineSwitchesForXMLOutput(XMLPrinter.DEFAULT_ENCODING, XMLPrinter.DEFAULT_DTD_PREFIX, XMLPrinter.DEFAULT_INDENT_TEXT);

  50.         getCommandLine().addSingleValueSwitch("project-name", DEFAULT_PROJECT_NAME);
  51.         getCommandLine().addSingleValueSwitch("default-configuration", true);
  52.         getCommandLine().addSingleValueSwitch("configuration");
  53.         getCommandLine().addToggleSwitch("csv");
  54.         getCommandLine().addToggleSwitch("json");
  55.         getCommandLine().addToggleSwitch("text");
  56.         getCommandLine().addToggleSwitch("txt");
  57.         getCommandLine().addToggleSwitch("xml");
  58.         getCommandLine().addToggleSwitch("yaml");
  59.         getCommandLine().addToggleSwitch("yml");
  60.         getCommandLine().addToggleSwitch("validate");
  61.         getCommandLine().addToggleSwitch("project");
  62.         getCommandLine().addToggleSwitch("groups");
  63.         getCommandLine().addToggleSwitch("classes");
  64.         getCommandLine().addToggleSwitch("methods");
  65.         getCommandLine().addMultipleValuesSwitch("scope-includes-list");
  66.         getCommandLine().addMultipleValuesSwitch("scope-excludes-list");
  67.         getCommandLine().addMultipleValuesSwitch("filter-includes-list");
  68.         getCommandLine().addMultipleValuesSwitch("filter-excludes-list");
  69.         getCommandLine().addToggleSwitch("show-all-metrics");
  70.         getCommandLine().addToggleSwitch("show-empty-metrics");
  71.         getCommandLine().addToggleSwitch("show-hidden-measurements");
  72.         getCommandLine().addSingleValueSwitch("sort", DEFAULT_SORT);
  73.         getCommandLine().addToggleSwitch("expand");
  74.         getCommandLine().addToggleSwitch("reverse");
  75.         getCommandLine().addToggleSwitch("enable-cross-class-measurements");
  76.     }

  77.     protected Collection<CommandLineException> parseCommandLine(String[] args) {
  78.         Collection<CommandLineException> exceptions = super.parseCommandLine(args);

  79.         if (!getCommandLine().isPresent("project") && !getCommandLine().isPresent("groups") && !getCommandLine().isPresent("classes") && !getCommandLine().isPresent("methods")) {
  80.             getCommandLine().getSwitch("project").setValue(true);
  81.             getCommandLine().getSwitch("groups").setValue(true);
  82.             getCommandLine().getSwitch("classes").setValue(true);
  83.             getCommandLine().getSwitch("methods").setValue(true);
  84.         }

  85.         int modeSwitch = 0;

  86.         if (getCommandLine().getToggleSwitch("csv")) {
  87.             modeSwitch++;
  88.         }
  89.         if (getCommandLine().getToggleSwitch("txt")) {
  90.             modeSwitch++;
  91.         }
  92.         if (getCommandLine().getToggleSwitch("text")) {
  93.             modeSwitch++;
  94.         }
  95.         if (getCommandLine().getToggleSwitch("xml")) {
  96.             modeSwitch++;
  97.         }
  98.         if (getCommandLine().getToggleSwitch("json")) {
  99.             modeSwitch++;
  100.         }
  101.         if (getCommandLine().getToggleSwitch("yaml")) {
  102.             modeSwitch++;
  103.         }
  104.         if (getCommandLine().getToggleSwitch("yml")) {
  105.             modeSwitch++;
  106.         }
  107.         if (modeSwitch != 1) {
  108.             exceptions.add(new CommandLineException("Must have one and only one of -csv, -json, -text, -txt, -xml, -yml, or -yaml"));
  109.         }

  110.         return exceptions;
  111.     }

  112.     protected void doProcessing() throws Exception {
  113.         LogManager.getLogger(OOMetrics.class).debug("Reading configuration ...");
  114.         getVerboseListener().print("Reading configuration ...");

  115.         String projectName = getCommandLine().getSingleSwitch("project-name");

  116.         MetricsFactory factory;
  117.         if (getCommandLine().isPresent("configuration")) {
  118.             factory = new MetricsFactory(projectName, new MetricsConfigurationLoader(getCommandLine().getToggleSwitch("validate")).load(getCommandLine().getSingleSwitch("configuration")));
  119.         } else {
  120.             factory = new MetricsFactory(projectName, new MetricsConfigurationLoader(getCommandLine().getToggleSwitch("validate")).load(getCommandLine().getSingleSwitch("default-configuration")));
  121.         }

  122.         MetricsGatherer gatherer = new MetricsGatherer(factory);
  123.         if (getCommandLine().isPresent("scope-includes-list") || getCommandLine().isPresent("scope-excludes-list")) {
  124.             gatherer.setScopeIncludes(createCollection(getCommandLine().getMultipleSwitch("scope-includes-list"), getCommandLine().getMultipleSwitch("scope-excludes-list")));
  125.         }
  126.         if (getCommandLine().isPresent("filter-includes-list") || getCommandLine().isPresent("filter-excludes-list")) {
  127.             gatherer.setFilterIncludes(createCollection(getCommandLine().getMultipleSwitch("filter-includes-list"), getCommandLine().getMultipleSwitch("filter-excludes-list")));
  128.         }
  129.         gatherer.addMetricsListener(getVerboseListener());

  130.         if (getCommandLine().isPresent("enable-cross-class-measurements")) {
  131.             LogManager.getLogger(OOMetrics.class).debug("Reading in all classes ...");
  132.             getVerboseListener().print("Reading in all classes ...");
  133.             ClassfileLoader loader = new AggregatingClassfileLoader();
  134.             loader.addLoadListener(getVerboseListener());
  135.             loader.load(getCommandLine().getParameters());

  136.             LogManager.getLogger(OOMetrics.class).debug("Computing metrics ...");
  137.             getVerboseListener().print("Computing metrics ...");
  138.             gatherer.visitClassfiles(loader.getAllClassfiles());
  139.         } else {
  140.             ClassfileLoader loader = new TransientClassfileLoader();
  141.             loader.addLoadListener(getVerboseListener());
  142.             loader.addLoadListener(new LoadListenerVisitorAdapter(gatherer));

  143.             LogManager.getLogger(OOMetrics.class).debug("Reading classes and computing metrics as we go ...");
  144.             getVerboseListener().print("Reading classes and computing metrics as we go ...");
  145.             loader.load(getCommandLine().getParameters());
  146.         }

  147.         if (getCommandLine().isPresent("show-all-metrics")) {
  148.             gatherer.getMetricsFactory().getAllClassMetrics().forEach(metrics -> gatherer.getMetricsFactory().includeClassMetrics(metrics));
  149.             gatherer.getMetricsFactory().getAllMethodMetrics().forEach(metrics -> gatherer.getMetricsFactory().includeMethodMetrics(metrics));
  150.         }

  151.         LogManager.getLogger(OOMetrics.class).debug("Printing results ...");
  152.         getVerboseListener().print("Printing results ...");

  153.         if (getCommandLine().isPresent("csv")) {
  154.             printCSVFiles(gatherer.getMetricsFactory());
  155.         } else if (getCommandLine().isPresent("json")) {
  156.             printJSONFile(gatherer.getMetricsFactory());
  157.         } else if (getCommandLine().isPresent("text") || getCommandLine().isPresent("txt")) {
  158.             printTextFile(gatherer.getMetricsFactory());
  159.         } else if (getCommandLine().isPresent("xml")) {
  160.             printXMLFile(gatherer.getMetricsFactory());
  161.         } else if (getCommandLine().isPresent("yaml") || getCommandLine().isPresent("yml")) {
  162.             printYAMLFile(gatherer.getMetricsFactory());
  163.         }

  164.         LogManager.getLogger(OOMetrics.class).debug("Done.");
  165.     }

  166.     private static Collection<String> createCollection(Collection<String> includes, Collection<String> excludes) throws IOException {
  167.         Collection<String> result = new HashSet<>();

  168.         for (String include : includes) {
  169.             result.addAll(Files.readAllLines(Paths.get(include)));
  170.         }

  171.         for (String exclude : excludes) {
  172.             result.removeAll(Files.readAllLines(Paths.get(exclude)));
  173.         }

  174.         return result;
  175.     }

  176.     private void printCSVFiles(MetricsFactory factory) throws IOException {
  177.         printCSVFile("project", "Project", factory.getConfiguration().getProjectMeasurements(), factory.getProjectMetrics());
  178.         printCSVFile("groups",  "Groups",  factory.getConfiguration().getGroupMeasurements(),   factory.getGroupMetrics());
  179.         printCSVFile("classes", "Classes", factory.getConfiguration().getClassMeasurements(),   factory.getClassMetrics());
  180.         printCSVFile("methods", "Methods", factory.getConfiguration().getMethodMeasurements(),  factory.getMethodMetrics());
  181.     }

  182.     private void printCSVFile(String name, String label, List<MeasurementDescriptor> descriptors, Collection<Metrics> metrics) throws IOException {
  183.         if (getCommandLine().getToggleSwitch(name)) {
  184.             if (getCommandLine().isPresent("out")) {
  185.                 setOut(new PrintWriter(new FileWriter(getCommandLine().getSingleSwitch("out") + "_" + name + ".csv")));
  186.             } else {
  187.                 getOut().print(label);
  188.                 getOut().println(":");
  189.             }

  190.             printMetrics(descriptors, metrics, CSVPrinter.class);

  191.             if (getCommandLine().isPresent("out")) {
  192.                 getOut().close();
  193.             }
  194.         }
  195.     }

  196.     private void printJSONFile(MetricsFactory factory) throws IOException {
  197.         printMetrics(factory.getProjectMetrics(), new JSONPrinter(getOut(), factory.getConfiguration()));
  198.         getOut().close();
  199.     }

  200.     private void printTextFile(MetricsFactory factory) throws IOException {
  201.         printTextFile("project", "Project metrics", factory.getConfiguration().getProjectMeasurements(), factory.getProjectMetrics());
  202.         printTextFile("groups",  "Group metrics",   factory.getConfiguration().getGroupMeasurements(),   factory.getGroupMetrics());
  203.         printTextFile("classes", "Class metrics",   factory.getConfiguration().getClassMeasurements(),   factory.getClassMetrics());
  204.         printTextFile("methods", "Method metrics",  factory.getConfiguration().getMethodMeasurements(),  factory.getMethodMetrics());
  205.         getOut().close();
  206.     }

  207.     private void printTextFile(String name, String label, List<MeasurementDescriptor> descriptors, Collection<Metrics> metrics) throws IOException {
  208.         if (getCommandLine().getToggleSwitch(name)) {
  209.             getOut().println(label);
  210.             getOut().println(label.replaceAll(".", "-"));
  211.             printMetrics(descriptors, metrics, TextPrinter.class);
  212.             getOut().println();
  213.         }
  214.     }

  215.     private void printXMLFile(MetricsFactory factory) throws IOException {
  216.         printMetrics(factory.getProjectMetrics(), new XMLPrinter(getOut(), factory.getConfiguration(), getCommandLine().getSingleSwitch("encoding"), getCommandLine().getSingleSwitch("dtd-prefix")));
  217.         getOut().close();
  218.     }

  219.     private void printYAMLFile(MetricsFactory factory) throws IOException {
  220.         printMetrics(factory.getProjectMetrics(), new YAMLPrinter(getOut(), factory.getConfiguration()));
  221.         getOut().close();
  222.     }

  223.     private void printMetrics(List<MeasurementDescriptor> descriptors, Collection<Metrics> metrics, Class<? extends Printer> clazz) throws IOException {
  224.         try {
  225.             printMetrics(metrics, clazz.getConstructor(PrintWriter.class, List.class).newInstance(getOut(), descriptors));
  226.         } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
  227.             throw new RuntimeException(ex);
  228.         }
  229.     }

  230.     private void printMetrics(Collection<Metrics> metrics, Printer printer) {
  231.         printer.setExpandCollectionMeasurements(getCommandLine().getToggleSwitch("expand"));
  232.         printer.setShowEmptyMetrics(getCommandLine().isPresent("show-empty-metrics"));
  233.         printer.setShowHiddenMeasurements(getCommandLine().isPresent("show-hidden-measurements"));
  234.         if (getCommandLine().isPresent("indent-text")) {
  235.             printer.setIndentText(getCommandLine().getSingleSwitch("indent-text"));
  236.         }

  237.         var comparator = new MetricsComparator(getCommandLine().getSingleSwitch("sort"));
  238.         if (getCommandLine().getToggleSwitch("reverse")) {
  239.             comparator.reverse();
  240.         }
  241.         printer.setComparator(comparator);

  242.         printer.visitMetrics(metrics);
  243.     }

  244.     public static void main(String[] args) throws Exception {
  245.         new OOMetrics().run(args);
  246.     }
  247. }