JSONPrinter.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.metrics;
import java.io.*;
import java.util.*;
import static java.util.stream.Collectors.joining;
public class JSONPrinter extends Printer {
private final MetricsConfiguration configuration;
public JSONPrinter(PrintWriter out, MetricsConfiguration configuration) {
super(out);
this.configuration = configuration;
}
public void visitMetrics(Collection<Metrics> metrics) {
visitProjectMetrics(metrics);
eol();
}
public void visitMetrics(Metrics metrics) {
visitProjectMetrics(metrics);
}
private void visitProjectMetrics(Collection<Metrics> metrics) {
if (hasVisibleMetrics(metrics)) {
append("[").eol();
raiseIndent();
indent().append(metrics.stream()
.filter(this::isVisibleMetrics)
.sorted(getComparator())
.map(projectMetrics -> {
StringWriter out = new StringWriter();
clonePrinter(out).visitProjectMetrics(projectMetrics);
return out.toString();
})
.collect(joining(", "))).eol();
lowerIndent();
indent().append("]");
} else {
append("[]");
}
}
private void visitProjectMetrics(Metrics metrics) {
append("{").eol();
raiseIndent();
indent().append("\"name\": \"").append(metrics.getName()).append("\",").eol();
indent().append("\"measurements\": ");
visitMeasurements(metrics, configuration.getProjectMeasurements());
append(",").eol();
indent().append("\"groups\": ");
visitGroupMetrics(metrics.getSubMetrics());
eol();
lowerIndent();
indent().append("}");
}
private void visitGroupMetrics(Collection<Metrics> metrics) {
if (hasVisibleMetrics(metrics)) {
append("[").eol();
raiseIndent();
indent().append(metrics.stream()
.filter(this::isVisibleMetrics)
.sorted(getComparator())
.map(groupMetrics -> {
StringWriter out = new StringWriter();
clonePrinter(out).visitGroupMetrics(groupMetrics);
return out.toString();
})
.collect(joining(", "))).eol();
lowerIndent();
indent().append("]");
} else {
append("[]");
}
}
private void visitGroupMetrics(Metrics metrics) {
append("{").eol();
raiseIndent();
indent().append("\"name\": \"").append(metrics.getName()).append("\",").eol();
indent().append("\"measurements\": ");
visitMeasurements(metrics, configuration.getGroupMeasurements());
append(",").eol();
indent().append("\"classes\": ");
visitClassMetrics(metrics.getSubMetrics());
eol();
lowerIndent();
indent().append("}");
}
private void visitClassMetrics(Collection<Metrics> metrics) {
if (hasVisibleMetrics(metrics)) {
append("[").eol();
raiseIndent();
indent().append(metrics.stream()
.filter(this::isVisibleMetrics)
.sorted(getComparator())
.map(classMetrics -> {
StringWriter out = new StringWriter();
clonePrinter(out).visitClassMetrics(classMetrics);
return out.toString();
})
.collect(joining(", "))).eol();
lowerIndent();
indent().append("]");
} else {
append("[]");
}
}
private void visitClassMetrics(Metrics metrics) {
append("{").eol();
raiseIndent();
indent().append("\"name\": \"").append(metrics.getName()).append("\",").eol();
indent().append("\"measurements\": ");
visitMeasurements(metrics, configuration.getClassMeasurements());
append(",").eol();
indent().append("\"methods\": ");
visitMethodMetrics(metrics.getSubMetrics());
eol();
lowerIndent();
indent().append("}");
}
private void visitMethodMetrics(Collection<Metrics> metrics) {
if (hasVisibleMetrics(metrics)) {
append("[").eol();
raiseIndent();
indent().append(metrics.stream()
.filter(this::isVisibleMetrics)
.sorted(getComparator())
.map(methodMetrics -> {
StringWriter out = new StringWriter();
clonePrinter(out).visitMethodMetrics(methodMetrics);
return out.toString();
})
.collect(joining(", "))).eol();
lowerIndent();
indent().append("]");
} else {
append("[]");
}
}
private void visitMethodMetrics(Metrics metrics) {
append("{").eol();
raiseIndent();
indent().append("\"name\": \"").append(metrics.getName()).append("\",").eol();
indent().append("\"measurements\": ");
visitMeasurements(metrics, configuration.getMethodMeasurements());
eol();
lowerIndent();
indent().append("}");
}
protected void visitMeasurements(Metrics metrics, List<MeasurementDescriptor> descriptors) {
if (hasVisibleMeasurements(descriptors)) {
append("[").eol();
raiseIndent();
indent().append(descriptors.stream()
.filter(this::isVisibleMeasurement)
.map(descriptor -> metrics.getMeasurement(descriptor.getShortName()))
.map(measurement -> {
StringWriter out = new StringWriter();
measurement.accept(clonePrinter(out));
return out.toString();
})
.collect(joining(", "))).eol();
lowerIndent();
indent().append("]");
} else {
append("[]");
}
}
public void visitStatisticalMeasurement(StatisticalMeasurement measurement) {
append("{").eol();
raiseIndent();
indent().append("\"short-name\": \"").append(measurement.getShortName()).append("\",").eol();
indent().append("\"long-name\": \"").append(measurement.getLongName()).append("\",").eol();
indent().append("\"value\": ").append(formatValue(measurement.getValue())).append(",").eol();
indent().append("\"minimum\": ").append(formatValue(measurement.getMinimum())).append(",").eol();
indent().append("\"median\": ").append(formatValue(measurement.getMedian())).append(",").eol();
indent().append("\"average\": ").append(formatValue(measurement.getAverage())).append(",").eol();
indent().append("\"standard-deviation\": ").append(formatValue(measurement.getStandardDeviation())).append(",").eol();
indent().append("\"maximum\": ").append(formatValue(measurement.getMaximum())).append(",").eol();
indent().append("\"sum\": ").append(formatValue(measurement.getSum())).append(",").eol();
var requestedPercentiles = measurement.getRequestedPercentiles();
if (requestedPercentiles.isEmpty()) {
indent().append("\"nb-data-points\": ").append(measurement.getNbDataPoints()).eol();
} else {
indent().append("\"nb-data-points\": ").append(measurement.getNbDataPoints()).append(",").eol();
indent().append("\"percentiles\": {").eol();
raiseIndent();
var i = requestedPercentiles.iterator();
while (i.hasNext()) {
var percentile = i.next();
indent();
append("\"p").append(percentile).append("\": ").append(formatValue(measurement.getPercentile(percentile)));
if (i.hasNext()) {
append(",");
}
eol();
}
lowerIndent();
indent().append("}").eol();
}
lowerIndent();
indent().append("}");
}
public void visitContextAccumulatorMeasurement(ContextAccumulatorMeasurement measurement) {
visitCollectionMeasurement(measurement);
}
public void visitNameListMeasurement(NameListMeasurement measurement) {
visitCollectionMeasurement(measurement);
}
public void visitSubMetricsAccumulatorMeasurement(SubMetricsAccumulatorMeasurement measurement) {
visitCollectionMeasurement(measurement);
}
protected void visitCollectionMeasurement(CollectionMeasurement measurement) {
append("{").eol();
raiseIndent();
indent().append("\"short-name\": \"").append(measurement.getShortName()).append("\",").eol();
indent().append("\"long-name\": \"").append(measurement.getLongName()).append("\",").eol();
if (isExpandCollectionMeasurements()) {
indent().append("\"value\": ").append(formatValue(measurement.getValue())).append(",").eol();
indent().append("\"members\": [").append(measurement.getValues().stream().sorted().map(value -> "\"" + value + "\"").collect(joining(", "))).append("]").eol();
} else {
indent().append("\"value\": ").append(formatValue(measurement.getValue())).eol();
}
lowerIndent();
indent().append("}");
}
protected void visitMeasurement(Measurement measurement) {
append("{").eol();
raiseIndent();
indent().append("\"short-name\": \"").append(measurement.getShortName()).append("\",").eol();
indent().append("\"long-name\": \"").append(measurement.getLongName()).append("\",").eol();
indent().append("\"value\": ").append(formatValue(measurement.getValue())).eol();
lowerIndent();
indent().append("}");
}
private String formatValue(Number value) {
if (value == null) {
return "null";
}
return formatValue(value.doubleValue());
}
private String formatValue(double value) {
if (Double.isNaN(value) || Double.isInfinite(value)) {
return "null";
}
return String.valueOf(value);
}
private JSONPrinter clonePrinter(StringWriter out) {
JSONPrinter result = new JSONPrinter(new PrintWriter(out), configuration);
result.setIndentLevel(getIndentLevel());
result.setIndentText(getIndentText());
result.setShowEmptyMetrics(isShowEmptyMetrics());
result.setShowEmptyMetrics(isShowEmptyMetrics());
result.setExpandCollectionMeasurements(isExpandCollectionMeasurements());
result.setComparator(getComparator());
return result;
}
}