001/*
002 * BridJ - Dynamic and blazing-fast native interop for Java.
003 * http://bridj.googlecode.com/
004 *
005 * Copyright (c) 2010-2013, Olivier Chafik (http://ochafik.com/)
006 * All rights reserved.
007 *
008 * Redistribution and use in source and binary forms, with or without
009 * modification, are permitted provided that the following conditions are met:
010 * 
011 *     * Redistributions of source code must retain the above copyright
012 *       notice, this list of conditions and the following disclaimer.
013 *     * Redistributions in binary form must reproduce the above copyright
014 *       notice, this list of conditions and the following disclaimer in the
015 *       documentation and/or other materials provided with the distribution.
016 *     * Neither the name of Olivier Chafik nor the
017 *       names of its contributors may be used to endorse or promote products
018 *       derived from this software without specific prior written permission.
019 * 
020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY
021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031package org.bridj;
032
033import org.bridj.util.ProcessUtils;
034
035import java.security.AccessController;
036import java.security.PrivilegedAction;
037import java.util.Set;
038import java.util.HashSet;
039import java.util.regex.Pattern;
040import java.io.*;
041import java.net.URL;
042
043import java.util.List;
044import java.util.Collections;
045import java.util.Collection;
046import java.util.ArrayList;
047import java.net.MalformedURLException;
048import java.net.URLClassLoader;
049import java.util.Arrays;
050import java.util.Iterator;
051import java.util.LinkedHashSet;
052import java.util.LinkedList;
053import org.bridj.util.StringUtils;
054
055/**
056 * Information about the execution platform (OS, architecture, native sizes...)
057 * and platform-specific actions.
058 * <ul>
059 * <li>To know if the JVM platform is 32 bits or 64 bits, use
060 * {@link Platform#is64Bits()}
061 * </li><li>To know if the OS is an Unix-like system, use
062 * {@link Platform#isUnix()}
063 * </li><li>To open files and URLs in a platform-specific way, use
064 * {@link Platform#open(File)}, {@link Platform#open(URL)}, {@link Platform#show(File)}
065 * </li></ul>
066 *
067 * @author ochafik
068 */
069public class Platform {
070
071    static final String osName = System.getProperty("os.name", "");
072    private static boolean inited;
073    static final String BridJLibraryName = "bridj";
074    public static final int POINTER_SIZE,
075            WCHAR_T_SIZE,
076            SIZE_T_SIZE,
077            TIME_T_SIZE,
078            CLONG_SIZE;
079    /*interface FunInt {
080     int apply();
081     }
082     static int tryInt(FunInt f, int defaultValue) {
083     try {
084     return f.apply();
085     } catch (Throwable th) {
086     return defaultValue;
087     }
088     }*/
089    static final ClassLoader systemClassLoader;
090
091    public static ClassLoader getClassLoader() {
092        return getClassLoader(BridJ.class);
093    }
094
095    public static ClassLoader getClassLoader(Class<?> cl) {
096        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
097        if (contextClassLoader != null) {
098            return contextClassLoader;
099        }
100        ClassLoader classLoader = cl == null ? null : cl.getClassLoader();
101        return classLoader == null ? systemClassLoader : classLoader;
102    }
103
104    public static InputStream getResourceAsStream(String path) {
105        URL url = getResource(path);
106        try {
107            return url != null ? url.openStream() : null;
108        } catch (IOException ex) {
109            if (BridJ.verbose) {
110                BridJ.warning("Failed to get resource '" + path + "'", ex);
111            }
112            return null;
113        }
114    }
115
116    public static URL getResource(String path) {
117        if (!path.startsWith("/")) {
118            path = "/" + path;
119        }
120
121        URL in = BridJ.class.getResource(path);
122        if (in != null) {
123            return in;
124        }
125
126        ClassLoader[] cls = {
127            BridJ.class.getClassLoader(),
128            Thread.currentThread().getContextClassLoader(),
129            systemClassLoader
130        };
131        for (ClassLoader cl : cls) {
132            if (cl != null && (in = cl.getResource(path)) != null) {
133                return in;
134            }
135        }
136        return null;
137    }
138    /*
139     public static class utsname {
140     public final String sysname, nodename, release, version, machine;
141     public utsname(String sysname, String nodename, String release, String version, String machine) {
142     this.sysname = sysname;
143     this.nodename = nodename;
144     this.release = release;
145     this.version = version;
146     this.machine = machine;
147     }
148     public String toString() {
149     StringBuilder b = new StringBuilder("{\n");
150     b.append("\tsysname: \"").append(sysname).append("\",\n");
151     b.append("\tnodename: \"").append(nodename).append("\",\n");
152     b.append("\trelease: \"").append(release).append("\",\n");
153     b.append("\tversion: \"").append(version).append("\",\n");
154     b.append("\tmachine: \"").append(machine).append("\"\n");
155     return b.append("}").toString();
156     }
157     }
158     public static native utsname uname();
159     */
160    static final List<String> embeddedLibraryResourceRoots = new ArrayList<String>();
161
162    /**
163     * BridJ is able to automatically extract native binaries bundled in the
164     * application's JARs, using a customizable root path and a predefined
165     * architecture-dependent subpath. This method adds an alternative root path
166     * to the search list.<br>
167     * For instance, if you want to load library "mylib" and call
168     * <code>addEmbeddedLibraryResourceRoot("my/company/lib/")</code>, BridJ
169     * will look for library in the following locations :
170     * <ul>
171     * <li>"my/company/lib/darwin_universal/libmylib.dylib" on MacOS X (or
172     * darwin_x86, darwin_x64, darwin_ppc if the binary is not universal)</li>
173     * <li>"my/company/lib/win32/mylib.dll" on Windows (use win64 on 64 bits
174     * architectures)</li>
175     * <li>"my/company/lib/linux_x86/libmylib.so" on Linux (use linux_x64 on 64
176     * bits architectures)</li>
177     * <li>"my/company/lib/sunos_x86/libmylib.so" on Solaris (use sunos_x64 /
178     * sunos_sparc on other architectures)</li>
179     * <li>"lib/armeabi/libmylib.so" on Android (for Android-specific reasons,
180     * only the "lib" sub-path can effectively contain loadable binaries)</li>
181     * </ul>
182     * For other platforms or for an updated list of supported platforms, please
183     * have a look at BridJ's JAR contents (under "org/bridj/lib") and/or to its
184     * source tree, browsable online.
185     */
186    public static synchronized void addEmbeddedLibraryResourceRoot(String root) {
187        embeddedLibraryResourceRoots.add(0, root);
188    }
189    static Set<File> temporaryExtractedLibraryCanonicalFiles = Collections.synchronizedSet(new LinkedHashSet<File>());
190
191    static void addTemporaryExtractedLibraryFileToDeleteOnExit(File file) throws IOException {
192        File canonicalFile = file.getCanonicalFile();
193
194        // Give a chance to NativeLibrary.release() to delete the file :
195        temporaryExtractedLibraryCanonicalFiles.add(canonicalFile);
196
197        // Ask Java to delete the file upon exit if it still exists
198        canonicalFile.deleteOnExit();
199    }
200    private static final String arch;
201    private static boolean is64Bits;
202    private static File extractedLibrariesTempDir;
203
204    static {
205        arch = System.getProperty("os.arch");
206        {
207            String dataModel = System.getProperty("sun.arch.data.model", System.getProperty("com.ibm.vm.bitmode"));
208            if ("32".equals(dataModel)) {
209                is64Bits = false;
210            } else if ("64".equals(dataModel)) {
211                is64Bits = true;
212            } else {
213                is64Bits =
214                        arch.contains("64")
215                        || arch.equalsIgnoreCase("sparcv9");
216            }
217        }
218        systemClassLoader = createClassLoader();
219
220        addEmbeddedLibraryResourceRoot("libs/");
221        if (!isAndroid()) {
222            addEmbeddedLibraryResourceRoot("lib/");
223            addEmbeddedLibraryResourceRoot("org/bridj/lib/");
224            if (!Version.VERSION_SPECIFIC_SUB_PACKAGE.equals("")) {
225                addEmbeddedLibraryResourceRoot("org/bridj/" + Version.VERSION_SPECIFIC_SUB_PACKAGE + "/lib/");
226            }
227        }
228
229        try {
230            extractedLibrariesTempDir = createTempDir("BridJExtractedLibraries");
231            initLibrary();
232        } catch (Throwable th) {
233            th.printStackTrace();
234        }
235        POINTER_SIZE = sizeOf_ptrdiff_t();
236        WCHAR_T_SIZE = sizeOf_wchar_t();
237        SIZE_T_SIZE = sizeOf_size_t();
238        TIME_T_SIZE = sizeOf_time_t();
239        CLONG_SIZE = sizeOf_long();
240
241        is64Bits = POINTER_SIZE == 8;
242
243        Runtime.getRuntime().addShutdownHook(new Thread() {
244            public void run() {
245                shutdown();
246            }
247        });
248    }
249    private static List<NativeLibrary> nativeLibraries = new ArrayList<NativeLibrary>();
250
251    static void addNativeLibrary(NativeLibrary library) {
252        synchronized (nativeLibraries) {
253            nativeLibraries.add(library);
254        }
255    }
256
257    private static void shutdown() {
258        //releaseNativeLibraries();
259        deleteTemporaryExtractedLibraryFiles();
260    }
261
262    private static void releaseNativeLibraries() {
263        synchronized (nativeLibraries) {
264            // Release libraries in reverse order :
265            for (int iLibrary = nativeLibraries.size(); iLibrary-- != 0;) {
266                NativeLibrary lib = nativeLibraries.get(iLibrary);
267                try {
268                    lib.release();
269                } catch (Throwable th) {
270                    BridJ.error("Failed to release library '" + lib.path + "' : " + th, th);
271                }
272            }
273        }
274    }
275
276    private static void deleteTemporaryExtractedLibraryFiles() {
277        synchronized (temporaryExtractedLibraryCanonicalFiles) {
278            temporaryExtractedLibraryCanonicalFiles.add(extractedLibrariesTempDir);
279
280            // Release libraries in reverse order :
281            List<File> filesToDeleteAfterExit = new ArrayList<File>();
282            for (File tempFile : Platform.temporaryExtractedLibraryCanonicalFiles) {
283                if (tempFile.delete()) {
284                    if (BridJ.verbose) {
285                        BridJ.info("Deleted temporary library file '" + tempFile + "'");
286                    }
287                } else {
288                    filesToDeleteAfterExit.add(tempFile);
289                }
290            }
291            if (!filesToDeleteAfterExit.isEmpty()) {
292                if (BridJ.verbose) {
293                    BridJ.info("Attempting to delete " + filesToDeleteAfterExit.size() + " files after JVM exit : " + StringUtils.implode(filesToDeleteAfterExit, ", "));
294                }
295
296                try {
297                    ProcessUtils.startJavaProcess(DeleteFiles.class, filesToDeleteAfterExit);
298                } catch (Throwable ex) {
299                    BridJ.error("Failed to launch process to delete files after JVM exit : " + ex, ex);
300                }
301            }
302        }
303    }
304
305    public static class DeleteFiles {
306
307        static boolean delete(List<File> files) {
308            for (Iterator<File> it = files.iterator(); it.hasNext();) {
309                File file = it.next();
310                if (file.delete()) {
311                    it.remove();
312                }
313            }
314            return files.isEmpty();
315        }
316        final static long TRY_DELETE_EVERY_MILLIS = 50,
317                FAIL_AFTER_MILLIS = 10000;
318
319        public static void main(String[] args) {
320            try {
321                List<File> files = new LinkedList<File>();
322                for (String arg : args) {
323                    files.add(new File(arg));
324                }
325
326                long start = System.currentTimeMillis();
327                while (!delete(files)) {
328                    long elapsed = System.currentTimeMillis() - start;
329                    if (elapsed > FAIL_AFTER_MILLIS) {
330                        BridJ.error("Failed to delete the following files : " + StringUtils.implode(files));
331                        System.exit(1);
332                    }
333
334                    Thread.sleep(TRY_DELETE_EVERY_MILLIS);
335                }
336            } catch (Throwable th) {
337                th.printStackTrace();
338            } finally {
339                System.exit(0);
340            }
341        }
342    }
343
344    static ClassLoader createClassLoader() {
345        List<URL> urls = new ArrayList<URL>();
346        for (String propName : new String[]{"java.class.path", "sun.boot.class.path"}) {
347            String prop = System.getProperty(propName);
348            if (prop == null) {
349                continue;
350            }
351
352            for (String path : prop.split(File.pathSeparator)) {
353                path = path.trim();
354                if (path.length() == 0) {
355                    continue;
356                }
357
358                URL url;
359                try {
360                    url = new URL(path);
361                } catch (MalformedURLException ex) {
362                    try {
363                        url = new File(path).toURI().toURL();
364                    } catch (MalformedURLException ex2) {
365                        url = null;
366                    }
367                }
368                if (url != null) {
369                    urls.add(url);
370                }
371            }
372        }
373        //System.out.println("URLs for synthetic class loader :");
374        //for (URL url : urls)
375        //      System.out.println("\t" + url);
376        return new URLClassLoader(urls.toArray(new URL[urls.size()]));
377    }
378
379    static String getenvOrProperty(String envName, String javaName, String defaultValue) {
380        String value = System.getenv(envName);
381        if (value == null) {
382            value = System.getProperty(javaName);
383        }
384        if (value == null) {
385            value = defaultValue;
386        }
387        return value;
388    }
389
390    public static synchronized void initLibrary() {
391        if (inited) {
392            return;
393        }
394        inited = true;
395
396        try {
397            boolean loaded = false;
398
399            String forceLibFile = getenvOrProperty("BRIDJ_LIBRARY", "bridj.library", null);
400
401            String lib = null;
402
403            if (forceLibFile != null) {
404                try {
405                    System.load(lib = forceLibFile);
406                    loaded = true;
407                } catch (Throwable ex) {
408                    BridJ.error("Failed to load forced library " + forceLibFile, ex);
409                }
410            }
411
412            if (!loaded) {
413                if (!Platform.isAndroid()) {
414                    try {
415                        File libFile = extractEmbeddedLibraryResource(BridJLibraryName);
416                        if (libFile == null) {
417                            throw new FileNotFoundException("Failed to extract embedded library '" + BridJLibraryName + "' (could be a classloader issue, or missing binary in resource path " + StringUtils.implode(embeddedLibraryResourceRoots, ", ") + ")");
418                        }
419
420                        if (BridJ.veryVerbose) {
421                            BridJ.info("Loading library " + libFile);
422                        }
423                        System.load(lib = libFile.toString());
424                        BridJ.setNativeLibraryFile(BridJLibraryName, libFile);
425                        loaded = true;
426                    } catch (IOException ex) {
427                        BridJ.error("Failed to load '" + BridJLibraryName + "'", ex);
428                    }
429                }
430                if (!loaded) {
431                    System.loadLibrary("bridj");
432                }
433            }
434            if (BridJ.veryVerbose) {
435                BridJ.info("Loaded library " + lib);
436            }
437
438            init();
439
440            //if (BridJ.protectedMode)
441            //          BridJ.info("Protected mode enabled");
442            if (BridJ.logCalls) {
443                BridJ.info("Calls logs enabled");
444            }
445
446        } catch (Throwable ex) {
447            throw new RuntimeException("Failed to initialize " + BridJ.class.getSimpleName() + " (" + ex + ")", ex);
448        }
449    }
450
451    private static native void init();
452
453    public static boolean isLinux() {
454        return isUnix() && osName.toLowerCase().contains("linux");
455    }
456
457    public static boolean isMacOSX() {
458        return isUnix() && (osName.startsWith("Mac") || osName.startsWith("Darwin"));
459    }
460
461    public static boolean isSolaris() {
462        return isUnix() && (osName.startsWith("SunOS") || osName.startsWith("Solaris"));
463    }
464
465    public static boolean isBSD() {
466        return isUnix() && (osName.contains("BSD") || isMacOSX());
467    }
468
469    public static boolean isUnix() {
470        return File.separatorChar == '/';
471    }
472
473    public static boolean isWindows() {
474        return File.separatorChar == '\\';
475    }
476
477    public static boolean isWindows7() {
478        return osName.equals("Windows 7");
479    }
480    /**
481     * Whether to use Unicode versions of Windows APIs rather than ANSI versions
482     * (for functions that haven't been bound yet : has no effect on functions
483     * that have already been bound).<br>
484     * Some Windows APIs such as SendMessage have two versions :
485     * <ul>
486     * <li>one that uses single-byte character strings (SendMessageA, with 'A'
487     * for ANSI strings)</li>
488     * <li>one that uses unicode character strings (SendMessageW, with 'W' for
489     * Wide strings).</li>
490     * </ul>
491     * <br>
492     * In a C/C++ program, this behaviour is controlled by the UNICODE macro
493     * definition.<br>
494     * By default, BridJ will use the Unicode versions. Set this field to false,
495     * set the bridj.useUnicodeVersionOfWindowsAPIs property to "false" or the
496     * BRIDJ_USE_UNICODE_VERSION_OF_WINDOWS_APIS environment variable to "0" to
497     * use the ANSI string version instead.
498     */
499    public static boolean useUnicodeVersionOfWindowsAPIs = !("false".equals(System.getProperty("bridj.useUnicodeVersionOfWindowsAPIs"))
500            || "0".equals(System.getenv("BRIDJ_USE_UNICODE_VERSION_OF_WINDOWS_APIS")));
501
502    private static String getArch() {
503        return arch;
504    }
505
506    /**
507     * Machine (as returned by `uname -m`, except for i686 which is actually
508     * i386), adjusted to the JVM platform (32 or 64 bits)
509     */
510    public static String getMachine() {
511        String arch = getArch();
512        if (arch.equals("amd64") || arch.equals("x86_64")) {
513            if (is64Bits()) {
514                return "x86_64";
515            } else {
516                return "i386"; // we are running a 32 bits JVM on a 64 bits platform
517            }
518        }
519        return arch;
520    }
521
522    public static boolean isAndroid() {
523        return "dalvik".equalsIgnoreCase(System.getProperty("java.vm.name")) && isLinux();
524    }
525
526    public static boolean isArm() {
527        String arch = getArch();
528        return "arm".equals(arch);
529    }
530
531    public static boolean isSparc() {
532        String arch = getArch();
533        return "sparc".equals(arch)
534                || "sparcv9".equals(arch);
535    }
536
537    public static boolean is64Bits() {
538        return is64Bits;
539    }
540
541    public static boolean isAmd64Arch() {
542        String arch = getArch();
543        return arch.equals("x86_64");
544    }
545
546    static List<String> getPossibleFileNames(String name) {
547        List<String> fileNames = new ArrayList<String>(1);
548        if (isWindows()) {
549            fileNames.add(name + ".dll");
550            fileNames.add(name + ".drv");
551        } else {
552            String jniName = "lib" + name + ".jnilib";
553            if (isMacOSX()) {
554                fileNames.add("lib" + name + ".dylib");
555                fileNames.add(jniName);
556            } else {
557                fileNames.add("lib" + name + ".so");
558                fileNames.add(name + ".so");
559                fileNames.add(jniName);
560            }
561        }
562
563        assert !fileNames.isEmpty();
564        if (name.contains(".")) {
565            fileNames.add(name);
566        }
567        return fileNames;
568    }
569
570    static synchronized List<String> getEmbeddedLibraryPaths(String name) {
571        List<String> paths = new ArrayList<String>(embeddedLibraryResourceRoots.size());
572        for (String root : embeddedLibraryResourceRoots) {
573            if (root == null) {
574                continue;
575            }
576
577            if (isWindows()) {
578                paths.add(root + (is64Bits() ? "win64/" : "win32/"));
579            } else if (isMacOSX()) {
580                if (isArm()) {
581                    paths.add(root + "iphoneos_arm32_arm/");
582                } else {
583                    paths.add(root + "darwin_universal/");
584                    if (isAmd64Arch()) {
585                        paths.add(root + "darwin_x64/");
586                    }
587                }
588            } else {
589                if (isAndroid()) {
590                    assert root.equals("libs/");
591                    paths.add(root + "armeabi/"); // Android SDK + NDK-style .so embedding = lib/armeabi/libTest.so
592                } else if (isLinux()) {
593                    if (isArm()) {
594                        //TODO: ARM support is still broken!
595                        //There are 2 ABIs: ARMEL and ARMHF
596                        //ARMEL should work ok
597                        //ARMHF has two (largely) incompatible calling conventions: softfp and hard/vfp.
598                        //The differences are largely down to which registers are used for passing double/float arguments; 
599                        //in hard/vfp mode, the vfp registers are used, whereas in softfp the normal cpu registers
600                        //are used. Calling a function with the wrong convention can lead to stack corruption (although I think
601                        //you are probably safe if you stay away from floats and doubles)...
602                        //At the time of writing, dyncall only supports softfp, but the common
603                        //linux distributions are using hard/vfp.
604                        //In the future we need to make bridj support both conventions.
605
606                        paths.add(root + getARMLinuxLibDir());
607                        //for compatibility with the older OpenIMAJ forks supporting ARM
608                        paths.add(root + getARMLinuxLibDir().replace("_arm", "_arm32_arm"));
609                    } else {
610                        paths.add(root + (is64Bits() ? "linux_x64/" : "linux_x86/"));
611                    }
612                } else if (isSolaris()) {
613                    if (isSparc()) {
614                        paths.add(root + (is64Bits() ? "sunos_sparc64/" : "sunos_sparc/"));
615                    } else {
616                        paths.add(root + (is64Bits() ? "sunos_x64/" : "sunos_x86/"));
617                    }
618                }
619            }
620        }
621
622        if (paths.isEmpty()) {
623            throw new RuntimeException("Platform not supported ! (os.name='" + osName + "', os.arch='" + System.getProperty("os.arch") + "')");
624        }
625        return paths;
626    }
627
628    static synchronized List<String> getEmbeddedLibraryResource(String name) {
629        List<String> paths = getEmbeddedLibraryPaths(name);
630        List<String> fileNames = getPossibleFileNames(name);
631        List<String> ret = new ArrayList<String>(paths.size() * fileNames.size());
632        for (String path : paths) {
633            for (String fileName : fileNames) {
634                ret.add(path + fileName);
635            }
636        }
637
638        if (BridJ.veryVerbose) {
639            BridJ.info("Embedded resource paths for library '" + name + "': " + ret);
640        }
641        return ret;
642    }
643
644    static void tryDeleteFilesInSameDirectory(final File legitFile, final Pattern fileNamePattern, long atLeastOlderThanMillis) {
645        final long maxModifiedDateForDeletion = System.currentTimeMillis() - atLeastOlderThanMillis;
646        new Thread(new Runnable() {
647            public void run() {
648                File dir = legitFile.getParentFile();
649                String legitFileName = legitFile.getName();
650                try {
651                    for (String name : dir.list()) {
652                        if (name.equals(legitFileName)) {
653                            continue;
654                        }
655
656                        if (!fileNamePattern.matcher(name).matches()) {
657                            continue;
658                        }
659
660                        File file = new File(dir, name);
661                        if (file.lastModified() > maxModifiedDateForDeletion) {
662                            continue;
663                        }
664
665                        if (file.delete() && BridJ.verbose) {
666                            BridJ.info("Deleted old binary file '" + file + "'");
667                        }
668                    }
669                } catch (SecurityException ex) {
670                    // no right to delete files in that directory
671                    BridJ.warning("Failed to delete files matching '" + fileNamePattern + "' in directory '" + dir + "'", ex);
672                } catch (Throwable ex) {
673                    BridJ.error("Unexpected error : " + ex, ex);
674                }
675            }
676        }).start();
677    }
678    static final long DELETE_OLD_BINARIES_AFTER_MILLIS = 24 * 60 * 60 * 1000; // 24 hours
679
680    static File extractEmbeddedLibraryResource(String name) throws IOException {
681        String firstLibraryResource = null;
682
683        List<String> libraryResources = getEmbeddedLibraryResource(name);
684        if (BridJ.veryVerbose) {
685            BridJ.info("Library resources for " + name + ": " + libraryResources);
686        }               
687        for (String libraryResource : libraryResources) {
688            if (firstLibraryResource == null) {
689                firstLibraryResource = libraryResource;
690            }
691            int i = libraryResource.lastIndexOf('.');
692            int len;
693            byte[] b = new byte[8196];
694            InputStream in = getResourceAsStream(libraryResource);
695            if (in == null) {
696                File f = new File(libraryResource);
697                if (!f.exists()) {
698                    f = new File(f.getName());
699                }
700                if (f.exists()) {
701                    return f.getCanonicalFile();
702                }
703                continue;
704            }
705            String fileName = new File(libraryResource).getName();
706            File libFile = new File(extractedLibrariesTempDir, fileName);
707            OutputStream out = new BufferedOutputStream(new FileOutputStream(libFile));
708            while ((len = in.read(b)) > 0) {
709                out.write(b, 0, len);
710            }
711            out.close();
712            in.close();
713
714            addTemporaryExtractedLibraryFileToDeleteOnExit(libFile);
715            addTemporaryExtractedLibraryFileToDeleteOnExit(libFile.getParentFile());
716
717            return libFile;
718        }
719        return null;
720    }
721    static final int maxTempFileAttempts = 20;
722
723    static File createTempDir(String prefix) throws IOException {
724        File dir;
725        for (int i = 0; i < maxTempFileAttempts; i++) {
726            dir = File.createTempFile(prefix, "");
727            if (dir.delete() && dir.mkdirs()) {
728                return dir;
729            }
730        }
731        throw new RuntimeException("Failed to create temp dir with prefix '" + prefix + "' despite " + maxTempFileAttempts + " attempts!");
732    }
733
734    /**
735     * Opens an URL with the default system action.
736     *
737     * @param url url to open
738     * @throws NoSuchMethodException if opening an URL on the current platform
739     * is not supported
740     */
741    public static final void open(URL url) throws NoSuchMethodException {
742        if (url.getProtocol().equals("file")) {
743            open(new File(url.getFile()));
744        } else {
745            if (Platform.isMacOSX()) {
746                execArgs("open", url.toString());
747            } else if (Platform.isWindows()) {
748                execArgs("rundll32", "url.dll,FileProtocolHandler", url.toString());
749            } else if (Platform.isUnix() && hasUnixCommand("gnome-open")) {
750                execArgs("gnome-open", url.toString());
751            } else if (Platform.isUnix() && hasUnixCommand("konqueror")) {
752                execArgs("konqueror", url.toString());
753            } else if (Platform.isUnix() && hasUnixCommand("mozilla")) {
754                execArgs("mozilla", url.toString());
755            } else {
756                throw new NoSuchMethodException("Cannot open urls on this platform");
757            }
758        }
759    }
760
761    /**
762     * Opens a file with the default system action.
763     *
764     * @param file file to open
765     * @throws NoSuchMethodException if opening a file on the current platform
766     * is not supported
767     */
768    public static final void open(File file) throws NoSuchMethodException {
769        if (Platform.isMacOSX()) {
770            execArgs("open", file.getAbsolutePath());
771        } else if (Platform.isWindows()) {
772            if (file.isDirectory()) {
773                execArgs("explorer", file.getAbsolutePath());
774            } else {
775                execArgs("start", file.getAbsolutePath());
776            }
777        } else if (Platform.isUnix() && hasUnixCommand("gnome-open")) {
778            execArgs("gnome-open", file.toString());
779        } else if (Platform.isUnix() && hasUnixCommand("konqueror")) {
780            execArgs("konqueror", file.toString());
781        } else if (Platform.isSolaris() && file.isDirectory()) {
782            execArgs("/usr/dt/bin/dtfile", "-folder", file.getAbsolutePath());
783        } else {
784            throw new NoSuchMethodException("Cannot open files on this platform");
785        }
786    }
787
788    /**
789     * Show a file in its parent directory, if possible selecting the file (not
790     * possible on all platforms).
791     *
792     * @param file file to show in the system's default file navigator
793     * @throws NoSuchMethodException if showing a file on the current platform
794     * is not supported
795     */
796    public static final void show(File file) throws NoSuchMethodException, IOException {
797        if (Platform.isWindows()) {
798            exec("explorer /e,/select,\"" + file.getCanonicalPath() + "\"");
799        } else {
800            open(file.getAbsoluteFile().getParentFile());
801        }
802    }
803
804    static final void execArgs(String... cmd) throws NoSuchMethodException {
805        try {
806            Runtime.getRuntime().exec(cmd);
807        } catch (Exception ex) {
808            ex.printStackTrace();
809            throw new NoSuchMethodException(ex.toString());
810        }
811    }
812
813    static final void exec(String cmd) throws NoSuchMethodException {
814        try {
815            Runtime.getRuntime().exec(cmd).waitFor();
816        } catch (Exception ex) {
817            ex.printStackTrace();
818            throw new NoSuchMethodException(ex.toString());
819        }
820    }
821
822    static final boolean hasUnixCommand(String name) {
823        try {
824            Process p = Runtime.getRuntime().exec(new String[]{"which", name});
825            return p.waitFor() == 0;
826        } catch (Exception ex) {
827            ex.printStackTrace();
828            return false;
829        }
830    }
831
832    static native int sizeOf_size_t();
833
834    static native int sizeOf_time_t();
835
836    static native int sizeOf_wchar_t();
837
838    static native int sizeOf_ptrdiff_t();
839
840        static native int sizeOf_long();
841        
842        static native int getMaxDirectMappingArgCount();
843        
844        private static final boolean contains(String data, String[] search) {
845                if (null != data && null != search) {
846                        for (int i = 0; i < search.length; i++) {
847                                if (data.indexOf(search[i]) >= 0) {
848                                        return true;
849                                }
850                        }
851                }
852                return false;
853        }
854    
855    /*
856     * [From https://github.com/Pi4J/pi4j/blob/develop/pi4j-core/src/main/java/com/pi4j/system/SystemInfo.java]
857     * <p>
858     * this method will to obtain the version info string from the 'bash' program
859     * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
860     */
861    private static String getBashVersionInfo() {
862        String versionInfo = "";
863        try {
864            
865            String cmd = "bash --version";
866            Process p = Runtime.getRuntime().exec(cmd); 
867            p.waitFor(); 
868            BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 
869            String line = reader.readLine();
870            if(p.exitValue() == 0) {
871                while(line != null) {
872                    if(!line.isEmpty()) { 
873                        versionInfo = line; // return only first output line of version info
874                        break;
875                    }
876                    line = reader.readLine();
877                }
878            }
879        }
880        catch (IOException ioe) { ioe.printStackTrace(); }
881        catch (InterruptedException ie) { ie.printStackTrace(); }
882        return versionInfo;
883    }
884
885    /*
886     * [From https://github.com/Pi4J/pi4j/blob/develop/pi4j-core/src/main/java/com/pi4j/system/SystemInfo.java]
887     * <p>
888     * this method will determine if a specified tag exists from the elf info in the '/proc/self/exe' program
889     * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
890     */    
891    private static boolean hasReadElfTag(String tag) {
892        String tagValue = getReadElfTag(tag);
893        if(tagValue != null && !tagValue.isEmpty())
894            return true;
895        return false;
896    }
897    
898    /*
899     * [From https://github.com/Pi4J/pi4j/blob/develop/pi4j-core/src/main/java/com/pi4j/system/SystemInfo.java]
900     * <p>
901     * this method will obtain a specified tag value from the elf info in the '/proc/self/exe' program
902     * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
903     */    
904    private static String getReadElfTag(String tag) {
905        String tagValue = null;
906        try {
907            String cmd = "/usr/bin/readelf -A /proc/self/exe";
908            Process p = Runtime.getRuntime().exec(cmd); 
909            p.waitFor();
910            if(p.exitValue() == 0) {
911                BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 
912                String line = reader.readLine();
913                while(line != null) {
914                    line = line.trim();
915                    if (line.startsWith(tag) && line.contains(":")) {
916                        String lineParts[] = line.split(":", 2);
917                        if(lineParts.length > 1)
918                            tagValue = lineParts[1].trim();
919                        break;
920                    }
921                    line = reader.readLine();
922                }
923            }
924        }
925        catch (IOException ioe) { ioe.printStackTrace(); }
926        catch (InterruptedException ie) { ie.printStackTrace(); }
927        return tagValue;
928    }
929        
930        /**
931         * ARM processors have two incompatible ABIs - one for use with the floating
932         * point unit (HardFP - armhf), the other without (SoftFP - armel). This
933         * generates the correct search path depending on the ABI in use by the JVM.
934         * Unfortunately, there isn't currently a standard property that describes
935         * the abi, so we have to "guess".
936         * <p>
937         * The implementation is derived from code in the PI4J project:
938         * https://github.com/Pi4J/pi4j/blob/develop/pi4j-core/src/main/java/com/pi4j/system/SystemInfo.java 
939         * 
940         * @return the library directory
941         */
942        private static final String getARMLinuxLibDir() {
943
944                final boolean isHF = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
945            private final String[] gnueabihf = new String[] { "gnueabihf", "armhf" };
946            public Boolean run() {                    
947                if ( contains(System.getProperty("sun.boot.library.path"), gnueabihf) ||
948                     contains(System.getProperty("java.library.path"), gnueabihf) ||
949                     contains(System.getProperty("java.home"), gnueabihf) || 
950                     getBashVersionInfo().contains("gnueabihf") ||
951                     hasReadElfTag("Tag_ABI_HardFP_use")) {
952                        return true; //
953                }
954                return false;
955            } } );
956                
957                return "linux_arm" + (isHF ? "hf" : "el") + "/";
958        }
959}