package de.uniluebeck.itm.wsn.drivers.core.util;
import com.google.common.base.Joiner;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
/**
* Utility class for JAR handling.
*
* @author Malte Legenhausen
*/
public final class JarUtil {
/**
* Path to the root directory of the jni libary files.
*/
private static final String JNI_PATH_ROOT = "/de/uniluebeck/itm/wsn/drivers/core/jni";
/**
* The directory name of the library folder in the home directory.
*/
private static final String LIB_HOME =
SystemUtils.getJavaIoTmpDir().toString() + File.separator + ".wsn-device-drivers";
/**
* System property for the java class path.
*/
private static final String JAVA_LIBRARY_PATH = "java.library.path";
/**
* Property name for library paths.
*/
private static final String USR_PATHS = "usr_paths";
/**
* Constructor.
*/
private JarUtil() {
}
/**
* Load a DLL or SO file that is contained in a JAR.
* This method is designed to work like System.loadLibrary(libName).
*
* @param libName The name of the library without file extension.
*/
public static void loadLibrary(final String libName) {
final String lib = nativeLibraryName(libName);
final String path = archAwarePath(lib);
try {
extractLibrary(path, lib);
prepareClassPath();
System.loadLibrary(libName);
} catch (final IOException e) {
throw new RuntimeException("Unable to extract libary to: " + path, e);
} catch (final URISyntaxException e) {
throw new RuntimeException("Unable to extract libary to: " + path, e);
}
}
/**
* Adds the library home to the java library path.
*/
private static void prepareClassPath() throws IOException {
try {
// This enables the java.library.path to be modified at runtime
// From a Sun engineer at http://forums.sun.com/thread.jspa?threadID=707176
Object[] paths = (Object[]) FieldUtils.readDeclaredStaticField(ClassLoader.class, USR_PATHS, true);
if (!ArrayUtils.contains(paths, LIB_HOME)) {
paths = ArrayUtils.add(paths, LIB_HOME);
FieldUtils.writeDeclaredStaticField(ClassLoader.class, USR_PATHS, paths, true);
}
System.setProperty(JAVA_LIBRARY_PATH, SystemUtils.JAVA_LIBRARY_PATH + File.pathSeparator + LIB_HOME);
} catch (IllegalAccessException e) {
throw new IOException("Failed to get permissions to set library path");
}
}
/**
* Add the native file extension to the given libName dependent of the operating system.
*
* @param libName The name of the library that has to be extended.
* @return The name of the library with system file extension.
*/
private static String nativeLibraryName(final String libName) {
String pattern;
if (SystemUtils.IS_OS_WINDOWS) {
pattern = "%s.dll";
} else if (SystemUtils.IS_OS_MAC_OSX) {
pattern = "lib%s.jnilib";
} else if (SystemUtils.IS_OS_LINUX) {
pattern = "lib%s.so";
} else {
throw new RuntimeException("Your operating system is not supported.");
}
return String.format(pattern, libName);
}
/**
* Generate the path to the lib.
* The path dependents on the architecture of the system.
*
* @param lib The native file name for the system.
* @return The path to the library.
*/
private static String archAwarePath(final String lib) {
return Joiner.on("/").join(JNI_PATH_ROOT, SystemUtils.OS_ARCH, lib);
}
/**
* Create the target file and all necessary directories.
*
* @param lib The full library name with extensions.
* @return The target file.
* @throws IOException when something during the IO operation happens.
*/
private static File createTargetFile(final String lib) throws IOException {
final String path = LIB_HOME + File.separator + lib;
final File target = new File(path);
if (!target.exists()) {
Files.createParentDirs(target);
target.createNewFile();
}
return target;
}
/**
* Extracts the library from the jar in the current working directory.
*
* @param path The path of the libary.
* @param lib The destinated library name.
* @throws IOException When a file operation during the extraction failed.
* @throws URISyntaxException When the classloader url can not be converted to a uri.
*/
private static void extractLibrary(final String path, final String lib) throws IOException, URISyntaxException {
final InputStream stream = JarUtil.class.getResourceAsStream(path);
if (stream == null) {
throw new IOException("Unable to find library on classpath: " + path);
}
// Initialize the target file and create if necessary.
final File target = createTargetFile(lib);
// Read the library from the resource to a temporary file.
final File temp = readStreamToTemp(stream);
// Only copy the source to target when the file has changed.
if (hasFileChanged(temp, target)) {
Files.copy(temp, target);
}
// Remove the temporary file when the program is closed.
temp.deleteOnExit();
}
/**
* Reads all data from the given stream and save it to a temp file.
*
* @param stream The stream with the data that has to be read.
* @return The temp file object.
* @throws IOException When something happend during the file operations.
*/
private static File readStreamToTemp(final InputStream stream) throws IOException {
final File tempDir = Files.createTempDir();
final File temp = new File(tempDir, "tmplib" + String.valueOf(System.currentTimeMillis()));
temp.createNewFile();
ByteSource.wrap(ByteStreams.toByteArray(stream)).copyTo(Files.asByteSink(temp));
return temp;
}
/**
* Check via Adler32 if a given targte file differs from the given source.
*
* @param source The source file.
* @param target The target file.
* @return True when the files are not equal, so they changed, else false.
* @throws IOException when something happened current the checksum calculation.
*/
private static boolean hasFileChanged(final File source, final File target) throws IOException {
return Files.hash(source, Hashing.adler32()) != Files.hash(target, Hashing.adler32());
}
}