Bläddra i källkod

Improve NativeLibraryLoader (#1973)

* Improve NativeLibraryLoader.

* Add javadoc.

* Moved library extraction requirement check into a separate method.

* Fix javadoc.

* Refactor library extraction check method.

* Extract natives to system temp directory retrieved by System.getProperty("java.io.tmpdir") instead of working directory.

* Renamed enum "Openal" to "OpenAL" and added javadoc on NativeLibraries.

* Update comments.
Ali-RS 2 år sedan
förälder
incheckning
44a50de4d7

+ 1 - 1
jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java

@@ -285,7 +285,7 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
         logger.log(Level.INFO, getBuildInfo());
         if (!lowPermissions) {
             if (NativeLibraryLoader.isUsingNativeBullet()) {
-                NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
+                NativeLibraryLoader.loadNativeLibrary(NativeLibraries.BulletJme.getName(), true);
             }
         }
     }

+ 235 - 0
jme3-desktop/src/main/java/com/jme3/system/NativeLibraries.java

@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Defines default native libraries that are loaded by
+ * {@link NativeLibraryLoader}.
+ *
+ * @author Ali-RS
+ */
+public enum NativeLibraries {
+
+    // Note: LWJGL 3 handles its native library extracting & loading using
+    // its own SharedLibraryLoader.
+
+    /**
+     * Native lwjgl libraries for LWJGL 2 required by jme3-lwjgl backend.
+     */
+    Lwjgl(new LibraryInfo("lwjgl", libPath ->
+            // Delegate loading to lwjgl.
+            System.setProperty("org.lwjgl.librarypath",
+                    Paths.get(libPath).getParent().toAbsolutePath().toString()))
+            .addNativeVariant(Platform.Windows32, "lwjgl.dll")
+            .addNativeVariant(Platform.Windows64, "lwjgl64.dll")
+            .addNativeVariant(Platform.Linux32, "liblwjgl.so")
+            .addNativeVariant(Platform.Linux64, "liblwjgl64.so")
+            .addNativeVariant(Platform.MacOSX32, "liblwjgl.dylib")
+            .addNativeVariant(Platform.MacOSX64, "liblwjgl.dylib")
+    ),
+
+    // OpenAL for LWJGL 2
+    // For OSX: Need to add lib prefix when extracting
+    /**
+     * Native OpenAL audio libraries for LWJGL 2 required by jme3-lwjgl backend.
+     */
+    OpenAL(new LibraryInfo("openal")
+            .addNativeVariant(Platform.Windows32, "OpenAL32.dll")
+            .addNativeVariant(Platform.Windows64, "OpenAL64.dll")
+            .addNativeVariant(Platform.Linux32,   "libopenal.so")
+            .addNativeVariant(Platform.Linux64,   "libopenal64.so")
+            .addNativeVariant(Platform.MacOSX32,  "openal.dylib", "libopenal.dylib")
+            .addNativeVariant(Platform.MacOSX64,  "openal.dylib", "libopenal.dylib")
+    ),
+
+    /**
+     * Native bullet physics libraries required by Minie library.
+     */
+    BulletJme(new LibraryInfo("bulletjme")
+            .addNativeVariant(Platform.Windows32, "native/windows/x86/bulletjme.dll", "bulletjme-x86.dll")
+            .addNativeVariant(Platform.Windows64, "native/windows/x86_64/bulletjme.dll", "bulletjme-x86_64.dll")
+            .addNativeVariant(Platform.Windows_ARM64, "native/windows/arm64/bulletjme.dll", "bulletjme-arm64.dll")
+            .addNativeVariant(Platform.Linux32, "native/linux/x86/libbulletjme.so", "libbulletjme-x86.so")
+            .addNativeVariant(Platform.Linux64, "native/linux/x86_64/libbulletjme.so", "libbulletjme-x86_64.so")
+            .addNativeVariant(Platform.Linux_ARM32, "native/linux/arm32/libbulletjme.so", "libbulletjme-arm32.so")
+            .addNativeVariant(Platform.Linux_ARM64, "native/linux/arm64/libbulletjme.so", "libbulletjme-arm64.so")
+            .addNativeVariant(Platform.MacOSX32, "native/osx/x86/libbulletjme.dylib", "libbulletjme-x86.dylib")
+            .addNativeVariant(Platform.MacOSX64, "native/osx/x86_64/libbulletjme.dylib", "libbulletjme-x86_64.dylib")
+            .addNativeVariant(Platform.MacOSX_ARM64, "native/osx/arm64/libbulletjme.dylib", "libbulletjme-arm64.dylib")
+    ),
+
+    // For OSX: Need to rename extension jnilib -> dylib when extracting
+    /**
+     * Native JInput joystick libraries required by jme3-lwjgl backend.
+     */
+    JInput(new LibraryInfo("jinput", libPath ->
+            // Delegate loading to jinput.
+            System.setProperty("net.java.games.input.librarypath",
+                    Paths.get(libPath).getParent().toAbsolutePath().toString()))
+            .addNativeVariant(Platform.Windows32, "jinput-raw.dll")
+            .addNativeVariant(Platform.Windows64, "jinput-raw_64.dll")
+            .addNativeVariant(Platform.Linux32, "libjinput-linux.so")
+            .addNativeVariant(Platform.Linux64, "libjinput-linux64.so")
+            .addNativeVariant(Platform.MacOSX32, "libjinput-osx.jnilib", "libjinput-osx.dylib")
+            .addNativeVariant(Platform.MacOSX64, "libjinput-osx.jnilib", "libjinput-osx.dylib")
+    ),
+
+    /**
+     * Native JInput DirectX 8 auxiliary libraries required by jme3-lwjgl backend.
+     * (only required on Windows)
+     */
+    JInputDX8(new LibraryInfo("jinput-dx8")
+            .addNativeVariant(Platform.Windows32, "jinput-dx8.dll", null)
+            .addNativeVariant(Platform.Windows64, "jinput-dx8_64.dll", null)
+            .addNativeVariant(Platform.Linux32, null)
+            .addNativeVariant(Platform.Linux64, null)
+            .addNativeVariant(Platform.MacOSX32, null)
+            .addNativeVariant(Platform.MacOSX64, null)
+    );
+
+    private final LibraryInfo library;
+
+
+    NativeLibraries(LibraryInfo library) {
+        this.library = library;
+    }
+
+    /**
+     * Register native libraries on {@link NativeLibraryLoader} so we can load them
+     * later on via {@link NativeLibraryLoader#loadNativeLibrary(String, boolean)}.
+     */
+    public static void registerDefaultLibraries() {
+        Lwjgl.registerLibrary();
+        OpenAL.registerLibrary();
+        BulletJme.registerLibrary();
+        JInput.registerLibrary();
+        JInputDX8.registerLibrary();
+    }
+
+    public LibraryInfo getLibrary() {
+        return library;
+    }
+
+    /**
+     * @return the library name. This is effectively equivalent to the
+     * call {@link LibraryInfo#getName()}
+     */
+    public String getName() {
+        return library.getName();
+    }
+
+    /**
+     * Registers this library's native variants into {@link NativeLibraryLoader} that can
+     * be loaded later via {@link NativeLibraryLoader#loadNativeLibrary(String, boolean)}.
+     */
+    private void registerLibrary() {
+        library.getNativeVariants().forEach(NativeLibraryLoader::registerNativeLibrary);
+    }
+
+    /**
+     * A helper class that defines a native library by name, list of its native variants
+     * for target platforms and a load function used to load library from an absolute
+     * path after extracted by {@link NativeLibraryLoader}.
+     */
+    public static class LibraryInfo {
+
+        private final String name;
+        private final List<NativeLibrary> nativeVariants = new ArrayList<>();
+        private final Consumer<String> loadFunction;
+
+        /**
+         * Define a library by the specified name and a default load function
+         * that uses {@link System#load(String)} to load extracted native from
+         * absolute path.
+         * @param name The library name. (not null)
+         */
+        public LibraryInfo(String name) {
+            this(name, System::load);
+        }
+
+        /**
+         * Define a library by the specified name and specified load function
+         * that is used to load extracted native from an absolute path string.
+         *
+         * @param name The library name (not null)
+         * @param loadFunction The load function for loading library from
+         *                     an absolute path string. (not null)
+         */
+        public LibraryInfo(String name, Consumer<String> loadFunction) {
+            this.name = name;
+            this.loadFunction = loadFunction;
+        }
+
+        /**
+         * @return the library name.
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * @return the list of native variants, each targeting a specific platform.
+         */
+        public List<NativeLibrary> getNativeVariants() {
+            return nativeVariants;
+        }
+
+        /**
+         * Adds a new native library that targets specified platform.
+         *
+         * @param platform The platform this library targets
+         * @param pathInNativesJar The path of native file inside library jar
+         * @return this
+         */
+        public LibraryInfo addNativeVariant(Platform platform, String pathInNativesJar) {
+            return addNativeVariant(platform, pathInNativesJar, null);
+        }
+
+        /**
+         * Adds a new native library that targets specified platform.
+         *
+         * @param platform The platform this library targets
+         * @param pathInNativesJar The path of native file inside library jar
+         * @param extractedAsFileName The filename that the library should be extracted as
+         * @return this
+         */
+        public LibraryInfo addNativeVariant(Platform platform, String pathInNativesJar, String extractedAsFileName) {
+             nativeVariants.add(new NativeLibrary(name, platform, pathInNativesJar, extractedAsFileName, loadFunction));
+             return this;
+        }
+    }
+}

