/*******************************************************************************
* 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.
******************************************************************************/
package com.badlogic.gdx.utils;
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.util.HashSet;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Loads shared libraries from a natives jar file (desktop) or arm folders (Android). For desktop projects, have the
* natives jar in the classpath, for Android projects put the shared libraries in the libs/armeabi and libs/armeabi-v7a
* folders.
*
* @author mzechner
*/
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 isAndroid = false;
static public boolean is64Bit = System.getProperty("os.arch").equals("amd64");
static {
String vm = System.getProperty("java.vm.name");
if (vm != null && vm.contains("Dalvik")) {
isAndroid = true;
isWindows = false;
isLinux = false;
isMac = false;
is64Bit = false;
}
}
static private HashSet<String> loadedLibraries = new HashSet();
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)
return "" + System.nanoTime(); // fallback
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) {
try {
input.close();
} catch (Exception ignored) {
}
}
return Long.toString(crc.getValue());
}
/** 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 + (is64Bit ? "64.so" : ".so");
if (isMac)
return "lib" + libraryName + ".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) {
libraryName = mapLibraryName(libraryName);
if (loadedLibraries.contains(libraryName))
return;
try {
if (isAndroid)
System.loadLibrary(libraryName);
else
System.load(extractFile(libraryName, null).getAbsolutePath());
} catch (Throwable ex) {
throw new GdxRuntimeException("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)
return SharedLibraryLoader.class.getResourceAsStream("/" + path);
// Read from JAR.
try {
ZipFile file = new ZipFile(nativesJar);
ZipEntry entry = file.getEntry(path);
if (entry == null)
throw new GdxRuntimeException("Couldn't find '" + path + "' in JAR: " + nativesJar);
return file.getInputStream(entry);
} catch (IOException ex) {
throw new GdxRuntimeException("Error reading '" + path + "' in JAR: " + nativesJar, ex);
}
}
/**
* Extracts the specified file into the temp directory if it does not already exist or the CRC does not match.
*
* @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 {
String sourceCrc = crc(readFile(sourcePath));
if (dirName == null)
dirName = sourceCrc;
File extractedDir = new File(System.getProperty("java.io.tmpdir") + "/libgdx" + System.getProperty("user.name")
+ "/" + dirName);
File extractedFile = new File(extractedDir, new File(sourcePath).getName());
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);
if (input == null)
return null;
extractedDir.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 GdxRuntimeException("Error extracting file: " + sourcePath, ex);
}
}
return extractedFile.exists() ? extractedFile : null;
}
}