[PATCH 2 of 2] Work on Sinfo-tkh - exports; using same logic for wst-description as winfo

Wald Commits scm-commit at wald.intevation.org
Tue Mar 6 17:14:44 CET 2018


# HG changeset patch
# User gernotbelger
# Date 1520352579 -3600
# Node ID 9c02733a1b3c1412169c0ec65effba70b797d3dc
# Parent  8596f95673b13b3f2ffcaa21ba2801e50d7e44ee
Work on Sinfo-tkh - exports; using same logic for wst-description as winfo

diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoExporter.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoExporter.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/AbstractSInfoExporter.java	Tue Mar 06 17:09:39 2018 +0100
@@ -52,8 +52,11 @@
         if (d instanceof CalculationResult) {
 
             final Object dat = ((CalculationResult) d).getData();
-            if (dat != null)
-                this.data = (RESULTS) dat;
+            if (dat != null) {
+                @SuppressWarnings("unchecked")
+                final RESULTS result = (RESULTS) dat;
+                this.data = result;
+            }
         }
     }
 
@@ -67,17 +70,20 @@
         final RiverInfo river = results.getRiver();
 
         /* write as csv */
-        writeCSVMeta(writer, results);
-        writeCSVHeader(writer, river);
+        writeCSVGlobalMetadata(writer, results);
+        writeCSVHeader(writer, results, river);
 
         for (final RESULT result : results.getResults()) {
-            writeCSVResult(writer, result);
+            writeCSVResult(writer, results, result);
         }
     }
 
-    protected abstract void writeCSVHeader(final CSVWriter writer, final RiverInfo river);
+    protected abstract void writeCSVHeader(final CSVWriter writer, final RESULTS results, final RiverInfo river);
 
-    protected abstract void writeCSVMeta(final CSVWriter writer, final RESULTS results);
+    /**
+     * Add metadata that is once written to the top of the file.
+     */
+    protected abstract void writeCSVGlobalMetadata(final CSVWriter writer, final RESULTS results);
 
     protected final void writeCSVMetaEntry(final CSVWriter writer, final String message, final Object... messageArgs) {
 
@@ -86,27 +92,30 @@
         writer.writeNext(new String[] { Resources.getMsg(meta, message, message, messageArgs) });
     }
 
