将lwjgl与gradle一起使用时出现java.lang.UnsatisfiedLink错误

将lwjgl与gradle一起使用时出现java.lang.UnsatisfiedLink错误,java,gradle,lwjgl,Java,Gradle,Lwjgl,我试图将Gradle与LWJGL3结合使用,但在构建时遇到了一个问题。build.gradle文件包含以下内容: apply plugin: 'application' mainClassName = "HelloWorld" repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } project.ext.lwjglVersio

我试图将Gradle与LWJGL3结合使用,但在构建时遇到了一个问题。
build.gradle
文件包含以下内容:

apply plugin: 'application'
mainClassName = "HelloWorld"
repositories {
    mavenCentral()
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
project.ext.lwjglVersion = "3.0.0a"
dependencies {
    compile "org.lwjgl:lwjgl:${lwjglVersion}"
    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-windows"
    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-linux"
    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-osx"
}
当我运行
gradle run
时,我得到以下输出:

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:runjava.lang.UnsatisfiedLinkError: no lwjgl in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1857)
        at java.lang.Runtime.loadLibrary0(Runtime.java:870)
        at java.lang.System.loadLibrary(System.java:1119)
        at org.lwjgl.LWJGLUtil.loadLibrarySystem(LWJGLUtil.java:337)
        at org.lwjgl.Sys$1.run(Sys.java:36)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.lwjgl.Sys.<clinit>(Sys.java:33)
        at org.lwjgl.LWJGLUtil.initialize(LWJGLUtil.java:309)
        at org.lwjgl.system.MemoryUtil.<clinit>(MemoryUtil.java:35)
        at org.lwjgl.Pointer.<clinit>(Pointer.java:22)
        at org.lwjgl.PointerBuffer.<init>(PointerBuffer.java:24)
        at org.lwjgl.PointerBuffer.allocateDirect(PointerBuffer.java:281)
        at org.lwjgl.BufferUtils.createPointerBuffer(BufferUtils.java:190)
        at org.lwjgl.system.libffi.Closure.<clinit>(Closure.java:45)
        at org.lwjgl.glfw.Callbacks.errorCallbackPrint(Callbacks.java:129)
        at HelloWorld.<clinit>(HelloWorld.java:29)
Exception in thread "main"  FAILED

FAILURE: Build failed with an exception.
导致错误的原因以及如何修复错误?

来自:

如果Java虚拟机找不到声明为本机的方法的适当本机语言定义,则引发


因此,考虑到java.library.path中没有lwjgl,由于某种原因,它无法找到本机库。很抱歉,没有gradle经验可以帮助您……

您可能已经注意到,在您下载的lwjgl文件夹(带有jar文件的文件夹)中,应该有一个名为“native”的目录。在这个目录中应该有三个子文件夹和系统名(windows、macos…)。这个本机文件夹应该在项目内部的文件夹中(我创建了一个名为lwjgl的文件夹)。然后,在程序的第一行中,编写
System.setProperty(“java.library.path”,“/lwjgl”)
。这一行告诉java在那里搜索所有本机文件。

您必须在运行时将Lwjgl的本机库链接到您的应用程序。感谢gradle,本机库位于类路径中。。。某处

你可以继续找到它们,将它们提取到一个临时位置,然后将它们链接起来,但已经有一个专门为你做这件事的网站

您需要做的就是将他们的类
SharedLibraryLoader
包含到项目中,并调用
load()
方法

如果此链接消失,以下是您需要的类的全部内容:

/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.UUID;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/** Loads shared libraries from JAR files. Call {@link SharedLibraryLoader#load() to load the
 * required LWJGL 3 native shared libraries.
 * @author mzechner
 * @author Nathan Sweet */
