From 75e1da01af55802c790b9094869a132aba2f9342 Mon Sep 17 00:00:00 2001
From: "Bernhard J. Berger" <bernhard.berger@uni-bremen.de>
Date: Thu, 2 Feb 2023 10:49:40 +0100
Subject: [PATCH] Working on task #3 to implement #7

---
 .../main/properties/JsonPropertiesReader.java | 159 ++++++++++++++++++
 .../main/properties/JsonPropertiesWriter.java |  75 +++++++++
 2 files changed, 234 insertions(+)
 create mode 100644 src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesReader.java
 create mode 100644 src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesWriter.java

diff --git a/src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesReader.java b/src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesReader.java
new file mode 100644
index 00000000..9bc06992
--- /dev/null
+++ b/src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesReader.java
@@ -0,0 +1,159 @@
+package de.evoal.core.main.properties;
+
+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.properties.io.PropertiesReader;
+import de.evoal.core.api.utils.EvoalIOException;
+import de.evoal.core.api.utils.Requirements;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.enterprise.context.Dependent;
+import javax.inject.Named;
+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
+@Dependent
+@Named("json-reader")
+public class JsonPropertiesReader implements PropertiesReader {
+
+    private JsonParser jsonParser;
+
+    private Set<String> properties;
+
+    private PropertiesSpecification specification;
+
+    @Override
+    public PropertiesReader init(final File inputFile, final PropertiesSpecification specification) throws EvoalIOException {
+        log.info("Creating JSON properties reader for {}.", specification);
+        this.specification = specification;
+
+        properties = specification.getProperties()
+                                  .stream()
+                                  .map(PropertySpecification::name)
+                                  .collect(Collectors.toSet());
+
+        try {
+            final ObjectMapper mapper = new ObjectMapper();
+            jsonParser = mapper.createParser(inputFile);
+            jsonParser.nextToken();
+            assertStartArray();
+        } catch (final IOException e) {
+            log.error("Failed to read JSON file {}.", inputFile, e);
+            throw new EvoalIOException("Failure while reading " + inputFile, e);
+        }
+
+        return this;
+    }
+
+    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();
+    }
+}
diff --git a/src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesWriter.java b/src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesWriter.java
new file mode 100644
index 00000000..05e8f36e
--- /dev/null
+++ b/src/core/de.evoal.core/src/main/java/de/evoal/core/main/properties/JsonPropertiesWriter.java
@@ -0,0 +1,75 @@
+package de.evoal.core.main.properties;
+
+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.PropertiesSpecification;
+import de.evoal.core.api.properties.PropertySpecification;
+import de.evoal.core.api.properties.io.PropertiesWriter;
+import de.evoal.core.api.utils.EvoalIOException;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.enterprise.context.Dependent;
+import javax.inject.Named;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@Slf4j
+@Dependent
+@Named("json-writer")
+public class JsonPropertiesWriter implements PropertiesWriter {
+    private FileOutputStream outputStream;
+    private JsonGenerator jsonGenerator;
+
+    @Override
+    public PropertiesWriter init(final File outputFile, final PropertiesSpecification specification) throws EvoalIOException {
+        try {
+            outputStream = new FileOutputStream(outputFile);
+
+            final ObjectMapper mapper = new ObjectMapper();
+            jsonGenerator = mapper.createGenerator(outputStream);
+            jsonGenerator.writeStartArray();
+        } catch (final IOException e) {
+            log.error("Failed to write {}.", outputFile, e);
+            throw new EvoalIOException("Failed to write " + outputFile, e);
+        }
+
+        return this;
+    }
+
+    @Override
+    public void add(final Properties properties) throws EvoalIOException {
+        try {
+            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();
+        } catch(final IOException e) {
+            log.error("Failure while writing properties.", e);
+            throw new EvoalIOException("Failure while writing properties.", e);
+        }
+    }
+
+    @Override
+    public void close() throws Exception {
+        jsonGenerator.writeEndArray();
+        jsonGenerator.close();
+        outputStream.close();
+    }
+}
-- 
GitLab