/* * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution 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 Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package com.jogamp.common.jvm; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import com.jogamp.common.net.Uri; import com.jogamp.common.os.NativeLibrary; import com.jogamp.common.util.JarUtil; import com.jogamp.common.util.PropertyAccess; import com.jogamp.common.util.SecurityUtil; import com.jogamp.common.util.cache.TempJarCache; import jogamp.common.Debug; import jogamp.common.os.PlatformPropsImpl; public class JNILibLoaderBase { public static final boolean DEBUG; protected static final boolean PERF; static { Debug.initSingleton(); DEBUG = Debug.debug("JNILibLoader"); PERF = DEBUG || PropertyAccess.isPropertyDefined("jogamp.debug.JNILibLoader.Perf", true); } private static final Object perfSync = new Object(); private static long perfTotal = 0; private static long perfCount = 0; public interface LoaderAction { /** * Loads the library specified by libname.
* The implementation should ignore, if the library has been loaded already.
* @param libname the library to load * @param ignoreError if true, errors during loading the library should be ignored * @param cl optional ClassLoader, used to locate the library * @return true if library loaded successful */ boolean loadLibrary(String libname, boolean ignoreError, ClassLoader cl); /** * Loads the library specified by libname.
* Optionally preloads the libraries specified by preload.
* The implementation should ignore, if any library has been loaded already.
* @param libname the library to load * @param preload the libraries to load before loading the main library if not null * @param preloadIgnoreError if true, errors during loading the preload-libraries should be ignored * @param cl optional ClassLoader, used to locate the library */ void loadLibrary(String libname, String[] preload, boolean preloadIgnoreError, ClassLoader cl); } private static class DefaultAction implements LoaderAction { @Override public boolean loadLibrary(final String libname, final boolean ignoreError, final ClassLoader cl) { boolean res = true; if(!isLoaded(libname)) { try { loadLibraryInternal(libname, cl); addLoaded(libname); if(DEBUG) { System.err.println("JNILibLoaderBase: loaded "+libname); } } catch (final UnsatisfiedLinkError e) { res = false; if(DEBUG) { e.printStackTrace(); } if (!ignoreError && e.getMessage().indexOf("already loaded") < 0) { throw e; } } } return res; } @Override public void loadLibrary(final String libname, final String[] preload, final boolean preloadIgnoreError, final ClassLoader cl) { if(!isLoaded(libname)) { if (null!=preload) { for (int i=0; i loaded = new HashSet(); private static LoaderAction loaderAction = new DefaultAction(); public static boolean isLoaded(final String libName) { return loaded.contains(libName); } public static void addLoaded(final String libName) { loaded.add(libName); if(DEBUG) { System.err.println("JNILibLoaderBase: Loaded Native Library: "+libName); } } public static void disableLoading() { setLoadingAction(null); } public static void enableLoading() { setLoadingAction(new DefaultAction()); } public static synchronized void setLoadingAction(final LoaderAction action) { loaderAction = action; } private static final String nativeJarTagPackage = "jogamp.nativetag"; // TODO: sync with gluegen-cpptasks-base.xml /** * * @param classFromJavaJar * @param classJarUri * @param jarBasename jar basename w/ suffix * @param nativeJarBasename native jar basename w/ suffix * @return * @throws IOException * @throws SecurityException * @throws URISyntaxException */ private static final boolean addNativeJarLibsImpl(final Class classFromJavaJar, final Uri classJarUri, final Uri.Encoded jarBasename, final Uri.Encoded nativeJarBasename) throws IOException, SecurityException, URISyntaxException { if (DEBUG) { final StringBuilder msg = new StringBuilder(); msg.append("JNILibLoaderBase: addNativeJarLibsImpl(").append(PlatformPropsImpl.NEWLINE); msg.append(" classFromJavaJar = ").append(classFromJavaJar).append(PlatformPropsImpl.NEWLINE); msg.append(" classJarURI = ").append(classJarUri).append(PlatformPropsImpl.NEWLINE); msg.append(" jarBasename = ").append(jarBasename).append(PlatformPropsImpl.NEWLINE); msg.append(" os.and.arch = ").append(PlatformPropsImpl.os_and_arch).append(PlatformPropsImpl.NEWLINE); msg.append(" nativeJarBasename = ").append(nativeJarBasename).append(PlatformPropsImpl.NEWLINE); msg.append(")"); System.err.println(msg.toString()); } final long t0 = PERF ? System.currentTimeMillis() : 0; // 'Platform.currentTimeMillis()' not yet available! boolean ok = false; final Uri jarSubURI = classJarUri.getContainedUri(); if (null == jarSubURI) { throw new IllegalArgumentException("JarSubURI is null of: "+classJarUri); } final Uri jarSubUriRoot = jarSubURI.getDirectory(); if (DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: initial: %s -> %s%n", jarSubURI, jarSubUriRoot); } final String nativeLibraryPath = String.format((Locale)null, "natives/%s/", PlatformPropsImpl.os_and_arch); if (DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: nativeLibraryPath: %s%n", nativeLibraryPath); } { // Attempt-1 a 'one slim native jar file' per 'os.and.arch' layout // with native platform libraries under 'natives/os.and.arch'! final Uri nativeJarURI = JarUtil.getJarFileUri( jarSubUriRoot.getEncoded().concat(nativeJarBasename) ); if (DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: module: %s -> %s%n", nativeJarBasename, nativeJarURI); } try { ok = TempJarCache.addNativeLibs(classFromJavaJar, nativeJarURI, nativeLibraryPath); } catch(final Exception e) { if(DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: Caught %s%n", e.getMessage()); e.printStackTrace(); } } } if (!ok) { final ClassLoader cl = classFromJavaJar.getClassLoader(); { // Attempt-2 a 'one big-fat jar file' layout, containing java classes // and all native platform libraries under 'natives/os.and.arch' per platform! final URL nativeLibraryURI = cl.getResource(nativeLibraryPath); if (null != nativeLibraryURI) { final Uri nativeJarURI = JarUtil.getJarFileUri( jarSubUriRoot.getEncoded().concat(jarBasename) ); try { if( TempJarCache.addNativeLibs(classFromJavaJar, nativeJarURI, nativeLibraryPath) ) { ok = true; if (DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: fat: %s -> %s%n", jarBasename, nativeJarURI); } } } catch(final Exception e) { if(DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: Caught %s%n", e.getMessage()); e.printStackTrace(); } } } } if (!ok) { // Attempt-3 to find via ClassLoader and Native-Jar-Tag, // assuming one slim native jar file per 'os.and.arch' // and native platform libraries under 'natives/os.and.arch'! final String moduleName; { final String packageName = classFromJavaJar.getPackage().getName(); final int idx = packageName.lastIndexOf('.'); if( 0 <= idx ) { moduleName = packageName.substring(idx+1); } else { moduleName = packageName; } } final String os_and_arch_dot = PlatformPropsImpl.os_and_arch.replace('-', '.'); final String nativeJarTagClassName = nativeJarTagPackage + "." + moduleName + "." + os_and_arch_dot + ".TAG"; // TODO: sync with gluegen-cpptasks-base.xml try { if(DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: ClassLoader/TAG: Locating module %s, os.and.arch %s: %s%n", moduleName, os_and_arch_dot, nativeJarTagClassName); } final Uri nativeJarTagClassJarURI = JarUtil.getJarUri(nativeJarTagClassName, cl); if (DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: ClassLoader/TAG: %s -> %s%n", nativeJarTagClassName, nativeJarTagClassJarURI); } ok = TempJarCache.addNativeLibs(classFromJavaJar, nativeJarTagClassJarURI, nativeLibraryPath); } catch (final Exception e ) { if(DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl: Caught %s%n", e.getMessage()); e.printStackTrace(); } } } } if (DEBUG || PERF) { final long tNow = System.currentTimeMillis() - t0; final long tTotal, tCount; synchronized(perfSync) { tCount = perfCount+1; tTotal = perfTotal + tNow; perfTotal = tTotal; perfCount = tCount; } final double tAvrg = tTotal / (double)tCount; System.err.printf("JNILibLoaderBase: addNativeJarLibsImpl.X: %s / %s -> ok: %b; duration: now %d ms, total %d ms (count %d, avrg %.3f ms)%n", jarBasename, nativeJarBasename, ok, tNow, tTotal, tCount, tAvrg); } return ok; } /** * Loads and adds a JAR file's native library to the TempJarCache, * calling {@link JNILibLoaderBase#addNativeJarLibs(Class[], String)} * with default JOGL deployment configuration: *
        return JNILibLoaderBase.addNativeJarLibs(classesFromJavaJars, "-all");
   * 
* If Class1.class is contained in a JAR file which name includes singleJarMarker -all, * implementation will attempt to resolve the native JAR file as follows: * * Otherwise the native JAR files will be resolved for each class's JAR file: * */ public static final boolean addNativeJarLibsJoglCfg(final Class[] classesFromJavaJars) { return addNativeJarLibs(classesFromJavaJars, "-all"); } /** * Loads and adds a JAR file's native library to the TempJarCache.
* The native library JAR file's URI is derived as follows: * * Where: * *

* Generic description: *

       final Class[] classesFromJavaJars = new Class[] { Class1.class, Class2.class };
       JNILibLoaderBase.addNativeJarLibs(classesFromJavaJars, "-all");
   * 
* If Class1.class is contained in a JAR file which name includes singleJarMarker, here -all, * implementation will attempt to resolve the native JAR file as follows: * * Otherwise the native JAR files will be resolved for each class's JAR file: * *

*

* Examples: *

*

* JOCL: *

        // only: jocl.jar -> jocl-natives-os.and.arch.jar
        addNativeJarLibs(new Class[] { JOCLJNILibLoader.class }, null, null );
   * 
*

*

* JOGL: *

       final ClassLoader cl = GLProfile.class.getClassLoader();
       // jogl-all.jar         -> jogl-all-natives-os.and.arch.jar
       // jogl-all-noawt.jar   -> jogl-all-noawt-natives-os.and.arch.jar
       // jogl-all-mobile.jar  -> jogl-all-mobile-natives-os.and.arch.jar
       // jogl-all-android.jar -> jogl-all-android-natives-os.and.arch.jar
       // nativewindow.jar     -> nativewindow-natives-os.and.arch.jar
       // jogl.jar             -> jogl-natives-os.and.arch.jar
       // newt.jar             -> newt-natives-os.and.arch.jar (if available)
       final String newtFactoryClassName = "com.jogamp.newt.NewtFactory";
       final Class[] classesFromJavaJars = new Class[] { NWJNILibLoader.class, GLProfile.class, null };
       if( ReflectionUtil.isClassAvailable(newtFactoryClassName, cl) ) {
           classesFromJavaJars[2] = ReflectionUtil.getClass(newtFactoryClassName, false, cl);
       }
       JNILibLoaderBase.addNativeJarLibs(classesFromJavaJars, "-all");
   * 
*

* * @param classesFromJavaJars For each given Class, load the native library JAR. * @param singleJarMarker Optional string marker like "-all" to identify the single 'all-in-one' JAR file * after which processing of the class array shall stop. * * @return true if either the 'all-in-one' native JAR or all native JARs loaded successful or were loaded already, * false in case of an error */ public static boolean addNativeJarLibs(final Class[] classesFromJavaJars, final String singleJarMarker) { if(DEBUG) { final StringBuilder msg = new StringBuilder(); msg.append("JNILibLoaderBase: addNativeJarLibs(").append(PlatformPropsImpl.NEWLINE); msg.append(" classesFromJavaJars = ").append(Arrays.asList(classesFromJavaJars)).append(PlatformPropsImpl.NEWLINE); msg.append(" singleJarMarker = ").append(singleJarMarker).append(PlatformPropsImpl.NEWLINE); msg.append(")"); System.err.println(msg.toString()); } boolean ok = false; if ( TempJarCache.isInitialized(true) ) { ok = addNativeJarLibsWithTempJarCache(classesFromJavaJars, singleJarMarker); } else if(DEBUG) { System.err.println("JNILibLoaderBase: addNativeJarLibs0: disabled due to uninitialized TempJarCache"); } return ok; } private static boolean addNativeJarLibsWithTempJarCache(final Class[] classesFromJavaJars, final String singleJarMarker) { boolean ok; int count = 0; try { boolean done = false; ok = true; for (int i = 0; i < classesFromJavaJars.length; ++i) { final Class c = classesFromJavaJars[i]; if (c == null) { continue; } final ClassLoader cl = c.getClassLoader(); final Uri classJarURI = JarUtil.getJarUri(c.getName(), cl); final Uri.Encoded jarName = JarUtil.getJarBasename(classJarURI); if (jarName == null) { continue; } final Uri.Encoded jarBasename = jarName.substring(0, jarName.indexOf(".jar")); if(DEBUG) { System.err.printf("JNILibLoaderBase: jarBasename: %s%n", jarBasename); } /** * If a jar marker was specified, and the basename contains the * marker, we're done. */ if (singleJarMarker != null) { if (jarBasename.indexOf(singleJarMarker) >= 0) { done = true; } } final Uri.Encoded nativeJarBasename = Uri.Encoded.cast( String.format((Locale)null, "%s-natives-%s.jar", jarBasename.get(), PlatformPropsImpl.os_and_arch) ); ok = JNILibLoaderBase.addNativeJarLibsImpl(c, classJarURI, jarName, nativeJarBasename); if (ok) { count++; } if (DEBUG && done) { System.err.printf("JNILibLoaderBase: addNativeJarLibs0: done: %s%n", jarBasename); } } } catch (final Exception x) { System.err.printf("JNILibLoaderBase: Caught %s: %s%n", x.getClass().getSimpleName(), x.getMessage()); if(DEBUG) { x.printStackTrace(); } ok = false; } if(DEBUG) { System.err.printf("JNILibLoaderBase: addNativeJarLibsWhenInitialized: count %d, ok %b%n", count, ok); } return ok; } /** * Loads the library specified by libname, using the {@link LoaderAction} set by {@link #setLoadingAction(LoaderAction)}.
* The implementation should ignore, if the library has been loaded already.
* @param libname the library to load * @param ignoreError if true, errors during loading the library should be ignored * @param cl optional ClassLoader, used to locate the library * @return true if library loaded successful */ protected static synchronized boolean loadLibrary(final String libname, final boolean ignoreError, final ClassLoader cl) { if (loaderAction != null) { return loaderAction.loadLibrary(libname, ignoreError, cl); } return false; } /** * Loads the library specified by libname, using the {@link LoaderAction} set by {@link #setLoadingAction(LoaderAction)}.
* Optionally preloads the libraries specified by preload.
* The implementation should ignore, if any library has been loaded already.
* @param libname the library to load * @param preload the libraries to load before loading the main library if not null * @param preloadIgnoreError if true, errors during loading the preload-libraries should be ignored * @param cl optional ClassLoader, used to locate the library */ protected static synchronized void loadLibrary(final String libname, final String[] preload, final boolean preloadIgnoreError, final ClassLoader cl) { if (loaderAction != null) { loaderAction.loadLibrary(libname, preload, preloadIgnoreError, cl); } } // private static final Class customLauncherClass; // FIXME: remove private static final Method customLoadLibraryMethod; static { final String sunAppletLauncherProperty = "sun.jnlp.applet.launcher"; final String sunAppletLauncherClassName = "org.jdesktop.applet.util.JNLPAppletLauncher"; final Method loadLibraryMethod = SecurityUtil.doPrivileged(new PrivilegedAction() { @Override public Method run() { // FIXME: remove final boolean usingJNLPAppletLauncher = PropertyAccess.getBooleanProperty(sunAppletLauncherProperty, true); Class launcherClass = null; Method loadLibraryMethod = null; if (usingJNLPAppletLauncher) { try { launcherClass = Class.forName(sunAppletLauncherClassName); } catch (final ClassNotFoundException cnfe) { // oops .. look like JNLPAppletLauncher doesn't exist, despite property // this may happen if a previous applet was using JNLPAppletLauncher in the same JVM System.err.println("JNILibLoaderBase: <"+sunAppletLauncherClassName+"> not found, despite enabled property <"+sunAppletLauncherProperty+">, JNLPAppletLauncher was probably used before"); System.setProperty(sunAppletLauncherProperty, Boolean.FALSE.toString()); } catch (final LinkageError le) { throw le; } if(null != launcherClass) { try { loadLibraryMethod = launcherClass.getDeclaredMethod("loadLibrary", new Class[] { String.class }); } catch (final NoSuchMethodException ex) { if(DEBUG) { ex.printStackTrace(); } launcherClass = null; } } } if(null==launcherClass) { final String launcherClassName = PropertyAccess.getProperty("jnlp.launcher.class", false); if(null!=launcherClassName) { try { launcherClass = Class.forName(launcherClassName); loadLibraryMethod = launcherClass.getDeclaredMethod("loadLibrary", new Class[] { String.class }); } catch (final ClassNotFoundException ex) { if(DEBUG) { ex.printStackTrace(); } } catch (final NoSuchMethodException ex) { if(DEBUG) { ex.printStackTrace(); } launcherClass = null; } } } return loadLibraryMethod; } } ); customLoadLibraryMethod = loadLibraryMethod; } private static void loadLibraryInternal(final String libraryName, final ClassLoader cl) { // Note: special-casing JAWT which is built in to the JDK int mode = 0; // 1 - custom, 2 - System.load( TempJarCache ), 3 - System.loadLibrary( name ), 4 - System.load( enumLibNames ) if (null!=customLoadLibraryMethod && !libraryName.equals("jawt")) { // FIXME: remove if(DEBUG) { System.err.println("JNILibLoaderBase: customLoad("+libraryName+") - mode 1"); } try { customLoadLibraryMethod.invoke(null, new Object[] { libraryName }); mode = 1; } catch (final Exception e) { Throwable t = e; if (t instanceof InvocationTargetException) { t = ((InvocationTargetException) t).getTargetException(); } if (t instanceof Error) { throw (Error) t; } if (t instanceof RuntimeException) { throw (RuntimeException) t; } // Throw UnsatisfiedLinkError for best compatibility with System.loadLibrary() throw (UnsatisfiedLinkError) new UnsatisfiedLinkError("can not load library "+libraryName).initCause(e); } } else { // System.err.println("sun.boot.library.path=" + Debug.getProperty("sun.boot.library.path", false)); final String libraryPath = NativeLibrary.findLibrary(libraryName, cl); // implicit TempJarCache usage if used/initialized if(DEBUG) { System.err.println("JNILibLoaderBase: loadLibraryInternal("+libraryName+"), TempJarCache: "+libraryPath); } if(null != libraryPath) { if(DEBUG) { System.err.println("JNILibLoaderBase: System.load("+libraryPath+") - mode 2"); } System.load(libraryPath); mode = 2; } else { if(DEBUG) { System.err.println("JNILibLoaderBase: System.loadLibrary("+libraryName+") - mode 3: SystemEnvLibraryPaths: "+NativeLibrary.getSystemEnvLibraryPaths()); } try { System.loadLibrary(libraryName); mode = 3; } catch (final UnsatisfiedLinkError ex1) { if(DEBUG) { System.err.println("ERROR mode 3 - "+ex1.getMessage()); } final List possiblePaths = NativeLibrary.enumerateLibraryPaths(libraryName, libraryName, libraryName, cl); // Iterate down these and see which one if any we can actually find. for (final Iterator iter = possiblePaths.iterator(); 0 == mode && iter.hasNext(); ) { final String path = iter.next(); if (DEBUG) { System.err.println("JNILibLoaderBase: System.load("+path+") - mode 4"); } try { System.load(path); mode = 4; } catch (final UnsatisfiedLinkError ex2) { if(DEBUG) { System.err.println("n/a - "+ex2.getMessage()); } if(!iter.hasNext()) { // Avoid misleading final exception, use our own throw new UnsatisfiedLinkError("Couldn't load library '"+libraryName+ "' generically including "+NativeLibrary.getSystemEnvLibraryPaths()+ // mode 3 ", nor as "+possiblePaths); // mode 4 } } } } } } if(DEBUG) { System.err.println("JNILibLoaderBase: loadLibraryInternal("+libraryName+"): OK - mode "+mode); } } }