public class SharedLibraryLoader {
    static public boolean isWindows = System.getProperty("os.name").contains("Windows");
    static public boolean isLinux = System.getProperty("os.name").contains("Linux");
    static public boolean isMac = System.getProperty("os.name").contains("Mac");
    static public boolean isIos = false;
    static public boolean isAndroid = false;
    static public boolean isARM = System.getProperty("os.arch").startsWith("arm");
    static public boolean is64Bit = System.getProperty("os.arch").equals("amd64")
        || System.getProperty("os.arch").equals("x86_64");

    // JDK 8 only.
    static public String abi = (System.getProperty("sun.arch.abi") != null ? System.getProperty("sun.arch.abi") : "");

    static {
        String vm = System.getProperty("java.runtime.name");
        if (vm != null && vm.contains("Android Runtime")) {
            isAndroid = true;
            isWindows = false;
            isLinux = false;
            isMac = false;
            is64Bit = false;
        }
        if (!isAndroid && !isWindows && !isLinux && !isMac) {
            isIos = true;
            is64Bit = false;
        }
    }

    static boolean load = true;

    static {
        // Don't extract natives if using JWS.
        try {
            Method method = Class.forName("javax.jnlp.ServiceManager").getDeclaredMethod("lookup", new Class[] {String.class});
            method.invoke(null, "javax.jnlp.PersistenceService");
            load = false;
        } catch (Throwable ex) {
            load = true;
        }   
    }

    /** Extracts the LWJGL native libraries from the classpath and sets the "org.lwjgl.librarypath" system property. */
    static public synchronized void load () {
        load(false);
    }

