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}