/*
* Copyright (c) 2009-2010 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.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Helper class for extracting the natives (dll, so) from the jars.
* This class should only be used internally.
*/
public final class Natives {
private static final Logger logger = Logger.getLogger(Natives.class.getName());
private static final byte[] buf = new byte[1024];
private static File extractionDirOverride = null;
private static File extractionDir = null;
public static void setExtractionDir(String name) {
extractionDirOverride = new File(name).getAbsoluteFile();
}
public static File getExtractionDir() {
if (extractionDirOverride != null) {
return extractionDirOverride;
}
if (extractionDir == null) {
File workingFolder = new File("").getAbsoluteFile();
if (!workingFolder.canWrite()) {
setStorageExtractionDir();
} else {
try {
File file = new File(workingFolder.getAbsolutePath() + File.separator + ".jmetestwrite");
file.createNewFile();
file.delete();
extractionDir = workingFolder;
} catch (Exception e) {
setStorageExtractionDir();
}
}
}
return extractionDir;
}
private static void setStorageExtractionDir() {
logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead.");
extractionDir = new File(JmeSystem.getStorageFolder(),
"natives_" + Integer.toHexString(computeNativesHash()));
if (!extractionDir.exists()) {
extractionDir.mkdir();
}
}
private static int computeNativesHash() {
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);
}
URLConnection conn = url.openConnection();
int hash = classpath.hashCode() ^ (int) conn.getLastModified();
return hash;
} catch (IOException ex) {
throw new UnsupportedOperationException(ex);
}
}
public static void extractNativeLib(String sysName, String name) throws IOException {
extractNativeLib(sysName, name, false, true);
}
public static void extractNativeLib(String sysName, String name, boolean load) throws IOException {
extractNativeLib(sysName, name, load, true);
}
public static void extractNativeLib(String sysName, String name, boolean load, boolean warning) throws IOException {
String fullname = System.mapLibraryName(name);
String path = "native/" + sysName + "/" + fullname;
URL url = Thread.currentThread().getContextClassLoader().getResource(path);
if (url == null) {
if (!warning) {
logger.log(Level.WARNING, "Cannot locate native library: {0}/{1}",
new String[]{sysName, fullname});
}
return;
}
URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();
File targetFile = new File(getExtractionDir(), fullname);
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.", fullname);
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 (FileNotFoundException ex) {
if (ex.getMessage().contains("used by another process")) {
return;
}
throw ex;
} finally {
if (load) {
System.load(targetFile.getAbsolutePath());
}
if(in != null){
in.close();
}
if(out != null){
out.close();
}
}
logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile});
}
protected static boolean isUsingNativeBullet() {
try {
Class clazz = Class.forName("com.jme3.bullet.util.NativeMeshUtil");
return clazz != null;
} catch (ClassNotFoundException ex) {
return false;
}
}
public static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException {
String renderer = settings.getRenderer();
String audioRenderer = settings.getAudioRenderer();
boolean needLWJGL = false;
boolean needOAL = false;
boolean needJInput = false;
boolean needNativeBullet = isUsingNativeBullet();
if (renderer != null) {
if (renderer.startsWith("LWJGL")) {
needLWJGL = true;
}
}
if (audioRenderer != null) {
if (audioRenderer.equals("LWJGL")) {
needLWJGL = true;
needOAL = true;
}
}
needJInput = settings.useJoysticks();
String libraryPath = getExtractionDir().toString();
if (needLWJGL) {
logger.log(Level.INFO, "Extraction Directory: {0}", getExtractionDir().toString());
// LWJGL supports this feature where
// it can load libraries from this path.
System.setProperty("org.lwjgl.librarypath", libraryPath);
}
if (needJInput) {
// AND Luckily enough JInput supports the same feature.
System.setProperty("net.java.games.input.librarypath", libraryPath);
}
switch (platform) {
case Windows64:
if (needLWJGL) {
extractNativeLib("windows", "lwjgl64");
}
if (needOAL) {
extractNativeLib("windows", "OpenAL64");
}
if (needJInput) {
extractNativeLib("windows", "jinput-dx8_64");
extractNativeLib("windows", "jinput-raw_64");
}
if (needNativeBullet) {
extractNativeLib("windows", "bulletjme64", true, false);
}
break;
case Windows32:
if (needLWJGL) {
extractNativeLib("windows", "lwjgl");
}
if (needOAL) {
extractNativeLib("windows", "OpenAL32");
}
if (needJInput) {
extractNativeLib("windows", "jinput-dx8");
extractNativeLib("windows", "jinput-raw");
}
if (needNativeBullet) {
extractNativeLib("windows", "bulletjme", true, false);
}
break;
case Linux64:
if (needLWJGL) {
extractNativeLib("linux", "lwjgl64");
}
if (needJInput) {
extractNativeLib("linux", "jinput-linux64");
}
if (needOAL) {
extractNativeLib("linux", "openal64");
}
if (needNativeBullet) {
extractNativeLib("linux", "bulletjme64", true, false);
}
break;
case Linux32:
if (needLWJGL) {
extractNativeLib("linux", "lwjgl");
}
if (needJInput) {
extractNativeLib("linux", "jinput-linux");
}
if (needOAL) {
extractNativeLib("linux", "openal");
}
if (needNativeBullet) {
extractNativeLib("linux", "bulletjme", true, false);
}
break;
case MacOSX_PPC32:
case MacOSX32:
case MacOSX_PPC64:
case MacOSX64:
if (needLWJGL) {
extractNativeLib("macosx", "lwjgl");
}
// if (needOAL)
// extractNativeLib("macosx", "openal");
if (needJInput) {
extractNativeLib("macosx", "jinput-osx");
}
if (needNativeBullet) {
extractNativeLib("macosx", "bulletjme", true, false);
}
break;
}
}
}