* NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Windows32, "native/windows/mystuff.dll");
* NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Windows64, "native/windows/mystuff64.dll");
* NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Linux32, "native/linux/libmystuff.so");
* NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Linux64, "native/linux/libmystuff64.so");
* NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.MacOSX32, "native/macosx/libmystuff.jnilib");
* NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.MacOSX64, "native/macosx/libmystuff.jnilib");
*
*
* NativeLibraryLoader.loadNativeLibrary("mystuff", true);
*
* It will load the right library automatically based on the platform.
*
* @author Kirill Vainer
*/
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 HashMapnull
to restore default
* functionality.
*
* @param path Path where to extract native libraries.
*/
public static void setCustomExtractionFolder(String path) {
extractionFolderOverride = new File(path).getAbsoluteFile();
}
/**
* Returns the folder where native libraries will be extracted.
* This is automatically determined at run-time based on the
* following criteria:natives_<hash>
where <hash>
* is computed automatically as the XOR of the classpath hash code
* and the last modified date of this class.
*
* @return Path where natives will be extracted to.
*/
public static File getExtractionFolder() {
if (extractionFolderOverride != null) {
return extractionFolderOverride;
}
if (extractionFolder == null) {
File workingFolder = new File("").getAbsoluteFile();
if (!workingFolder.canWrite()) {
setExtractionDirToStorageDir();
} else {
try {
File file = new File(workingFolder + File.separator + ".jmetestwrite");
file.createNewFile();
file.delete();
extractionFolder = workingFolder;
} catch (Exception e) {
setExtractionDirToStorageDir();
}
}
}
return extractionFolder;
}
private static void setExtractionDirToStorageDir() {
logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead.");
extractionFolder = new File(JmeSystem.getStorageFolder(),
"natives_" + Integer.toHexString(computeNativesHash()));
if (!extractionFolder.exists()) {
extractionFolder.mkdir();
}
}
private static int computeNativesHash() {
URLConnection conn = null;
try {
String classpath = System.getProperty("java.class.path");
URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/Natives.class");
StringBuilder sb = new StringBuilder(url.toString());
if (sb.indexOf("jar:") == 0) {
sb.delete(0, 4);
sb.delete(sb.indexOf("!"), sb.length());
sb.delete(sb.lastIndexOf("/") + 1, sb.length());
}
try {
url = new URL(sb.toString());
} catch (MalformedURLException ex) {
throw new UnsupportedOperationException(ex);
}
conn = url.openConnection();
int hash = classpath.hashCode() ^ (int) conn.getLastModified();
return hash;
} catch (IOException ex) {
throw new UnsupportedOperationException(ex);
} finally {
if (conn != null) {
try {
conn.getInputStream().close();
conn.getOutputStream().close();
} catch (IOException ex) { }
}
}
}
/**
* First extracts the native library and then loads it.
*
* @param name The name of the library to load.
* @param isRequired If true and the library fails to load, throw exception. If
* false, do nothing if it fails to load.
*/
public static void loadNativeLibrary(String name, boolean isRequired) {
if (JmeSystem.isLowPermissions()) {
throw new UnsupportedOperationException("JVM is running under "
+ "reduced permissions. Cannot load native libraries.");
}
Platform platform = JmeSystem.getPlatform();
NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform));
if (library == null) {
// No library exists for this platform.
if (isRequired) {
throw new UnsatisfiedLinkError(
"The required native library '" + name + "'"
+ " is not available for your OS: " + platform);
} else {
logger.log(Level.FINE, "The optional native library ''{0}''" +
" is not available for your OS: {1}",
new Object[]{name, platform});
return;
}
}
String pathInJar = library.getPathInNativesJar();
if (pathInJar == null) {
// This platform does not require the native library to be loaded.
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) {
// 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.
try {
System.loadLibrary(name);
logger.log(Level.FINE, "Loaded system installed " +
"version of native library: {0}", name);
} catch (UnsatisfiedLinkError e) {
if (isRequired) {
throw new UnsatisfiedLinkError(
"The required native library '" + name + "'"
+ " was not found in the classpath via '" + pathInJar + "'");
} else {
logger.log(Level.FINE, "The optional native library ''{0}''" +
" was not found in the classpath via ''{1}''",
new Object[]{name, pathInJar});
}
}
return;
}
// The library has been found and is ready to be extracted.
// Determine what filename it should be extracted as.
String loadedAsFileName;
if (library.isJNI()) {
// JNI libraries on Mac / JDK6 use jnilib extension.
// JNI libraries on Mac / JDK7 use dylib extension.
String fileNameInJarWithoutExtension
= fileNameInJar.substring(0, fileNameInJar.lastIndexOf("."));
if (platform.is64Bit() && !fileNameInJarWithoutExtension.endsWith("64")) {
// This is to avoid conflicts with 32-bit versions of the
// same library when extracting.
fileNameInJarWithoutExtension += "64";
}
String systemJniExtension;
String dummyLib = System.mapLibraryName("");
if (dummyLib.contains(".")) {
systemJniExtension = dummyLib.substring(dummyLib.lastIndexOf("."));
} else {
systemJniExtension = "";
}
loadedAsFileName = fileNameInJarWithoutExtension + systemJniExtension;
} else {
// Not a JNI library.
// Just use the original filename as it is in the JAR.
loadedAsFileName = fileNameInJar;
}
File extactionDirectory = getExtractionFolder();
URLConnection conn;
InputStream in;
try {
conn = url.openConnection();
in = conn.getInputStream();
} catch (IOException ex) {
// Maybe put more detail here? Not sure..
throw new UnsatisfiedLinkError("Failed to open file: '" + url +
"'. Error: " + ex);
}
File targetFile = new File(extactionDirectory, 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();
// 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;
}
}
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;
// 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());
} catch (IOException ex) {
if (ex.getMessage().contains("used by another process")) {
return;
} else {
throw new UnsatisfiedLinkError("Failed to extract native "
+ "library to: " + targetFile);
}
} finally {
// XXX: HACK. Vary loading method based on library name..
// lwjgl and jinput handle loading by themselves.
if (name.equals("lwjgl")) {
System.setProperty("org.lwjgl.librarypath",
extactionDirectory.getAbsolutePath());
} else if (name.equals("jinput")) {
System.setProperty("net.java.games.input.librarypath",
extactionDirectory.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) { }
}
}
logger.log(Level.FINE, "Loaded native library from ''{0}'' into ''{1}''",
new Object[]{url, targetFile});
}
}