org.scijava.nativelib.BaseJniExtractor Maven / Gradle / Ivy
Show all versions of native-lib-loader Show documentation
/*
* #%L
* Native library loader for extracting and loading native libraries from Java.
* %%
* Copyright (C) 2010 - 2015 Board of Regents of the University of
* Wisconsin-Madison and Glencoe Software, Inc.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. 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.
*
* 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 HOLDERS 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.
* #L%
*/
// This code is derived from Richard van der Hoff's mx-native-loader project:
// http://opensource.mxtelecom.com/maven/repo/com/wapmx/native/mx-native-loader/1.7/
// See NOTICE.txt for details.
// Copyright 2006 MX Telecom Ltd
package org.scijava.nativelib;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.util.Enumeration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Richard van der Hoff ([email protected])
*/
public abstract class BaseJniExtractor implements JniExtractor {
private static final Logger LOGGER = LoggerFactory.getLogger(
"org.scijava.nativelib.BaseJniExtractor");
private static final String JAVA_TMPDIR = "java.io.tmpdir";
private Class> libraryJarClass;
/**
* We use a resource path of the form META-INF/lib/${mx.sysinfo}/ This way
* native builds for multiple architectures can be packaged together without
* interfering with each other And by setting mx.sysinfo the jvm can pick the
* native libraries appropriate for itself.
*/
private String[] nativeResourcePaths;
public BaseJniExtractor() throws IOException {
init(null);
}
public BaseJniExtractor(final Class> libraryJarClass) throws IOException {
init(libraryJarClass);
}
private void init(final Class> libraryJarClass) throws IOException {
this.libraryJarClass = libraryJarClass;
final String mxSysInfo = MxSysInfo.getMxSysInfo();
if (mxSysInfo != null) {
nativeResourcePaths =
new String[] { "META-INF/lib/" + mxSysInfo + "/", "META-INF/lib/" };
}
else {
nativeResourcePaths = new String[] { "META-INF/lib/" };
}
}
/**
* this is where native dependencies are extracted to (e.g. tmplib/).
*
* @return native working dir
*/
public abstract File getNativeDir();
/**
* this is where JNI libraries are extracted to (e.g.
* tmplib/classloaderName.1234567890000.0/).
*
* @return jni working dir
*/
public abstract File getJniDir();
/** {@inheritDoc} */
public File extractJni(final String libPath, final String libname)
throws IOException
{
String mappedlibName = System.mapLibraryName(libname);
LOGGER.debug("mappedLib is " + mappedlibName);
/*
* On Darwin, the default mapping is to .jnilib; but we use .dylibs so that library interdependencies are
* handled correctly. if we don't find a .jnilib, try .dylib instead.
*/
URL lib = null;
// if no class specified look for resources in the jar of this class
if (null == libraryJarClass) {
libraryJarClass = this.getClass();
}
lib = libraryJarClass.getClassLoader().getResource(libPath + mappedlibName);
if (null == lib) {
/*
* On OS X, the default mapping changed from .jnilib to .dylib as of JDK 7, so
* we need to be prepared for the actual library and mapLibraryName disagreeing
* in either direction.
*/
final String altLibName;
if (mappedlibName.endsWith(".jnilib")) {
altLibName =
mappedlibName.substring(0, mappedlibName.length() - 7) + ".dylib";
}
else if (mappedlibName.endsWith(".dylib")) {
altLibName =
mappedlibName.substring(0, mappedlibName.length() - 6) + ".jnilib";
}
else {
altLibName = null;
}
if (altLibName != null) {
lib = getClass().getClassLoader().getResource(libPath + altLibName);
if (lib != null) {
mappedlibName = altLibName;
}
}
}
if (null != lib) {
LOGGER.debug("URL is " + lib.toString());
LOGGER.debug("URL path is " + lib.getPath());
return extractResource(getJniDir(), lib, mappedlibName);
}
LOGGER.info("Couldn't find resource " + libPath + " " +
mappedlibName);
throw new IOException("Couldn't find resource " + libPath + " " +
mappedlibName);
}
/** {@inheritDoc} */
public void extractRegistered() throws IOException {
LOGGER.debug("Extracting libraries registered in classloader " +
this.getClass().getClassLoader());
for (final String nativeResourcePath : nativeResourcePaths) {
final Enumeration resources =
this.getClass().getClassLoader().getResources(
nativeResourcePath + "AUTOEXTRACT.LIST");
while (resources.hasMoreElements()) {
final URL res = resources.nextElement();
extractLibrariesFromResource(res);
}
}
}
private void extractLibrariesFromResource(final URL resource)
throws IOException
{
LOGGER.debug("Extracting libraries listed in " + resource);
BufferedReader reader = null;
try {
reader =
new BufferedReader(
new InputStreamReader(resource.openStream(), "UTF-8"));
for (String line; (line = reader.readLine()) != null;) {
URL lib = null;
for (final String nativeResourcePath : nativeResourcePaths) {
lib =
this.getClass().getClassLoader().getResource(
nativeResourcePath + line);
if (lib != null) break;
}
if (lib != null) {
extractResource(getNativeDir(), lib, line);
}
else {
throw new IOException("Couldn't find native library " + line +
"on the classpath");
}
}
}
finally {
if (reader != null) {
reader.close();
}
}
}
/**
* Extract a resource to the tmp dir (this entry point is used for unit
* testing)
*
* @param dir the directory to extract the resource to
* @param resource the resource on the classpath
* @param outputName the filename to copy to (within the tmp dir)
* @return the extracted file
* @throws IOException
*/
File extractResource(final File dir, final URL resource,
final String outputName) throws IOException
{
final InputStream in = resource.openStream(); // TODO there's also a
// getResourceAsStream
// create a temporary file with same suffix (i.e. ".dylib")
String prefix = outputName;
String suffix = null;
final int lastDotIndex = outputName.lastIndexOf('.');
if (-1 != lastDotIndex) {
prefix = outputName.substring(0, lastDotIndex);
suffix = outputName.substring(lastDotIndex);
}
// clean up leftover libraries from previous runs
deleteLeftoverFiles(prefix, suffix);
// make a temporary file with our prefix and suffix
//
// (CreateTempFile javadoc only guarantees 3 characters of suffix [due
// to 8.3 filename legacy issues]. Theoretically a problem for ".dylib",
// but not in practice.)
final File outfile = File.createTempFile(prefix, suffix);
LOGGER.debug("Extracting '" + resource + "' to '" +
outfile.getAbsolutePath() + "'");
// copy resource stream to temporary file
final FileOutputStream out = new FileOutputStream(outfile);
copy(in, out);
out.close();
in.close();
// note that this doesn't always work:
outfile.deleteOnExit();
return outfile;
}
/**
* Looks in the temporary directory for leftover versions of temporary shared
* libraries.
*
* If a temporary shared library is in use by another instance it won't
* delete.
*
* There is a very unlikely race condition if another instance created a
* temporary shared library and now I delete it just before it tries to load
* it.
*
* Another issue is that createTempFile only guarantees to use the first three
* characters of the prefix, so I could delete a similarly-named temporary
* shared library if I haven't loaded it yet.
*
* @param prefix
* @param suffix
*/
void deleteLeftoverFiles(final String prefix, final String suffix) {
final File tmpDirectory = new File(System.getProperty(JAVA_TMPDIR));
final File[] files = tmpDirectory.listFiles(new FilenameFilter() {
public boolean accept(final File dir, final String name) {
return name.startsWith(prefix) && name.endsWith(suffix);
}
});
if (files == null) return;
for (final File file : files) {
// attempt to delete
try {
file.delete();
}
catch (final SecurityException e) {
// not likely
}
}
}
/**
* copy an InputStream to an OutputStream.
*
* @param in InputStream to copy from
* @param out OutputStream to copy to
* @throws IOException if there's an error
*/
static void copy(final InputStream in, final OutputStream out)
throws IOException
{
final byte[] tmp = new byte[8192];
int len = 0;
while (true) {
len = in.read(tmp);
if (len <= 0) {
break;
}
out.write(tmp, 0, len);
}
}
}