+ 66 - 15
jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2023 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,8 @@
  */
 package com.jme3.system;
 
+import java.util.function.Consumer;
+
 /**
  * Holds information about a native library for a particular platform.
  * 
@@ -42,6 +44,7 @@ final class NativeLibrary {
     private final Platform platform;
     private final String pathInNativesJar;
     private final String extractedAsFileName;
+    private final Consumer<String> loadFunction;
 
     /**
      * Key for map to find a library for a name and platform.
@@ -76,6 +79,55 @@ final class NativeLibrary {
             return true;
         }
     }
+
+    /**
+     * Create a new NativeLibrary. The extracted file name will be the same as
+     * in jar and will be loaded via {@link System#load(String)}.
+     *
+     * @param name The name of the library (not null)
+     * @param platform The platform associated to this native library (not null)
+     * @param pathInNativesJar The path to the library in the classpath (if it is null,
+     *                         the library loading will be ignored on this platform)
+     **/
+    public NativeLibrary(String name, Platform platform, String pathInNativesJar) {
+        this(name, platform, pathInNativesJar, null);
+    }
+
+    /**
+     * Create a new NativeLibrary. The extracted file will be loaded
+     * via {@link System#load(String)}.
+     *
+     * @param name The name of the library (not null)
+     * @param platform The platform associated to this native library (not null)
+     * @param pathInNativesJar The path to the library in the classpath (if it is null,
+     *                         the library loading will be ignored on this platform)
+     * @param extractedAsFileName The name that should be given to the extracted file
+     *                            (if set to null, then the filename in the natives
+     *                            jar shall be used)
+     */
+    public NativeLibrary(String name, Platform platform, String pathInNativesJar, String extractedAsFileName) {
+        this(name, platform, pathInNativesJar, extractedAsFileName, System::load);
+    }
+
+    /**
+     * Create a new NativeLibrary.
+     *
+     * @param name The name of the library (not null)
+     * @param platform The platform associated to this native library (not null)
+     * @param pathInNativesJar The path to the library in the classpath (if it is null,
+     *                         the library loading will be ignored on this platform)
+     * @param extractedAsFileName The name that should be given to the extracted file
+     *                            (if set to null, then the filename in the natives
+     *                            jar shall be used)
+     * @param loadFunction The function used to load the library from absolute path (not null)
+     */
+    public NativeLibrary(String name, Platform platform, String pathInNativesJar, String extractedAsFileName, Consumer<String> loadFunction) {
+        this.name = name;
+        this.platform = platform;
+        this.pathInNativesJar = pathInNativesJar;
+        this.extractedAsFileName = extractedAsFileName;
+        this.loadFunction = loadFunction;
+    }
     
     /**
      * The name of the library. 
@@ -90,7 +142,7 @@ final class NativeLibrary {
     /**
      * The OS + architecture combination for which this library
      * should be extracted.
-     * 
+     *
      * @return platform associated to this native library
      */
     public Platform getPlatform() {
@@ -99,12 +151,12 @@ final class NativeLibrary {
 
     /**
      * The filename that the library should be extracted as.
-     * 
+     *
      * In some cases, this differs from the {@link #getPathInNativesJar() path in the natives jar},
      * since the names of the libraries specified in the jars are often incorrect.
      * If set to <code>null</code>, then the filename in the
      * natives jar shall be used.
-     * 
+     *
      * @return the name that should be given to the extracted file.
      */
     public String getExtractedAsName() {
@@ -113,10 +165,10 @@ final class NativeLibrary {
 
     /**
      * Path inside the natives jar or classpath where the library is located.
-     * 
+     *
      * This library must be compatible with the {@link #getPlatform() platform}
      * which this library is associated with.
-     * 
+     *
      * @return path to the library in the classpath
      */
     public String getPathInNativesJar() {
@@ -124,19 +176,18 @@ final class NativeLibrary {
     }
 
     /**
-     * Create a new NativeLibrary.
+     * @return the load function used for loading this native library.
+     * It loads the native library from absolute path on disk.
+     * By default, it loads with {@link System#load(java.lang.String) }.
      */
-    public NativeLibrary(String name, Platform platform, String pathInNativesJar, String extractedAsFileName) {
-        this.name = name;
-        this.platform = platform;
-        this.pathInNativesJar = pathInNativesJar;
-        this.extractedAsFileName = extractedAsFileName;
+    public Consumer<String> getLoadFunction() {
+        return loadFunction;
     }
 
     /**
-     * Create a new NativeLibrary.
+     * @return key for map to find a library for a name and platform.
      */
-    public NativeLibrary(String name, Platform platform, String pathInNativesJar) {
-        this(name, platform, pathInNativesJar, null);
+    public Key getKey() {
+        return new NativeLibrary.Key(getName(), getPlatform());
     }
 }

+ 101 - 197
jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java

@@ -35,6 +35,9 @@ import java.io.*;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -70,15 +73,27 @@ import java.util.logging.Logger;
 public final class NativeLibraryLoader {
     
     private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
-    private static final byte[] buf = new byte[1024 * 100];
     private static File extractionFolderOverride = null;
     private static File extractionFolder = null;
     
-    private static final HashMap<NativeLibrary.Key, NativeLibrary> nativeLibraryMap
-            = new HashMap<>();
-    
+    private static final HashMap<NativeLibrary.Key, NativeLibrary> nativeLibraryMap = new HashMap<>();
+
+    static {
+        NativeLibraries.registerDefaultLibraries();
+    }
+
+    /**
+     * Register a new native library.
+     *
+     * This simply registers a known library, the actual extraction and loading
+     * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }.
+     */
+    public static void registerNativeLibrary(NativeLibrary library) {
+        nativeLibraryMap.put(library.getKey(), library);
+    }
+
     /**
-     * Register a new known library.
+     * Register a new native library.
      * 
      * This simply registers a known library, the actual extraction and loading
      * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }.
@@ -99,7 +114,7 @@ public final class NativeLibraryLoader {
     }
     
     /**
-     * Register a new known JNI library.
+     * Register a new native library.
      * 
      * This simply registers a known library, the actual extraction and loading
      * is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }.
@@ -119,57 +134,6 @@ public final class NativeLibraryLoader {
         registerNativeLibrary(name, platform, path, null);
     }
     
-    static {
-        // Note: LWJGL 3 handles its native library extracting & loading using
-        // its own SharedLibraryLoader.
-
-        // LWJGL 2
-        registerNativeLibrary("lwjgl", Platform.Windows32, "lwjgl.dll");
-        registerNativeLibrary("lwjgl", Platform.Windows64, "lwjgl64.dll");
-        registerNativeLibrary("lwjgl", Platform.Linux32,   "liblwjgl.so");
-        registerNativeLibrary("lwjgl", Platform.Linux64,   "liblwjgl64.so");
-        registerNativeLibrary("lwjgl", Platform.MacOSX32,  "liblwjgl.dylib");
-        registerNativeLibrary("lwjgl", Platform.MacOSX64,  "liblwjgl.dylib");
-
-        // OpenAL for LWJGL 2
-        // For OSX: Need to add lib prefix when extracting
-        registerNativeLibrary("openal", Platform.Windows32, "OpenAL32.dll");
-        registerNativeLibrary("openal", Platform.Windows64, "OpenAL64.dll");
-        registerNativeLibrary("openal", Platform.Linux32,   "libopenal.so");
-        registerNativeLibrary("openal", Platform.Linux64,   "libopenal64.so");
-        registerNativeLibrary("openal", Platform.MacOSX32,  "openal.dylib", "libopenal.dylib");
-        registerNativeLibrary("openal", Platform.MacOSX64,  "openal.dylib", "libopenal.dylib");
-
-        // BulletJme
-        registerNativeLibrary("bulletjme", Platform.Windows32, "native/windows/x86/bulletjme.dll");
-        registerNativeLibrary("bulletjme", Platform.Windows64, "native/windows/x86_64/bulletjme.dll");
-        registerNativeLibrary("bulletjme", Platform.Windows_ARM64, "native/windows/arm64/bulletjme.dll");
-        registerNativeLibrary("bulletjme", Platform.Linux32,   "native/linux/x86/libbulletjme.so");
-        registerNativeLibrary("bulletjme", Platform.Linux64,   "native/linux/x86_64/libbulletjme.so");
-        registerNativeLibrary("bulletjme", Platform.Linux_ARM32, "native/linux/arm32/libbulletjme.so");
-        registerNativeLibrary("bulletjme", Platform.Linux_ARM64, "native/linux/arm64/libbulletjme.so");
-        registerNativeLibrary("bulletjme", Platform.MacOSX32,  "native/osx/x86/libbulletjme.dylib");
-        registerNativeLibrary("bulletjme", Platform.MacOSX64,  "native/osx/x86_64/libbulletjme.dylib");
-        registerNativeLibrary("bulletjme", Platform.MacOSX_ARM64,  "native/osx/arm64/libbulletjme.dylib");
-
-        // JInput
-        // For OSX: Need to rename extension jnilib -> dylib when extracting
-        registerNativeLibrary("jinput", Platform.Windows32, "jinput-raw.dll");
-        registerNativeLibrary("jinput", Platform.Windows64, "jinput-raw_64.dll");
-        registerNativeLibrary("jinput", Platform.Linux32,   "libjinput-linux.so");
-        registerNativeLibrary("jinput", Platform.Linux64,   "libjinput-linux64.so");
-        registerNativeLibrary("jinput", Platform.MacOSX32,  "libjinput-osx.jnilib", "libjinput-osx.dylib");
-        registerNativeLibrary("jinput", Platform.MacOSX64,  "libjinput-osx.jnilib", "libjinput-osx.dylib");
-        
-        // JInput Auxiliary (only required on Windows)
-        registerNativeLibrary("jinput-dx8", Platform.Windows32, "jinput-dx8.dll");
-        registerNativeLibrary("jinput-dx8", Platform.Windows64, "jinput-dx8_64.dll");
-        registerNativeLibrary("jinput-dx8", Platform.Linux32,   null);
-        registerNativeLibrary("jinput-dx8", Platform.Linux64,   null);
-        registerNativeLibrary("jinput-dx8", Platform.MacOSX32,  null);
-        registerNativeLibrary("jinput-dx8", Platform.MacOSX64,  null);
-    }
-    
     private NativeLibraryLoader() {
     }
     
@@ -211,8 +175,8 @@ public final class NativeLibraryLoader {
      * <ul>
      * <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom
      * extraction folder} has been specified, it is returned.
-     * <li>If the user can write to the working folder, then it 
-     * is returned.</li>
+     * <li>If the user can write to "java.io.tmpdir" folder, then it
+     * is used.</li>
      * <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder}
      * is used, to prevent collisions, a special subfolder is used
      * called <code>natives_&lt;hash&gt;</code> where &lt;hash&gt;
@@ -227,15 +191,20 @@ public final class NativeLibraryLoader {
             return extractionFolderOverride;
         }
         if (extractionFolder == null) {
-            File workingFolder = new File("").getAbsoluteFile();
-            if (!workingFolder.canWrite()) {
+            File userTempDir = new File(System.getProperty("java.io.tmpdir"));
+            if (!userTempDir.canWrite()) {
                 setExtractionFolderToUserCache();
             } else {
                 try {
-                    File file = new File(workingFolder + File.separator + ".jmetestwrite");
-                    file.createNewFile();
-                    file.delete();
-                    extractionFolder = workingFolder;
+                    File jmeTempDir = new File(userTempDir, "jme3");
+                    if (!jmeTempDir.exists()) {
+                        jmeTempDir.mkdir();
+                    }
+                    extractionFolder = new File(jmeTempDir, "natives_" + Integer.toHexString(computeNativesHash()));
+
+                    if (!extractionFolder.exists()) {
+                        extractionFolder.mkdir();
+                    }
                 } catch (Exception e) {
                     setExtractionFolderToUserCache();
                 }
@@ -431,18 +400,7 @@ public final class NativeLibraryLoader {
             return;
         }
         
-        String fileNameInJar;
-        if (pathInJar.contains("/")) {
-            fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1);
-        } else {
-            fileNameInJar = pathInJar;
-        }
-        
         URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
-        if (url == null) {
-            url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
-        }
-        
         if (url == null) {
             return;
         }
@@ -451,33 +409,16 @@ public final class NativeLibraryLoader {
         if (library.getExtractedAsName() != null) {
             loadedAsFileName = library.getExtractedAsName();
         } else {
-            loadedAsFileName = fileNameInJar;
+            loadedAsFileName = Paths.get(pathInJar).getFileName().toString();
         }
         
         URLConnection conn = url.openConnection();
-        InputStream in = conn.getInputStream();
-        
+
         File targetFile = new File(targetDir, loadedAsFileName);
-        OutputStream out = null;
-        try {
-            out = new FileOutputStream(targetFile);
-            int len;
-            while ((len = in.read(buf)) > 0) {
-                out.write(buf, 0, len);
-            }
-        } finally {
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (IOException ex) {
-                }
-            }
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (IOException ex) {
-                }
-            }
+
+        try (InputStream in = conn.getInputStream()) {
+            Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+            targetFile.setLastModified(conn.getLastModified());
         }
     }
 
@@ -519,48 +460,19 @@ public final class NativeLibraryLoader {
             // This platform does not require the native library to be loaded.
             return;
         }
-        
-        final String fileNameInJar;
-
-        if (pathInJar.contains("/")) {
-            fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1);
-        } else {
-            fileNameInJar = pathInJar;
-        }
 
         URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
-        
-        if (url == null) {
-            // Try the root of the classpath as well.
-            url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
-        }
 
         if (url == null) {
-            // Attempt to load it as a system library.
-            String unmappedName = unmapLibraryName(fileNameInJar);
-            try {
-                // XXX: HACK. Vary loading method based on library name.
-                // lwjgl and jinput handle loading by themselves.
-                if (!name.equals("lwjgl") && !name.equals("jinput")) {
-                    // Need to unmap it from library specific parts.
-                    System.loadLibrary(unmappedName);
-                    logger.log(Level.FINE, "Loaded system installed "
-                            + "version of native library: {0}", unmappedName);
-                }
-            } catch (UnsatisfiedLinkError e) {
-                if (isRequired) {
-                    throw new UnsatisfiedLinkError(
-                            "The required native library '" + unmappedName + "'"
-                            + " was not found in the classpath via '" + pathInJar
-                            + "'. Error message: " + e.getMessage());
-                } else if (logger.isLoggable(Level.FINE)) {
-                    logger.log(Level.FINE, "The optional native library ''{0}''" + 
-                                           " was not found in the classpath via ''{1}''" +
-                                           ". Error message: {2}",
-                                           new Object[]{unmappedName, pathInJar, e.getMessage()});
-                }
+            if (isRequired) {
+                throw new UnsatisfiedLinkError(
+                        "The required native library '" + library.getName() + "'"
+                                + " was not found in the classpath via '" + pathInJar);
+            } else if (logger.isLoggable(Level.FINE)) {
+                logger.log(Level.FINE, "The optional native library ''{0}''" +
+                                " was not found in the classpath via ''{1}''.",
+                        new Object[]{library.getName(), pathInJar});
             }
-            
             return;
         }
         
@@ -571,16 +483,14 @@ public final class NativeLibraryLoader {
             loadedAsFileName = library.getExtractedAsName();
         } else {
             // Just use the original filename as it is in the JAR.
-            loadedAsFileName = fileNameInJar;
+            loadedAsFileName = Paths.get(pathInJar).getFileName().toString();
         }
         
         File extractionDirectory = getExtractionFolder();
         URLConnection conn;
-        InputStream in;
-        
+
         try {
             conn = url.openConnection();
-            in = conn.getInputStream();
         } catch (IOException ex) {
             // Maybe put more detail here? Not sure.
             throw new UncheckedIOException("Failed to open file: '" + url + 
@@ -588,72 +498,66 @@ public final class NativeLibraryLoader {
         }
         
         File targetFile = new File(extractionDirectory, loadedAsFileName);
-        OutputStream out = null;
-        try {
-            if (targetFile.exists()) {
-                // OK, compare last modified date of this file to 
-                // file in jar
-                long targetLastModified = targetFile.lastModified();
-                long sourceLastModified = conn.getLastModified();
+        try (InputStream in = conn.getInputStream()) {
+            if (isExtractingRequired(conn, targetFile)) {
+                Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
 
-                // Allow ~1 second range for OSes that only support low precision
-                if (targetLastModified + 1000 > sourceLastModified) {
-                    logger.log(Level.FINE, "Not copying library {0}. " + 
-                                           "Latest already extracted.", 
-                                           loadedAsFileName);
-                    return;
+                // NOTE: On OSes that support "Date Created" property,
+                // this will cause the last modified date to be lower than
+                // date created which makes no sense
+                targetFile.setLastModified(conn.getLastModified());
+
+                if (logger.isLoggable(Level.FINE)) {
+                    logger.log(Level.FINE, "Extracted native library from ''{0}'' into ''{1}''. ",
+                            new Object[]{url, targetFile});
+                }
+            } else {
+                if (logger.isLoggable(Level.FINE)) {
+                    logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.",
+                            loadedAsFileName);
                 }
             }
 
-            out = new FileOutputStream(targetFile);
-            int len;
-            while ((len = in.read(buf)) > 0) {
-                out.write(buf, 0, len);
-            }
-            
-            in.close();
-            in = null;
-            out.close();
-            out = null;
+            library.getLoadFunction().accept(targetFile.getAbsolutePath());
 
-            // NOTE: On OSes that support "Date Created" property, 
-            // this will cause the last modified date to be lower than
-            // date created which makes no sense
-            targetFile.setLastModified(conn.getLastModified());
+            if (logger.isLoggable(Level.FINE)) {
+                logger.log(Level.FINE, "Loaded native library {0}.", library.getName());
+            }
         } catch (IOException ex) {
-            if (ex.getMessage().contains("used by another process")) {
+            /*if (ex.getMessage().contains("used by another process")) {
                 return;
-            } else {
-                throw new UncheckedIOException("Failed to extract native "
-                        + "library to: " + targetFile, ex);
-            }
-        } finally {
-            // XXX: HACK. Vary loading method based on library name.
-            // lwjgl and jinput handle loading by themselves.
-            if (name.equals("lwjgl") || name.equals("lwjgl3")) {
-                System.setProperty("org.lwjgl.librarypath", 
-                                   extractionDirectory.getAbsolutePath());
-            } else if (name.equals("jinput")) {
-                System.setProperty("net.java.games.input.librarypath", 
-                                   extractionDirectory.getAbsolutePath());
-            } else {
-                // all other libraries (openal, bulletjme, custom)
-                // will load directly in here.
-                System.load(targetFile.getAbsolutePath());
-            }
-            
-            if(in != null){
-                try { in.close(); } catch (IOException ex) { }
-            }
-            if(out != null){
-                try { out.close(); } catch (IOException ex) { }
-            }
+            }*/
+
+            throw new UncheckedIOException("Failed to extract native library to: "
+                    + targetFile, ex);
         }
+    }
 
-        if (logger.isLoggable(Level.FINE)) {
-            logger.log(Level.FINE, "Loaded native library from ''{0}'' into ''{1}''",
-                    new Object[]{url, targetFile});
+    /**
+     * Checks if library extraction is required by comparing source and target
+     * last modified date. Returns true if target file does not exist.
+     *
+     * @param conn the source file
+     * @param targetFile the target file
+     * @return false if target file exist and the difference in last modified date is
+     *          less than 1 second, true otherwise
+     */
+    private static boolean isExtractingRequired(URLConnection conn, File targetFile) {
+        if (!targetFile.exists()) {
+            // Extract anyway if the file doesn't exist
+            return true;
         }
+
+        // OK, if the file exists then compare last modified date
+        // of this file to file in jar
+        long targetLastModified = targetFile.lastModified();
+        long sourceLastModified = conn.getLastModified();
+
+        // Allow ~1 second range for OSes that only support low precision
+        return Math.abs(sourceLastModified - targetLastModified) >= 1000;
+
+        // Note extraction should also work fine if user who was using
+        // a newer version of library, downgraded to an older version
+        // which will make above check invalid and extract it again.
     }
-    
 }

+ 3 - 2
jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java

@@ -40,6 +40,7 @@ import com.jme3.audio.plugins.OGGLoader;
 import com.jme3.audio.plugins.WAVLoader;
 import com.jme3.system.AppSettings;
 import com.jme3.system.JmeSystem;
+import com.jme3.system.NativeLibraries;
 import com.jme3.system.NativeLibraryLoader;
 
 import java.io.*;
@@ -61,8 +62,8 @@ public class TestMusicPlayer extends javax.swing.JFrame {
         // started, but in this test we do not create a LwjglContext,
         // so we should handle loading natives ourselves if running
         // with lwjgl 2.
-        NativeLibraryLoader.loadNativeLibrary("lwjgl", false);
-        NativeLibraryLoader.loadNativeLibrary("openal", false);
+        NativeLibraryLoader.loadNativeLibrary(NativeLibraries.Lwjgl.getName(), false);
+        NativeLibraryLoader.loadNativeLibrary(NativeLibraries.OpenAL.getName(), false);
     }
 
     public TestMusicPlayer() {

+ 5 - 5
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java

@@ -235,14 +235,14 @@ public abstract class LwjglContext implements JmeContext {
         if (JmeSystem.isLowPermissions()) {
             return;
         }
-        if ("LWJGL".equals(settings.getAudioRenderer())) {
-            NativeLibraryLoader.loadNativeLibrary("openal", true);
+        if (AppSettings.LWJGL_OPENAL.equals(settings.getAudioRenderer())) {
+            NativeLibraryLoader.loadNativeLibrary(NativeLibraries.OpenAL.getName(), true);
         }
         if (settings.useJoysticks()) {
-            NativeLibraryLoader.loadNativeLibrary("jinput", true);
-            NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true);
+            NativeLibraryLoader.loadNativeLibrary(NativeLibraries.JInput.getName(), true);
+            NativeLibraryLoader.loadNativeLibrary(NativeLibraries.JInputDX8.getName(), true);
         }
-        NativeLibraryLoader.loadNativeLibrary("lwjgl", true);
+        NativeLibraryLoader.loadNativeLibrary(NativeLibraries.Lwjgl.getName(), true);
     }
     protected int getNumSamplesToUse() {
         int samples = 0;

+ 1 - 1
jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java

@@ -120,7 +120,7 @@ public abstract class LwjglContextVR implements JmeContext {
         }
 
         if (NativeLibraryLoader.isUsingNativeBullet()) {
-            NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
+            NativeLibraryLoader.loadNativeLibrary(NativeLibraries.BulletJme.getName(), true);
         }
     }