diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 11c06c4622..645a83b9ed 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -285,7 +285,7 @@ public void initialize(AppSettings settings) { logger.log(Level.INFO, getBuildInfo()); if (!lowPermissions) { if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.BulletJme.getName(), true); } } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraries.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraries.java new file mode 100644 index 0000000000..b64a2fa84b --- /dev/null +++ b/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 nativeVariants = new ArrayList<>(); + private final Consumer 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 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 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; + } + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java index 1ce38b0477..a4ad86c55a 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibrary.java +++ b/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 loadFunction; /** * Key for map to find a library for a name and platform. @@ -76,6 +79,55 @@ public boolean equals(Object obj) { 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 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 @@ public String getName() { /** * 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 @@ public Platform getPlatform() { /** * 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 null, 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 @@ public String getExtractedAsName() { /** * 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 @@ public String getPathInNativesJar() { } /** - * 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 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()); } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index 8c5c790be2..209034c1e8 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -35,6 +35,9 @@ 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 @@ 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 nativeLibraryMap - = new HashMap<>(); - + private static final HashMap 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 static void registerNativeLibrary(String name, Platform platform, } /** - * 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 static void registerNativeLibrary(String name, Platform platform, 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 static void setCustomExtractionFolder(String path) { *
    *
  • If a {@link #setCustomExtractionFolder(java.lang.String) custom * extraction folder} has been specified, it is returned. - *
  • If the user can write to the working folder, then it - * is returned.
  • + *
  • If the user can write to "java.io.tmpdir" folder, then it + * is used.
  • *
  • Otherwise, the {@link JmeSystem#getStorageFolder() storage folder} * is used, to prevent collisions, a special subfolder is used * called natives_<hash> where <hash> @@ -227,15 +191,20 @@ public static File getExtractionFolder() { 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 static void extractNativeLibrary(Platform platform, String name, File tar 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 static void extractNativeLibrary(Platform platform, String name, File tar 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 static void loadNativeLibrary(String name, boolean isRequired) { // 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 static void loadNativeLibrary(String name, boolean isRequired) { 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 static void loadNativeLibrary(String name, boolean isRequired) { } 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. } - } diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java index 777ed656ac..da6b4e9a2f 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java @@ -40,6 +40,7 @@ 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() { diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 8aed65f07c..124c5744b4 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -235,14 +235,14 @@ protected void loadNatives() { 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; diff --git a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java index 01dbf7df2a..0ec3f7498a 100644 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java +++ b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java @@ -120,7 +120,7 @@ protected void loadNatives() { } if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + NativeLibraryLoader.loadNativeLibrary(NativeLibraries.BulletJme.getName(), true); } }