diff --git a/.gitignore b/.gitignore index 9f25cc2301ab500393825b61c652ac5751c5dc3b..7cbe242d3e57f29e4c908eaef1e72c996c53f81a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,8 @@ src/main/resources/mapping/liblouis # Exported SVG files *.svg + +# Native libraries +*.so +*.dylib +*.dll diff --git a/.gitmodules b/.gitmodules index c5dd365bd9aec06336502855f51c391593fd54cd..6279666aaacb70d51061cd2eaf45864903353052 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third_party/liblouis"] path = third_party/liblouis url = https://github.com/liblouis/liblouis +[submodule "third_party/liblouis_bin"] + path = third_party/liblouis_bin + url = ../../brailleplot/liblouis_bin.git diff --git a/build.gradle b/build.gradle index 59cf0b22950c45a026679cd9f96d0232bc297d16..d56141ec73bfe993b742785d3cf4376a3c4085f3 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,23 @@ clean { delete "$rootDir/src/main/resources/mapping/liblouis" } +// Copy native liblouis libraries for major platforms to the resource folder +task copyLibLouisBinary(type: Copy) { + from "$projectDir/third_party/liblouis_bin/bin/" + into "$rootDir/src/main/resources/native/liblouis" +} +processResources.dependsOn copyLibLouisBinary +// Abort if files are missing +gradle.taskGraph.afterTask { copyLibLouisBinary -> + if(copyLibLouisBinary.state.noSource){ + throw new GradleException("Could not find liblouis native libraries in \"$projectDir/third_party/liblouis_bin/bin/\". Please make sure you updated all git submodules using \"git submodule update --init --recursive\" ") + } +} +// Delete libraries on "clean" task +clean { + delete "$rootDir/src/main/resources/native/liblouis" +} + // Define the main class for the application mainClassName = 'de.tudresden.inf.mci.brailleplot.App' 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 16b15f62e479ef629606156067266a1d5c410a34..78f51686d080f7b769c57e316d3f8a257db82340 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java @@ -12,6 +12,8 @@ import de.tudresden.inf.mci.brailleplot.csvparser.CsvParser; import de.tudresden.inf.mci.brailleplot.csvparser.CsvType; import de.tudresden.inf.mci.brailleplot.datacontainers.PointList; import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer; +import de.tudresden.inf.mci.brailleplot.datacontainers.SimpleCategoricalPointListContainerImpl; +import de.tudresden.inf.mci.brailleplot.diagrams.CategoricalBarChart; import de.tudresden.inf.mci.brailleplot.diagrams.LineChart; @@ -35,6 +37,7 @@ import de.tudresden.inf.mci.brailleplot.commandline.SettingType; import de.tudresden.inf.mci.brailleplot.commandline.SettingsReader; import de.tudresden.inf.mci.brailleplot.commandline.SettingsWriter; +import de.tudresden.inf.mci.brailleplot.rendering.LiblouisBrailleTextRasterizer; import de.tudresden.inf.mci.brailleplot.rendering.MasterRenderer; import de.tudresden.inf.mci.brailleplot.svgexporter.BoolFloatingPointDataSvgExporter; @@ -45,7 +48,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; -import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; @@ -53,7 +55,7 @@ import java.io.Reader; import java.util.Iterator; import java.net.URL; - +import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.ConcurrentLinkedDeque; @@ -158,8 +160,6 @@ public final class App { try { // Logging example mLogger.info("Application started"); - // Needed for Windows machines - System.setProperty("jna.library.path", System.getProperty("user.dir") + "/third_party"); // Parse command line parameters CommandLineParser cliParser = new CommandLineParser(); SettingsWriter settings = cliParser.parse(args); @@ -175,16 +175,17 @@ public final class App { // Config Parsing - URL configPath; + JavaPropertiesConfigurationParser configParser; + URL defaultConfig = getClass().getClassLoader().getResource("config/default.properties"); 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"); + URL configUrl = getClass().getResource("/config/index_everest_d_v4.properties"); + configParser = new JavaPropertiesConfigurationParser(configUrl, defaultConfig); mLogger.warn("ATTENTION! Using default specific config from resources. Please remove default config behavior before packaging the jar."); } else { - File configFile = new File(settingsReader.getSetting(SettingType.PRINTER_CONFIG_PATH).get()); - configPath = configFile.toURL(); + Path configPath = Path.of(settingsReader.getSetting(SettingType.PRINTER_CONFIG_PATH).get()); + configParser = new JavaPropertiesConfigurationParser(configPath, defaultConfig); } - JavaPropertiesConfigurationParser configParser = new JavaPropertiesConfigurationParser(configPath, getClass().getClassLoader().getResource("config/default.properties")); Printer indexV4Printer = configParser.getPrinter(); Format a4Format = configParser.getFormat("A4"); Representation representationParameters = configParser.getRepresentation(); @@ -195,14 +196,24 @@ public final class App { InputStream csvStream = classloader.getResourceAsStream("examples/csv/2_line_plot.csv"); Reader csvReader = new BufferedReader(new InputStreamReader(csvStream)); + CsvParser csvParser = new CsvParser(csvReader, ',', '\"'); PointListContainer<PointList> container = csvParser.parse(CsvType.DOTS, CsvOrientation.HORIZONTAL); mLogger.debug("Internal data representation:\n {}", container.toString()); LineChart lineChart = new LineChart(container); - lineChart.setTitle("Liniendiagramm"); - lineChart.setXAxisName("X-Achsen Einheit"); - lineChart.setYAxisName("Y-Achsen Einheit"); + lineChart.setTitle(settingsReader.getSetting(SettingType.DIAGRAM_TITLE).orElse("")); + lineChart.setXAxisName(settingsReader.getSetting(SettingType.X_AXIS_LABEL).orElse("")); + lineChart.setYAxisName(settingsReader.getSetting(SettingType.Y_AXIS_LABEL).orElse("")); + + /* + CategoricalBarChart barChart = new CategoricalBarChart(new SimpleCategoricalPointListContainerImpl(container)); + barChart.setTitle(settingsReader.getSetting(SettingType.DIAGRAM_TITLE).orElse("")); + barChart.setXAxisName(settingsReader.getSetting(SettingType.X_AXIS_LABEL).orElse("")); + barChart.setYAxisName(settingsReader.getSetting(SettingType.Y_AXIS_LABEL).orElse("")); + */ + LiblouisBrailleTextRasterizer.initModule(); + MasterRenderer renderer = new MasterRenderer(indexV4Printer, representationParameters, a4Format); RasterCanvas canvas = renderer.rasterize(lineChart); Iterator<MatrixData<Boolean>> iter = canvas.getPageIterator(); diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/CommandLineParser.java b/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/CommandLineParser.java index 81e77493f2815a72fe27a5da3ea8de51adb7a686..709190192e4f6eed51d28709741fcf3ae87c3a6a 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/CommandLineParser.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/CommandLineParser.java @@ -24,7 +24,10 @@ public class CommandLineParser { mOptions.addOption("h", SettingType.DISPLAY_HELP.toString(), false, "Print help and exit") .addOption("c", SettingType.CSV_LOCATION.toString(), true, "Path to CSV") .addOption("s", SettingType.SEMANTIC_MAPPING.toString(), true, "Literal for semantic mapping") - .addOption("p", SettingType.PRINTER_CONFIG_PATH.toString(), true, "Path to printer configuration file"); + .addOption("p", SettingType.PRINTER_CONFIG_PATH.toString(), true, "Path to printer configuration file") + .addOption("t", SettingType.DIAGRAM_TITLE.toString(), true, "Title of the diagram.") + .addOption("x", SettingType.X_AXIS_LABEL.toString(), true, "Label of X-axis including unit.") + .addOption("y", SettingType.Y_AXIS_LABEL.toString(), true, "Label of Y-axis including unit."); } /** diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/SettingType.java b/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/SettingType.java index 010c9c528891ffdb81549f715eac363625469e76..7740652be678cd701d7aa24b5e2aa0d98404e48a 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/SettingType.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/commandline/SettingType.java @@ -10,7 +10,10 @@ public enum SettingType { DISPLAY_HELP("help"), CSV_LOCATION("csv-path"), PRINTER_CONFIG_PATH("printer-config-path"), - SEMANTIC_MAPPING("semantic-mapping"); + SEMANTIC_MAPPING("semantic-mapping"), + DIAGRAM_TITLE("title"), + X_AXIS_LABEL("xLabel"), + Y_AXIS_LABEL("yLabel"); private final String mName; @@ -28,6 +31,12 @@ public enum SettingType { return PRINTER_CONFIG_PATH; case "semantic-mapping": return SEMANTIC_MAPPING; + case "title": + return DIAGRAM_TITLE; + case "xLabel": + return X_AXIS_LABEL; + case "yLabel": + return Y_AXIS_LABEL; default: throw new IllegalArgumentException("Setting not available"); } 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 b951ece2f3101e6b6f588c597faa639c7d5c7944..7a698aae4d46311daa1ae8a94c6daad17d148f3f 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 @@ -1,5 +1,6 @@ package de.tudresden.inf.mci.brailleplot.configparser; +import de.tudresden.inf.mci.brailleplot.util.UrlHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -172,7 +173,7 @@ public abstract class ConfigurationParser { mLogger.debug("Starting parsing properties file from java resources: \"{}\"", resource); try { - parseConfigFile(resource.openStream(), getParentUrl(resource), assertCompleteness); + parseConfigFile(resource.openStream(), UrlHelper.getParentUrl(resource), assertCompleteness); } catch (IOException e) { throw new ConfigurationParsingException("Could not open resource at \"" + resource.toString() + "\"", e); } @@ -191,7 +192,7 @@ public abstract class ConfigurationParser { mLogger.debug("Starting parsing properties file from file system: \"{}\"", filePath); try { - parseConfigFile(new FileInputStream(filePath.toFile()), filePath.toFile().toURI().toURL(), assertCompleteness); + parseConfigFile(new FileInputStream(filePath.toFile()), UrlHelper.getParentUrl(filePath.toFile().toURI().toURL()), assertCompleteness); } catch (FileNotFoundException | MalformedURLException e) { throw new ConfigurationParsingException("Configuration file could not be read at \"" + filePath.toString() + "\""); } @@ -211,7 +212,7 @@ public abstract class ConfigurationParser { // reset internal property buffer mPrinterProperties.clear(); mFormatProperties.clear(); - mValidator.setSearchPath(getPath(path)); + mValidator.setSearchPath(getPathNoFilePrefix(path)); // load and parse file parse(config, path); // build printer object from added properties @@ -240,30 +241,14 @@ public abstract class ConfigurationParser { } } - /** - * Returns the URL to the parent directory of a File / Resource. - * @param resourcePath The URL to analyze. - * @return The URL to the parent directory of the specified URL. - * @throws ConfigurationParsingException if the generated URL is not a valid URL. - */ - private static URL getParentUrl(final URL resourcePath) throws ConfigurationParsingException { - String fileString = resourcePath.getPath(); - String parentString = fileString.substring(0, fileString.lastIndexOf("/")); - try { - return new URL(resourcePath.getProtocol(), resourcePath.getHost(), parentString); - } catch (MalformedURLException e) { - throw new ConfigurationParsingException("Could not create URL to parent path", e); - } - } - /** * Return a String representation of the path of a {@link URL}. * Strips the {@literal "}file:{@literal "} prefix from an URL, if it exist. * @param url The URL that needs to be stripped * @return The String representation of the path of a URL where the leading {@literal "}file:{@literal "} prefix is stripped. */ - private static String getPath(final URL url) { - String urlString = url.getPath(); + private static String getPathNoFilePrefix(final URL url) throws ConfigurationParsingException { + String urlString = UrlHelper.getPathString(url); return urlString.replaceAll("^file:", ""); } } 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 190f43ca1562b4c62cdf07e39ad82b87263e9df1..01e938d1f5b1958b6fed42f93571e9d84e69dabd 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,5 +1,7 @@ package de.tudresden.inf.mci.brailleplot.configparser; +import de.tudresden.inf.mci.brailleplot.util.UrlHelper; + import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; @@ -19,6 +21,7 @@ import java.util.Properties; public final class JavaPropertiesConfigurationParser extends ConfigurationParser { private static final String INCLUDE_FILE_EXTENSION = ".properties"; + private static final String CONFIG_RESOURCE_ROOT = "config"; /** * Constructor. @@ -88,7 +91,7 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser String value = properties.getProperty(key); // check for special property key: 'include' if (key.equalsIgnoreCase("include")) { - includeResource(value, path); + includeResources(value, path); } else if (key.equalsIgnoreCase("include-file")) { includeFiles(value, path); } else { @@ -121,20 +124,30 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser */ private void includeFiles(final String fileList, final URL parentUrl) throws ConfigurationParsingException, ConfigurationValidationException { for (String s : fileList.split(",")) { - - Path parentPath = null; - try { - parentPath = Path.of(parentUrl.toURI()); - } catch (URISyntaxException e) { - throw new ConfigurationParsingException("Could not generate URI", e); + s = s.trim() + INCLUDE_FILE_EXTENSION; + + Path p = Path.of(s); + String newPathString; + Path newPath; + if (p.isAbsolute()) { + newPath = p; + newPathString = newPath.toString(); + } else { + Path parentPath = null; + try { + parentPath = Path.of(parentUrl.toURI()); + } catch (URISyntaxException e) { + throw new ConfigurationParsingException("Could not generate path from URL", e); + } + newPath = parentPath.resolve(s); + newPathString = newPath.toAbsolutePath().toString(); } - 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()); + Objects.requireNonNull(is); + parse(is, UrlHelper.getParentUrl(newPath.toUri().toURL())); } catch (IOException e) { throw new ConfigurationParsingException("Could not open include file", e); } @@ -148,20 +161,30 @@ public final class JavaPropertiesConfigurationParser extends ConfigurationParser * @throws ConfigurationParsingException If errors occurred while reading from a resource. * @throws ConfigurationValidationException On any error while checking the parsed properties validity. */ - private void includeResource(final String fileList, final URL parentUrl) throws ConfigurationParsingException, ConfigurationValidationException { + private void includeResources(final String fileList, final URL parentUrl) throws ConfigurationParsingException, ConfigurationValidationException { for (String s : fileList.split(",")) { + s = s.trim() + INCLUDE_FILE_EXTENSION; + boolean isAbsolutePath = s.startsWith("/"); URL newUrl = null; - try { - newUrl = new URL(parentUrl + "/" + s.trim() + INCLUDE_FILE_EXTENSION); - } catch (MalformedURLException e) { - throw new ConfigurationParsingException("Could not generate URI", e); + // If the value begins with a "/", treat path as absolute path in resources + if (isAbsolutePath) { + String urlString = CONFIG_RESOURCE_ROOT + s; + newUrl = getClass().getClassLoader().getResource(urlString); + // else treat relative + } else { + try { + newUrl = new URL(parentUrl.getProtocol(), parentUrl.getHost(), UrlHelper.getPathString(parentUrl) + "/" + s); + } catch (MalformedURLException e) { + throw new ConfigurationParsingException("Could not create URL to relative resource", e); + } } - mLogger.debug("Prepare recursive parsing of properties file in the java resources at \"{}\"", newUrl); + mLogger.debug("Prepare recursive parsing of properties file in the java resources at \"{}\"", UrlHelper.getString(newUrl)); try (InputStream is = newUrl.openStream()) { - parse(is, newUrl); + Objects.requireNonNull(is); + parse(is, UrlHelper.getParentUrl(newUrl)); } catch (IOException e) { throw new ConfigurationParsingException("Could not open include resource", e); } diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java index 25b9e69487c01e65fbbc41b0d0be09a506a0aad1..08201f0a21e60d78e40478073803bdbb92582d7d 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java @@ -8,8 +8,11 @@ import de.tudresden.inf.mci.brailleplot.layout.InsufficientRenderingAreaExceptio import de.tudresden.inf.mci.brailleplot.layout.RasterCanvas; import de.tudresden.inf.mci.brailleplot.layout.Rectangle; import de.tudresden.inf.mci.brailleplot.printerbackend.NotSupportedFileExtensionException; +import de.tudresden.inf.mci.brailleplot.util.NativeLibraryHelper; +import de.tudresden.inf.mci.brailleplot.util.NoSuchNativeLibraryException; import org.liblouis.DisplayException; import org.liblouis.DisplayTable; +import org.liblouis.Louis; import org.liblouis.TranslationException; import org.liblouis.TranslationResult; import org.liblouis.Translator; @@ -27,6 +30,8 @@ import static java.lang.Math.ceil; public class LiblouisBrailleTextRasterizer implements Rasterizer<BrailleText> { + private static boolean mNativeLibInitialized = false; + private AbstractBrailleTableParser mParser; // Parameters for rasterizing private int x; @@ -239,4 +244,33 @@ public class LiblouisBrailleTextRasterizer implements Rasterizer<BrailleText> { mTranslator = temp; return length; } + + /** + * Initializes the Module. + * @throws LibLouisLibraryMissingException If liblouis could not be loaded from neither the jar or the default JNI include path. + */ + public static void initModule() throws LibLouisLibraryMissingException { + if (!mNativeLibInitialized) { + try { + NativeLibraryHelper.loadNativeLibrary("liblouis"); + } catch (NoSuchNativeLibraryException e) { + // Even if the library is not distributed within the jar file, it might be installed on the system. + } + try { + Louis.getVersion(); + } catch (java.lang.UnsatisfiedLinkError e) { + throw new LibLouisLibraryMissingException(e); + } + mNativeLibInitialized = true; + } + } + + /** + * Indicates, the native liblouis library was not found. + */ + public static class LibLouisLibraryMissingException extends NoSuchNativeLibraryException { + LibLouisLibraryMissingException(final Throwable cause) { + super(cause); + } + } } diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/util/GeneralResource.java b/src/main/java/de/tudresden/inf/mci/brailleplot/util/GeneralResource.java index 9ff63c0cdf4bbd20520ab2e02aee0ac5c062fb63..c6ab27d9deb66773c3aec3131c4b9f9f4106b8a9 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/util/GeneralResource.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/util/GeneralResource.java @@ -47,25 +47,26 @@ public final class GeneralResource { * @throws IOException If the given path neither determines a valid external file, nor a valid resource. */ public GeneralResource(final String resourcePath, final String searchPath) throws IOException { + mLogger.debug("Requested GeneralResource for path \"" + resourcePath + "\" with search path \"" + searchPath + "\""); File checkFile = new File(resourcePath); - mLogger.debug("Checking referenced path: " + checkFile); + mLogger.trace("Checking referenced path: " + checkFile); if (checkFile.isFile()) { - mLogger.trace("Interpreting path as file: " + checkFile.getCanonicalPath()); + mLogger.debug("Interpreting path as file: " + checkFile.getCanonicalPath()); mResourcePath = checkFile.getCanonicalPath(); mValidExternalFile = true; } checkFile = checkFile.getAbsoluteFile(); - mLogger.debug("Checking referenced path as absolute path: " + checkFile); + mLogger.trace("Checking referenced path as absolute path: " + checkFile); if (checkFile.isFile()) { - mLogger.trace("Interpreting path as absolute file: " + checkFile.getCanonicalPath()); + mLogger.debug("Interpreting path as absolute file: " + checkFile.getCanonicalPath()); mResourcePath = checkFile.getCanonicalPath(); mValidExternalFile = true; } if (Objects.nonNull(searchPath)) { checkFile = new File(searchPath + File.separator + resourcePath); - mLogger.debug("Looking for referenced path in search path: " + checkFile); + mLogger.trace("Looking for referenced path in search path: " + checkFile); if (checkFile.isFile()) { - mLogger.trace("Interpreting path as search path relative file: " + checkFile.getCanonicalPath()); + mLogger.debug("Interpreting path as search path relative file: " + checkFile.getCanonicalPath()); mResourcePath = checkFile.getCanonicalPath(); mValidExternalFile = true; } @@ -77,9 +78,9 @@ public final class GeneralResource { String resourceClassPath = stripJarPath(resourcePath); resourceClassPath = resourceClassPath.replace(File.separator, "/"); // class paths are always separated by forward slash InputStream checkStream = getClass().getClassLoader().getResourceAsStream(resourceClassPath); - mLogger.debug("Checking referenced path as resource: " + resourceClassPath); + mLogger.trace("Checking referenced path as resource: " + resourceClassPath); if (Objects.nonNull(checkStream)) { - mLogger.trace("Interpreting path as resource stream: " + resourceClassPath); + mLogger.debug("Interpreting path as resource stream: " + resourceClassPath); mResourcePath = resourceClassPath; mValidPackedResource = true; } @@ -87,9 +88,9 @@ public final class GeneralResource { String relativeResourcePath = new File(resourceSearchPath + File.separator + resourceClassPath).toPath().normalize().toString(); relativeResourcePath = relativeResourcePath.replace("\\", "/"); checkStream = getClass().getClassLoader().getResourceAsStream(relativeResourcePath); - mLogger.debug("Checking referenced path as search path relative resource: " + relativeResourcePath); + mLogger.trace("Checking referenced path as search path relative resource: " + relativeResourcePath); if (Objects.nonNull(checkStream)) { - mLogger.trace("Interpreting path as resource stream: " + relativeResourcePath); + mLogger.debug("Interpreting path as resource stream: " + relativeResourcePath); mResourcePath = relativeResourcePath; mValidPackedResource = true; } @@ -141,7 +142,7 @@ public final class GeneralResource { * @param path Absolute classpath pointing to jar resource. Omit the leading "/". If this point to a resource directory the directory and all contents are exported. * @return A File instance representing the resource on the file system. */ - public static File getOrExportResourceFile(final String path) { + public static synchronized File getOrExportResourceFile(final String path) { if (isRunFromCompiledJar()) { try { JarFile jar = openJarFile(); @@ -178,7 +179,7 @@ public final class GeneralResource { } else { Class cl = getClassRef(); URL resource = cl.getResource("/" + path); // preceding slash for absolute classpath reference - String directoryPath = resource.getPath(); + String directoryPath = UrlHelper.getPathString(resource); return new File(directoryPath); } } @@ -188,7 +189,9 @@ public final class GeneralResource { if (!isRunFromCompiledJar()) { throw new IllegalStateException("Not running from jar."); } - File jarFile = new File(getClassRef().getProtectionDomain().getCodeSource().getLocation().getPath()); + URL jarUrl = getClassRef().getProtectionDomain().getCodeSource().getLocation(); + String jarPath = UrlHelper.getPathString(jarUrl); + File jarFile = new File(jarPath); return new JarFile(jarFile); } catch (Exception e) { throw new RuntimeException("Error while retrieving JarFile reference.", e); diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..fb12d84492efcbfc339696d9e772692ae93417a6 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java @@ -0,0 +1,118 @@ +package de.tudresden.inf.mci.brailleplot.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * This class offers static methods for native library loading purposes. + * @author Georg Graßnick + * @version 2019.09.26 + */ +public final class NativeLibraryHelper { + + private static final Logger LOG = LoggerFactory.getLogger(NativeLibraryHelper.class); + private static final String LIB_PATH = calculateLibPath(); + + private NativeLibraryHelper() { } + + /** + * Loads a library from the resources according to the current system architecture and operating system. + * @param libName The name of the library. Library prefixes ({@literal "}lib{@literal "}) are not treated separately. + * The system specific file ending is added automatically and must not be included in the parameter. + * @return A File object representing the file of the library. The JNI is automatically set up to take of the library, + * so there is most likely no need for this return value. + * @throws NoSuchNativeLibraryException If the requested library could not be found or read. + */ + public static synchronized File loadNativeLibrary(final String libName) throws NoSuchNativeLibraryException { + File libFile; + try { + String libPath = "native/" + libName + "/" + LIB_PATH + "/" + libName + dynamicLibFileEnding(); + libFile = GeneralResource.getOrExportResourceFile(libPath).getAbsoluteFile(); + registerNewSystemLibPath(libFile.getParent()); + } catch (Exception e) { + throw new NoSuchNativeLibraryException("Could not provide native library from java resources", e); + } + LOG.debug("Found and exported native library \"" + libFile + "\" for requested library \"" + libName + "\""); + return libFile; + } + + /** + * Adds a path in the file system to the search path of the JNI. + * @param path The path to add. + */ + private static synchronized void registerNewSystemLibPath(final String path) { + String currentLibPath = System.getProperty("jna.library.path"); + String newLibPath = null; + boolean pathExists = false; + + if (currentLibPath == null) { + newLibPath = path; + } else { + // Do not insert path if it already is included + String[] existingPaths = currentLibPath.split(File.pathSeparator); + for (String s : existingPaths) { + if (s.equals(path)) { + pathExists = true; + break; + } + } + newLibPath = currentLibPath + File.pathSeparator + path; + } + + if (!pathExists) { + LOG.debug("Setting JNI library path property to \"" + newLibPath + "\""); + System.setProperty("jna.library.path", newLibPath); + } + } + + private static String calculateLibPath() { + return getArch() + "/" + getOs(); + } + + private static String getArch() { + String arch = System.getProperty("os.arch"); + // reference: https://stackoverflow.com/a/36926327 + switch (arch) { + case "x86": + case "i386": + case "i486": + case "i586": + case "i686": + return "x86_32"; + case "x86_64": + case "amd64": + return "x86_64"; + default: + throw new RuntimeException("Operating System architecture \"" + arch + "\" is currently not supported"); + } + } + + private static String getOs() { + String name = System.getProperty("os.name"); + String nameLow = name.toLowerCase(); + if (nameLow.contains("linux")) { + return "linux"; + } else if (nameLow.contains("mac")) { + return "osx"; + } else if (nameLow.contains("win")) { + return "win32"; + } else { + throw new RuntimeException("Operating System \"" + name + "\" is currently not supported"); + } + } + + private static String dynamicLibFileEnding() { + switch (getOs()) { + case "win32": + return ".dll"; + case "linux": + return ".so"; + case "osx": + return ".dylib"; + default: + throw new IllegalStateException("If this exception was thrown, something is wrong with your code"); + } + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/util/NoSuchNativeLibraryException.java b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NoSuchNativeLibraryException.java new file mode 100644 index 0000000000000000000000000000000000000000..2f7331e5f542838d14a77027252710f5dd426b50 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NoSuchNativeLibraryException.java @@ -0,0 +1,23 @@ +package de.tudresden.inf.mci.brailleplot.util; + +/** + * Indicates, some error has occurred while trying to load a native library from the java resources. + * @author Georg Graßnick + * @version 2019.09.26 + */ +public class NoSuchNativeLibraryException extends RuntimeException { + + public NoSuchNativeLibraryException() { } + + public NoSuchNativeLibraryException(final String message) { + super(message); + } + + public NoSuchNativeLibraryException(final Throwable cause) { + super(cause); + } + + public NoSuchNativeLibraryException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/util/UrlHelper.java b/src/main/java/de/tudresden/inf/mci/brailleplot/util/UrlHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..3b923258ce5b4c3310a41ed46f78fca935a7124e --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/util/UrlHelper.java @@ -0,0 +1,48 @@ +package de.tudresden.inf.mci.brailleplot.util; + +import de.tudresden.inf.mci.brailleplot.configparser.ConfigurationParsingException; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +/** + * This class offers static helper methods for working wth {@link URL} objects. + * @author Georg Graßnick + * @version 2019.09.27 + */ +public final class UrlHelper { + + private UrlHelper() { } + + /** + * Get the string representation of the path of a {@link URL}. + * For example, spaces are taken care of here. + * @param url The {@link URL} to analyze. + * @return The string representation of the path of the {@link URL}. + */ + public static String getPathString(final URL url) { + return URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8); + } + + public static String getString(final URL url) { + return URLDecoder.decode(url.toString(), StandardCharsets.UTF_8); + } + + /** + * Returns the URL to the parent directory of a File / Resource. + * @param resourcePath The URL to analyze. + * @return The URL to the parent directory of the specified URL. + * @throws RuntimeException if the generated URL is not a valid URL. + */ + public static URL getParentUrl(final URL resourcePath) throws ConfigurationParsingException { + String fileString = getPathString(resourcePath); + String parentString = fileString.substring(0, fileString.lastIndexOf("/")); + try { + return new URL(resourcePath.getProtocol(), resourcePath.getHost(), parentString); + } catch (MalformedURLException e) { + throw new RuntimeException("Could not create URL to parent path", e); + } + } +} diff --git a/third_party/liblouis_bin b/third_party/liblouis_bin new file mode 160000 index 0000000000000000000000000000000000000000..24b6908d0b167ce785c0f9ce8295d9845b69a303 --- /dev/null +++ b/third_party/liblouis_bin @@ -0,0 +1 @@ +Subproject commit 24b6908d0b167ce785c0f9ce8295d9845b69a303