ClassMetrics.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 java.util.*;
import java.io.*;
import java.util.stream.*;
import com.jeantessier.classreader.*;
import static java.util.stream.Collectors.*;
public class ClassMetrics extends DirectoryExplorerCommand {
private static final String EOL = System.getProperty("line.separator", "\n");
private boolean list;
protected void populateCommandLineSwitches() {
super.populateCommandLineSwitches();
getCommandLine().addToggleSwitch("list");
getCommandLine().addToggleSwitch("instruction-counts");
}
public void doProcessing() throws Exception {
list = getCommandLine().getToggleSwitch("list");
MetricsGatherer metrics = new MetricsGatherer();
ClassfileLoader loader = new TransientClassfileLoader();
loader.addLoadListener(getVerboseListener());
loader.addLoadListener(new LoadListenerVisitorAdapter(metrics));
loader.load(getCommandLine().getParameters());
getVerboseListener().print("Printing report ...");
getOut().println(metrics.getClasses().size() + " class(es)");
printCollection(metrics.getClasses());
getOut().println(metrics.getInterfaces().size() + " interface(s)");
printCollection(metrics.getInterfaces());
getOut().println();
getOut().println(metrics.getMethods().size() + " method(s) (average " + (metrics.getMethods().size() / (metrics.getClasses().size() + (double) metrics.getInterfaces().size())) + " per class/interface)");
getOut().println(metrics.getFields().size() + " field(s) (average " + (metrics.getFields().size() / (metrics.getClasses().size() + (double) metrics.getInterfaces().size())) + " per class/interface)");
getOut().println();
printCFMIC(" synthetic element(s)", metrics.getSyntheticClasses(), metrics.getSyntheticFields(), metrics.getSyntheticMethods(), metrics.getSyntheticInnerClasses());
printCFM(" deprecated element(s)", metrics.getDeprecatedClasses(), metrics.getDeprecatedFields(), metrics.getDeprecatedMethods());
printCFMIC(" public element(s)", metrics.getPublicClasses(), metrics.getPublicFields(), metrics.getPublicMethods(), metrics.getPublicInnerClasses());
printFMIC(" protected element(s)", metrics.getProtectedFields(), metrics.getProtectedMethods(), metrics.getProtectedInnerClasses());
printFMIC(" private element(s)", metrics.getPrivateFields(), metrics.getPrivateMethods(), metrics.getPrivateInnerClasses());
printCFMIC(" package element(s)", metrics.getPackageClasses(), metrics.getPackageFields(), metrics.getPackageMethods(), metrics.getPackageInnerClasses());
printCMIC(" abstract element(s)", metrics.getAbstractClasses(), metrics.getAbstractMethods(), metrics.getAbstractInnerClasses());
printFMIC(" static element(s)", metrics.getStaticFields(), metrics.getStaticMethods(), metrics.getStaticInnerClasses());
printCFMIC(" final element(s)", metrics.getFinalClasses(), metrics.getFinalFields(), metrics.getFinalMethods(), metrics.getFinalInnerClasses());
printCIC(" annotation element(s)", metrics.getAnnotationClasses(), metrics.getAnnotationInnerClasses());
printCFIC(" enum element(s)", metrics.getEnumClasses(), metrics.getEnumFields(), metrics.getEnumInnerClasses());
getOut().println(metrics.getSuperClasses().size() + " super class(es)");
printCollection(metrics.getSuperClasses());
getOut().println(metrics.getModuleClasses().size() + " module class(es)");
printCollection(metrics.getModuleClasses());
getOut().println(metrics.getSynchronizedMethods().size() + " synchronized method(s)");
printCollection(metrics.getSynchronizedMethods());
getOut().println(metrics.getNativeMethods().size() + " native method(s)");
printCollection(metrics.getNativeMethods());
getOut().println(metrics.getBridgeMethods().size() + " bridge method(s)");
printCollection(metrics.getBridgeMethods());
getOut().println(metrics.getVarargsMethods().size() + " varargs method(s)");
printCollection(metrics.getVarargsMethods());
getOut().println(metrics.getStrictMethods().size() + " strict method(s)");
printCollection(metrics.getStrictMethods());
getOut().println(metrics.getVolatileFields().size() + " volatile field(s)");
printCollection(metrics.getVolatileFields());
getOut().println(metrics.getTransientFields().size() + " transient field(s)");
printCollection(metrics.getTransientFields());
getOut().println(metrics.getConstantPoolEntryCounts().values().stream().reduce(0L, Long::sum) + " constant pool entry(ies)");
if (list) {
getOut().println(
metrics.getConstantPoolEntryCounts().entrySet().stream()
.map(entry -> String.format("%12d %s", entry.getValue(), com.jeantessier.classreader.impl.ConstantPoolEntry.stringValueOf(entry.getKey().byteValue())))
.collect(joining(EOL))
);
}
getOut().println(metrics.getAttributeCounts().values().stream().reduce(0L, Long::sum) + " attribute(s)");
getOut().println(
Arrays.stream(AttributeType.values())
.map(AttributeType::getAttributeName)
.map(name -> String.format("%12d %s attribute(s)", metrics.getAttributeCounts().get(name), name))
.collect(joining(EOL))
);
getOut().format("%12d custom attribute(s)%n", metrics.getCustomAttributes().size());
if (list) {
getOut().println(
metrics.getCustomAttributes().stream()
.collect(groupingBy(Custom_attribute::getName))
.entrySet().stream()
.flatMap(entry -> Stream.concat(
Stream.of(String.format("%16d %s", entry.getValue().size(), entry.getKey())),
entry.getValue().stream()
.collect(groupingBy(attribute -> attribute.getInfo().length))
.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(histoEntry -> String.format("%20dx %s", histoEntry.getValue().size(), histoEntry.getKey() + " bytes"))))
.collect(joining(EOL))
);
}
if (getCommandLine().getToggleSwitch("instruction-counts")) {
getOut().println();
getOut().println("Instruction counts:");
getOut().println(
IntStream.range(0, 256)
.mapToObj(opcode -> String.format(" 0x%02X %s: %d", opcode, com.jeantessier.classreader.impl.Instruction.getMnemonic(opcode), metrics.getInstructionCounts()[opcode]))
.collect(joining(EOL))
);
}
}
private void printCMIC(String label, Collection<Classfile> classes, Collection<Method_info> methods, Collection<InnerClass> innerClasses) throws IOException {
print(label, classes, null, methods, innerClasses);
}
private void printCFMIC(String label, Collection<Classfile> classes, Collection<Field_info> fields, Collection<Method_info> methods, Collection<InnerClass> innerClasses) throws IOException {
print(label, classes, fields, methods, innerClasses);
}
private void printCFM(String label, Collection<Classfile> classes, Collection<Field_info> fields, Collection<Method_info> methods) throws IOException {
print(label, classes, fields, methods, null);
}
private void printCFIC(String label, Collection<Classfile> classes, Collection<Field_info> fields, Collection<InnerClass> innerClasses) throws IOException {
print(label, classes, fields, null, innerClasses);
}
private void printCIC(String label, Collection<Classfile> classes, Collection<InnerClass> innerClasses) throws IOException {
print(label, classes, null, null, innerClasses);
}
private void printFMIC(String label, Collection<Field_info> fields, Collection<Method_info> methods, Collection<InnerClass> innerClasses) throws IOException {
print(label, null, fields, methods, innerClasses);
}
private void print(String label, Collection<Classfile> classes, Collection<Field_info> fields, Collection<Method_info> methods, Collection<InnerClass> innerClasses) throws IOException {
getOut().println(((classes != null ? classes.size() : 0) +
(fields != null ? fields.size() : 0) +
(methods != null ? methods.size() : 0) +
(innerClasses != null ? innerClasses.size() : 0)) + label);
if (classes != null) {
getOut().println(" " + classes.size() + " class(es)");
printCollection(classes);
}
if (fields != null) {
getOut().println(" " + fields.size() + " field(s)");
printCollection(fields);
}
if (methods != null) {
getOut().println(" " + methods.size() + " method(s)");
printCollection(methods);
}
if (innerClasses != null) {
getOut().println(" " + innerClasses.size() + " inner class(es)");
printCollection(innerClasses);
}
}
private void printCollection(Collection<?> collection) throws IOException {
if (list && !collection.isEmpty()) {
getOut().println(
collection.stream()
.map(name -> " " + name)
.collect(joining(EOL))
);
}
}
public static void main(String[] args) throws Exception {
new ClassMetrics().run(args);
}
}