-    protected final void writeCSVResult(final CSVWriter writer, final RESULT result) {
+    protected final void writeCSVResult(final CSVWriter writer, final RESULTS results, final RESULT result) {
 
-        writeCSVResultHeader(writer, result);
+        writeCSVResultMetadata(writer, results, result);
 
         /* nwo the value rows */
         final Collection<ROW> rows = result.getRows();
         for (final ROW row : rows) {
-            writeCSVRow(writer, row);
+            writeCSVRow(writer, results, row);
         }
     }
 
-    protected abstract void writeCSVResultHeader(CSVWriter writer, RESULT result);
+    /**
+     * Add metadata that is written once per result set.
+     */
+    protected abstract void writeCSVResultMetadata(CSVWriter writer, RESULTS results, RESULT result);
 
-    protected final void writeCSVRow(final CSVWriter writer, final ROW row) {
+    protected final void writeCSVRow(final CSVWriter writer, final RESULTS results, final ROW row) {
         getLog().debug("writeCSVFlowDepthRow");
 
-        final String[] formattedRow = formatCSVRow(row);
+        final String[] formattedRow = formatCSVRow(results, row);
         writer.writeNext(formattedRow);
     }
 
-    protected abstract String[] formatCSVRow(final ROW row);
+    protected abstract String[] formatCSVRow(RESULTS results, final ROW row);
 
     @Override
     protected final void writePDF(final OutputStream outStream) {
@@ -144,7 +153,7 @@
         addJRMetaData(source, results);
 
         for (final RESULT result : results.getResults()) {
-            addJRTableData(source, result);
+            addJRTableData(source, results, result);
         }
 
         return source;
@@ -152,16 +161,16 @@
 
     protected abstract void addJRMetaData(final MetaAndTableJRDataSource source, final RESULTS results);
 
-    protected final void addJRTableData(final MetaAndTableJRDataSource source, final RESULT result) {
+    protected final void addJRTableData(final MetaAndTableJRDataSource source, final RESULTS results, final RESULT result) {
 
         final Collection<ROW> rows = result.getRows();
 
         for (final ROW row : rows) {
 
-            final String[] formattedRow = formatPDFRow(row);
+            final String[] formattedRow = formatPDFRow(results, row);
             source.addData(formattedRow);
         }
     }
 
-    protected abstract String[] formatPDFRow(final ROW row);
+    protected abstract String[] formatPDFRow(RESULTS results, final ROW row);
 }
\ No newline at end of file
diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthExporter.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthExporter.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/FlowDepthExporter.java	Tue Mar 06 17:09:39 2018 +0100
@@ -71,7 +71,7 @@
     }
 
     @Override
-    protected void writeCSVResultHeader(final CSVWriter writer, final FlowDepthCalculationResult result) {
+    protected void writeCSVResultMetadata(final CSVWriter writer, final FlowDepthCalculationResults results, final FlowDepthCalculationResult result) {
 
         /* first some specific metadata */
         final BedHeightInfo sounding = result.getSounding();
@@ -104,7 +104,7 @@
     }
 
     @Override
-    protected final void writeCSVMeta(final CSVWriter writer, final FlowDepthCalculationResults results) {
+    protected void writeCSVGlobalMetadata(final CSVWriter writer, final FlowDepthCalculationResults results) {
         log.info("FlowDepthExporter.writeCSVMeta");
 
         final String calcModeLabel = results.getCalcModeLabel();
@@ -144,8 +144,9 @@
      * @param river
      * @param useTkh
      */
+
     @Override
-    protected final void writeCSVHeader(final CSVWriter writer, final RiverInfo river) {
+    protected void writeCSVHeader(final CSVWriter writer, final FlowDepthCalculationResults results, final RiverInfo river) {
         log.info("FlowDepthExporter.writeCSVHeader");
 
         final Collection<String> header = new ArrayList<>(11);
@@ -169,7 +170,7 @@
     }
 
     @Override
-    protected final String[] formatCSVRow(final FlowDepthRow row) {
+    protected String[] formatCSVRow(final FlowDepthCalculationResults results, final FlowDepthRow row) {
         return formatFlowDepthRow(row);
     }
 
@@ -277,7 +278,7 @@
     }
 
     @Override
-    protected final String[] formatPDFRow(final FlowDepthRow row) {
+    protected String[] formatPDFRow(final FlowDepthCalculationResults results, final FlowDepthRow row) {
         return formatFlowDepthRow(row);
     }
 }
\ No newline at end of file
diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhcalculation/TkhCalculator.java	Tue Mar 06 17:09:39 2018 +0100
@@ -214,7 +214,7 @@
         // Some regular input values may give a negative calculation result; that is unwanted
         if (tkh < 0.0)
             return 0.0;
-        else
-            return tkh;
+
+        return tkh;
     }
 }
\ No newline at end of file
diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java	Tue Mar 06 17:09:39 2018 +0100
@@ -30,6 +30,7 @@
 import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
 import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
 import org.dive4elements.river.artifacts.states.WaterlevelData;
+import org.dive4elements.river.exports.WaterlevelDescriptionBuilder;
 import org.dive4elements.river.model.River;
 
 /**
@@ -56,8 +57,11 @@
         /* find relevant bed-heights */
         final Collection<BedHeightsFinder> bedHeights = BedHeightsFinder.createTkhBedHeights(river, problems, calcRange);
 
+        /* misuse winfo-artifact to calculate waterlevels in the same way */
+        final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo);
+
         /* calculate waterlevels */
-        final WQKms[] kms = calculateWaterlevels(sinfo, problems);
+        final WQKms[] kms = calculateWaterlevels(winfo, problems);
 
         final RiverInfoProvider infoProvider = RiverInfoProvider.forRange(this.context, river, calcRange);
 
@@ -65,12 +69,15 @@
 
         final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name());
 
+        final WaterlevelDescriptionBuilder descBuilder = new WaterlevelDescriptionBuilder(winfo, this.context);
+        final String descriptionHeader = descBuilder.getColumnHeader();
+
         /* for each waterlevel, do a tkh calculation */
-        final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange);
+        final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange, descriptionHeader);
 
         for (final WQKms wqKms : kms) {
 
-            final TkhCalculationResult result = calculateResult(calcRange, infoProvider, wqKms, bedHeights, problems);
+            final TkhCalculationResult result = calculateResult(calcRange, infoProvider, wqKms, bedHeights, descBuilder, problems);
             if (result != null)
                 // FIXME: must be sorted by station!
                 results.addResult(result);
@@ -79,10 +86,7 @@
         return new CalculationResult(results, problems);
     }
 
