com.izforge.izpack.util.Librarian Maven / Gradle / Ivy
/*
* IzPack - Copyright 2001-2012 Julien Ponge, All Rights Reserved.
*
* http://izpack.org/
* http://izpack.codehaus.org/
*
* Copyright 2002 Elmar Grom
*
* 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.izforge.izpack.util;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class handles loading of native libraries. There must only be one instance of
* Librarian
per Java runtime, therefore this class is implemented as a 'Singleton'.
*
*
* Librarian
is capable of loading native libraries from a variety of different
* source locations. However, you should place your library files in the 'native' directory. The
* primary reason for supporting different source locations is to facilitate testing in a
* development environment, without the need to actually packing the application into a *.jar file.
*
* @author Elmar Grom
*/
public class Librarian implements CleanupClient
{
/**
* The logger.
*/
private static final Logger logger = Logger.getLogger(Librarian.class.getName());
/**
* Used to identify jar URL protocols
*/
private static final String JAR_PROTOCOL = "jar";
/**
* Used to identify file URL protocols
*/
private static final String FILE_PROTOCOL = "file";
/**
* The default directory for native library files.
*/
private static final String NATIVE = "/com/izforge/izpack/bin/native/";
/**
* A list that is used to track all libraries that have been loaded. This list is used to ensure
* that each library is loaded only once.
*/
private List trackList = new ArrayList();
/**
* A list of references to clients that use libraries that were extracted from a *.jar file.
* This is needed because the clients need to be called for freeing their libraries.
*/
private List clients = new ArrayList();
/**
* A list of fully qualified library names. This is needed to delete the temporary library files
* after use. The index of each name corresponds to the index of the respective client in the
* clients
list.
*/
private List temporaryFileNames = new ArrayList();
/**
* The extension to use for native libraries.
*/
private String extension = "";
/**
* Constructs a Librarian.
*
* @param factory the factory
* @param housekeeper the house keeper
*/
public Librarian(TargetFactory factory, Housekeeper housekeeper)
{
housekeeper.registerForCleanup(this);
extension = '.' + factory.getNativeLibraryExtension();
}
/**
* Loads a library.
*
* @param name the library name
* @param client the native library client
* @throws UnsatisfiedLinkError if the library cannot be loaded
*/
public synchronized void loadLibrary(String name, NativeLibraryClient client) throws UnsatisfiedLinkError
{
name = strip(name);
if (!trackList.contains(name))
{
// no attempt has been made to load the library yet
boolean loaded = loadArchSpecificLibrary(name, client);
if (!loaded)
{
String name64 = name + "_x64";
loaded = loadArchSpecificLibrary(name64, client);
}
if (loaded)
{
trackList.add(name);
}
else
{
throw new UnsatisfiedLinkError("Failed to load library: " + name);
}
}
}
/*--------------------------------------------------------------------------*/
/**
* This method attempts to remove all native libraries that have been temporarily created from
* the system.
* This method calls LibraryRemover which starts a new process which
* waits a little bit for exit of this process and tries than to delete the given files.
* If the version is 1.5.x or higher this process should be exit in one second, else
* the native libraries will be not deleted.
* Tests with the different methods produces hinds that the
* FreeLibraryAndExitThread (handle, 0) call in the dlls are the
* reason for VM crashes (version 1.5.x). May be this is a bug in the VM.
* But never seen a docu that this behavior is compatible with a VM.
* Since more than a year all 1.5 versions produce this crash. Therfore we make
* now a work around for it.
* But the idea to exit the thread for removing the file locking to give the
* possibility to delete the dlls are really nice. Therefore we use it with
* VMs which are compatible with it. (Klaus Bartz 2006.06.20)
*/
@Override
public void cleanUp()
{
// This method will be used the SelfModifier stuff of uninstall
// instead of killing the thread in the dlls which provokes a
// segmentation violation with a 1.5 (also known as 5.0) VM.
if (!temporaryFileNames.isEmpty())
{
try
{
LibraryRemover.invoke(temporaryFileNames);
}
catch (IOException exception)
{
logger.log(Level.WARNING, "Cleanup failed for native libraries: " + exception.getMessage(), exception);
}
}
clients.clear();
}
/**
* Returns the resource URL for the named library.
*
* @param name the library name
* @return the library's resource URL, or null if it is not found
*/
protected URL getResourcePath(String name)
{
String resource = NATIVE + "izpack/" + name + extension;
URL url = getClass().getResource(resource);
if (url == null) {
resource = NATIVE + "3rdparty/" + name + extension;
url = getClass().getResource(resource);
}
return url;
}
/**
* Loads the requested library. If the library is already loaded, this method returns
* immediately, without an attempt to load the library again.
*
* Invocation Example: This assumes that the call is made from the class that links with
* the library. If this is not the case, this
must be replaced by the reference
* of the class that links with the library.
*
*
* Librarian.getInstance ().loadLibrary ("MyLibrary", this);
*
*
* Loading of a native library file works as follows:
*
* - If the library is already loaded there is nothing to do.
*
- An attempt is made to load the library by its name. If there is no system path set to
* the library, this attempt will fail.
*
- If the client is located on the local file system, an attempt is made to load the
* library from the local files system as well.
*
- If the library is located inside a *.jar file, it is extracted to 'java.io.tmpdir' and
* an attempt is made to load it from there.
*
*
*
* Loading from the local file system and from the *.jar file is attempted for the following
* potential locations of the library in this order:
*
* - The same directory where the client is located
*
- The native library directory
*
*
* @param name the name of the library. A file extension and path are not needed, in fact if
* supplied, both is stripped off. A specific extension is appended.
* @param client the object that made the load request
* @return true if the
*/
private boolean loadArchSpecificLibrary(String name, NativeLibraryClient client)
{
boolean result = false;
if (loadFromDLLPath(name, client) || loadSystemLibrary(name, client) || loadFromClassPath(name, client))
{
result = true;
}
return result;
}
/**
* Attempts to load a library from the DLL_PATH system property.
*
* @param name the library name
* @param client the native library client
* @return true if the library was loaded successfully, otherwise false
*/
private boolean loadFromDLLPath(String name, NativeLibraryClient client)
{
String property = System.getProperty("DLL_PATH");
if (property != null)
{
String path = property + "/" + name + extension;
path = path.replace('/', File.separatorChar);
return load(path, client);
}
return false;
}
/**
* Attempts to load a library from the classpath.
*
* @param name the library name
* @param client the native library client
* @return true if the library was loaded successfully, otherwise false
*/
private boolean loadFromClassPath(String name, NativeLibraryClient client)
{
boolean result = false;
URL url = getResourcePath(name);
if (url != null)
{
String protocol = url.getProtocol();
if (protocol.equalsIgnoreCase(FILE_PROTOCOL))
{
// its a local file
try
{
String path = new File(url.toURI()).getPath();
result = load(path, client);
}
catch (URISyntaxException exception)
{
logger.log(Level.WARNING, "Failed to load library: " + name + ": " + exception.getMessage(),
exception);
}
}
else if (protocol.equalsIgnoreCase(JAR_PROTOCOL))
{
// its a jar file. Extract and load it from 'java.io.tmpdir'
result = loadJarLibrary(name, url, client);
}
}
return result;
}
/**
* Attempts to load a library from a jar.
*
* @param name the library name
* @param url the library URL within the jar
* @param client the native library client
* @return true if the library was loaded successfully, otherwise false
*/
private boolean loadJarLibrary(String name, URL url, NativeLibraryClient client)
{
boolean result = false;
File file = null;
InputStream in = null;
FileOutputStream out = null;
String path = null;
try
{
file = File.createTempFile(name, extension, FileUtils.getTempDirectory());
in = url.openStream();
out = new FileOutputStream(file);
IOUtils.copy(in, out);
path = file.getAbsolutePath();
}
catch (IOException exception)
{
logger.log(Level.WARNING, "Failed to load library: " + name + ": " + exception.getMessage(), exception);
}
finally
{
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
if (path != null)
{
result = load(path, client);
}
if (!result)
{
FileUtils.deleteQuietly(file);
}
else
{
temporaryFileNames.add(path);
file.deleteOnExit();
}
return result;
}
/**
* Loads a system library.
*
* @param name the library name
* @param client the native library client
* @return true if the library was loaded successfully, otherwise false
*/
private boolean loadSystemLibrary(String name, NativeLibraryClient client)
{
try
{
System.loadLibrary(name);
clients.add(client);
return true;
}
catch (Throwable exception)
{
logger.log(Level.FINE, "Failed to load library: " + name + ": " + exception.getMessage(), exception);
}
return false;
}
/**
* Loads a library given its path.
*
* @param path the library path
* @param client the native library client
* @return true if the library was loaded successfully, otherwise false
*/
private boolean load(String path, NativeLibraryClient client)
{
boolean result = false;
try
{
System.load(path);
clients.add(client);
result = true;
}
catch (Throwable exception)
{
logger.log(Level.FINE, "Failed to load library: " + path + ": " + exception.getMessage(), exception);
}
return result;
}
/**
* Strips the extension of the library name, if it has one.
*
* @param name the name of the library
* @return the name without an extension
*/
private String strip(String name)
{
int extensionStart = name.lastIndexOf('.');
int nameStart = name.lastIndexOf('/');
if (nameStart < 0)
{
nameStart = name.lastIndexOf('\\');
}
nameStart++;
String shortName;
if (extensionStart > 0)
{
shortName = name.substring(nameStart, extensionStart);
}
else
{
shortName = name.substring(nameStart, name.length());
}
return (shortName);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy