diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java index 9074a22d9cf8b30bb99ea335862ca556cd1b938b..7dc49223a3ed0186247595eefab2baa51d3d36a4 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java @@ -42,9 +42,11 @@ import tec.units.ri.unit.MetricPrefix; import javax.measure.Quantity; import javax.measure.quantity.Length; import java.io.BufferedReader; +import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.net.URL; import java.util.Optional; import java.util.concurrent.ConcurrentLinkedDeque; @@ -166,17 +168,14 @@ public final class App { } // Config Parsing - String configPath; + URL configPath; if (!settingsReader.isPresent(SettingType.PRINTER_CONFIG_PATH)) { // TODO: exception if missing this argument, until then use default location for test runs - configPath = getClass().getResource("/config/index_everest_d_v4.properties").getFile(); + configPath = getClass().getResource("/config/index_everest_d_v4.properties"); } else { - configPath = settingsReader.getSetting(SettingType.PRINTER_CONFIG_PATH).get(); + configPath = new URL(settingsReader.getSetting(SettingType.PRINTER_CONFIG_PATH).get()); } - JavaPropertiesConfigurationParser configParser = new JavaPropertiesConfigurationParser( - configPath, - "/config/default.properties" - ); + JavaPropertiesConfigurationParser configParser = new JavaPropertiesConfigurationParser(configPath, getClass().getClassLoader().getResource("config/default.properties")); Printer indexV4Printer = configParser.getPrinter(); Format a4Format = configParser.getFormat("A4"); diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/GeneralResource.java b/src/main/java/de/tudresden/inf/mci/brailleplot/GeneralResource.java index 92ba1c802aecb8ce2b300db2ce69ed4e821d5e73..c647880df7baf46b1d6022bc464584c31d2b76a3 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/GeneralResource.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/GeneralResource.java @@ -70,8 +70,8 @@ public final class GeneralResource { validExternalFile = true; } } - String resourceClassPath = resourcePath.replace("\\", "/"); // classpaths are always separated by forward slash - InputStream checkStream = getClass().getResourceAsStream(resourceClassPath); + String resourceClassPath = resourcePath.replace(File.separator, "/"); // classpaths are always separated by forward slash + InputStream checkStream = getClass().getClassLoader().getResourceAsStream(resourceClassPath); mLogger.trace("checking referenced path as resource: " + resourceClassPath); if (Objects.nonNull(checkStream)) { mLogger.trace("interpreting path as resource stream: " + resourceClassPath); @@ -81,7 +81,7 @@ public final class GeneralResource { if (Objects.nonNull(searchPath)) { String relativeResourcePath = new File(searchPath + File.separator + resourceClassPath).toPath().normalize().toString(); relativeResourcePath = relativeResourcePath.replace("\\", "/"); - checkStream = getClass().getResourceAsStream(relativeResourcePath); + checkStream = getClass().getClassLoader().getResourceAsStream(relativeResourcePath); mLogger.trace("checking referenced path as search path relative resource: " + relativeResourcePath); if (Objects.nonNull(checkStream)) { mLogger.trace("interpreting path as resource stream: " + relativeResourcePath); 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 bdd14b694134e933355410cd845d1c23ee7b9b7f..9fbcdbb8f4b1580adfe5ae1e37b084c21c530b6d 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 @@ -5,8 +5,13 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -27,41 +32,34 @@ public abstract class ConfigurationParser { private ConfigurationValidator mValidator; private Printer mPrinter; private Map<String, Format> mFormats = new HashMap<>(); - private File mCurrentConfigFile; private List<PrinterProperty> mPrinterProperties = new ArrayList<>(); private Map<String, List<FormatProperty>> mFormatProperties = new HashMap<>(); private Printer mDefaultPrinter; private Format mDefaultFormat; - private final Logger mLogger; + protected final Logger mLogger = LoggerFactory.getLogger(getClass()); ConfigurationParser() { - mLogger = LoggerFactory.getLogger(this.getClass()); - }; + } + + ; /** * Internal algorithm used for parsing of the configuration file. * 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. + * This method is called by ({@link #parseConfigFile(InputStream, URL, boolean)}). + * + * @param inStream 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(InputStream input) throws ConfigurationParsingException, ConfigurationValidationException; - - - /** - * Get the current configuration file. - * @return A {@link File} object representing the configuration file. - */ - public final File getConfigFile() { - return mCurrentConfigFile; - } + protected abstract void parse(InputStream inStream, URL path) throws ConfigurationParsingException, ConfigurationValidationException; /** * Get the printer configuration. + * * @return A {@link Printer} object, representing the printers properties. */ public final Printer getPrinter() { @@ -70,6 +68,7 @@ public abstract class ConfigurationParser { /** * Get the names of all available format configurations. + * * @return A {@link Set}<{@link String}> containing the name of each format. */ public final Set<String> getFormatNames() { @@ -78,6 +77,7 @@ public abstract class ConfigurationParser { /** * Get a specific format configuration. + * * @param formatName The name of the format. * @return A {@link Format} object, representing the formats properties. * @throws NoSuchElementException If no format has the specified name. @@ -93,6 +93,7 @@ public abstract class ConfigurationParser { /** * Set a {@link ConfigurationValidator} for this parser. * This method should be called inside the concrete parsers constructor + * * @param validator The {@link ConfigurationValidator} object. */ protected void setValidator(final ConfigurationValidator validator) { @@ -101,6 +102,7 @@ public abstract class ConfigurationParser { /** * Get the {@link ConfigurationValidator} for this parser. + * * @return A {@link ConfigurationValidator} object. */ protected ConfigurationValidator getValidator() { @@ -109,6 +111,7 @@ public abstract class ConfigurationParser { /** * Add a general printer property to the internal printer configuration representation. + * * @param property The represented property of the printer. */ protected void addProperty(final PrinterProperty property) { @@ -117,6 +120,7 @@ public abstract class ConfigurationParser { /** * Add a specific format property to the internal list of format configuration representation. + * * @param property The represented property of a specific format. */ protected void addProperty(final FormatProperty property) { @@ -130,34 +134,61 @@ public abstract class ConfigurationParser { /** * Set the optional default configurations for {@link Printer} and {@link Format} objects created by this parser. * This method should be called inside the concrete parsers constructor. + * * @param defaultPrinter A {@link Printer} object containing the default properties or null for no default to be set. - * @param defaultFormat A {@link Format} object containing the default properties or null for no default to be set. + * @param defaultFormat A {@link Format} object containing the default properties or null for no default to be set. */ protected final void setDefaults(final Printer defaultPrinter, final Format defaultFormat) { mDefaultPrinter = defaultPrinter; mDefaultFormat = defaultFormat; } + /** + * Parse a configuration file from the resource folder + * As we know the root path of the configuration files, there is no need to specify any further. + */ + protected final void parseConfigFileFromResource(final URL resource, final boolean assertCompleteness) throws ConfigurationParsingException, ConfigurationValidationException { + Objects.requireNonNull(resource); + + mLogger.debug("Starting parsing properties file from java resources: \"{}\"", resource); + + try { + parseConfigFile(resource.openStream(), getParentUrl(resource), assertCompleteness); + } catch (IOException|ConfigurationParsingException e) { + throw new ConfigurationParsingException("Could not open resource at \"" + resource.toString() + "\"", e); + } + } + + protected final void parseConfigFileFromFileSystem(final Path filePath, final boolean assertCompleteness) throws ConfigurationParsingException, ConfigurationValidationException { + Objects.requireNonNull(filePath); + + mLogger.debug("Starting parsing properties file from file system: \"{}\"", filePath); + + try { + parseConfigFile(new FileInputStream(filePath.toFile()), filePath.toFile().toURI().toURL(), assertCompleteness); + } catch (FileNotFoundException | MalformedURLException e) { + throw new ConfigurationParsingException("Configuration file could not be read at \"" + filePath.toString() + "\""); + } + } + /** * Parse the specified configuration file. * This method should be called inside the concrete parsers constructor after the optional default configurations * ({@link #setDefaults(Printer, Format)}) and the validator ({@link #setValidator(ConfigurationValidator)}) have been set. - * @param filePath The configuration file to be parsed. The type depends on the concrete implementation of the parser. + * + * @param config The {@link File} to be parsed. The type depends on the concrete implementation of the parser. * @param assertCompleteness Signals whether to check for existence of all required properties or not. - * @throws ConfigurationParsingException On any error while accessing the configuration file or syntax + * @throws ConfigurationParsingException On any error while accessing the configuration file or syntax * @throws ConfigurationValidationException On any error while checking the parsed properties validity. */ - protected final void parseConfigFile(final String filePath, final boolean assertCompleteness) + private void parseConfigFile(final InputStream config, final URL path, final boolean assertCompleteness) throws ConfigurationParsingException, ConfigurationValidationException { // reset internal property buffer mPrinterProperties.clear(); mFormatProperties.clear(); + mValidator.setSearchPath(getPath(path)); // load and parse file - mCurrentConfigFile = new File(filePath); - InputStream input = openInputStream(filePath); - getValidator().setSearchPath(Objects.requireNonNullElse(getConfigFile().getParent(), "")); - parse(input); - closeInputStream(input); + parse(config, path); // build printer object from added properties mPrinter = new Printer(mPrinterProperties); if (mDefaultPrinter != null) { @@ -179,42 +210,19 @@ public abstract class ConfigurationParser { } } - /** - * 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 InputStream openInputStream(final String filePath) throws ConfigurationParsingException { - // This has to take the fact into consideration, that a resource from a packaged jar is not a real file in - // the file system and must be read via classloader resource stream. We want to be able to process files as - // well as packed resources. + private URL getParentUrl(final URL resourcePath) throws ConfigurationParsingException { + String fileString = resourcePath.getPath(); + String parentString = fileString.substring(0, fileString.lastIndexOf("/")); try { - // first try to read as file - mLogger.info("trying to open as file: " + filePath); - return new FileInputStream(filePath); - } catch (IOException e) { - // if that fails, try to read as resource - mLogger.info("trying to open as resource: " + filePath); - InputStream resourceStream = this.getClass().getResourceAsStream(filePath); - if (Objects.isNull(resourceStream)) { - // if that also fails: - throw new ConfigurationParsingException("Unable to read configuration file / resource.", e); - } - return resourceStream; + return new URL(resourcePath.getProtocol(), resourcePath.getHost(), parentString); + } catch (MalformedURLException e) { + throw new ConfigurationParsingException("Could not create URL to parent path", 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 InputStream input) throws ConfigurationParsingException { - try { - input.close(); - } catch (IOException e) { - throw new ConfigurationParsingException("Unable to close input stream.", e); - } + protected String getPath(final URL url) { + String urlString = url.getPath(); + String ret = urlString.replace("file:", ""); + return ret; } } 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 39611e2397549052991b5f6823219e8e2453788b..9a571bad77bf0006709e7e0e33b563967855fa00 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,24 +1,27 @@ package de.tudresden.inf.mci.brailleplot.configparser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.io.BufferedInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Objects; import java.util.Properties; -import java.util.Stack; /** * Concrete parser for configuration files in Java Property File format. - * @author Leonard Kupper - * @version 2019.07.18 + * @author Leonard Kupper, Georg Graßnick + * @version 2019.09.16 */ public final class JavaPropertiesConfigurationParser extends ConfigurationParser { - Stack<File> mInclusionStack = new Stack<>(); - private final Logger mLogger; + private static final String INCLUDE_FILE_EXTENSION = ".properties"; /** * Constructor. @@ -29,39 +32,66 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser * @throws ConfigurationParsingException On any error while accessing the configuration file or syntax. * @throws ConfigurationValidationException On any error while checking the parsed properties validity. */ - public JavaPropertiesConfigurationParser( - final String filePath, - final String defaultPath - ) throws ConfigurationParsingException, ConfigurationValidationException { - mLogger = LoggerFactory.getLogger(this.getClass()); - setValidator(new JavaPropertiesConfigurationValidator()); - parseConfigFile(defaultPath, false); + public JavaPropertiesConfigurationParser(final Path filePath, final URL defaultPath) throws ConfigurationParsingException, ConfigurationValidationException { + setup(); + parseConfigFileFromResource(defaultPath, false); + setDefaults(getPrinter(), getFormat("default")); + parseConfigFileFromFileSystem(filePath, true); + } + + public JavaPropertiesConfigurationParser(final Path filePath, final Path defaultPath) throws ConfigurationParsingException, ConfigurationValidationException { + setup(); + parseConfigFileFromFileSystem(defaultPath, false); + setDefaults(getPrinter(), getFormat("default")); + parseConfigFileFromFileSystem(filePath, true); + } + + public JavaPropertiesConfigurationParser(final URL filePath, final Path defaultPath) throws ConfigurationParsingException, ConfigurationValidationException { + setup(); + parseConfigFileFromFileSystem(defaultPath, false); setDefaults(getPrinter(), getFormat("default")); - parseConfigFile(filePath, true); + parseConfigFileFromResource(filePath, true); + } + + public JavaPropertiesConfigurationParser(final URL filePath, final URL defaultPath) throws ConfigurationParsingException, ConfigurationValidationException { + setup(); + parseConfigFileFromResource(defaultPath, false); + setDefaults(getPrinter(), getFormat("default")); + parseConfigFileFromResource(filePath, true); + } + + private void setup() { + setValidator(new JavaPropertiesConfigurationValidator()); } /** * Concrete internal algorithm used for parsing the Java Property File. - * 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. + * This method is called by ({@link ConfigurationParser#parseConfigFileFromFileSystem(Path, boolean)} (InputStream, boolean)}) + * or {@link ConfigurationParser#parseConfigFileFromResource(URL, boolean)} and will call itself recursively for every included file. + * @param inStream The fileToParse 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(final InputStream input) throws ConfigurationParsingException, ConfigurationValidationException { + protected void parse(final InputStream inStream, final URL path) throws ConfigurationParsingException, ConfigurationValidationException { + Objects.requireNonNull(inStream); + Objects.requireNonNull(path); // Create property instance for current recursion level Properties properties = new Properties(); + try { // Load properties from the .properties file - properties.load(input); + properties.load(inStream); } catch (IOException e) { - throw new ConfigurationParsingException("Unable to load properties from file.", e); + throw new ConfigurationParsingException("Unable to load properties from file \"" + inStream + "\"", e); } // Iterate over all properties as key -> value pairs for (String key : properties.stringPropertyNames()) { String value = properties.getProperty(key); // check for special property key: 'include' - if (("include").equals(key.toLowerCase())) { - includeFiles(value); + if (key.equalsIgnoreCase("include")) { + includeResource(value, path); + } else if (key.equalsIgnoreCase("include-file")) { + includeFiles(value, path); } else { parseProperty(key, value); } @@ -69,6 +99,7 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser } private void parseProperty(final String key, final String value) throws ConfigurationValidationException { + mLogger.trace("Parsed property \"{}\" with value \"{}\"", key, value); ValidProperty property = getValidator().validate(key, value); if (property instanceof FormatProperty) { addProperty((FormatProperty) property); @@ -78,38 +109,51 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser } } - private void includeFiles(final String fileList) throws ConfigurationParsingException, ConfigurationValidationException { - for (String includeName : fileList.split(",")) { - if (mInclusionStack.empty()) { - mInclusionStack.push(getConfigFile()); - } - File includeFile, parentFile = Objects.requireNonNullElse(mInclusionStack.peek().getParentFile(), new File("")); + /** + * Recursively parses the configuration file. + * This method handles files on the local file system. + * @param fileList + * @throws ConfigurationParsingException + * @throws ConfigurationValidationException + */ + private void includeFiles(final String fileList, final URL parentUrl) throws ConfigurationParsingException, ConfigurationValidationException { + for (String s : fileList.split(",")) { + + Path parentPath = null; 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(); - if (!includeFile.isFile()) { - throw new ConfigurationParsingException("Given include path is not a file: " + includeFile); - } - } catch (IOException e) { - throw new ConfigurationParsingException("Can not find include file.", e); + parentPath = Path.of(parentUrl.toURI()); + } catch (URISyntaxException e) { + throw new ConfigurationParsingException("Could not generate URI", e); } - if (mInclusionStack.contains(includeFile)) { - continue; + Path newPath = parentPath.getParent().resolve(s.trim() + INCLUDE_FILE_EXTENSION); + String newPathString = newPath.toAbsolutePath().toString(); + + mLogger.debug("Prepare recursive parsing of properties file in the file system for file \"{}\"", newPathString); + + try (InputStream is = new BufferedInputStream(new FileInputStream(newPathString))) { + parse(is, newPath.toUri().toURL()); + } catch (IOException e) { + throw new ConfigurationParsingException("Could not open include file", e); } - InputStream includeInput = openInputStream(includeFile.getAbsolutePath()); + } + } + + private void includeResource(final String fileList, final URL parentUrl) throws ConfigurationParsingException, ConfigurationValidationException { + for (String s : fileList.split(",")) { + + URL newUrl = null; try { - mInclusionStack.push(includeFile); - mLogger.info("Including config file: " + includeFile); - getValidator().setSearchPath(Objects.requireNonNullElse(mInclusionStack.peek().getParent(), "")); - parse(includeInput); - mInclusionStack.pop(); - getValidator().setSearchPath(Objects.requireNonNullElse(mInclusionStack.peek().getParent(), "")); - } finally { - closeInputStream(includeInput); + newUrl = new URL(parentUrl + "/" + s.trim() + INCLUDE_FILE_EXTENSION); + } catch (MalformedURLException e) { + throw new ConfigurationParsingException("Could not generate URI", e); + } + + mLogger.debug("Prepare recursive parsing of properties file in the java resources at \"{}\"", newUrl); + + try (InputStream is = newUrl.openStream()) { + parse(is, newUrl); + } catch (IOException e) { + throw new ConfigurationParsingException("Could not open include resource", e); } } }