-    private WQKms[] calculateWaterlevels(final SINFOArtifact sinfo, final Calculation problems) {
-
-        /* misuse winfo-artifact to calculate waterlevels in the same way */
-        final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo);
+    private WQKms[] calculateWaterlevels(final WINFOArtifact winfo, final Calculation problems) {
 
         final CalculationResult waterlevelData = winfo.getWaterlevelData(this.context);
 
@@ -99,7 +103,7 @@
     }
 
     private TkhCalculationResult calculateResult(final DoubleRange calcRange, final RiverInfoProvider riverInfo, final WQKms wkms,
-            final Collection<BedHeightsFinder> bedHeights, final Calculation problems) {
+            final Collection<BedHeightsFinder> bedHeights, final WaterlevelDescriptionBuilder descBuilder, final Calculation problems) {
 
         // FIXME: wo kommt das her? via winfo kein jahr vorhanden, oder doch? aber soll in metadaten ausgegeben werden...
         final int wspYear = -1;
@@ -109,9 +113,10 @@
 
         final RiverInfoProvider riverInfoProvider = riverInfo.forWaterlevel(waterlevel);
 
-        final String label = waterlevel.getName();
+        // FIXME: check with winfo how the name is generated
+        final String wstLabel = waterlevel.getName();
 
-        final WstInfo wstInfo = new WstInfo(label, wspYear, riverInfoProvider.getReferenceGauge());
+        final WstInfo wstInfo = new WstInfo(wstLabel, wspYear, riverInfoProvider.getReferenceGauge());
 
         final Collection<TkhResultRow> rows = new ArrayList<>();
 
@@ -124,7 +129,8 @@
             final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wkms);
 
             /* initialize tkh calculator */
-            final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, this.context, problems, label, riverInfoProvider.getRiver(), calcRange,
+            final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, this.context, problems, wstLabel, riverInfoProvider.getRiver(),
+                    calcRange,
                     dischargeProvider, bedHeightsProvider);
             if (tkhCalculator == null) {
                 /* just abort, problems have already been updated by buildTkhCalculator() */
@@ -144,13 +150,14 @@
 
                 final Tkh tkh = tkhCalculator.getTkh(station, wst);
 
+                final String description = descBuilder.getDesc(wkms);
                 final String gaugeLabel = riverInfoProvider.findGauge(station);
                 final String location = riverInfoProvider.getLocation(station);
 
-                rows.add(new TkhResultRow(tkh, label, gaugeLabel, location));
+                rows.add(new TkhResultRow(tkh, description, gaugeLabel, location));
             }
         }
 
-        return new TkhCalculationResult(label, wstInfo, true, rows);
+        return new TkhCalculationResult(wstLabel, wstInfo, true, rows);
     }
 }
\ No newline at end of file
diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResults.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResults.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculationResults.java	Tue Mar 06 17:09:39 2018 +0100
@@ -20,7 +20,16 @@
 
     private static final long serialVersionUID = 1L;
 
-    public TkhCalculationResults(final String calcModeLabel, final String user, final RiverInfo river, final DoubleRange calcRange) {
+    private final String descriptionHeader;
+
+    public TkhCalculationResults(final String calcModeLabel, final String user, final RiverInfo river, final DoubleRange calcRange,
+            final String descriptionHeader) {
         super(calcModeLabel, user, river, calcRange);
+
+        this.descriptionHeader = descriptionHeader;
+    }
+
+    public String getDescriptionHeader() {
+        return this.descriptionHeader;
     }
 }
\ No newline at end of file
diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhExporter.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhExporter.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhExporter.java	Tue Mar 06 17:09:39 2018 +0100
@@ -36,6 +36,10 @@
 // REMARK: must be public because its registered in generators.xml
 public class TkhExporter extends AbstractSInfoExporter<TkhResultRow, TkhCalculationResult, TkhCalculationResults> {
 
+    private static enum ExportMode {
+        pdf, csv
+    }
+
     /** The log used in this exporter. */
     private static Logger log = Logger.getLogger(TkhExporter.class);
 
@@ -57,7 +61,7 @@
     }
 
     @Override
