/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see .
*/
package de.hft.stuttgart.citydoctor2.reporting;
import java.io.OutputStream;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.CheckConfiguration;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.checkresult.CheckReport;
import de.hft.stuttgart.citydoctor2.checkresult.Environment;
import de.hft.stuttgart.citydoctor2.checkresult.ErrorDetails;
import de.hft.stuttgart.citydoctor2.checkresult.ErrorReport;
import de.hft.stuttgart.citydoctor2.checkresult.ErrorStatistic;
import de.hft.stuttgart.citydoctor2.checkresult.ErrorStatus;
import de.hft.stuttgart.citydoctor2.checkresult.Feature;
import de.hft.stuttgart.citydoctor2.checkresult.FeatureReport;
import de.hft.stuttgart.citydoctor2.checkresult.GlobalErrorStatistics;
import de.hft.stuttgart.citydoctor2.checkresult.GlobalStatistics;
import de.hft.stuttgart.citydoctor2.checkresult.Header;
import de.hft.stuttgart.citydoctor2.checkresult.ModelStatistics;
import de.hft.stuttgart.citydoctor2.checkresult.PlanCheck;
import de.hft.stuttgart.citydoctor2.checkresult.PlanParameter;
import de.hft.stuttgart.citydoctor2.checkresult.Statistics;
import de.hft.stuttgart.citydoctor2.checkresult.ValidationPlan;
import de.hft.stuttgart.citydoctor2.checkresult.ValidationResults;
import de.hft.stuttgart.citydoctor2.checkresult.utility.CheckReportWriteException;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.LandObject;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation;
import de.hft.stuttgart.citydoctor2.datastructure.WaterObject;
import de.hft.stuttgart.citydoctor2.utils.Localization;
/**
* Report for creating a XML report out of a stream of feature results
*
* @author Matthias Betz
*
*/
public class XmlStreamReporter implements StreamReporter {
private static final Logger logger = LogManager.getLogger(XmlStreamReporter.class);
private OutputStream output;
private CheckReport report;
private Map reportMap;
private ValidationConfiguration config;
public XmlStreamReporter(OutputStream output, String fileName, ValidationConfiguration config) {
this.output = output;
this.config = config;
reportMap = new HashMap<>();
report = new CheckReport();
report.setHeader(createHeader(fileName));
report.setValidationPlan(createValidationPlan());
report.setValidationResults(new ValidationResults());
}
private Header createHeader(String fileName) {
Header header = new Header();
header.setDate(ZonedDateTime.now());
header.setFile(fileName);
Environment env = new Environment();
header.setEnvironment(env);
env.setJavaVersion(System.getProperties().getProperty("java.vm.version"));
env.setJavaVmVendor(System.getProperties().getProperty("java.vm.vendor"));
env.setJavaVmVersion(System.getProperties().getProperty("java.version"));
env.setOsArch(System.getProperties().getProperty("os.arch"));
env.setOsName(System.getProperties().getProperty("os.name"));
env.setValidationVersion(Localization.getText(Localization.VERSION));
return header;
}
private ValidationPlan createValidationPlan() {
ValidationPlan plan = new ValidationPlan();
plan.setNumberOfRoundingPlaces(config.getNumberOfRoundingPlaces());
List checkConfigs = new ArrayList<>();
plan.setChecks(checkConfigs);
for (Entry e : config.getChecks().entrySet()) {
PlanCheck checkConfig = new PlanCheck();
checkConfigs.add(checkConfig);
checkConfig.setName(e.getKey().toString());
if (e.getValue().isEnabled()) {
checkConfig.setActivated(true);
Map checkParams = e.getValue().getParameters();
if (checkParams != null && !checkParams.isEmpty()) {
List parameters = new ArrayList<>();
checkConfig.setParameters(parameters);
for (Entry entry : checkParams.entrySet()) {
PlanParameter param = new PlanParameter();
param.setName(entry.getKey());
param.setValue(entry.getValue());
parameters.add(param);
}
}
} else {
checkConfig.setActivated(false);
}
}
plan.setSchematronFile(config.getSchematronFilePath());
return plan;
}
@Override
public void report(CityObject co) {
if (co instanceof Building) {
reportBuilding((Building) co);
} else if (co instanceof Vegetation) {
reportVegetation((Vegetation) co);
} else if (co instanceof TransportationObject) {
reportTrans((TransportationObject) co);
} else if (co instanceof BridgeObject) {
reportBridge((BridgeObject) co);
} else if (co instanceof WaterObject) {
reportWater((WaterObject) co);
} else if (co instanceof LandObject) {
reportLand((LandObject) co);
} else {
throw new IllegalStateException("Not reportable CityObject found: " + co.getClass().getSimpleName());
}
}
@Override
public void reportGlobalError(CheckError err) {
report.getValidationResults().getGlobalErrors().add(createErrorReport(err));
}
private void reportLand(LandObject lo) {
FeatureReport fr = createCityObjectReportNode(lo);
report.getValidationResults().getLandReports().add(fr);
}
private void reportWater(WaterObject wo) {
FeatureReport fr = createCityObjectReportNode(wo);
report.getValidationResults().getWaterReports().add(fr);
}
private void reportBridge(BridgeObject bo) {
FeatureReport fr = createCityObjectReportNode(bo);
report.getValidationResults().getBridgeReports().add(fr);
}
private void reportTrans(TransportationObject to) {
FeatureReport fr = createCityObjectReportNode(to);
report.getValidationResults().getTransportationReports().add(fr);
}
private void reportVegetation(Vegetation v) {
FeatureReport fr = createCityObjectReportNode(v);
report.getValidationResults().getVegetationReports().add(fr);
}
private void reportBuilding(Building co) {
FeatureReport fr = createCityObjectReportNode(co);
for (BuildingPart bp : co.getBuildingParts()) {
reportMap.put(bp.getGmlId().getGmlString(), fr);
}
report.getValidationResults().getBuildingReports().add(fr);
}
private FeatureReport createCityObjectReportNode(CityObject co) {
FeatureReport fr = new FeatureReport();
reportMap.put(co.getGmlId().getGmlString(), fr);
fr.setGmlId(co.getGmlId().getGmlString());
List errors = new ArrayList<>();
co.collectContainedErrors(errors);
// Filter out duplicate errors (from linked polygons)
Set errorSet = new HashSet<>(errors);
for (CheckError err : errorSet) {
ErrorReport errReport = createErrorReport(err);
fr.getErrors().add(errReport);
}
return fr;
}
private ErrorReport createErrorReport(CheckError err) {
ErrorReport errReport = new ErrorReport();
errReport.setId(err.getErrorId().toString());
if (err.getType() == ErrorType.WARNING) {
errReport.setStatus(ErrorStatus.WARNING);
} else if (err.getType() == ErrorType.ERROR) {
errReport.setStatus(ErrorStatus.ERROR);
} else {
throw new IllegalStateException("Unknown error type: " + err.getType());
}
if (err.getFeature() != null) {
Feature feature = new Feature();
feature.setGmlId(err.getFeature().getGmlId().getGmlString());
feature.setType(err.getFeature().getCheckClass().getSimpleName());
errReport.setFeature(feature);
}
XmlStreamErrorHandler handler = new XmlStreamErrorHandler(config.getParserConfiguration());
err.report(handler);
ErrorDetails details = handler.getDetails();
errReport.setErrorDetails(details);
return errReport;
}
@Override
public void finishReport() throws CheckReportWriteException {
GlobalStatistics stats = new GlobalStatistics();
GlobalErrorStatistics globErrStats = new GlobalErrorStatistics();
globErrStats
.setNumErrorBridgeObjects(getNumberOfErrorFeatures(report.getValidationResults().getBridgeReports()));
globErrStats.setNumErrorBuildings(getNumberOfErrorFeatures(report.getValidationResults().getBuildingReports()));
globErrStats.setNumErrorLandObjects(getNumberOfErrorFeatures(report.getValidationResults().getLandReports()));
globErrStats.setNumErrorTransportation(
getNumberOfErrorFeatures(report.getValidationResults().getTransportationReports()));
globErrStats
.setNumErrorVegetation(getNumberOfErrorFeatures(report.getValidationResults().getVegetationReports()));
globErrStats.setNumErrorWaterObjects(getNumberOfErrorFeatures(report.getValidationResults().getWaterReports()));
stats.setGlobalErrorStats(globErrStats);
ModelStatistics modelStats = new ModelStatistics();
modelStats.setNumBridgeObjects(report.getValidationResults().getBridgeReports().size());
modelStats.setNumBuildings(report.getValidationResults().getBuildingReports().size());
modelStats.setNumLandObjects(report.getValidationResults().getLandReports().size());
modelStats.setNumTransportation(report.getValidationResults().getTransportationReports().size());
modelStats.setNumVegetation(report.getValidationResults().getVegetationReports().size());
modelStats.setNumWaterObjects(report.getValidationResults().getWaterReports().size());
stats.setModelStats(modelStats);
ErrorStatisticsCollector globalErrorCount = new ErrorStatisticsCollector();
for (ErrorReport errReport : report.getValidationResults().getGlobalErrors()) {
globalErrorCount.addError(errReport.getId());
}
createStatistics(globalErrorCount, report.getValidationResults().getBridgeReports());
createStatistics(globalErrorCount, report.getValidationResults().getBuildingReports());
createStatistics(globalErrorCount, report.getValidationResults().getLandReports());
createStatistics(globalErrorCount, report.getValidationResults().getTransportationReports());
createStatistics(globalErrorCount, report.getValidationResults().getVegetationReports());
createStatistics(globalErrorCount, report.getValidationResults().getWaterReports());
addStatisticsObjects(stats.getErrorStats(), globalErrorCount);
report.setGlobalStatistics(stats);
report.saveAs(output);
}
private void createStatistics(ErrorStatisticsCollector globalErrorCount, List reports) {
for (FeatureReport fReport : reports) {
if (fReport.getErrors().isEmpty()) {
fReport.setErrors(null);
continue;
}
ErrorStatisticsCollector collector = new ErrorStatisticsCollector();
for (ErrorReport errReport : fReport.getErrors()) {
collector.addError(errReport.getId());
globalErrorCount.addError(errReport.getId());
}
Statistics reportStats = new Statistics();
addStatisticsObjects(reportStats.getErrorStats(), collector);
fReport.setStatistics(reportStats);
}
}
private void addStatisticsObjects(List stats, ErrorStatisticsCollector collector) {
for (Entry er : collector.getErrorCounts().entrySet()) {
if (er.getValue().get() > 0) {
ErrorStatistic errStats = new ErrorStatistic();
errStats.setName(er.getKey());
errStats.setCount(er.getValue().get());
stats.add(errStats);
}
}
}
private int getNumberOfErrorFeatures(List reports) {
int errorFeatures = 0;
for (FeatureReport fReport : reports) {
boolean hasError = hasError(fReport);
if (hasError) {
errorFeatures++;
}
}
return errorFeatures;
}
private boolean hasError(FeatureReport fReport) {
return !fReport.getErrors().isEmpty();
}
@Override
public void addError(String gmlId, CheckError err) {
FeatureReport fReport = reportMap.get(gmlId);
if (fReport == null) {
logger.warn("Error reported for unknown gml id: {}, adding to global errors instead", gmlId);
reportGlobalError(err);
} else {
fReport.getErrors().add(createErrorReport(err));
}
}
}