From 6144897e26df7f8e3a685be8c7b7e39206936ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Henning?= <stu114708@informatik.uni-kiel.de> Date: Mon, 20 Jun 2016 18:40:04 +0200 Subject: [PATCH] refactored export factory --- .../analysis/TraceAnalysisConfiguration.java | 17 +- ...otDependencyGraphConfigurationFactory.java | 147 ------------------ .../dot/ClusterLabelMapper.java | 22 +++ .../DotComponentsDependencyExportStage.java | 3 +- .../DotContainersDependencyExportStage.java | 3 +- .../DotDependencyGraphExporterFactory.java | 73 +++++++++ .../DotOperationsDependencyExportStage.java | 3 +- .../dot/VertexLabelMapper.java | 63 ++++++++ 8 files changed, 173 insertions(+), 158 deletions(-) delete mode 100644 src/main/java/kieker/analysis/dev/dependencygraphs/DotDependencyGraphConfigurationFactory.java create mode 100644 src/main/java/kieker/analysis/dev/dependencygraphs/dot/ClusterLabelMapper.java rename src/main/java/kieker/analysis/dev/dependencygraphs/{ => dot}/DotComponentsDependencyExportStage.java (95%) rename src/main/java/kieker/analysis/dev/dependencygraphs/{ => dot}/DotContainersDependencyExportStage.java (95%) create mode 100644 src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotDependencyGraphExporterFactory.java rename src/main/java/kieker/analysis/dev/dependencygraphs/{ => dot}/DotOperationsDependencyExportStage.java (95%) create mode 100644 src/main/java/kieker/analysis/dev/dependencygraphs/dot/VertexLabelMapper.java diff --git a/src/main/java/kieker/analysis/TraceAnalysisConfiguration.java b/src/main/java/kieker/analysis/TraceAnalysisConfiguration.java index f34d1c55..6419bec8 100644 --- a/src/main/java/kieker/analysis/TraceAnalysisConfiguration.java +++ b/src/main/java/kieker/analysis/TraceAnalysisConfiguration.java @@ -13,9 +13,7 @@ import kieker.analysis.dev.dependencygraphs.AssemblyDependencyGraphCreatorStage; import kieker.analysis.dev.dependencygraphs.AssemblyDependencyGraphLevel; import kieker.analysis.dev.dependencygraphs.DeploymentDependencyGraphCreatorStage; import kieker.analysis.dev.dependencygraphs.DeploymentDependencyGraphLevel; -import kieker.analysis.dev.dependencygraphs.DotComponentsDependencyExportStage; -import kieker.analysis.dev.dependencygraphs.DotContainersDependencyExportStage; -import kieker.analysis.dev.dependencygraphs.DotOperationsDependencyExportStage; +import kieker.analysis.dev.dependencygraphs.dot.DotDependencyGraphExporterFactory; import kieker.analysis.domain.AggregatedTrace; import kieker.analysis.domain.Trace; import kieker.analysis.domain.systemdependency.SoftwareSystem; @@ -28,6 +26,7 @@ import kieker.analysis.trace.graphoutput.DotTraceGraphFileWriterStage; import kieker.analysis.trace.traversal.AggrTraceTraverserStage; import kieker.analysis.trace.traversal.TraceTraverserStage; import kieker.analysis.util.graph.Graph; +import kieker.analysis.util.graph.export.dot.DotFileWriterStage; import kieker.analysis.util.graph.export.graphml.GraphMLFileWriterStage; import kieker.common.record.IMonitoringRecord; @@ -51,6 +50,8 @@ public class TraceAnalysisConfiguration extends Configuration { public TraceAnalysisConfiguration(final File importDirectory) { + final DotDependencyGraphExporterFactory dotDependencyGraphExporterFactory = new DotDependencyGraphExporterFactory(); + // Create the stages final ReadingComposite reader = new ReadingComposite(importDirectory); final MultipleInstanceOfFilter<IMonitoringRecord> typeFilter = new MultipleInstanceOfFilter<>(); @@ -108,11 +109,11 @@ public class TraceAnalysisConfiguration extends Configuration { Distributor<SoftwareSystem> x2SoftwareSystemDistributor = new Distributor<>(new CopyByReferenceStrategy()); // TODO name required // ContainerStatisticsDecoratorStage containersStatisticsDecorator = new ContainerStatisticsDecoratorStage(); //TODO remove DeploymentDependencyGraphCreatorStage operationsDependencyGraphCreator = new DeploymentDependencyGraphCreatorStage(DeploymentDependencyGraphLevel.OPERATION); - DotOperationsDependencyExportStage dotOperationsDependencyExporter = new DotOperationsDependencyExportStage(graphFilesOutputDir); + DotFileWriterStage dotOperationsDependencyExporter = dotDependencyGraphExporterFactory.getDeploymentOperationExporter(graphFilesOutputDir); DeploymentDependencyGraphCreatorStage componentDependencyGraphCreator = new DeploymentDependencyGraphCreatorStage(DeploymentDependencyGraphLevel.COMPONENT); - DotComponentsDependencyExportStage dotComponentDependencyExporter = new DotComponentsDependencyExportStage(graphFilesOutputDir); + DotFileWriterStage dotComponentDependencyExporter = dotDependencyGraphExporterFactory.getDeploymentComponentExporter(graphFilesOutputDir); DeploymentDependencyGraphCreatorStage containerDependencyGraphCreator = new DeploymentDependencyGraphCreatorStage(DeploymentDependencyGraphLevel.CONTAINER); - DotContainersDependencyExportStage dotContainerDependencyExporter = new DotContainersDependencyExportStage(graphFilesOutputDir); + DotFileWriterStage dotContainerDependencyExporter = dotDependencyGraphExporterFactory.getDeploymentContainerExporter(graphFilesOutputDir); SoftwareSystemAggregatorStage softwareSystemAggregator = new SoftwareSystemAggregatorStage(); OperationsStatisticsDecoratorStage operationsStatisticsDecoratorAss = new OperationsStatisticsDecoratorStage(); @@ -120,9 +121,9 @@ public class TraceAnalysisConfiguration extends Configuration { ComponentStatisticsDecoratorStage componentsStatisticsDecoratorAss = new ComponentStatisticsDecoratorStage(); Distributor<SoftwareSystem> x2SoftwareSystemDistributorAss = new Distributor<>(new CopyByReferenceStrategy()); // TODO name required AssemblyDependencyGraphCreatorStage operationsDependencyGraphCreatorAss = new AssemblyDependencyGraphCreatorStage(AssemblyDependencyGraphLevel.OPERATION); - DotOperationsDependencyExportStage dotOperationsDependencyExporterAss = new DotOperationsDependencyExportStage(graphFilesOutputDir); + DotFileWriterStage dotOperationsDependencyExporterAss = dotDependencyGraphExporterFactory.getAssemblyOperationExporter(graphFilesOutputDir); AssemblyDependencyGraphCreatorStage componentDependencyGraphCreatorAss = new AssemblyDependencyGraphCreatorStage(AssemblyDependencyGraphLevel.COMPONENT); - DotComponentsDependencyExportStage dotComponentDependencyExporterAss = new DotComponentsDependencyExportStage(graphFilesOutputDir); + DotFileWriterStage dotComponentDependencyExporterAss = dotDependencyGraphExporterFactory.getAssemblyComponentExporter(graphFilesOutputDir); super.connectPorts(aggregatedTraceDistributor.getNewOutputPort(), softwareSystemCreator.getInputPort()); super.connectPorts(softwareSystemCreator.getOutputPort(), softwareSystemDistributor.getInputPort()); diff --git a/src/main/java/kieker/analysis/dev/dependencygraphs/DotDependencyGraphConfigurationFactory.java b/src/main/java/kieker/analysis/dev/dependencygraphs/DotDependencyGraphConfigurationFactory.java deleted file mode 100644 index 627119fe..00000000 --- a/src/main/java/kieker/analysis/dev/dependencygraphs/DotDependencyGraphConfigurationFactory.java +++ /dev/null @@ -1,147 +0,0 @@ -package kieker.analysis.dev.dependencygraphs; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import kieker.analysis.util.graph.Vertex; -import kieker.analysis.util.graph.export.dot.DotExportConfiguration; -import kieker.analysis.util.graph.util.dot.attributes.DotClusterAttribute; -import kieker.analysis.util.graph.util.dot.attributes.DotEdgeAttribute; -import kieker.analysis.util.graph.util.dot.attributes.DotGraphAttribute; -import kieker.analysis.util.graph.util.dot.attributes.DotNodeAttribute; - -public class DotDependencyGraphConfigurationFactory { - - public DotExportConfiguration getAssemblyComponentConfiguration() { - DotExportConfiguration configuration = getNewBaseConfiguration(); - - configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "box"); - configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper("<<assembly component>>")); - - return configuration; - } - - public DotExportConfiguration getAssemblyOperationConfiguration() { - DotExportConfiguration configuration = getNewBaseConfiguration(); - - configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "oval"); - configuration.addClusterAttribute(DotClusterAttribute.LABEL, new ClusterLabelMapper("<<assembly component>>")); - configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper()); - - return configuration; - } - - public DotExportConfiguration getDeploymentContainerConfiguration() { - DotExportConfiguration configuration = getNewBaseConfiguration(); - - configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "box3d"); - configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper("<<execution container>>")); - - return configuration; - } - - public DotExportConfiguration getDeploymentComponentConfiguration() { - DotExportConfiguration configuration = getNewBaseConfiguration(); - - configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "box"); - configuration.addClusterAttribute(DotClusterAttribute.LABEL, new ClusterLabelMapper("<<execution container>>")); - configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper("<<deployment component>>")); - - return configuration; - } - - public DotExportConfiguration getDeploymentOperationConfiguration() { - DotExportConfiguration configuration = getNewBaseConfiguration(); - - configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "oval"); - configuration.addClusterAttribute(DotClusterAttribute.LABEL, new ClusterLabelMapper("<<execution container>>", "<<deployment component>>")); - configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper()); - - return configuration; - } - - private DotExportConfiguration getNewBaseConfiguration() { - DotExportConfiguration configuration = new DotExportConfiguration(); - - configuration.addGraphAttribute(DotGraphAttribute.RANKDIR, g -> "LR"); - configuration.addEdgeAttribute(DotEdgeAttribute.LABEL, (e -> e.getProperty("calls").toString())); - - return configuration; - } - - // TODO move outside - private class VertexLabelMapper implements Function<Vertex, String> { - - private final String annotation; - - public VertexLabelMapper() { - this.annotation = null; - } - - public VertexLabelMapper(final String annotation) { - this.annotation = annotation; - } - - @Override - public String apply(final Vertex vertex) { - final StringBuilder statistics = new StringBuilder(); - if (this.annotation != null) { - statistics.append(this.annotation); - statistics.append("\\n"); - } - statistics.append(vertex.getProperty("Name").toString()); - statistics.append("\\n"); - statistics.append(generateStatistics(vertex)); - return statistics.toString(); - } - - private String generateStatistics(final Vertex vertex) { - final String temporalUnit = "xs"; // TODO temp - final List<String> statisticStrings = new ArrayList<>(5); - if (vertex.getProperty("MinDuration") != null) { - statisticStrings.add("min: " + vertex.getProperty("MinDuration").toString() + temporalUnit); - } - if (vertex.getProperty("MaxDuration") != null) { - statisticStrings.add("max: " + vertex.getProperty("MaxDuration").toString() + temporalUnit); - } - if (vertex.getProperty("TotalDuration") != null) { - statisticStrings.add("total: " + vertex.getProperty("TotalDuration").toString() + temporalUnit); - } - if (vertex.getProperty("MeanDuration") != null) { - statisticStrings.add("avg: " + vertex.getProperty("MeanDuration").toString() + temporalUnit); - } - if (vertex.getProperty("MedianDuration") != null) { - statisticStrings.add("med: " + vertex.getProperty("MedianDuration").toString() + temporalUnit); - } - - // If there are more than 3 statistics elements add a line break after the 3rd last - if (statisticStrings.size() > 3) { - final int thirdLast = statisticStrings.size() - 3; - statisticStrings.set(thirdLast, statisticStrings.get(thirdLast).concat("\\n")); - } - - return statisticStrings.stream().collect(Collectors.joining(", ")); - } - - } - - // TODO move outside - private class ClusterLabelMapper implements Function<Vertex, String> { - - private final List<String> annotations; - - public ClusterLabelMapper(final String... annotations) { - this.annotations = Arrays.asList(annotations); - } - - @Override - public String apply(final Vertex vertex) { - return this.annotations.get(vertex.getDepth()) + "\\n" + vertex.getProperty("Name").toString(); - } - - } - -} diff --git a/src/main/java/kieker/analysis/dev/dependencygraphs/dot/ClusterLabelMapper.java b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/ClusterLabelMapper.java new file mode 100644 index 00000000..b7d44633 --- /dev/null +++ b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/ClusterLabelMapper.java @@ -0,0 +1,22 @@ +package kieker.analysis.dev.dependencygraphs.dot; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +import kieker.analysis.util.graph.Vertex; + +class ClusterLabelMapper implements Function<Vertex, String> { + + private final List<String> annotations; + + public ClusterLabelMapper(final String... annotations) { + this.annotations = Arrays.asList(annotations); + } + + @Override + public String apply(final Vertex vertex) { + return this.annotations.get(vertex.getDepth()) + "\\n" + vertex.getProperty("Name").toString(); + } + +} diff --git a/src/main/java/kieker/analysis/dev/dependencygraphs/DotComponentsDependencyExportStage.java b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotComponentsDependencyExportStage.java similarity index 95% rename from src/main/java/kieker/analysis/dev/dependencygraphs/DotComponentsDependencyExportStage.java rename to src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotComponentsDependencyExportStage.java index cde67826..4f54d524 100644 --- a/src/main/java/kieker/analysis/dev/dependencygraphs/DotComponentsDependencyExportStage.java +++ b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotComponentsDependencyExportStage.java @@ -1,4 +1,4 @@ -package kieker.analysis.dev.dependencygraphs; +package kieker.analysis.dev.dependencygraphs.dot; import java.util.ArrayList; import java.util.List; @@ -12,6 +12,7 @@ import kieker.analysis.util.graph.util.dot.attributes.DotEdgeAttribute; import kieker.analysis.util.graph.util.dot.attributes.DotGraphAttribute; import kieker.analysis.util.graph.util.dot.attributes.DotNodeAttribute; +@Deprecated public class DotComponentsDependencyExportStage extends DotFileWriterStage { public DotComponentsDependencyExportStage(final String outputDirectory) { diff --git a/src/main/java/kieker/analysis/dev/dependencygraphs/DotContainersDependencyExportStage.java b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotContainersDependencyExportStage.java similarity index 95% rename from src/main/java/kieker/analysis/dev/dependencygraphs/DotContainersDependencyExportStage.java rename to src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotContainersDependencyExportStage.java index c64c1bc3..67d96549 100644 --- a/src/main/java/kieker/analysis/dev/dependencygraphs/DotContainersDependencyExportStage.java +++ b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotContainersDependencyExportStage.java @@ -1,4 +1,4 @@ -package kieker.analysis.dev.dependencygraphs; +package kieker.analysis.dev.dependencygraphs.dot; import java.util.ArrayList; import java.util.List; @@ -12,6 +12,7 @@ import kieker.analysis.util.graph.util.dot.attributes.DotEdgeAttribute; import kieker.analysis.util.graph.util.dot.attributes.DotGraphAttribute; import kieker.analysis.util.graph.util.dot.attributes.DotNodeAttribute; +@Deprecated public class DotContainersDependencyExportStage extends DotFileWriterStage { public DotContainersDependencyExportStage(final String outputDirectory) { diff --git a/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotDependencyGraphExporterFactory.java b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotDependencyGraphExporterFactory.java new file mode 100644 index 00000000..ac297236 --- /dev/null +++ b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotDependencyGraphExporterFactory.java @@ -0,0 +1,73 @@ +package kieker.analysis.dev.dependencygraphs.dot; + +import kieker.analysis.util.graph.export.dot.DotExportConfiguration; +import kieker.analysis.util.graph.export.dot.DotFileWriterStage; +import kieker.analysis.util.graph.util.dot.attributes.DotClusterAttribute; +import kieker.analysis.util.graph.util.dot.attributes.DotEdgeAttribute; +import kieker.analysis.util.graph.util.dot.attributes.DotGraphAttribute; +import kieker.analysis.util.graph.util.dot.attributes.DotNodeAttribute; + +public class DotDependencyGraphExporterFactory { + + public DotFileWriterStage getAssemblyComponentExporter(final String outputDirectory) { + DotExportConfiguration configuration = getNewBaseConfiguration(); + + configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "box"); + configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper("<<assembly component>>")); + + return createStage(outputDirectory, configuration); + } + + public DotFileWriterStage getAssemblyOperationExporter(final String outputDirectory) { + DotExportConfiguration configuration = getNewBaseConfiguration(); + + configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "oval"); + configuration.addClusterAttribute(DotClusterAttribute.LABEL, new ClusterLabelMapper("<<assembly component>>")); + configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper()); + + return createStage(outputDirectory, configuration); + } + + public DotFileWriterStage getDeploymentContainerExporter(final String outputDirectory) { + DotExportConfiguration configuration = getNewBaseConfiguration(); + + configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "box3d"); + configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper("<<execution container>>")); + + return createStage(outputDirectory, configuration); + } + + public DotFileWriterStage getDeploymentComponentExporter(final String outputDirectory) { + DotExportConfiguration configuration = getNewBaseConfiguration(); + + configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "box"); + configuration.addClusterAttribute(DotClusterAttribute.LABEL, new ClusterLabelMapper("<<execution container>>")); + configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper("<<deployment component>>")); + + return createStage(outputDirectory, configuration); + } + + public DotFileWriterStage getDeploymentOperationExporter(final String outputDirectory) { + DotExportConfiguration configuration = getNewBaseConfiguration(); + + configuration.addDefaultNodeAttribute(DotNodeAttribute.SHAPE, g -> "oval"); + configuration.addClusterAttribute(DotClusterAttribute.LABEL, new ClusterLabelMapper("<<execution container>>", "<<deployment component>>")); + configuration.addNodeAttribute(DotNodeAttribute.LABEL, new VertexLabelMapper()); + + return createStage(outputDirectory, configuration); + } + + private DotExportConfiguration getNewBaseConfiguration() { + DotExportConfiguration configuration = new DotExportConfiguration(); + + configuration.addGraphAttribute(DotGraphAttribute.RANKDIR, g -> "LR"); + configuration.addEdgeAttribute(DotEdgeAttribute.LABEL, (e -> e.getProperty("calls").toString())); + + return configuration; + } + + private DotFileWriterStage createStage(final String outputDirectory, final DotExportConfiguration configuration) { + return new DotFileWriterStage(outputDirectory, configuration); + } + +} diff --git a/src/main/java/kieker/analysis/dev/dependencygraphs/DotOperationsDependencyExportStage.java b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotOperationsDependencyExportStage.java similarity index 95% rename from src/main/java/kieker/analysis/dev/dependencygraphs/DotOperationsDependencyExportStage.java rename to src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotOperationsDependencyExportStage.java index b5b8ebc1..d6cee46d 100644 --- a/src/main/java/kieker/analysis/dev/dependencygraphs/DotOperationsDependencyExportStage.java +++ b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/DotOperationsDependencyExportStage.java @@ -1,4 +1,4 @@ -package kieker.analysis.dev.dependencygraphs; +package kieker.analysis.dev.dependencygraphs.dot; import java.util.ArrayList; import java.util.List; @@ -12,6 +12,7 @@ import kieker.analysis.util.graph.util.dot.attributes.DotEdgeAttribute; import kieker.analysis.util.graph.util.dot.attributes.DotGraphAttribute; import kieker.analysis.util.graph.util.dot.attributes.DotNodeAttribute; +@Deprecated public class DotOperationsDependencyExportStage extends DotFileWriterStage { public DotOperationsDependencyExportStage(final String outputDirectory) { diff --git a/src/main/java/kieker/analysis/dev/dependencygraphs/dot/VertexLabelMapper.java b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/VertexLabelMapper.java new file mode 100644 index 00000000..38f2c73c --- /dev/null +++ b/src/main/java/kieker/analysis/dev/dependencygraphs/dot/VertexLabelMapper.java @@ -0,0 +1,63 @@ +package kieker.analysis.dev.dependencygraphs.dot; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import kieker.analysis.util.graph.Vertex; + +class VertexLabelMapper implements Function<Vertex, String> { + + private final String annotation; + + public VertexLabelMapper() { + this.annotation = null; + } + + public VertexLabelMapper(final String annotation) { + this.annotation = annotation; + } + + @Override + public String apply(final Vertex vertex) { + final StringBuilder statistics = new StringBuilder(); + if (this.annotation != null) { + statistics.append(this.annotation); + statistics.append("\\n"); + } + statistics.append(vertex.getProperty("Name").toString()); + statistics.append("\\n"); + statistics.append(generateStatistics(vertex)); + return statistics.toString(); + } + + private String generateStatistics(final Vertex vertex) { + final String temporalUnit = "xs"; // TODO temp + final List<String> statisticStrings = new ArrayList<>(5); + if (vertex.getProperty("MinDuration") != null) { + statisticStrings.add("min: " + vertex.getProperty("MinDuration").toString() + temporalUnit); + } + if (vertex.getProperty("MaxDuration") != null) { + statisticStrings.add("max: " + vertex.getProperty("MaxDuration").toString() + temporalUnit); + } + if (vertex.getProperty("TotalDuration") != null) { + statisticStrings.add("total: " + vertex.getProperty("TotalDuration").toString() + temporalUnit); + } + if (vertex.getProperty("MeanDuration") != null) { + statisticStrings.add("avg: " + vertex.getProperty("MeanDuration").toString() + temporalUnit); + } + if (vertex.getProperty("MedianDuration") != null) { + statisticStrings.add("med: " + vertex.getProperty("MedianDuration").toString() + temporalUnit); + } + + // If there are more than 3 statistics elements add a line break after the 3rd last + if (statisticStrings.size() > 3) { + final int thirdLast = statisticStrings.size() - 3; + statisticStrings.set(thirdLast, statisticStrings.get(thirdLast).concat("\\n")); + } + + return statisticStrings.stream().collect(Collectors.joining(", ")); + } + +} -- GitLab