From 1991ebdaf91bad0cad01f50127a63d2e603cb15c Mon Sep 17 00:00:00 2001
From: "Bernhard J. Berger" <bernhard.berger@uni-bremen.de>
Date: Thu, 2 Feb 2023 10:08:13 +0100
Subject: [PATCH] #7 Created interfaces from existing PropertiesReader and
 PropertiesWriter. Moved the JSON-based implementation to the main package
 since they are not part of the actual API. The reader and writer can be
 instanciated useing the PropertiesIOFactory which chooses an implementation
 based on the file ending.

---
 .../properties/io/PropertiesIOFactory.java    |  30 ++++
 .../api/properties/io/PropertiesReader.java   | 151 ++----------------
 .../api/properties/io/PropertiesWriter.java   |  69 +++-----
 .../FileBasedPropertiesStreamSupplier.java    |   3 +-
 .../core/api/utils/EvoalIOException.java      |  11 ++
 .../src/main/java/module-info.java            |   1 +
 .../main/internal/StatementExecutor.java      |  15 +-
 7 files changed, 95 insertions(+), 185 deletions(-)
 create mode 100644 src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesIOFactory.java
 create mode 100644 src/core/de.evoal.core/src/main/java/de/evoal/core/api/utils/EvoalIOException.java

diff --git a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesIOFactory.java b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesIOFactory.java
new file mode 100644
index 00000000..d06c783d
--- /dev/null
+++ b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesIOFactory.java
@@ -0,0 +1,30 @@
+package de.evoal.core.api.properties.io;
+
+import de.evoal.core.api.cdi.BeanFactory;
+import de.evoal.core.api.properties.PropertiesSpecification;
+import de.evoal.core.api.utils.EvoalIOException;
+
+import java.io.File;
+
+/**
+ * Factory class for creating instances of {@code PropertiesReader} and {@code PropertiesWriter}.
+ */
+public final class PropertiesIOFactory {
+    private PropertiesIOFactory() {}
+
+    public static PropertiesReader reader(final File filename, final PropertiesSpecification specification) throws EvoalIOException {
+        final String [] parts = filename.toString().split("\\.");
+        final String extension = parts[parts.length - 1];
+        final String name = extension + "-reader";
+
+        return BeanFactory.create(name, PropertiesReader.class).init(filename, specification);
+    }
+
+    public static PropertiesWriter writer(final File filename, final PropertiesSpecification specification) throws EvoalIOException {
+        final String [] parts = filename.toString().split("\\.");
+        final String extension = parts[parts.length - 1];
+        final String name = extension + "-writer";
+
+        return BeanFactory.create(name, PropertiesWriter.class).init(filename, specification);
+    }
+}
diff --git a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesReader.java b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesReader.java
index ad7873ea..6dacc2d0 100644
--- a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesReader.java
+++ b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesReader.java
@@ -1,145 +1,24 @@
 package de.evoal.core.api.properties.io;
 
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonToken;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import de.evoal.core.api.properties.Properties;
 import de.evoal.core.api.properties.PropertiesSpecification;
-import de.evoal.core.api.properties.PropertySpecification;
-import de.evoal.core.api.utils.Requirements;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
+import de.evoal.core.api.utils.EvoalIOException;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
 
