All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bytedeco.javacpp.Loader Maven / Gradle / Ivy

There is a newer version: 1.5.11
Show newest version
/*
 * Copyright (C) 2011-2016 Samuel Audet
 *
 * Licensed either under the Apache License, Version 2.0, or (at your option)
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation (subject to the "Classpath" exception),
 * either version 2, or any later version (collectively, 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
 *     http://www.gnu.org/licenses/
 *     http://www.gnu.org/software/classpath/license.html
 *
 * or as provided in the LICENSE.txt file that accompanied this code.
 * 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 org.bytedeco.javacpp;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.WeakHashMap;
import org.bytedeco.javacpp.annotation.Platform;
import org.bytedeco.javacpp.tools.Builder;
import org.bytedeco.javacpp.tools.Logger;

/**
 * The Loader contains functionality to load native libraries, but also has a bit
 * of everything that does not fit anywhere else. In addition to its library loading
 * features, it also has utility methods to get the platform name, to load properties
 * from Java resources and from Class annotations, to extract file resources to the
 * temporary directory, and to get the {@code offsetof()} or {@code sizeof()} a native
 * {@code struct}, {@code class}, or {@code union} with its {@link Pointer} peer class
 * and a {@link HashMap} initialized by the native libraries inside {@code JNI_OnLoad()}.
 *
 * @author Samuel Audet
 */
public class Loader {

    private static final Logger logger = Logger.create(Loader.class);

    /** Value created out of "java.vm.name", "os.name", and "os.arch" system properties.
     *  Returned by {@link #getPlatform()} as default. */
    private static final String PLATFORM;
    /** Default platform properties loaded and returned by {@link #loadProperties()}. */
    private static Properties platformProperties = null;

    static {
        String jvmName = System.getProperty("java.vm.name", "").toLowerCase();
        String osName  = System.getProperty("os.name", "").toLowerCase();
        String osArch  = System.getProperty("os.arch", "").toLowerCase();
        String abiType = System.getProperty("sun.arch.abi", "").toLowerCase();
        if (jvmName.startsWith("dalvik") && osName.startsWith("linux")) {
            osName = "android";
        } else if (jvmName.startsWith("robovm") && osName.startsWith("darwin")) {
            osName = "ios";
            osArch = "arm";
        } else if (osName.startsWith("mac os x") || osName.startsWith("darwin")) {
            osName = "macosx";
        } else {
            int spaceIndex = osName.indexOf(' ');
            if (spaceIndex > 0) {
                osName = osName.substring(0, spaceIndex);
            }
        }
        if (osArch.equals("i386") || osArch.equals("i486") || osArch.equals("i586") || osArch.equals("i686")) {
            osArch = "x86";
        } else if (osArch.equals("amd64") || osArch.equals("x86-64") || osArch.equals("x64")) {
            osArch = "x86_64";
        } else if (osArch.startsWith("aarch64") || osArch.startsWith("armv8") || osArch.startsWith("arm64")) {
            osArch = "arm64";
        } else if ((osArch.startsWith("arm")) && abiType.equals("gnueabihf")) {
            osArch = "armhf";
	} else if (osArch.startsWith("arm")) {
            osArch = "arm";
        }
        PLATFORM = osName + "-" + osArch;
    }

    /**
     * Returns either the value of the "org.bytedeco.javacpp.platform"
     * system property, or {@link #PLATFORM} when the former is not set.
     *
     * @return {@code System.getProperty("org.bytedeco.javacpp.platform", platform)}
     * @see #PLATFORM
     */
    public static String getPlatform() {
        return System.getProperty("org.bytedeco.javacpp.platform", PLATFORM);
    }

