JSONMetricsReport.java
/*
* Copyright (c) 2001-2025, 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.dependency;
import java.io.*;
import java.util.*;
import static java.util.stream.Collectors.*;
public class JSONMetricsReport extends MetricsReport {
public JSONMetricsReport(PrintWriter out) {
super(out);
}
public void process(MetricsGatherer metrics) {
print("{");
printProgrammingElementStats(metrics);
print(",");
printDependencyStats(metrics);
if (isShowingClassesPerPackageHistogram() ||
isShowingFeaturesPerClassHistogram() ||
isShowingInboundsPerPackageHistogram() ||
isShowingOutboundsPerPackageHistogram() ||
isShowingInboundsPerClassHistogram() ||
isShowingOutboundsPerClassHistogram() ||
isShowingInboundsPerFeatureHistogram() ||
isShowingOutboundsPerFeatureHistogram()) {
print(",");
printHistograms(metrics);
}
if (isShowingClassesPerPackageChart() ||
isShowingFeaturesPerClassChart() ||
isShowingInboundsPerPackageChart() ||
isShowingOutboundsPerPackageChart() ||
isShowingInboundsPerClassChart() ||
isShowingOutboundsPerClassChart() ||
isShowingInboundsPerFeatureChart() ||
isShowingOutboundsPerFeatureChart()) {
print(",");
printChart(metrics);
}
print("}");
println();
}
private void printProgrammingElementStats(MetricsGatherer metrics) {
printProgrammingElementStats("packages", metrics.getPackages());
print(",");
printProgrammingElementStats("classes", metrics.getClasses());
print(",");
printProgrammingElementStats("features", metrics.getFeatures());
}
private void printProgrammingElementStats(String label, Collection<? extends Node> nodes) {
var nbElements = nodes.size();
var nbConfirmedElements = countConfirmedNodes(nodes);
var ratio = nbConfirmedElements / (double) nbElements;
print("\"" + label + "\":{");
print("\"count\":" + nbElements);
print(",");
print("\"confirmed\":" + nbConfirmedElements);
print(",");
print("\"ratio\":" + formatValue(ratio));
if (isListingElements()) {
print(",");
print("\"elements\": [");
print(renderNodes(nodes));
print("]");
}
print("}");
}
private String renderNodes(Collection<? extends Node> nodes) {
return nodes.stream()
.sorted()
.map(node -> "{\"name\":\"" + node.getName() + "\",\"simpleName\":\"" + node.getSimpleName() + "\",\"confirmed\":" + node.isConfirmed() + "}")
.collect(joining(","));
}
private void printDependencyStats(MetricsGatherer metrics) {
print("\"outbounds\": {");
print("\"packages\":" + metrics.getNbOutboundPackages() + ",");
print("\"packageRatio\":" + formatValue(metrics.getNbOutboundPackages() / (double) metrics.getPackages().size()) + ",");
print("\"classes\":" + metrics.getNbOutboundClasses() + ",");
print("\"classRatio\":" + formatValue(metrics.getNbOutboundClasses() / (double) metrics.getClasses().size()) + ",");
print("\"features\":" + metrics.getNbOutboundFeatures() + ",");
print("\"featureRatio\":" + formatValue(metrics.getNbOutboundFeatures() / (double) metrics.getFeatures().size()) + ",");
print("\"total\":" + metrics.getNbOutbound());
print("}");
print(",");
print("\"inbounds\": {");
print("\"packages\":" + metrics.getNbInboundPackages() + ",");
print("\"packageRatio\":" + formatValue(metrics.getNbInboundPackages() / (double) metrics.getPackages().size()) + ",");
print("\"classes\":" + metrics.getNbInboundClasses() + ",");
print("\"classRatio\":" + formatValue(metrics.getNbInboundClasses() / (double) metrics.getClasses().size()) + ",");
print("\"features\":" + metrics.getNbInboundFeatures() + ",");
print("\"featureRatio\":" + formatValue(metrics.getNbInboundFeatures() / (double) metrics.getFeatures().size()) + ",");
print("\"total\":" + metrics.getNbInbound());
print("}");
}
private void printHistograms(MetricsGatherer metrics) {
var histograms = new HashMap<String, Map<Long, Long>>();
if (isShowingClassesPerPackageHistogram()) {
histograms.put("classesPerPackage", metrics.getHistogram(MetricsGatherer.CLASSES_PER_PACKAGE));
}
if (isShowingFeaturesPerClassHistogram()) {
histograms.put("featuresPerClass", metrics.getHistogram(MetricsGatherer.FEATURES_PER_CLASS));
}
if (isShowingInboundsPerPackageHistogram()) {
histograms.put("inboundsPerPackage", metrics.getHistogram(MetricsGatherer.INBOUNDS_PER_PACKAGE));
}
if (isShowingOutboundsPerPackageHistogram()) {
histograms.put("outboundsPerPackage", metrics.getHistogram(MetricsGatherer.OUTBOUNDS_PER_PACKAGE));
}
if (isShowingInboundsPerClassHistogram()) {
histograms.put("inboundsPerClass", metrics.getHistogram(MetricsGatherer.OUTBOUNDS_PER_CLASS));
}
if (isShowingOutboundsPerClassHistogram()) {
histograms.put("outboundsPerClass", metrics.getHistogram(MetricsGatherer.OUTBOUNDS_PER_CLASS));
}
if (isShowingInboundsPerFeatureHistogram()) {
histograms.put("inboundsPerFeature", metrics.getHistogram(MetricsGatherer.OUTBOUNDS_PER_FEATURE));
}
if (isShowingOutboundsPerFeatureHistogram()) {
histograms.put("outboundsPerFeature", metrics.getHistogram(MetricsGatherer.OUTBOUNDS_PER_FEATURE));
}
print("\"histograms\":{");
print(
histograms.entrySet().stream()
.map(entry ->
"\"" + entry.getKey() + "\":[" + new TreeMap<>(entry.getValue()).entrySet().stream().map(pair -> "[" + pair.getKey() + "," + pair.getValue() + "]").collect(joining(",")) + "]")
.collect(joining(","))
);
print("}");
}
private void printChart(MetricsGatherer metrics) {
print("\"chart\":[");
// Headings
print("[");
print("\"n\"");
if (isShowingClassesPerPackageChart()) {
print(",\"classes per package\"");
}
if (isShowingFeaturesPerClassChart()) {
print(",\"features per class\"");
}
if (isShowingInboundsPerPackageChart()) {
print(",\"inbounds per package\"");
}
if (isShowingOutboundsPerPackageChart()) {
print(",\"outbounds per package\"");
}
if (isShowingInboundsPerClassChart()) {
print(",\"inbounds per class\"");
}
if (isShowingOutboundsPerClassChart()) {
print(",\"outbounds per class\"");
}
if (isShowingInboundsPerFeatureChart()) {
print(",\"inbounds per feature\"");
}
if (isShowingOutboundsPerFeatureChart()) {
print(",\"outbounds per feature\"");
}
print("]");
// Data
for (int k=0; k<=metrics.getChartMaximum(); k++) {
long[] dataPoint = metrics.getChartData(k);
print(",[");
print(k);
if (isShowingClassesPerPackageChart()) {
print("," + dataPoint[MetricsGatherer.CLASSES_PER_PACKAGE]);
}
if (isShowingFeaturesPerClassChart()) {
print("," + dataPoint[MetricsGatherer.FEATURES_PER_CLASS]);
}
if (isShowingInboundsPerPackageChart()) {
print("," + dataPoint[MetricsGatherer.INBOUNDS_PER_PACKAGE]);
}
if (isShowingOutboundsPerPackageChart()) {
print("," + dataPoint[MetricsGatherer.OUTBOUNDS_PER_PACKAGE]);
}
if (isShowingInboundsPerClassChart()) {
print("," + dataPoint[MetricsGatherer.INBOUNDS_PER_CLASS]);
}
if (isShowingOutboundsPerClassChart()) {
print("," + dataPoint[MetricsGatherer.OUTBOUNDS_PER_CLASS]);
}
if (isShowingInboundsPerFeatureChart()) {
print("," + dataPoint[MetricsGatherer.INBOUNDS_PER_FEATURE]);
}
if (isShowingOutboundsPerFeatureChart()) {
print("," + dataPoint[MetricsGatherer.OUTBOUNDS_PER_FEATURE]);
}
print("]");
}
print("]");
}
private String formatValue(double value) {
if (Double.isNaN(value) || Double.isInfinite(value)) {
return "null";
}
return String.valueOf(value);
}
}