-    protected final void writeCSVMeta(final CSVWriter writer, final TkhCalculationResults results) {
+    protected void writeCSVGlobalMetadata(final CSVWriter writer, final TkhCalculationResults results) {
         log.info("TkhExporter.writeCSVMeta");
 
         final String calcModeLabel = results.getCalcModeLabel();
@@ -95,20 +99,25 @@
     /**
      * Write the header, with different headings depending on whether at a gauge or at a location.
      */
+
     @Override
-    protected final void writeCSVHeader(final CSVWriter writer, final RiverInfo river) {
+    protected void writeCSVHeader(final CSVWriter writer, final TkhCalculationResults results, final RiverInfo river) {
         log.info("TkhExporter.writeCSVHeader");
 
         final Collection<String> header = new ArrayList<>(11);
 
         header.add(msg(SInfoI18NStrings.CSV_KM_HEADER));
         header.add(msgUnit(CSV_TKH_HEADER, SInfoI18NStrings.UNIT_CM));
-        header.add(msgUnit(CSV_TKHKIND_HEADER, SInfoI18NStrings.UNIT_CM));
+        header.add(msg(CSV_TKHKIND_HEADER));
         header.add(msgUnit(SInfoI18NStrings.CSV_MEAN_BED_HEIGHT_HEADER, river.getWstUnit()));
 
         header.add(msgUnit(SInfoI18NStrings.CSV_WATERLEVEL_HEADER, river.getWstUnit()));
         header.add(msgUnit(SInfoI18NStrings.CSV_DISCHARGE_HEADER, SInfoI18NStrings.UNIT_CUBIC_M));
-        header.add(msg(SInfoI18NStrings.CSV_LABEL_HEADER));
+
+        final String descriptionHeader = results.getDescriptionHeader();
+        if (descriptionHeader != null)
+            header.add(msg(descriptionHeader));
+
         header.add(msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
         header.add(msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
 
@@ -116,7 +125,8 @@
     }
 
     @Override
-    protected void writeCSVResultHeader(final CSVWriter writer, final TkhCalculationResult result) {
+    // FIXME: rename
+    protected void writeCSVResultMetadata(final CSVWriter writer, final TkhCalculationResults results, final TkhCalculationResult result) {
 
         /* first some specific metadata */
         final WstInfo wst = result.getWst();
@@ -127,21 +137,23 @@
         writeCSVMetaEntry(writer, SInfoI18NStrings.CSV_META_HEADER_WATERLEVEL_NAME, wst.getLabel());
         // "# Bezugspegel: "
         writeCSVMetaEntry(writer, SInfoI18NStrings.CSV_META_HEADER_WATERLEVEL_GAUGE, wst.getGauge());
-        // "# Jahr/Zeitraum der Wasserspiegellage: "
-        writeCSVMetaEntry(writer, SInfoI18NStrings.CSV_META_HEADER_WATERLEVEL_YEAR, Integer.toString(wst.getYear()));
+        // // "# Jahr/Zeitraum der Wasserspiegellage: "
+        // writeCSVMetaEntry(writer, SInfoI18NStrings.CSV_META_HEADER_WATERLEVEL_YEAR, Integer.toString(wst.getYear()));
     }
 
     @Override
-    protected final String[] formatCSVRow(final TkhResultRow row) {
-        return formatRow(row);
+    protected String[] formatCSVRow(final TkhCalculationResults results, final TkhResultRow row) {
+        return formatRow(results, row, ExportMode.csv);
     }
 
     /**
      * Format a row of a flow depth result into an array of string, both used by csv and pdf
      *
+     * @param results
+     *
      * @param useTkh
      */
-    private String[] formatRow(final TkhResultRow row) {
+    private String[] formatRow(final TkhCalculationResults results, final TkhResultRow row, final ExportMode mode) {
 
         final Collection<String> lines = new ArrayList<>(11);
 
@@ -166,7 +178,9 @@
         lines.add(getQFormatter().format(roundedDischarge));
 
         // Bezeichnung
-        lines.add(row.getWaterlevelLabel());
+        // REMARK: always export this column in pdf-mode, because WInfo also does it (no need for two jasper-templates).
+        if (results.getDescriptionHeader() != null || mode == ExportMode.pdf)
+            lines.add(row.getWaterlevelLabel());
 
         // Bezugspegel
         lines.add(row.getGauge());
@@ -219,13 +233,19 @@
         source.addMetaData("bedheight_header", msg(CSV_MEAN_BED_HEIGHT_HEADER_SHORT));
         source.addMetaData("waterlevel_header", msg(SInfoI18NStrings.CSV_WATERLEVEL_HEADER));
         source.addMetaData("discharge_header", msg(SInfoI18NStrings.CSV_DISCHARGE_HEADER));
-        source.addMetaData("waterlevel_name_header", msg(SInfoI18NStrings.CSV_LABEL_HEADER));
+
+        // REMARK: actually the column makes no sense if description header is null. But (software symmetry...) WINFO also
+        // writes an empty column into the pdf in that case (most probably to avoid the need for two jasper templates).
+        final String descriptionHeader = results.getDescriptionHeader();
+        final String waterlevelNameHeader = descriptionHeader == null ? msg(SInfoI18NStrings.CSV_LABEL_HEADER) : descriptionHeader;
+        source.addMetaData("waterlevel_name_header", waterlevelNameHeader);
+
         source.addMetaData("gauge_header", msg(SInfoI18NStrings.CSV_GAUGE_HEADER));
         source.addMetaData("location_header", msg(SInfoI18NStrings.CSV_LOCATION_HEADER));
     }
 
     @Override
-    protected final String[] formatPDFRow(final TkhResultRow row) {
-        return formatRow(row);
+    protected String[] formatPDFRow(final TkhCalculationResults results, final TkhResultRow row) {
+        return formatRow(results, row, ExportMode.pdf);
     }
 }
\ No newline at end of file
diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhState.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhState.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhState.java	Tue Mar 06 17:09:39 2018 +0100
@@ -16,7 +16,9 @@
 import org.dive4elements.river.artifacts.D4EArtifact;
 import org.dive4elements.river.artifacts.model.Calculation;
 import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.DataFacet;
 import org.dive4elements.river.artifacts.model.EmptyFacet;
+import org.dive4elements.river.artifacts.model.FacetTypes;
 import org.dive4elements.river.artifacts.model.ReportFacet;
 import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
 import org.dive4elements.river.artifacts.sinfo.common.TkhProcessor;
@@ -30,7 +32,6 @@
 
     private static final long serialVersionUID = 1L;
 
-
     /**
      * From this state can only be continued trivially.
      */
@@ -83,13 +84,10 @@
             facets.add(TkhProcessor.createTkhFacet(context, hash, this.id, result, index));
         }
 
-        // if (!resultList.isEmpty()) {
-        // final Facet csv = new DataFacet(FacetTypes.CSV, "CSV data", ComputeType.ADVANCE, hash, this.id);
-        // final Facet pdf = new DataFacet(FacetTypes.PDF, "PDF data", ComputeType.ADVANCE, hash, this.id);
-        //
-        // facets.add(csv);
-        // facets.add(pdf);
-        // }
+        if (!resultList.isEmpty()) {
+            facets.add(new DataFacet(FacetTypes.CSV, "CSV data", ComputeType.ADVANCE, hash, this.id));
+            facets.add(new DataFacet(FacetTypes.PDF, "PDF data", ComputeType.ADVANCE, hash, this.id));
+        }
 
         final Calculation report = res.getReport();
 
diff -r 8596f95673b1 -r 9c02733a1b3c artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/WinfoArtifactWrapper.java
--- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/WinfoArtifactWrapper.java	Tue Mar 06 17:08:51 2018 +0100
+++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/WinfoArtifactWrapper.java	Tue Mar 06 17:09:39 2018 +0100
@@ -18,10 +18,9 @@
 
 /**
  * Ugly wrapper around WINfoArtifact in order to a) not to break serialization of WInfoArtifact b) be able to copy data
- * into it
+ * into it.
  *
  * @author Gernot Belger
- *
  */
 class WinfoArtifactWrapper extends WINFOArtifact {
 


More information about the Dive4Elements-commits mailing list