From e29627ba0dad0dfafb39ba510feb8b596e431f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georg=20Gra=C3=9Fnick?= <georg.grassnick@mailbox.tu-dresden.de> Date: Thu, 26 Sep 2019 19:33:41 +0200 Subject: [PATCH] Load native libraries from resources on runtime --- .gitignore | 5 + build.gradle | 17 +++ .../de/tudresden/inf/mci/brailleplot/App.java | 2 - .../LiblouisBrailleTextRasterizer.java | 9 ++ .../brailleplot/util/NativeLibraryHelper.java | 106 ++++++++++++++++++ .../util/NoSuchNativeLibraryException.java | 23 ++++ 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java create mode 100644 src/main/java/de/tudresden/inf/mci/brailleplot/util/NoSuchNativeLibraryException.java diff --git a/.gitignore b/.gitignore index 9f25cc23..e113ca2e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,8 @@ src/main/resources/mapping/liblouis # Exported SVG files *.svg + +# Native libraries +*.so +*.dynlib +*.dll diff --git a/build.gradle b/build.gradle index 59cf0b22..d56141ec 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 ada22108..14225cbe 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java @@ -151,8 +151,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); 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 d47dec2b..e9a28cb4 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 @@ -7,6 +7,8 @@ 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.TranslationException; @@ -42,6 +44,13 @@ public class LiblouisBrailleTextRasterizer implements Rasterizer<BrailleText> { * @param printer Needed to get the semantictable according to the printer config. */ public LiblouisBrailleTextRasterizer(final Printer printer) { + + 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. + } + Objects.requireNonNull(printer, "The given printer for the LiblouisBrailleTextRasterizer was null!"); try { mParser = AbstractBrailleTableParser.getParser(printer, "semantictable"); 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 00000000..cdc570ab --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java @@ -0,0 +1,106 @@ +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) { + // TODO Check if path is already included and abort, if so + String currentLibPath = System.getProperty("jna.library.path"); + String newLibPath = null; + if (currentLibPath == null) { + newLibPath = path; + } else { + newLibPath = currentLibPath + File.pathSeparator + path; + } + 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 ".dynlib"; + 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 00000000..2f7331e5 --- /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); + } +} -- GitLab