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:  http://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2016 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 java.io.File;
033import java.io.FileNotFoundException;
034import java.io.IOException;
035import java.io.InputStream;
036import java.nio.file.Files;
037import java.nio.file.Path;
038import java.nio.file.Paths;
039import java.nio.file.StandardCopyOption;
040import java.util.Set;
041import java.util.TreeSet;
042import java.util.logging.ConsoleHandler;
043import java.util.logging.FileHandler;
044import java.util.logging.Level;
045import java.util.logging.Logger;
046
047public class NativeLibraryLoader {
048
049        private static final Set<String> loadedLibraries = new TreeSet<String>();
050        private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
051        private static boolean initialized;
052
053        // private constructor
054        private NativeLibraryLoader() {
055                // forbid object construction
056        }
057
058        public static synchronized void load(String fileName) {
059                // check for debug property; if found enable all logging levels
060                if (!initialized) {
061                        initialized = true;
062                        if (System.getProperty("pi4j.debug") != null) {
063                                logger.setLevel(Level.ALL);
064                                try {
065                                        // create an appending file handler
066                                        FileHandler fileHandler = new FileHandler("pi4j.log");
067                                        fileHandler.setLevel(Level.ALL);
068                                        ConsoleHandler consoleHandler = new ConsoleHandler();
069                                        consoleHandler.setLevel(Level.ALL);
070
071                                        // add to the desired loggers
072                                        logger.addHandler(fileHandler);
073                                        logger.addHandler(consoleHandler);
074                                } catch (IOException e) {
075                                        System.err.println("Unable to setup logging to debug. No logging will be done. Error: ");
076                                        e.printStackTrace();
077                                }
078                        }
079                }
080
081                // first, make sure that this library has not already been previously loaded
082                if (loadedLibraries.contains(fileName)) {
083                        logger.fine("Library [" + fileName + "] has already been loaded; no need to load again.");
084                        return;
085                }
086
087                loadedLibraries.add(fileName);
088
089        //
090        // path = /lib/{platform}/{linking:static|dynamic}/{filename}
091        //
092        String platform = System.getProperty("pi4j.platform", "raspberrypi");
093        String linking = System.getProperty("pi4j.linking", "static");
094                String path = "/lib/" + platform + "/" + linking + "/" + fileName;
095                logger.fine("Attempting to load [" + fileName + "] using path: [" + path + "]");
096                try {
097                        loadLibraryFromClasspath(path);
098                        logger.fine("Library [" + fileName + "] loaded successfully using embedded resource file: [" + path + "]");
099                } catch (Exception | UnsatisfiedLinkError e) {
100                        logger.log(Level.SEVERE, "Unable to load [" + fileName + "] using path: [" + path + "]", e);
101                        // either way, we did what we could, no need to remove now the library from the loaded libraries since we run inside one VM and nothing could possibly change, so there is no point in
102                        // trying out this logic again
103                }
104        }
105
106        /**
107         * Loads library from classpath
108         *
109         * The file from classpath is copied into system temporary directory and then loaded. The temporary file is deleted after exiting. Method uses String as filename because the pathname is
110         * "abstract", not system-dependent.
111         *
112         * @param path
113         *            The file path in classpath as an absolute path, e.g. /package/File.ext (could be inside jar)
114         * @throws IOException
115         *             If temporary file creation or read/write operation fails
116         * @throws IllegalArgumentException
117         *             If source file (param path) does not exist
118         * @throws IllegalArgumentException
119         *             If the path is not absolute or if the filename is shorter than three characters (restriction of {@see File#createTempFile(java.lang.String, java.lang.String)}).
120         */
121        public static void loadLibraryFromClasspath(String path) throws IOException {
122                Path inputPath = Paths.get(path);
123
124                if (!inputPath.isAbsolute()) {
125                        throw new IllegalArgumentException("The path has to be absolute, but found: " + inputPath);
126                }
127
128                String fileNameFull = inputPath.getFileName().toString();
129                int dotIndex = fileNameFull.indexOf('.');
130                if (dotIndex < 0 || dotIndex >= fileNameFull.length() - 1) {
131                        throw new IllegalArgumentException("The path has to end with a file name and extension, but found: " + fileNameFull);
132                }
133
134                String fileName = fileNameFull.substring(0, dotIndex);
135                String extension = fileNameFull.substring(dotIndex);
136
137                Path target = Files.createTempFile(fileName, extension);
138                File targetFile = target.toFile();
139                targetFile.deleteOnExit();
140
141                try (InputStream source = NativeLibraryLoader.class.getResourceAsStream(inputPath.toString())) {
142                        if (source == null) {
143                                throw new FileNotFoundException("File " + inputPath + " was not found in classpath.");
144                        }
145                        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
146                }
147                // Finally, load the library
148                System.load(target.toAbsolutePath().toString());
149        }
150}