    /** Extracts the LWJGL native libraries from the classpath and sets the "org.lwjgl.librarypath" system property. */
    static public synchronized void load (boolean disableOpenAL) {      
        if (!load) return;

        SharedLibraryLoader loader = new SharedLibraryLoader();
        File nativesDir = null;
        try {
            if (SharedLibraryLoader.isWindows) {
                nativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? "lwjgl.dll" : "lwjgl32.dll", null).getParentFile();
                if (!disableOpenAL)
                    loader.extractFile(SharedLibraryLoader.is64Bit ? "OpenAL.dll" : "OpenAL32.dll", nativesDir.getName());
            } else if (SharedLibraryLoader.isMac) {
                nativesDir = loader.extractFile("liblwjgl.dylib", null).getParentFile();
                if (!disableOpenAL) loader.extractFile("libopenal.dylib", nativesDir.getName());
            } else if (SharedLibraryLoader.isLinux) {
                nativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? "liblwjgl.so" : "liblwjgl32.so", null).getParentFile();
                if (!disableOpenAL)
                    loader.extractFile(SharedLibraryLoader.is64Bit ? "libopenal.so" : "libopenal32.so", nativesDir.getName());
            }
        } catch (Throwable ex) {
            throw new RuntimeException("Unable to extract LWJGL natives.", ex);
        }
        System.setProperty("org.lwjgl.librarypath", nativesDir.getAbsolutePath());
        load = false;
    }

    static private final HashSet<String> loadedLibraries = new HashSet<String>();

    private String nativesJar;

    public SharedLibraryLoader () {
    }

    /** Fetches the natives from the given natives jar file. Used for testing a shared lib on the fly.
     * @param nativesJar */
    public SharedLibraryLoader (String nativesJar) {
        this.nativesJar = nativesJar;
    }

    /** Returns a CRC of the remaining bytes in the stream. */
    public String crc (InputStream input) {
        if (input == null) throw new IllegalArgumentException("input cannot be null.");
        CRC32 crc = new CRC32();
        byte[] buffer = new byte[4096];
        try {
            while (true) {
                int length = input.read(buffer);
                if (length == -1) break;
                crc.update(buffer, 0, length);
            }
        } catch (Exception ex) {
            if(input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                }
            }
        }
        return Long.toString(crc.getValue(), 16);
    }

    /** Maps a platform independent library name to a platform dependent name. */
    public String mapLibraryName (String libraryName) {
        if (isWindows) return libraryName + (is64Bit ? "64.dll" : ".dll");
        if (isLinux) return "lib" + libraryName + (isARM ? "arm" + abi : "") + (is64Bit ? "64.so" : ".so");
        if (isMac) return "lib" + libraryName + (is64Bit ? "64.dylib" : ".dylib");
        return libraryName;
    }

    /** Loads a shared library for the platform the application is running on.
     * @param libraryName The platform independent library name. If not contain a prefix (eg lib) or suffix (eg .dll). */
    public synchronized void load (String libraryName) {
        // in case of iOS, things have been linked statically to the executable, bail out.
        if (isIos) return;

        libraryName = mapLibraryName(libraryName);
        if (loadedLibraries.contains(libraryName)) return;

        try {
            if (isAndroid)
                System.loadLibrary(libraryName);
            else
                loadFile(libraryName);
        } catch (Throwable ex) {
            throw new RuntimeException("Couldn't load shared library '" + libraryName + "' for target: "
                + System.getProperty("os.name") + (is64Bit ? ", 64-bit" : ", 32-bit"), ex);
        }
        loadedLibraries.add(libraryName);
    }

    private InputStream readFile (String path) {
        if (nativesJar == null) {
            InputStream input = SharedLibraryLoader.class.getResourceAsStream("/" + path);
            if (input == null) throw new RuntimeException("Unable to read file for extraction: " + path);
            return input;
        }

        // Read from JAR.
        ZipFile file = null;
        try {
            file = new ZipFile(nativesJar);
            ZipEntry entry = file.getEntry(path);
            if (entry == null) throw new RuntimeException("Couldn't find '" + path + "' in JAR: " + nativesJar);
            return file.getInputStream(entry);
        } catch (IOException ex) {
            throw new RuntimeException("Error reading '" + path + "' in JAR: " + nativesJar, ex);
        } finally {
            if(file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /** Extracts the specified file into the temp directory if it does not already exist or the CRC does not match. If file
     * extraction fails and the file exists at java.library.path, that file is returned.
     * @param sourcePath The file to extract from the classpath or JAR.
     * @param dirName The name of the subdirectory where the file will be extracted. If null, the file's CRC will be used.
     * @return The extracted file. */
    public File extractFile (String sourcePath, String dirName) throws IOException {
        try {
            String sourceCrc = crc(readFile(sourcePath));
            if (dirName == null) dirName = sourceCrc;

            File extractedFile = getExtractedFile(dirName, new File(sourcePath).getName());
            return extractFile(sourcePath, sourceCrc, extractedFile);
        } catch (RuntimeException ex) {
            // Fallback to file at java.library.path location, eg for applets.
            File file = new File(System.getProperty("java.library.path"), sourcePath);
            if (file.exists()) return file;
            throw ex;
        }
    }

    /** Returns a path to a file that can be written. Tries multiple locations and verifies writing succeeds. */
    private File getExtractedFile (String dirName, String fileName) {
        // Temp directory with username in path.
        File idealFile = new File(System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name") + "/"
            + dirName, fileName);
        if (canWrite(idealFile)) return idealFile;

        // System provided temp directory.
        try {
            File file = File.createTempFile(dirName, null);
            if (file.delete()) {
                file = new File(file, fileName);
                if (canWrite(file)) return file;
            }
        } catch (IOException ignored) {
        }

        // User home.
        File file = new File(System.getProperty("user.home") + "/.libgdx/" + dirName, fileName);
        if (canWrite(file)) return file;

        // Relative directory.
        file = new File(".temp/" + dirName, fileName);
        if (canWrite(file)) return file;

        return idealFile; // Will likely fail, but we did our best.
    }

    /** Returns true if the parent directories of the file can be created and the file can be written. */
    private boolean canWrite (File file) {
        File parent = file.getParentFile();
        File testFile;
        if (file.exists()) {
            if (!file.canWrite() || !canExecute(file)) return false;
            // Don't overwrite existing file just to check if we can write to directory.
            testFile = new File(parent, UUID.randomUUID().toString());
        } else {
            parent.mkdirs();
            if (!parent.isDirectory()) return false;
            testFile = file;
        }
        try {
            new FileOutputStream(testFile).close();
            if (!canExecute(testFile)) return false;
            return true;
        } catch (Throwable ex) {
            return false;
        } finally {
            testFile.delete();
        }
    }

    private boolean canExecute (File file) {
        try {
            Method canExecute = File.class.getMethod("canExecute");
            if ((Boolean)canExecute.invoke(file)) return true;

            Method setExecutable = File.class.getMethod("setExecutable", boolean.class, boolean.class);
            setExecutable.invoke(file, true, false);

            return (Boolean)canExecute.invoke(file);
        } catch (Exception ignored) {
        }
        return false;
    }

    private File extractFile (String sourcePath, String sourceCrc, File extractedFile) throws IOException {
        String extractedCrc = null;
        if (extractedFile.exists()) {
            try {
                extractedCrc = crc(new FileInputStream(extractedFile));
            } catch (FileNotFoundException ignored) {
            }
        }

        // If file doesn't exist or the CRC doesn't match, extract it to the temp dir.
        if (extractedCrc == null || !extractedCrc.equals(sourceCrc)) {
            try {
                InputStream input = readFile(sourcePath);
                extractedFile.getParentFile().mkdirs();
                FileOutputStream output = new FileOutputStream(extractedFile);
                byte[] buffer = new byte[4096];
                while (true) {
                    int length = input.read(buffer);
                    if (length == -1) break;
                    output.write(buffer, 0, length);
                }
                input.close();
                output.close();
            } catch (IOException ex) {
                throw new RuntimeException("Error extracting file: " + sourcePath + "\nTo: " + extractedFile.getAbsolutePath(), ex);
            }
        }

        return extractedFile;
    }

    /** Extracts the source file and calls System.load. Attemps to extract and load from multiple locations. Throws runtime
     * exception if all fail. */
    private void loadFile (String sourcePath) {
        String sourceCrc = crc(readFile(sourcePath));

        String fileName = new File(sourcePath).getName();

        // Temp directory with username in path.
        File file = new File(System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name") + "/" + sourceCrc,
            fileName);
        Throwable ex = loadFile(sourcePath, sourceCrc, file);
        if (ex == null) return;

        // System provided temp directory.
        try {
            file = File.createTempFile(sourceCrc, null);
            if (file.delete() && loadFile(sourcePath, sourceCrc, file) == null) return;
        } catch (Throwable ignored) {
        }

        // User home.
        file = new File(System.getProperty("user.home") + "/.libgdx/" + sourceCrc, fileName);
        if (loadFile(sourcePath, sourceCrc, file) == null) return;

        // Relative directory.
        file = new File(".temp/" + sourceCrc, fileName);
        if (loadFile(sourcePath, sourceCrc, file) == null) return;

        // Fallback to java.library.path location, eg for applets.
        file = new File(System.getProperty("java.library.path"), sourcePath);
        if (file.exists()) {
            System.load(file.getAbsolutePath());
            return;
        }

        throw new RuntimeException(ex);
    }

    /** @return null if the file was extracted and loaded. */
    private Throwable loadFile (String sourcePath, String sourceCrc, File extractedFile) {
        try {
            System.load(extractFile(sourcePath, sourceCrc, extractedFile).getAbsolutePath());
            return null;
        } catch (Throwable ex) {
            ex.printStackTrace();
            return ex;
        }
    }
}
/*******************************************************************************
*版权所有2011见作者档案。
* 
*根据Apache许可证2.0版(以下简称“许可证”)获得许可;
*除非遵守许可证,否则不得使用此文件。
*您可以通过以下方式获得许可证副本:
* 
*   http://www.apache.org/licenses/LICENSE-2.0
* 
*除非适用法律要求或书面同意,软件
*根据许可证进行的分发是按“原样”进行分发的,
*无任何明示或暗示的保证或条件。
*请参阅许可证以了解管理权限和权限的特定语言
*许可证下的限制。
******************************************************************************/
导入java.io.File;
导入java.io.FileInputStream;
导入java.io.FileNotFoundException;
导入java.io.FileOutputStream;
导入java.io.IOException;
导入java.io.InputStream;
导入java.lang.reflect.Method;
导入java.util.HashSet;
导入java.util.UUID;
导入java.util.zip.CRC32;
导入java.util.zip.ZipEntry;
导入java.util.zip.ZipFile;
/**从JAR文件加载共享库。调用{@link SharedLibraryLoader#load()来加载
*必需的LWJGL 3本机共享库。
*@作者mzechner
*@作者Nathan Sweet*/
公共类SharedLibraryLoader{
静态公共布尔值isWindows=System.getProperty(“os.name”)。包含(“Windows”);
静态公共布尔值isLinux=System.getProperty(“os.name”)。包含(“Linux”);
静态公共布尔值isMac=System.getProperty(“os.name”)。包含(“Mac”);
静态公共布尔isIos=false;
静态公共布尔isAndroid=false;
静态公共布尔值isARM=System.getProperty(“os.arch”).startsWith(“arm”);
静态公共布尔值为64bit=System.getProperty(“os.arch”).equals(“amd64”)
||System.getProperty(“os.arch”).equals(“x86_64”);
//仅限JDK 8。
静态公共字符串abi=(System.getProperty(“sun.arch.abi”)!=null?System.getProperty(“sun.arch.abi”):“”);
静止的{
字符串vm=System.getProperty(“java.runtime.name”);
if(vm!=null&&vm.contains(“Android运行时”)){
isAndroid=真;
isWindows=false;
isLinux=假;
isMac=假;
IS64位=假;
}
if(!isAndroid&!isWindows&!isLinux&&!isMac){
isIos=真;
IS64位=假;
}
}
静态布尔加载=真;
静止的{
//如果使用JWS,不要提取本地人。
试一试{
方法Method=Class.forName(“javax.jnlp.ServiceManager”).getDeclaredMethod(“查找”,新类[]{String.Class});
invoke(null,“javax.jnlp.PersistenceService”);
负载=假;
}捕获(可丢弃的ex){
负载=真;
}   
}
/**从类路径中提取LWJGL本机库,并设置“org.LWJGL.librarypath”系统属性*/
静态公共同步无效负载(){
加载(假);
}
/**从类路径中提取LWJGL本机库,并设置“org.LWJGL.librarypath”系统属性*/
静态公共同步无效加载(布尔禁用OpenAL){
如果(!load)返回;
SharedLibraryLoader=新的SharedLibraryLoader();
文件nativesDir=null;
试一试{
if(SharedLibraryLoader.isWindows){
nativesDir=loader.extractFile(SharedLibraryLoader.is64位?“lwjgl.dll”:“lwjgl32.dll”,null).getParentFile();
如果(!disableOpenAL)
提取文件(SharedLibraryLoader.is64位?“OpenAL.dll”:“OpenAL32.dll”,nativesDir.getName());
}else if(SharedLibraryLoader.isMac){
nativesDir=loader.extractFile(“liblwjgl.dylib”,null).getParentFile();
if(!disableOpenAL)loader.extractFile(“libopenal.dylib”,nativesDir.getName());
}else if(SharedLibraryLoader.isLinux){
nativesDir=loader.extractFile(SharedLibraryLoader.is64位?“liblwjgl.so”:“liblwjgl32.so”,null).getParentFile();
如果(!disableOpenAL)
提取文件(SharedLibraryLoader.is64位?“libopenal.so”:“libopenal32.so”,nativesDir.getName());
}
/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.UUID;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/** Loads shared libraries from JAR files. Call {@link SharedLibraryLoader#load() to load the
 * required LWJGL 3 native shared libraries.
 * @author mzechner
 * @author Nathan Sweet */
public class SharedLibraryLoader {
    static public boolean isWindows = System.getProperty("os.name").contains("Windows");
    static public boolean isLinux = System.getProperty("os.name").contains("Linux");
    static public boolean isMac = System.getProperty("os.name").contains("Mac");
    static public boolean isIos = false;
    static public boolean isAndroid = false;
    static public boolean isARM = System.getProperty("os.arch").startsWith("arm");
    static public boolean is64Bit = System.getProperty("os.arch").equals("amd64")
        || System.getProperty("os.arch").equals("x86_64");

    // JDK 8 only.
    static public String abi = (System.getProperty("sun.arch.abi") != null ? System.getProperty("sun.arch.abi") : "");

    static {
        String vm = System.getProperty("java.runtime.name");
        if (vm != null && vm.contains("Android Runtime")) {
            isAndroid = true;
            isWindows = false;
            isLinux = false;
            isMac = false;
            is64Bit = false;
        }
        if (!isAndroid && !isWindows && !isLinux && !isMac) {
            isIos = true;
            is64Bit = false;
        }
    }

    static boolean load = true;

    static {
        // Don't extract natives if using JWS.
        try {
            Method method = Class.forName("javax.jnlp.ServiceManager").getDeclaredMethod("lookup", new Class[] {String.class});
            method.invoke(null, "javax.jnlp.PersistenceService");
            load = false;
        } catch (Throwable ex) {
            load = true;
        }   
    }

    /** Extracts the LWJGL native libraries from the classpath and sets the "org.lwjgl.librarypath" system property. */
    static public synchronized void load () {
        load(false);
    }

    /** Extracts the LWJGL native libraries from the classpath and sets the "org.lwjgl.librarypath" system property. */
    static public synchronized void load (boolean disableOpenAL) {      
        if (!load) return;

        SharedLibraryLoader loader = new SharedLibraryLoader();
        File nativesDir = null;
        try {
            if (SharedLibraryLoader.isWindows) {
                nativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? "lwjgl.dll" : "lwjgl32.dll", null).getParentFile();
                if (!disableOpenAL)
                    loader.extractFile(SharedLibraryLoader.is64Bit ? "OpenAL.dll" : "OpenAL32.dll", nativesDir.getName());
            } else if (SharedLibraryLoader.isMac) {
                nativesDir = loader.extractFile("liblwjgl.dylib", null).getParentFile();
                if (!disableOpenAL) loader.extractFile("libopenal.dylib", nativesDir.getName());
            } else if (SharedLibraryLoader.isLinux) {
                nativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? "liblwjgl.so" : "liblwjgl32.so", null).getParentFile();
                if (!disableOpenAL)
                    loader.extractFile(SharedLibraryLoader.is64Bit ? "libopenal.so" : "libopenal32.so", nativesDir.getName());
            }
        } catch (Throwable ex) {
            throw new RuntimeException("Unable to extract LWJGL natives.", ex);
        }
        System.setProperty("org.lwjgl.librarypath", nativesDir.getAbsolutePath());
        load = false;
    }

    static private final HashSet<String> loadedLibraries = new HashSet<String>();

    private String nativesJar;

    public SharedLibraryLoader () {
    }

    /** Fetches the natives from the given natives jar file. Used for testing a shared lib on the fly.
     * @param nativesJar */
    public SharedLibraryLoader (String nativesJar) {
        this.nativesJar = nativesJar;
    }

    /** Returns a CRC of the remaining bytes in the stream. */
    public String crc (InputStream input) {
        if (input == null) throw new IllegalArgumentException("input cannot be null.");
        CRC32 crc = new CRC32();
        byte[] buffer = new byte[4096];
        try {
            while (true) {
                int length = input.read(buffer);
                if (length == -1) break;
                crc.update(buffer, 0, length);
            }
        } catch (Exception ex) {
            if(input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                }
            }
        }
        return Long.toString(crc.getValue(), 16);
    }

    /** Maps a platform independent library name to a platform dependent name. */
    public String mapLibraryName (String libraryName) {
        if (isWindows) return libraryName + (is64Bit ? "64.dll" : ".dll");
        if (isLinux) return "lib" + libraryName + (isARM ? "arm" + abi : "") + (is64Bit ? "64.so" : ".so");
        if (isMac) return "lib" + libraryName + (is64Bit ? "64.dylib" : ".dylib");
        return libraryName;
    }

    /** Loads a shared library for the platform the application is running on.
     * @param libraryName The platform independent library name. If not contain a prefix (eg lib) or suffix (eg .dll). */
    public synchronized void load (String libraryName) {
        // in case of iOS, things have been linked statically to the executable, bail out.
        if (isIos) return;

        libraryName = mapLibraryName(libraryName);
        if (loadedLibraries.contains(libraryName)) return;

        try {
            if (isAndroid)
                System.loadLibrary(libraryName);
            else
                loadFile(libraryName);
        } catch (Throwable ex) {
            throw new RuntimeException("Couldn't load shared library '" + libraryName + "' for target: "
                + System.getProperty("os.name") + (is64Bit ? ", 64-bit" : ", 32-bit"), ex);
        }
        loadedLibraries.add(libraryName);
    }

    private InputStream readFile (String path) {
        if (nativesJar == null) {
            InputStream input = SharedLibraryLoader.class.getResourceAsStream("/" + path);
            if (input == null) throw new RuntimeException("Unable to read file for extraction: " + path);
            return input;
        }

        // Read from JAR.
        ZipFile file = null;
        try {
            file = new ZipFile(nativesJar);
            ZipEntry entry = file.getEntry(path);
            if (entry == null) throw new RuntimeException("Couldn't find '" + path + "' in JAR: " + nativesJar);
            return file.getInputStream(entry);
        } catch (IOException ex) {
            throw new RuntimeException("Error reading '" + path + "' in JAR: " + nativesJar, ex);
        } finally {
            if(file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /** Extracts the specified file into the temp directory if it does not already exist or the CRC does not match. If file
     * extraction fails and the file exists at java.library.path, that file is returned.
     * @param sourcePath The file to extract from the classpath or JAR.
     * @param dirName The name of the subdirectory where the file will be extracted. If null, the file's CRC will be used.
     * @return The extracted file. */
    public File extractFile (String sourcePath, String dirName) throws IOException {
        try {
            String sourceCrc = crc(readFile(sourcePath));
            if (dirName == null) dirName = sourceCrc;

            File extractedFile = getExtractedFile(dirName, new File(sourcePath).getName());
            return extractFile(sourcePath, sourceCrc, extractedFile);
        } catch (RuntimeException ex) {
            // Fallback to file at java.library.path location, eg for applets.
            File file = new File(System.getProperty("java.library.path"), sourcePath);
            if (file.exists()) return file;
            throw ex;
        }
    }

    /** Returns a path to a file that can be written. Tries multiple locations and verifies writing succeeds. */
    private File getExtractedFile (String dirName, String fileName) {
        // Temp directory with username in path.
        File idealFile = new File(System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name") + "/"
            + dirName, fileName);
        if (canWrite(idealFile)) return idealFile;

        // System provided temp directory.
        try {
            File file = File.createTempFile(dirName, null);
            if (file.delete()) {
                file = new File(file, fileName);
                if (canWrite(file)) return file;
            }
        } catch (IOException ignored) {
        }

        // User home.
        File file = new File(System.getProperty("user.home") + "/.libgdx/" + dirName, fileName);
        if (canWrite(file)) return file;

        // Relative directory.
        file = new File(".temp/" + dirName, fileName);
        if (canWrite(file)) return file;

        return idealFile; // Will likely fail, but we did our best.
    }

    /** Returns true if the parent directories of the file can be created and the file can be written. */
    private boolean canWrite (File file) {
        File parent = file.getParentFile();
        File testFile;
        if (file.exists()) {
            if (!file.canWrite() || !canExecute(file)) return false;
            // Don't overwrite existing file just to check if we can write to directory.
            testFile = new File(parent, UUID.randomUUID().toString());
        } else {
            parent.mkdirs();
            if (!parent.isDirectory()) return false;
            testFile = file;
        }
        try {
            new FileOutputStream(testFile).close();
            if (!canExecute(testFile)) return false;
            return true;
        } catch (Throwable ex) {
            return false;
        } finally {
            testFile.delete();
        }
    }

    private boolean canExecute (File file) {
        try {
            Method canExecute = File.class.getMethod("canExecute");
            if ((Boolean)canExecute.invoke(file)) return true;

            Method setExecutable = File.class.getMethod("setExecutable", boolean.class, boolean.class);
            setExecutable.invoke(file, true, false);

            return (Boolean)canExecute.invoke(file);
        } catch (Exception ignored) {
        }
        return false;
    }

    private File extractFile (String sourcePath, String sourceCrc, File extractedFile) throws IOException {
        String extractedCrc = null;
        if (extractedFile.exists()) {
            try {
                extractedCrc = crc(new FileInputStream(extractedFile));
            } catch (FileNotFoundException ignored) {
            }
        }

        // If file doesn't exist or the CRC doesn't match, extract it to the temp dir.
        if (extractedCrc == null || !extractedCrc.equals(sourceCrc)) {
            try {
                InputStream input = readFile(sourcePath);
                extractedFile.getParentFile().mkdirs();
                FileOutputStream output = new FileOutputStream(extractedFile);
                byte[] buffer = new byte[4096];
                while (true) {
                    int length = input.read(buffer);
                    if (length == -1) break;
                    output.write(buffer, 0, length);
                }
                input.close();
                output.close();
            } catch (IOException ex) {
                throw new RuntimeException("Error extracting file: " + sourcePath + "\nTo: " + extractedFile.getAbsolutePath(), ex);
            }
        }

        return extractedFile;
    }

    /** Extracts the source file and calls System.load. Attemps to extract and load from multiple locations. Throws runtime
     * exception if all fail. */
    private void loadFile (String sourcePath) {
        String sourceCrc = crc(readFile(sourcePath));

        String fileName = new File(sourcePath).getName();

        // Temp directory with username in path.
        File file = new File(System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name") + "/" + sourceCrc,
            fileName);
        Throwable ex = loadFile(sourcePath, sourceCrc, file);
        if (ex == null) return;

        // System provided temp directory.
        try {
            file = File.createTempFile(sourceCrc, null);
            if (file.delete() && loadFile(sourcePath, sourceCrc, file) == null) return;
        } catch (Throwable ignored) {
        }

        // User home.
        file = new File(System.getProperty("user.home") + "/.libgdx/" + sourceCrc, fileName);
        if (loadFile(sourcePath, sourceCrc, file) == null) return;

        // Relative directory.
        file = new File(".temp/" + sourceCrc, fileName);
        if (loadFile(sourcePath, sourceCrc, file) == null) return;

        // Fallback to java.library.path location, eg for applets.
        file = new File(System.getProperty("java.library.path"), sourcePath);
        if (file.exists()) {
            System.load(file.getAbsolutePath());
            return;
        }

        throw new RuntimeException(ex);
    }

    /** @return null if the file was extracted and loaded. */
    private Throwable loadFile (String sourcePath, String sourceCrc, File extractedFile) {
        try {
            System.load(extractFile(sourcePath, sourceCrc, extractedFile).getAbsolutePath());
            return null;
        } catch (Throwable ex) {
            ex.printStackTrace();
            return ex;
        }
    }
}