From b19a3d47f08080677f0b1ad267f20bec1c553ead Mon Sep 17 00:00:00 2001
From: Leonard Kupper <leonard.kupper@mailbox.tu-dresden.de>
Date: Wed, 21 Aug 2019 12:02:26 +0200
Subject: [PATCH] Restructure config parser to handle internal recursive calls
 for include files.

---
 .../configparser/ConfigurationParser.java     | 49 ++++++++-------
 .../JavaPropertiesConfigurationParser.java    | 59 +++++++++++++++----
 2 files changed, 78 insertions(+), 30 deletions(-)

diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/ConfigurationParser.java b/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/ConfigurationParser.java
index 4eeaccb9..49872d3a 100644
--- a/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/ConfigurationParser.java
+++ b/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/ConfigurationParser.java
@@ -19,11 +19,10 @@ import java.util.Set;
 
 public abstract class ConfigurationParser {
 
-    private FileInputStream mInput;
     private ConfigurationValidator mValidator;
     private Printer mPrinter;
     private Map<String, Format> mFormats = new HashMap<>();
-    private String mConfigFilePath;
+    private File mCurrentConfigFile;
     private List<PrinterProperty> mPrinterProperties = new ArrayList<>();
     private Map<String, List<FormatProperty>> mFormatProperties = new HashMap<>();
     private Printer mDefaultPrinter;
@@ -35,14 +34,15 @@ public abstract class ConfigurationParser {
 
     /**
      * Internal algorithm used for parsing of the configuration file.
-     * Implement this method by parsing information from the configuration file (see {@link #getInput()}), optionally validating it (see {@link ConfigurationValidator}),
+     * Implement this method by parsing information from the given input stream, optionally validating it (see {@link ConfigurationValidator}),
      * constructing {@link PrinterProperty} and {@link FormatProperty} objects from this information and adding them with the methods
      * {@link #addProperty(PrinterProperty)} and {@link #addProperty(FormatProperty)}.
      * This method is called by ({@link #parseConfigFile(String, boolean)}).
+     * @param input The input stream to read the configuration from.
      * @throws ConfigurationParsingException On any error while accessing the configuration file or syntax.
      * @throws ConfigurationValidationException On any error while checking the parsed properties validity.
      */
-    protected abstract void parse() throws ConfigurationParsingException, ConfigurationValidationException;
+    protected abstract void parse(FileInputStream input) throws ConfigurationParsingException, ConfigurationValidationException;
 
 
     /**
@@ -50,7 +50,7 @@ public abstract class ConfigurationParser {
      * @return A {@link File} object representing the configuration file.
      */
     public final File getConfigFile() {
-        return new File(mConfigFilePath);
+        return mCurrentConfigFile;
     }
 
     /**
@@ -131,14 +131,6 @@ public abstract class ConfigurationParser {
         mDefaultFormat = defaultFormat;
     }
 
-    /**
-     * Get the input data from the current configuration file.
-     * @return A {@link FileInputStream} from the current configuration file.
-     */
-    protected FileInputStream getInput() {
-        return mInput;
-    }
-
     /**
      * Parse the specified configuration file.
      * This method should be called inside the concrete parsers constructor after the optional default configurations
@@ -154,8 +146,10 @@ public abstract class ConfigurationParser {
         mPrinterProperties.clear();
         mFormatProperties.clear();
         // load and parse file
-        setConfigFile(filePath);
-        parse();
+        mCurrentConfigFile = new File(filePath);
+        FileInputStream input = openInputStream(filePath);
+        parse(input);
+        closeInputStream(input);
         // build printer object from added properties
         mPrinter = new Printer(mPrinterProperties);
         if (mDefaultPrinter != null) {
@@ -177,15 +171,30 @@ public abstract class ConfigurationParser {
         }
     }
 
-
-
-    private void setConfigFile(final String filePath) throws ConfigurationParsingException {
-        mConfigFilePath = filePath;
+    /**
+     * Opens the input stream for the given file path.
+     * @param filePath The file to read from.
+     * @return A {@link FileInputStream} for the given file path.
+     * @throws ConfigurationParsingException On any error while opening the stream. (e.g. missing file)
+     */
+    final FileInputStream openInputStream(final String filePath) throws ConfigurationParsingException {
         try {
-            mInput = new FileInputStream(mConfigFilePath);
+            return new FileInputStream(filePath);
         } catch (IOException e) {
             throw new ConfigurationParsingException("Unable to read configuration file", e);
         }
     }
 
+    /**
+     * Closes the given input stream.
+     * @param input The {@link FileInputStream} to be closed.
+     * @throws ConfigurationParsingException On any error while closing the stream.
+     */
+    final void closeInputStream(final FileInputStream input) throws ConfigurationParsingException {
+        try {
+            input.close();
+        } catch (IOException e) {
+            throw new ConfigurationParsingException("Unable to close input stream.", e);
+        }
+    }
 }
diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/JavaPropertiesConfigurationParser.java b/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/JavaPropertiesConfigurationParser.java
index 577fa325..df66792b 100644
--- a/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/JavaPropertiesConfigurationParser.java
+++ b/src/main/java/de/tudresden/inf/mci/brailleplot/configparser/JavaPropertiesConfigurationParser.java
@@ -1,7 +1,10 @@
 package de.tudresden.inf.mci.brailleplot.configparser;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.Properties;
+import java.util.Stack;
 
 /**
  * Concrete parser for configuration files in Java Property File format.
@@ -10,7 +13,8 @@ import java.util.Properties;
  */
 public final class JavaPropertiesConfigurationParser extends ConfigurationParser {
 
-    private Properties mProperties = new Properties();
+    //ArrayList<String> mInclusionStack;
+    Stack<File> mInclusionStack = new Stack<>();
 
     /**
      * Constructor.
@@ -33,23 +37,29 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser
 
     /**
      * Concrete internal algorithm used for parsing the Java Property File.
-     * This method is called by ({@link #parseConfigFile(String, boolean)}).
+     * This method is called by ({@link #parseConfigFile(String, boolean)}) and will call itself recursively for every included file.
+     * @param input The input stream to read the configuration properties from.
      * @throws ConfigurationParsingException On any error while accessing the configuration file or syntax.
      * @throws ConfigurationValidationException On any error while checking the parsed properties validity.
      */
-    protected void parse() throws ConfigurationParsingException, ConfigurationValidationException {
-        // Load properties from the .properties file
+    protected void parse(final FileInputStream input) throws ConfigurationParsingException, ConfigurationValidationException {
+        // Create property instance for current recursion level
+        Properties properties = new Properties();
         try {
-            // Reset java property instance
-            mProperties.clear();
-            mProperties.load(getInput());
+            // Load properties from the .properties file
+            properties.load(input);
         } catch (IOException e) {
             throw new ConfigurationParsingException("Unable to load properties from file.", e);
         }
         // Iterate over all properties as key -> value pairs
-        for (String key : mProperties.stringPropertyNames()) {
-            String value = mProperties.getProperty(key);
-            parseProperty(key, value);
+        for (String key : properties.stringPropertyNames()) {
+            String value = properties.getProperty(key);
+            // check for special property key: 'include'
+            if (("include").equals(key.toLowerCase())) {
+                includeFiles(value);
+            } else {
+                parseProperty(key, value);
+            }
         }
     }
 
@@ -62,4 +72,33 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser
             addProperty((PrinterProperty) property);
         }
     }
+
+    private void includeFiles(final String fileList) throws ConfigurationParsingException, ConfigurationValidationException {
+        for (String includeName : fileList.split(",")) {
+            if (mInclusionStack.empty()) {
+                mInclusionStack.push(getConfigFile());
+            }
+            File includeFile, parentFile = mInclusionStack.peek().getParentFile();
+            try {
+                String findIncludePath = parentFile.getAbsolutePath() + File.separator + includeName.trim();
+                File abstractPath = new File(findIncludePath);
+                if (!abstractPath.exists()) {
+                    abstractPath = new File(findIncludePath + ".properties");
+                }
+                includeFile = abstractPath.getCanonicalFile();
+            } catch (IOException e) {
+                throw new ConfigurationParsingException("Can not find include file.", e);
+            }
+            if (mInclusionStack.contains(includeFile)) {
+                System.out.println("Skipping " + includeFile);
+                continue;
+            }
+            FileInputStream includeInput = openInputStream(includeFile.getAbsolutePath());
+            mInclusionStack.push(includeFile);
+            System.out.println("Including " + mInclusionStack);
+            parse(includeInput);
+            mInclusionStack.pop();
+            closeInputStream(includeInput);
+        }
+    }
 }
-- 
GitLab