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 - 2013 Pi4J
015 * %%
016 * Licensed under the Apache License, Version 2.0 (the "License"); you
017 * may not use this file except in compliance with the License. You may obtain a copy of the License
018 * at
019 * 
020 * http://www.apache.org/licenses/LICENSE-2.0
021 * 
022 * Unless required by applicable law or agreed to in writing, software distributed under the License
023 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
024 * or implied. See the License for the specific language governing permissions and limitations under
025 * the License.
026 * #L%
027 */
028
029import java.io.File;
030import java.io.FileNotFoundException;
031import java.io.FileOutputStream;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.OutputStream;
035import java.net.URL;
036import java.util.ArrayList;
037import java.util.Collections;
038import java.util.List;
039import java.util.logging.ConsoleHandler;
040import java.util.logging.FileHandler;
041import java.util.logging.Level;
042import java.util.logging.Logger;
043
044import com.pi4j.system.SystemInfo;
045
046public class NativeLibraryLoader {
047
048    private static List<String> loadedLibraries = null;
049    private static Logger logger = Logger.getLogger("com.pi4j.util.NativeLibraryLoader");
050    private static FileHandler fileHandler;
051    private static ConsoleHandler consoleHandler;
052    private static boolean initialized  = false;
053
054    // private constructor 
055    private NativeLibraryLoader() {
056        // forbid object construction 
057    }
058    
059    public static synchronized void load(String libraryName) {
060        load(libraryName, null);
061    }
062
063    public static synchronized void load(String libraryName, String fileName) {
064        // check for debug property; if found enabled all logging levels
065        if (initialized == false) {
066            initialized = true;
067            String debug = System.getProperty("pi4j.debug");
068            if (debug != null) {
069                logger.setLevel(Level.ALL);
070                try {
071                    // create an appending file handler
072                    fileHandler = new FileHandler("pi4j.log");
073                    fileHandler.setLevel(Level.ALL);
074                    consoleHandler = new ConsoleHandler();
075                    consoleHandler.setLevel(Level.ALL);
076    
077                    // add to the desired loggers
078                    logger.addHandler(fileHandler);
079                    logger.addHandler(consoleHandler);
080                } 
081                catch (IOException e) {} 
082            }
083        }
084        
085        // debug
086        if (fileName == null || fileName.length() == 0) {
087            logger.fine("Load library [" + libraryName + "] (no alternate embedded file provided)");
088        } else {
089            logger.fine("Load library [" + libraryName + "] (alternate embedded file: " + fileName + ")");
090        }
091        // create instance if null
092        if (loadedLibraries == null) {
093            loadedLibraries = Collections.synchronizedList(new ArrayList<String>());
094        }
095        // first, make sure that this library has not already been previously loaded
096        if (loadedLibraries.contains(libraryName)) {
097            // debug
098            logger.fine("Library [" + libraryName + "] has already been loaded; no need to load again.");            
099        } else {
100            // ---------------------------------------------
101            // ATTEMPT LOAD FROM SYSTEM LIBS
102            // ---------------------------------------------
103            
104            // assume library loaded successfully, add to tracking collection
105            loadedLibraries.add(libraryName);
106            
107            try {
108                // debug
109                logger.fine("Attempting to load library [" + libraryName + "] using the System.loadLibrary(name) method.");            
110                
111                // attempt to load the native library from the system classpath loader
112                System.loadLibrary(libraryName);
113                
114                // debug
115                logger.fine("Library [" + libraryName + "] loaded successfully using the System.loadLibrary(name) method.");                            
116            } catch (UnsatisfiedLinkError e) {
117                // if a filename was not provided, then throw exception
118                if (fileName == null) {
119                    // debug
120                    logger.severe("Library [" + libraryName + "] could not be located using the System.loadLibrary(name) method and no embedded file path was provided as an auxillary lookup.");                            
121
122                    // library load failed, remove from tracking collection
123                    loadedLibraries.remove(libraryName);
124                    throw e;
125                }
126
127                // debug
128                logger.fine("Library [" + libraryName + "] could not be located using the System.loadLibrary(name) method; attempting to resolve the library using embedded resources in the JAR file.");                            
129
130                
131                // ---------------------------------------------
132                // ATTEMPT LOAD BASED ON EDUCATED GUESS OF ABI
133                // ---------------------------------------------
134                
135                // check for system properties
136                boolean armhf_force = false;
137                if(System.getProperty("pi4j.armhf") != null)
138                    armhf_force = true; 
139                boolean armel_force = false;
140                if(System.getProperty("pi4j.armel") != null)
141                    armel_force = true; 
142                        
143                URL resourceUrl;
144                
145                // first attempt to determine if we are running on a hard float (armhf) based system  
146                if(armhf_force) {
147                    // attempt to get the native library from the JAR file in the 'lib/hard-float' directory
148                    resourceUrl = NativeLibraryLoader.class.getResource("/lib/hard-float/" + fileName);
149                } else if(armel_force){
150                    // attempt to get the native library from the JAR file in the 'lib/soft-float' directory
151                    resourceUrl = NativeLibraryLoader.class.getResource("/lib/soft-float/" + fileName);
152                } else {                
153                    logger.fine("AUTO-DETECTED HARD-FLOAT ABI : " + SystemInfo.isHardFloatAbi());
154                    if(SystemInfo.isHardFloatAbi()) {
155                        // attempt to get the native library from the JAR file in the 'lib/hard-float' directory
156                        resourceUrl = NativeLibraryLoader.class.getResource("/lib/hard-float/" + fileName);
157                    } else {
158                        // attempt to get the native library from the JAR file in the 'lib/soft-float' directory
159                        resourceUrl = NativeLibraryLoader.class.getResource("/lib/soft-float/" + fileName);
160                    }
161                }
162                
163                try {               
164                    // load library file from embedded resource
165                    loadLibraryFromResource(resourceUrl, libraryName, fileName);
166                    
167                    // debug
168                    logger.fine("Library [" + libraryName + "] loaded successfully using embedded resource file: [" + resourceUrl.toString() + "]");
169                } 
170                
171                catch(Exception|UnsatisfiedLinkError ex) {
172                    // ---------------------------------------------
173                    // ATTEMPT LOAD BASED USING HARD-FLOAT (armhf)
174                    // ---------------------------------------------
175                    
176                    // attempt to get the native library from the JAR file in the 'lib/hard-float' directory
177                    URL resourceUrlHardFloat = NativeLibraryLoader.class.getResource("/lib/hard-float/" + fileName);
178                    
179                    try {
180                        // load library file from embedded resource
181                        loadLibraryFromResource(resourceUrlHardFloat, libraryName, fileName);
182                        
183                        // debug
184                        logger.info("Library [" + libraryName + "] loaded successfully using embedded resource file: [" + resourceUrlHardFloat.toString() + "] (ARMHF)");                                                        
185                    } catch(UnsatisfiedLinkError ule_hard_float) {
186                        // debug
187                        logger.fine("Failed to load library [" + libraryName + "] using the System.load(file) method using embedded resource file: [" + resourceUrlHardFloat.toString() + "]");            
188
189                        // ---------------------------------------------
190                        // ATTEMPT LOAD BASED USING SOFT-FLOAT (armel)
191                        // ---------------------------------------------
192                        
193                        // attempt to get the native library from the JAR file in the 'lib/soft-float' directory
194                        URL resourceUrlSoftFloat = NativeLibraryLoader.class.getResource("/lib/soft-float/" + fileName);
195                        
196                        try {
197                            // load library file from embedded resource
198                            loadLibraryFromResource(resourceUrlSoftFloat, libraryName, fileName);
199                            
200                            // debug
201                            logger.info("Library [" + libraryName + "] loaded successfully using embedded resource file: [" + resourceUrlSoftFloat.toString() + "] (ARMEL)");                                                        
202                        } catch (Throwable err) {
203                            // debug
204                            logger.severe("Failed to load library [" + libraryName + "] using the System.load(file) method using embedded resource file: [" + resourceUrlSoftFloat.toString() + "]");            
205                            logger.throwing(logger.getName(), "load", err);
206    
207                            // library load failed, remove from tracking collection
208                            loadedLibraries.remove(libraryName);
209    
210                            logger.severe("ERROR:  The native library ["
211                                        + libraryName
212                                        + " : "
213                                        + fileName
214                                        + "] could not be found in the JVM library path nor could it be loaded from the embedded JAR resource file; you may need to explicitly define the library path '-Djava.library.path' where this native library can be found.");
215                        }
216                    } catch (Exception ex_hard_float) {
217                        // debug
218                        logger.severe("Failed to load library [" + libraryName + "] using the System.load(file) method using embedded resource file: [" + resourceUrlHardFloat.toString() + "]");            
219                        logger.throwing(logger.getName(), "load", ex_hard_float);
220                        
221                        // library load failed, remove from tracking collection
222                        loadedLibraries.remove(libraryName);
223    
224                        logger.severe("ERROR:  The native library ["
225                                    + libraryName
226                                    + " : "
227                                    + fileName
228                                    + "] could not be found in the JVM library path nor could it be loaded from the embedded JAR resource file; you may need to explicitly define the library path '-Djava.library.path' where this native library can be found.");
229                    }
230                }
231            }
232        }
233    }
234
235    private static void loadLibraryFromResource(URL resourceUrl, String libraryName, String fileName) throws UnsatisfiedLinkError, Exception {
236        // create a 1Kb read buffer
237        byte[] buffer = new byte[1024];
238        int byteCount = 0;
239        
240        // debug
241        logger.fine("Attempting to load library [" + libraryName + "] using the System.load(file) method using embedded resource file: [" + resourceUrl.toString() + "]");            
242        
243        // open the resource file stream
244        InputStream inputStream = resourceUrl.openStream();
245
246        // get the system temporary directory path
247        File tempDirectory = new File(System.getProperty("java.io.tmpdir"));
248        
249        // check to see if the temporary path exists
250        if (!tempDirectory.exists()) {
251            // debug
252            logger.warning("The Java system temporary path [" + tempDirectory.getAbsolutePath() + "] does not exist.");            
253            
254            // instead of the system defined temporary path, let just use the application path
255            tempDirectory = new File("");
256        }
257        
258        // create a temporary file to copy the native library content to
259        File tempFile = new File(tempDirectory.getAbsolutePath() + "/" + fileName);
260        
261        // make sure that this temporary file does not exist; if it does then delete it
262        if (tempFile.exists()) {
263            // debug
264            logger.warning("The temporary file already exists [" + tempFile.getAbsolutePath() + "]; attempting to delete it now.");            
265            
266            // delete file immediately 
267            tempFile.delete();
268        }
269        
270        // create output stream object
271        OutputStream outputStream = null;
272        
273        try {
274            // create the new file
275            outputStream = new FileOutputStream(tempFile);
276        } catch(FileNotFoundException fnfe) {
277            // error
278            logger.severe("The temporary file [" + tempFile.getAbsolutePath() + "] cannot be created; it is a directory, not a file.");            
279            throw(fnfe);
280        } catch(SecurityException se) {
281            // error
282            logger.severe("The temporary file [" + tempFile.getAbsolutePath() + "] cannot be created; a security exception was detected. " + se.getMessage());            
283            throw(se);
284        }
285        
286        try {
287            // copy the library file content
288            while ((byteCount = inputStream.read(buffer)) >= 0) {
289                outputStream.write(buffer, 0, byteCount);
290            }
291            
292            // flush all write data from stream 
293            outputStream.flush();
294            
295            // close the output stream
296            outputStream.close();                            
297        } catch(IOException ioe) {
298            // error
299            logger.severe("The temporary file [" + tempFile.getAbsolutePath() + "] could not be written to; an IO exception was detected. " + ioe.getMessage());            
300            throw(ioe);                            
301        }
302
303        // close the input stream
304        inputStream.close();
305        
306        try {
307            // attempt to load the new temporary library file
308            System.load(tempFile.getAbsolutePath());
309            
310            try {
311                // ensure that this temporary file is removed when the program exits
312                tempFile.deleteOnExit();
313            } catch(SecurityException dse) {
314                // warning
315                logger.warning("The temporary file [" + tempFile.getAbsolutePath() + "] cannot be flagged for removal on program termination; a security exception was detected. " + dse.getMessage());            
316            }
317        } catch(UnsatisfiedLinkError ule) {
318            // if unable to load the library and the temporary file
319            // exists; then delete the temporary file immediately
320            if(tempFile.exists())
321                tempFile.delete();
322            
323            throw(ule);
324        } catch(Exception ex) {
325            // if unable to load the library and the temporary file
326            // exists; then delete the temporary file immediately
327            if (tempFile.exists()) {
328                tempFile.delete();
329            }
330            
331            throw(ex);
332        }
333    }
334}
335