-@Slf4j
-public class PropertiesReader implements AutoCloseable, Iterator<Properties> {
-
-    private final JsonParser jsonParser;
-
-    private final Set<String> properties;
-
-    private final PropertiesSpecification specification;
-
-    public PropertiesReader(final File inputFile, final PropertiesSpecification specification) throws IOException {
-        log.info("Creating properties reader for {}.", specification);
-        this.specification = specification;
-
-        properties = specification.getProperties()
-                                  .stream()
-                                  .map(PropertySpecification::name)
-                                  .collect(Collectors.toSet());
-
-        final ObjectMapper mapper = new ObjectMapper();
-        jsonParser = mapper.createParser(inputFile);
-        jsonParser.nextToken();
-        assertStartArray();
-    }
-
-    private Properties readProperties() throws IOException {
-        assertStartArray();
-
-        final Map<String, Object> entries = new TreeMap<>();
-        while(!JsonToken.END_ARRAY.equals(jsonParser.currentToken())) {
-            Requirements.requireEqual(jsonParser.currentToken(), JsonToken.START_OBJECT);
-
-            String name = null;
-            Object value = null;
-
-            while(!JsonToken.END_OBJECT.equals(jsonParser.currentToken())) {
-                final String fieldName = jsonParser.nextFieldName();
-
-                if("name".equals(fieldName)) {
-                    name = jsonParser.nextTextValue();
-                } else if("value".equals(fieldName)) {
-                    final JsonToken token = jsonParser.nextValue();
-
-                    if(token == JsonToken.VALUE_NUMBER_FLOAT) {
-                        value = jsonParser.getDoubleValue();
-                    } else if(token == JsonToken.VALUE_STRING) {
-                        value = jsonParser.getValueAsString();
-                    } else if(token == JsonToken.VALUE_FALSE) {
-                        value = false;
-                    } else if(token == JsonToken.VALUE_TRUE) {
-                        value = true;
-                    } else if(token == JsonToken.VALUE_NUMBER_INT) {
-                        value = jsonParser.getValueAsInt();
-                    } else {
-                        throw new RuntimeException("Unsupported token type " + token);
-                    }
-                }
-            }
-            assertEndObject();
-
-            entries.put(name, value);
-        }
-
-        assertEndArray();
-
-        try {
-            final PropertiesSpecification spec =
-                    PropertiesSpecification.builder()
-                                           .add(entries.keySet()
-                                                       .stream()
-                                                       .filter(properties::contains)
-                                                       .map(specification::find)
-                                                       .map(PropertySpecification::type)
-                                               )
-                                           .build();
-
-            return new Properties(spec).putAll(entries);
-        } catch(final NullPointerException e) {
-            log.error("Failed to read properties file entry {} for specification {}.", entries, specification);
-
-            entries.keySet()
-                    .stream()
-                    .filter(k -> !properties.contains(k))
-                    .forEach(n -> System.err.println("There is no properties specification for " + n));
-            throw e;
-        }
-    }
-
-    private void assertEndArray() throws IOException {
-        Requirements.requireEqual(jsonParser.currentToken(), JsonToken.END_ARRAY);
-        jsonParser.nextToken();
-    }
-
-
-    private void assertEndObject() throws IOException {
-        Requirements.requireEqual(jsonParser.currentToken(), JsonToken.END_OBJECT);
-        jsonParser.nextToken();
-    }
-
-
-    private void assertStartArray() throws IOException {
-        Requirements.requireEqual(jsonParser.currentToken(), JsonToken.START_ARRAY);
-        jsonParser.nextToken();
-    }
-
-    private void assertStartObject() throws IOException {
-        Requirements.requireEqual(jsonParser.currentToken(), JsonToken.START_OBJECT);
-        jsonParser.nextToken();
-    }
-
-    @Override
-    public void close() throws Exception {
-        jsonParser.close();
-    }
-
-    @Override
-    public boolean hasNext() {
-        return !JsonToken.END_ARRAY.equals(jsonParser.currentToken());
-    }
-
-    @Override
-    @SneakyThrows(IOException.class)
-    public Properties next() {
-        return readProperties();
-    }
+/**
+ * Base interface for reading properties from disk.
+ */
+public interface PropertiesReader extends AutoCloseable, Iterator<Properties> {
+    /**
+     * The reader's initialisation method. Receives the file to read and the
+     *   specification to use.
+     *
+     * @param input The input file.
+     * @param specification The input specification.
+     *
+     * @return The instance itself.
+     */
+    public PropertiesReader init(final File input, final PropertiesSpecification specification) throws EvoalIOException;
 }
diff --git a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java
index 21b887eb..7503d2b2 100644
--- a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java
+++ b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java
@@ -1,53 +1,30 @@
 package de.evoal.core.api.properties.io;
 
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import de.evoal.core.api.properties.Properties;
-import de.evoal.core.api.properties.PropertySpecification;
-import lombok.SneakyThrows;
+import de.evoal.core.api.properties.PropertiesSpecification;
+import de.evoal.core.api.utils.EvoalIOException;
+import lombok.NonNull;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 
-public class PropertiesWriter implements AutoCloseable {
-    private final FileOutputStream outputStream;
-    private final JsonGenerator jsonGenerator;
-
-    public PropertiesWriter(final File outputFile) throws IOException {
-        outputStream = new FileOutputStream(outputFile);
-
-        final ObjectMapper mapper = new ObjectMapper();
-        jsonGenerator = mapper.createGenerator(outputStream);
-        jsonGenerator.writeStartArray();
-    }
-
-    @SneakyThrows(IOException.class)
-    public void addProperties(final Properties properties) {
-        jsonGenerator.writeStartArray();
-        for(final PropertySpecification spec : properties.getSpecification().getProperties()) {
-            jsonGenerator.writeStartObject();
-            jsonGenerator.writeStringField("name", spec.name());
-
-            final Object value = properties.get(spec);
-            if(value instanceof Double || value instanceof Float) {
-                jsonGenerator.writeNumberField("value", ((Number)properties.get(spec)).doubleValue());
-            } else if(value instanceof Integer) {
-                jsonGenerator.writeNumberField("value", ((Number)properties.get(spec)).longValue());
-            } else if(value instanceof Boolean) {
-                jsonGenerator.writeBooleanField("value", (Boolean)properties.get(spec));
-            } else if(value instanceof String) {
-                jsonGenerator.writeStringField("value", (String)properties.get(spec));
-            }
-            jsonGenerator.writeEndObject();
-        }
-        jsonGenerator.writeEndArray();
-    }
-
-    @Override
-    public void close() throws Exception {
-        jsonGenerator.writeEndArray();
-        jsonGenerator.close();
-        outputStream.close();
-    }
+/**
+ * Base interface for properties writers. A properties writer's task is to serialize a sequence of properties.
+ */
+public interface PropertiesWriter extends AutoCloseable {
+    /**
+     * Writes a new properties instance to the repository.
+     *
+     * @param properties The properties to write.
+     * @throws EvoalIOException An exception to signal some problem while serialising the data.
+     */
+    public void add(final @NonNull Properties properties) throws EvoalIOException;
+
+    /**
+     * Inits the writer with the specification information.
+     *
+     * @param specification The properties specification of the properties to write.
+     *
+     * @return The instance itself.
+     */
+    public PropertiesWriter init(final File outputFile, final PropertiesSpecification specification) throws EvoalIOException;
 }
diff --git a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/stream/FileBasedPropertiesStreamSupplier.java b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/stream/FileBasedPropertiesStreamSupplier.java
index a008a8c5..c5dcee1a 100644
--- a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/stream/FileBasedPropertiesStreamSupplier.java
+++ b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/properties/stream/FileBasedPropertiesStreamSupplier.java
@@ -1,6 +1,7 @@
 package de.evoal.core.api.properties.stream;
 
 import de.evoal.core.api.properties.PropertiesSpecification;
+import de.evoal.core.api.properties.io.PropertiesIOFactory;
 import de.evoal.core.api.properties.io.PropertiesReader;
 import lombok.extern.slf4j.Slf4j;
 
@@ -22,7 +23,7 @@ public class FileBasedPropertiesStreamSupplier extends PropertiesBasedProperties
         super(Collections.emptyList());
         log.info("Creating properties stream for {}.", filename);
 
-        try(final PropertiesReader reader = new PropertiesReader(filename, specification)) {
+        try(final PropertiesReader reader = PropertiesIOFactory.reader(filename, specification)) {
             while(reader.hasNext()) {
                 properties.add(reader.next());
             }
diff --git a/src/core/de.evoal.core/src/main/java/de/evoal/core/api/utils/EvoalIOException.java b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/utils/EvoalIOException.java
new file mode 100644
index 00000000..ad6b34f3
--- /dev/null
+++ b/src/core/de.evoal.core/src/main/java/de/evoal/core/api/utils/EvoalIOException.java
@@ -0,0 +1,11 @@
+package de.evoal.core.api.utils;
+
+public class EvoalIOException extends EvoalException {
+    public EvoalIOException(final String message) {
+        super(message);
+    }
+
+    public EvoalIOException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/core/de.evoal.core/src/main/java/module-info.java b/src/core/de.evoal.core/src/main/java/module-info.java
index 43401de0..dabb7fc3 100644
--- a/src/core/de.evoal.core/src/main/java/module-info.java
+++ b/src/core/de.evoal.core/src/main/java/module-info.java
@@ -95,4 +95,5 @@ module de.evoal.core {
     opens de.evoal.core.api.ea.constraints.calculation to weld.core.impl;
     opens de.evoal.core.api.properties.info to weld.core.impl;
     opens de.evoal.core.api.ea.constraints.strategies to weld.core.impl;
+    opens de.evoal.core.main.properties to weld.core.impl;
 }
diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/StatementExecutor.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/StatementExecutor.java
index 47ed93c6..c1799238 100644
--- a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/StatementExecutor.java
+++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/StatementExecutor.java
@@ -2,7 +2,9 @@ package de.evoal.generator.main.internal;
 
 import de.evoal.core.api.properties.Properties;
 import de.evoal.core.api.properties.PropertiesSpecification;
+import de.evoal.core.api.properties.io.PropertiesIOFactory;
 import de.evoal.core.api.properties.io.PropertiesWriter;
+import de.evoal.core.api.utils.EvoalIOException;
 import de.evoal.generator.api.GeneratorFunction;
 import de.evoal.languages.model.generator.*;
 import de.evoal.languages.model.generator.util.GeneratorSwitch;
@@ -156,9 +158,18 @@ public class StatementExecutor extends GeneratorSwitch<Object> {
 
         new File(filename).getParentFile().mkdirs();
 
-        try(final PropertiesWriter writer = new PropertiesWriter(new File(filename))) {
+        PropertiesSpecification.Builder resultSpec = PropertiesSpecification.builder();
+        stream.peek(p -> resultSpec.add(p.getSpecification()));
+
+        try(final PropertiesWriter writer = PropertiesIOFactory.writer(new File(filename), resultSpec.build())) {
             stream.limit(count)
-                  .forEach(writer::addProperties);
+                  .forEach(p -> {
+                      try {
+                          writer.add(p);
+                      } catch (final EvoalIOException e) {
+                          log.error("Failed to writer properties.", e);
+                      }
+                  });
         } catch (final Exception e) {
             log.error("Failed to write properties to file '{}'.", filename);
         }
-- 
GitLab