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