    /**
     * Loads the {@link Properties} associated with the default {@link #getPlatform()}.
     *
     * @return {@code loadProperties(getPlatform(), null)}
     * @see #loadProperties(String, String)
     */
    public static Properties loadProperties() {
        String name = getPlatform();
        if (platformProperties != null && name.equals(platformProperties.getProperty("platform"))) {
            return platformProperties;
        }
        return platformProperties = loadProperties(name, null);
    }
    /**
     * Loads from resources the default {@link Properties} of the specified platform name.
     * The resource must be at {@code "org/bytedeco/javacpp/properties/" + name + ".properties"}.
     *
     * @param name the platform name
     * @param defaults the fallback platform name (null == "generic")
     * @return the Properties from resources
     */
    public static Properties loadProperties(String name, String defaults) {
        if (defaults == null) {
            defaults = "generic";
        }
        Properties p = new Properties();
        p.put("platform", name);
        p.put("platform.path.separator", File.pathSeparator);
        String s = System.mapLibraryName("/");
        int i = s.indexOf('/');
        p.put("platform.library.prefix", s.substring(0, i));
        p.put("platform.library.suffix", s.substring(i + 1));
        name = "properties/" + name + ".properties";
        InputStream is = Loader.class.getResourceAsStream(name);
        try {
            try {
                p.load(new InputStreamReader(is));
            } catch (NoSuchMethodError e) {
                p.load(is);
            }
        } catch (Exception e) {
            name = "properties/" + defaults + ".properties";
            InputStream is2 = Loader.class.getResourceAsStream(name);
            try {
                try {
                    p.load(new InputStreamReader(is2));
                } catch (NoSuchMethodError e2) {
                    p.load(is2);
                }
            } catch (Exception e2) {
                // give up and return defaults
            } finally {
                try {
                    if (is2 != null) {
                        is2.close();
                    }
                } catch (IOException ex) {
                    logger.error("Unable to close resource : " + ex.getMessage());
                }
            }
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException ex) {
                logger.error("Unable to close resource : " + ex.getMessage());
            }
        }
        return p;
    }

    /**
     * If annotated with properties, returns the argument as "enclosing Class".
     * If no properties are found on the Class, makes a search for the first Class
     * with properties that we can use, and returns it as the enclosing Class found.
     *
     * @param cls the Class to start the search from
     * @return the enclosing Class
     * @see org.bytedeco.javacpp.annotation.Platform
     * @see org.bytedeco.javacpp.annotation.Properties
     */
    public static Class getEnclosingClass(Class cls) {
        Class c = cls;
        // Find first enclosing declaring class with some properties to use
        while (c.getDeclaringClass() != null) {
            if (c.isAnnotationPresent(org.bytedeco.javacpp.annotation.Properties.class)) {
                break;
            }
            if (c.isAnnotationPresent(Platform.class)) {
                Platform p = c.getAnnotation(Platform.class);
                if (p.pragma().length > 0 || p.define().length > 0 || p.include().length > 0 || p.cinclude().length > 0 ||
                        p.includepath().length > 0 || p.compiler().length > 0 || p.linkpath().length > 0 ||
                        p.link().length > 0 || p.frameworkpath().length > 0 || p.framework().length > 0 ||
                        p.preloadpath().length > 0 || p.preload().length > 0 || p.library().length() > 0) {
                    break;
                }
            }
            c = c.getDeclaringClass();
        }
        return c;
    }


    /**
     * For all the classes, loads all properties from each Class annotations for the given platform.
     * @see #loadProperties(Class, java.util.Properties, boolean)
     */
    public static ClassProperties loadProperties(Class[] cls, Properties properties, boolean inherit) {
        ClassProperties cp = new ClassProperties(properties);
        for (Class c : cls) {
            cp.load(c, inherit);
        }
        return cp;
    }
    /**
     * Loads all properties from Class annotations for the given platform. The platform
     * of interest needs to be specified as the value of the "platform" key in the
     * properties argument. It is also possible to indicate whether to load all the classes
     * specified in the {@link org.bytedeco.javacpp.annotation.Properties#inherit()}
     * annotation recursively via the inherit argument.
     *
     * @param cls the Class of which to return Properties
     * @param properties the platform Properties to inherit
     * @param inherit indicates whether or not to inherit properties from other classes
     * @return all the properties associated with the Class for the given platform
     */
    public static ClassProperties loadProperties(Class cls, Properties properties, boolean inherit) {
        ClassProperties cp = new ClassProperties(properties);
        cp.load(cls, inherit);
        return cp;
    }

    /**
     * Returns the {@link Class} object that contains a caller's method.
     *
     * @param i the offset on the call stack of the method of interest
     * @return the Class found from the calling context, or {@code null} if not found
     */
    public static Class getCallerClass(int i) {
        Class[] classContext = null;
        try {
            new SecurityManager() {
                @Override public Class[] getClassContext() {
                    return super.getClassContext();
                }
            }.getClassContext();
        } catch (NoSuchMethodError e) {
            logger.error("No definition of this method : " + e.getMessage());
        }
        if (classContext != null) {
            for (int j = 0; j < classContext.length; j++) {
                if (classContext[j] == Loader.class) {
                    return classContext[i+j];
                }
            }
        } else {
            // SecurityManager.getClassContext() returns null on Android 4.0
            try {
                StackTraceElement[] classNames = Thread.currentThread().getStackTrace();
                for (int j = 0; j < classNames.length; j++) {
                    if (Class.forName(classNames[j].getClassName()) == Loader.class) {
                        return Class.forName(classNames[i+j].getClassName());
                    }
                }
            } catch (ClassNotFoundException e) {
                logger.error("No definition for the class found : " + e.getMessage());
            }
        }
        return null;
    }

    /**
     * Extracts by name a resource using the {@link ClassLoader} of the caller.
     *
     * @param name the name of the resource passed to {@link Class#getResource(String)}
     * @see #extractResource(URL, File, String, String)
     */
    public static File extractResource(String name, File directory,
            String prefix, String suffix) throws IOException {
        Class cls = getCallerClass(2);
        return extractResource(cls, name, directory, prefix, suffix);
    }
    /**
     * Extracts by name a resource using the {@link ClassLoader} of the specified {@link Class}.
     *
     * @param cls the Class from which to load resources
     * @param name the name of the resource passed to {@link Class#getResource(String)}
     * @see #extractResource(URL, File, String, String)
     */
    public static File extractResource(Class cls, String name, File directory,
            String prefix, String suffix) throws IOException {
        return extractResource(cls.getResource(name), directory, prefix, suffix);
    }
    /**
     * Extracts a resource into the specified directory and with the specified
     * prefix and suffix for the filename. If both prefix and suffix are {@code null},
     * the original filename is used, so directoryOrFile must not be {@code null}.
     *
     * @param resourceURL the URL of the resource to extract
     * @param directoryOrFile the output directory or file ({@code null == System.getProperty("java.io.tmpdir")})
     * @param prefix the prefix of the temporary filename to use
     * @param suffix the suffix of the temporary filename to use
     * @return the File object representing the extracted file
     * @throws IOException if fails to extract resource properly
     */
    public static File extractResource(URL resourceURL, File directoryOrFile,
            String prefix, String suffix) throws IOException {
        InputStream is = resourceURL != null ? resourceURL.openStream() : null;
        OutputStream os = null;
        if (is == null) {
            return null;
        }
        File file = null;
        boolean fileExisted = false;
        try {
            if (prefix == null && suffix == null) {
                if (directoryOrFile == null) {
                    directoryOrFile = new File(System.getProperty("java.io.tmpdir"));
                }
                if (directoryOrFile.isDirectory()) {
                    file = new File(directoryOrFile, new File(resourceURL.getPath()).getName());
                } else {
                    file = directoryOrFile;
                }
                fileExisted = file.exists();
            } else {
                file = File.createTempFile(prefix, suffix, directoryOrFile);
            }
            os = new FileOutputStream(file);
            byte[] buffer = new byte[1024];
            int length;
            while ((length = is.read(buffer)) != -1) {
                os.write(buffer, 0, length);
            }
            is.close();
            os.close();
        } catch (IOException e) {
            if (file != null && !fileExisted) {
                file.delete();
            }
            throw e;
        } finally {
            is.close();
            if (os != null) {
                os.close();
            }
        }
        return file;
    }

    /** User-specified cache directory set and returned by {@link #getCacheDir()}. */
    static File cacheDir = null;
    /** Temporary directory set and returned by {@link #getTempDir()}. */
    static File tempDir = null;
    /** Contains all the native libraries that we have loaded to avoid reloading them. */
    static Map loadedLibraries = Collections.synchronizedMap(new HashMap());

    /**
     * Creates and returns {@code System.getProperty("org.bytedeco.javacpp.cachedir")}, or null when not set.
     *
     * @return {@link #cacheDir}
     */
    public static File getCacheDir() {
        if (cacheDir == null) {
            String dirName = System.getProperty("org.bytedeco.javacpp.cachedir", null);
            if (dirName != null) {
                File f = new File(dirName);
                if (f.exists() || f.mkdirs()) {
                    cacheDir = f;
                }
            }
        }
        return cacheDir;
    }

    /**
     * Creates a unique name for {@link #tempDir} out of
     * {@code System.getProperty("java.io.tmpdir")} and {@code System.nanoTime()}.
     *
     * @return {@link #tempDir}
     */
    public static File getTempDir() {
        if (tempDir == null) {
            File tmpdir = new File(System.getProperty("java.io.tmpdir"));
            File f;
            for (int i = 0; i < 1000; i++) {
                f = new File(tmpdir, "javacpp" + System.nanoTime());
                if (f.mkdir()) {
                    tempDir = f;
                    tempDir.deleteOnExit();
                    break;
                }
            }
        }
        return tempDir;
    }

    /** Returns {@code System.getProperty("org.bytedeco.javacpp.loadlibraries")}.
     *  Flag set by the {@link Builder} to tell us not to try to load anything. */
    public static boolean isLoadLibraries() {
        String s = System.getProperty("org.bytedeco.javacpp.loadlibraries", "true").toLowerCase();
        return s.equals("true") || s.equals("t") || s.equals("");
    }

    /** Returns {@code load(getCallerClass(2), loadProperties(), false)}. */
    public static String load() {
        return load(getCallerClass(2), loadProperties(), false);
    }
    /**
     * Loads native libraries associated with the {@link Class} of the caller.
     *
     * @param pathsFirst search the paths first before bundled resources
     * @return {@code load(getCallerClass(2), loadProperties(), pathsFirst) }
     * @see #getCallerClass(int)
     * @see #load(Class, Properties, boolean)
     */
    public static String load(boolean pathsFirst) {
        Class cls = getCallerClass(2);
        return load(cls, loadProperties(), pathsFirst);
    }
    /** Returns {@code load(cls, loadProperties(), false)}. */
    public static String load(Class cls) {
        return load(cls, loadProperties(), false);
    }
    /**
     * Loads native libraries associated with the given {@link Class}.
     *
     * @param cls the Class to get native library information from
     * @param properties the platform Properties to inherit
     * @param pathsFirst search the paths first before bundled resources
     * @return the full path to the main file loaded, or the library name if unknown
     *         (but {@code if (!isLoadLibraries() || cls == null) { return null; }})
     * @throws NoClassDefFoundError on Class initialization failure
     * @throws UnsatisfiedLinkError on native library loading failure
     * @see #findLibrary(Class, ClassProperties, String, boolean)
     * @see #loadLibrary(URL[], String)
     */
    public static String load(Class cls, Properties properties, boolean pathsFirst) {
        if (!isLoadLibraries() || cls == null) {
            return null;
        }

        // Find the top enclosing class, to match the library filename
        cls = getEnclosingClass(cls);
        ClassProperties p = loadProperties(cls, properties, true);

        // Force initialization of all the target classes in case they need it
        List targets = p.get("target");
        if (targets.isEmpty()) {
            if (p.getInheritedClasses() != null) {
                for (Class c : p.getInheritedClasses()) {
                    targets.add(c.getName());
                }
            }
            targets.add(cls.getName());
        }
        for (String s : targets) {
            try {
                if (logger.isDebugEnabled()) {
                    logger.debug("Loading class " + s);
                }
                Class.forName(s, true, cls.getClassLoader());
            } catch (ClassNotFoundException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Failed to load class " + s + ": " + ex);
                }
                Error e = new NoClassDefFoundError(ex.toString());
                e.initCause(ex);
                throw e;
            }
        }

        // Preload native libraries desired by our class
        List preloads = new ArrayList();
        preloads.addAll(p.get("platform.preload"));
        preloads.addAll(p.get("platform.link"));
        UnsatisfiedLinkError preloadError = null;
        for (String preload : preloads) {
            try {
                URL[] urls = findLibrary(cls, p, preload, pathsFirst);
                loadLibrary(urls, preload);
            } catch (UnsatisfiedLinkError e) {
                preloadError = e;
            }
        }

        try {
            String library = p.getProperty("platform.library");
            URL[] urls = findLibrary(cls, p, library, pathsFirst);
            return loadLibrary(urls, library);
        } catch (UnsatisfiedLinkError e) {
            if (preloadError != null && e.getCause() == null) {
                e.initCause(preloadError);
            }
            throw e;
        }
    }

    /**
     * Finds from where the library may be extracted and loaded among the {@link Class}
     * resources. But in case that fails, and depending on the value of {@code pathsFirst},
     * either as a fallback or in priority over bundled resources, also searches the paths
     * found in the "platform.preloadpath" and "platform.linkpath" class properties as well as
     * the "java.library.path" system property, in that order.
     *
     * @param cls the Class whose package name and {@link ClassLoader} are used to extract from resources
     * @param properties contains the directories to scan for if we fail to extract the library from resources
     * @param libnameversion the name of the library + "@" + optional version tag
     *                       + "#" + a second optional name used at extraction
     * @param pathsFirst search the paths first before bundled resources
     * @return URLs that point to potential locations of the library
     */
    public static URL[] findLibrary(Class cls, ClassProperties properties, String libnameversion, boolean pathsFirst) {
        String[] split = libnameversion.split("#");
        String libnameversion2 = libnameversion = split[0];
        if (split.length > 1) {
            libnameversion2 = split[1];
        }
        String[] s = libnameversion.split("@");
        String[] s2 = libnameversion2.split("@");
        String libname = s[0];
        String libname2 = s2[0];
        String version = s.length > 1 ? s[s.length-1] : "";
        String version2 = s2.length > 1 ? s2[s2.length-1] : "";

        // If we do not already have the native library file ...
        String filename = loadedLibraries.get(libnameversion);
        if (filename != null) {
            try {
                return new URL[] { new File(filename).toURI().toURL() };
            } catch (IOException ex) {
                return new URL[] { };
            }
        }

        String subdir = properties.getProperty("platform") + '/';
        String prefix = properties.getProperty("platform.library.prefix", "");
        String suffix = properties.getProperty("platform.library.suffix", "");
        String[] styles = {
            prefix + libname + suffix + version, // Linux style
            prefix + libname + version + suffix, // Mac OS X style
            prefix + libname + suffix            // without version
        };
        String[] styles2 = {
            prefix + libname2 + suffix + version2, // Linux style
            prefix + libname2 + version2 + suffix, // Mac OS X style
            prefix + libname2 + suffix             // without version
        };

        String[] suffixes = properties.get("platform.library.suffix").toArray(new String[0]);
        if (suffixes.length > 1) {
            styles = new String[3 * suffixes.length];
            styles2 = new String[3 * suffixes.length];
            for (int i = 0; i < suffixes.length; i++) {
                styles[3 * i    ] = prefix + libname + suffixes[i] + version; // Linux style
                styles[3 * i + 1] = prefix + libname + version + suffixes[i]; // Mac OS X style
                styles[3 * i + 2] = prefix + libname + suffixes[i];           // without version
                styles2[3 * i    ] = prefix + libname2 + suffixes[i] + version2; // Linux style
                styles2[3 * i + 1] = prefix + libname2 + version2 + suffixes[i]; // Mac OS X style
                styles2[3 * i + 2] = prefix + libname2 + suffixes[i];            // without version
            }
        }

        List paths = new ArrayList();
        paths.addAll(properties.get("platform.preloadpath"));
        paths.addAll(properties.get("platform.linkpath"));
        String libpath = System.getProperty("java.library.path", "");
        if (libpath.length() > 0) {
            paths.addAll(Arrays.asList(libpath.split(File.pathSeparator)));
        }
        ArrayList urls = new ArrayList(styles.length * (1 + paths.size()));
        for (int i = 0; cls != null && i < styles.length; i++) {
            // ... then find it from in our resources ...
            URL u = cls.getResource(subdir + styles[i]);
            if (u != null) {
                if (!styles[i].equals(styles2[i])) {
                    try {
                        u = new URL(u + "#" + styles2[i]);
                    } catch (MalformedURLException e) {
                        throw new RuntimeException(e);
                    }
                }
                urls.add(u);
            }
        }
        // ... and in case of bad resources search the paths last, or first on user request.
        int k = pathsFirst ? 0 : urls.size();
        for (int i = 0; paths.size() > 0 && i < styles.length; i++) {
            for (String path : paths) {
                File file = new File(path, styles[i]);
                if (file.exists()) {
                    try {
                        URL u = file.toURI().toURL();
                        if (!styles[i].equals(styles2[i])) {
                            u = new URL(u + "#" + styles2[i]);
                        }
                        urls.add(k++, u);
                    } catch (MalformedURLException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            }
        }
        return urls.toArray(new URL[urls.size()]);
    }

    /**
     * Tries to load the library from the URLs in order, extracting resources as necessary.
     * Finally, if all fails, falls back on {@link System#loadLibrary(String)}.
     *
     * @param urls the URLs to try loading the library from
     * @param libnameversion the name of the library + "@" + optional version tag
     *                       + "#" + a second optional name used at extraction
     * @return the full path of the file loaded, or the library name if unknown
     *         (but {@code if (!isLoadLibraries) { return null; }})
     * @throws UnsatisfiedLinkError on failure
     */
    public static String loadLibrary(URL[] urls, String libnameversion) {
        if (!isLoadLibraries()) {
            return null;
        }

        // If we do not already have the native library file ...
        String filename = loadedLibraries.get(libnameversion);
        if (filename != null) {
            return filename;
        }

        File tempFile = null;
        UnsatisfiedLinkError loadError = null;
        try {
            for (URL url : urls) {
                File file;
                try {
                    // ... and if the URL is not already a file without fragments, etc ...
                    file = new File(url.toURI());
                } catch (Exception e) {
                    String name = new File(url.getPath()).getName();
                    if (url.getRef() != null) {
                        // ... get the URL fragment to let users rename library files ...
                        name = url.getRef();
                    }
                    // ... then check if it has not already been extracted, and if not ...
                    file = new File(getCacheDir() != null ? getCacheDir() : getTempDir(), name);
                    if (!file.exists()) {
                        if (tempFile != null && tempFile.exists()) {
                            tempFile.deleteOnExit();
                        }
                        // ... then extract it from our resources ...
                        if (logger.isDebugEnabled()) {
                            logger.debug("Extracting " + url);
                        }
                        extractResource(url, file, null, null);
                        if (getCacheDir() == null) {
                            tempFile = file;
                        }
                    } else while (System.currentTimeMillis() - file.lastModified() < 1000) {
                        // ... else wait until the file is at least 1 second old ...
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException ex) {
                            // ... reset interrupt to be nice ...
                            Thread.currentThread().interrupt();
                        }
                    }
                }
                if (file != null && file.exists()) {
                    filename = file.getAbsolutePath();
                    try {
                        // ... and load it!
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loading " + filename);
                        }
                        loadedLibraries.put(libnameversion, filename);
                        System.load(filename);
                        return filename;
                    } catch (UnsatisfiedLinkError e) {
                        loadError = e;
                        loadedLibraries.remove(libnameversion);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Failed to load " + filename + ": " + e);
                        }
                    }
                }
            }
            // ... or as last resort, try to load it via the system.
            String libname = libnameversion.split("#")[0].split("@")[0];
            if (logger.isDebugEnabled()) {
                logger.debug("Loading library " + libname);
            }
            loadedLibraries.put(libnameversion, libname);
            System.loadLibrary(libname);
            return libname;
        } catch (UnsatisfiedLinkError e) {
            loadedLibraries.remove(libnameversion);
            if (loadError != null && e.getCause() == null) {
                e.initCause(loadError);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to load for " + libnameversion + ": " + e);
            }
            throw e;
        } catch (IOException ex) {
            loadedLibraries.remove(libnameversion);
            if (loadError != null && ex.getCause() == null) {
                ex.initCause(loadError);
            }
            Error e = new UnsatisfiedLinkError(ex.toString());
            e.initCause(ex);
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to extract for " + libnameversion + ": " + e);
            }
            throw e;
        } finally {
            if (tempFile != null && tempFile.exists()) {
                tempFile.deleteOnExit();
            }
            // But under Windows, it won't get deleted!
        }
    }

    // So, let's use a shutdown hook...
    static {
        if (getPlatform().startsWith("windows")) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override public void run() {
                    if (tempDir == null) {
                        return;
                    }
                    try {
                        // ... to launch a separate process ...
                        List command = new ArrayList();
                        command.add(System.getProperty("java.home") + "/bin/java");
                        command.add("-classpath");
                        command.add((new File(Loader.class.getProtectionDomain().getCodeSource().getLocation().toURI())).toString());
                        command.add(Loader.class.getName());
                        command.add(tempDir.getAbsolutePath());
                        new ProcessBuilder(command).start();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } catch (URISyntaxException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    // ... that makes sure to delete all our files.
    public static void main(String[] args) throws InterruptedException {
        File tmpdir = new File(System.getProperty("java.io.tmpdir"));
        File tempDir = new File(args[0]);
        if (!tmpdir.equals(tempDir.getParentFile()) ||
                !tempDir.getName().startsWith("javacpp")) {
            // Someone is trying to break us ... ?
            return;
        }
        for (File file : tempDir.listFiles()) {
            while (file.exists() && !file.delete()) {
                Thread.sleep(100);
            }
        }
        tempDir.delete();
    }


    /**
     * Contains {@code offsetof()} and {@code sizeof()} values of native types
     * of {@code struct}, {@code class}, and {@code union}. A {@link WeakHashMap}
     * is used to prevent the Loader from hanging onto Class objects the user may
     * be trying to unload.
     */
    static WeakHashMap,HashMap> memberOffsets =
            new WeakHashMap,HashMap>();

    /**
     * Called by native libraries to put {@code offsetof()} and {@code sizeof()} values in {@link #memberOffsets}.
     * Tries to load the Class object for typeName using the {@link ClassLoader} of the Loader.
     *
     * @param typeName the name of the peer Class acting as interface to the native type
     * @param member the name of the native member variable (can be null to retrieve the Class object only)
     * @param offset the value of {@code offsetof()} (or {@code sizeof()} when {@code member.equals("sizeof")})
     * @return {@code Class.forName(typeName, false)}
     * @throws ClassNotFoundException on Class initialization failure
     */
    static Class putMemberOffset(String typeName, String member, int offset) throws ClassNotFoundException {
        Class c = Class.forName(typeName.replace('/', '.'), false, Loader.class.getClassLoader());
        if (member != null) {
            putMemberOffset(c.asSubclass(Pointer.class), member, offset);
        }
        return c;
    }
    /**
     * Called by native libraries to put {@code offsetof()} and {@code sizeof()} values in {@link #memberOffsets}.
     *
     * @param type the peer Class acting as interface to the native type
     * @param member the name of the native member variable
     * @param offset the value of {@code offsetof()} (or {@code sizeof()} when {@code member.equals("sizeof")})
     */
    static synchronized void putMemberOffset(Class type, String member, int offset) {
        HashMap offsets = memberOffsets.get(type);
        if (offsets == null) {
            memberOffsets.put(type, offsets = new HashMap());
        }
        offsets.put(member, offset);
    }

    /**
     * Gets {@code offsetof()} values from {@link #memberOffsets} filled by native libraries.
     *
     * @param type the peer Class acting as interface to the native type
     * @param member the name of the native member variable
     * @return {@code memberOffsets.get(type).get(member)}
     */
    public static int offsetof(Class type, String member) {
        // Should we synchronize that?
        return memberOffsets.get(type).get(member);
    }

    /**
     * Gets {@code sizeof()} values from {@link #memberOffsets} filled by native libraries.
     *
     * @param type the peer Class acting as interface to the native type
     * @return {@code memberOffsets.get(type).get("sizeof")}
     */
    public static int sizeof(Class type) {
        // Should we synchronize that?
        return memberOffsets.get(type).get("sizeof");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy