001package com.pi4j.util;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  NativeLibraryLoader.java
009 *
010 * This file is part of the Pi4J project. More information about
011 * this project can be found here:  https://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2019 Pi4J
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU Lesser General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 *
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Lesser Public License for more details.
025 *
026 * You should have received a copy of the GNU General Lesser Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
029 * #L%
030 */
031
032import com.pi4j.platform.Platform;
033
034import java.io.File;
035import java.io.FileNotFoundException;
036import java.io.IOException;
037import java.io.InputStream;
038import java.nio.file.Files;
039import java.nio.file.Path;
040import java.nio.file.Paths;
041import java.nio.file.StandardCopyOption;
042import java.util.Set;
043import java.util.TreeSet;
044import java.util.logging.ConsoleHandler;
045import java.util.logging.FileHandler;
046import java.util.logging.Level;
047import java.util.logging.Logger;
048
049public class NativeLibraryLoader {
050
051        private static final Set<String> loadedLibraries = new TreeSet<>();
052        private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
053        private static boolean initialized;
054
055        // private constructor
056        private NativeLibraryLoader() {
057                // forbid object construction
058        }
059
060        public static synchronized void load(String fileName) {
061                // check for debug property; if found enable all logging levels
062                if (!initialized) {
063                        initialized = true;
064                        if (System.getProperty("pi4j.debug") != null) {
065                                logger.setLevel(Level.ALL);
066                                try {
067                                        // create an appending file handler
068                                        FileHandler fileHandler = new FileHandler("pi4j.log");
069                                        fileHandler.setLevel(Level.ALL);
070                                        ConsoleHandler consoleHandler = new ConsoleHandler();
071                                        consoleHandler.setLevel(Level.ALL);
072
073                                        // add to the desired loggers
074                                        logger.addHandler(fileHandler);
075                                        logger.addHandler(consoleHandler);
076                                } catch (IOException e) {
077                                        System.err.println("Unable to setup logging to debug. No logging will be done. Error: ");
078                                        e.printStackTrace();
079                                }
080                        }
081                }
082
083                // first, make sure that this library has not already been previously loaded
084                if (loadedLibraries.contains(fileName)) {
085                        logger.fine("Library [" + fileName + "] has already been loaded; no need to load again.");
086                        return;
087                }
088
089                loadedLibraries.add(fileName);
090
091        //
092        // path = /lib/{platform}/{linking:static|dynamic}/{filename}
093        //
094        String platform = System.getProperty("pi4j.platform", Platform.RASPBERRYPI.getId());
095
096        // NOTE: As of 2018-04-23 in Pi4J 1.2 SNAPSHOT builds, Pi4J no longer includes
097        //       a statically linked wiringPi lib for the Raspberry Pi platform.  The
098        //       default linking for the Raspberry Pi platform should always be "dynamic"
099        String linking = System.getProperty("pi4j.linking",
100                platform.equalsIgnoreCase(Platform.RASPBERRYPI.getId()) ? "dynamic" : "static");
101
102                String path = "/lib/" + platform + "/" + linking + "/" + fileName;
103                logger.fine("Attempting to load [" + fileName + "] using path: [" + path + "]");
104                try {
105                        loadLibraryFromClasspath(path);
106                        logger.fine("Library [" + fileName + "] loaded successfully using embedded resource file: [" + path + "]");
107                } catch (Exception | UnsatisfiedLinkError e) {
108                        logger.log(Level.SEVERE, "Unable to load [" + fileName + "] using path: [" + path + "]", e);
109                        // either way, we did what we could, no need to remove now the library from the loaded libraries
110            // since we run inside one VM and nothing could possibly change, so there is no point in
111                        // trying out this logic again
112                }
113        }
114
115        /**
116         * Loads library from classpath
117         *
118         * The file from classpath is copied into system temporary directory and then loaded. The temporary file is
119     * deleted after exiting. Method uses String as filename because the pathname is
120         * "abstract", not system-dependent.
121         *
122         * @param path
123         *            The file path in classpath as an absolute path, e.g. /package/File.ext (could be inside jar)
124         * @throws IOException
125         *             If temporary file creation or read/write operation fails
126         * @throws IllegalArgumentException
127         *             If source file (param path) does not exist
128         * @throws IllegalArgumentException
129         *             If the path is not absolute or if the filename is shorter than three characters (restriction
130     *             of {@see File#createTempFile(java.lang.String, java.lang.String)}).
131         */
132        public static void loadLibraryFromClasspath(String path) throws IOException {
133                Path inputPath = Paths.get(path);
134
135                if (!inputPath.isAbsolute()) {
136                        throw new IllegalArgumentException("The path has to be absolute, but found: " + inputPath);
137                }
138
139                String fileNameFull = inputPath.getFileName().toString();
140                int dotIndex = fileNameFull.indexOf('.');
141                if (dotIndex < 0 || dotIndex >= fileNameFull.length() - 1) {
142                        throw new IllegalArgumentException("The path has to end with a file name and extension, but found: " + fileNameFull);
143                }
144
145                String fileName = fileNameFull.substring(0, dotIndex);
146                String extension = fileNameFull.substring(dotIndex);
147
148                Path target = Files.createTempFile(fileName, extension);
149                File targetFile = target.toFile();
150                targetFile.deleteOnExit();
151
152                try (InputStream source = NativeLibraryLoader.class.getResourceAsStream(inputPath.toString())) {
153                        if (source == null) {
154                                throw new FileNotFoundException("File " + inputPath + " was not found in classpath.");
155                        }
156                        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
157                }
158                // Finally, load the library
159                System.load(target.toAbsolutePath().toString());
160        }
161}