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;
034import java.util.Set;
035import java.util.HashSet;
036import java.util.regex.Pattern;
037import java.io.*;
038import java.net.URL;
039
040import java.util.List;
041import java.util.Collections;
042import java.util.Collection;
043import java.util.ArrayList;
044import java.net.MalformedURLException;
045import java.net.URLClassLoader;
046import java.util.Arrays;
047import java.util.Iterator;
048import java.util.LinkedHashSet;
049import java.util.LinkedList;
050import org.bridj.util.StringUtils;
051
052/**
053 * Information about the execution platform (OS, architecture, native sizes...)
054 * and platform-specific actions.
055 * <ul>
056 * <li>To know if the JVM platform is 32 bits or 64 bits, use
057 * {@link Platform#is64Bits()}
058 * </li><li>To know if the OS is an Unix-like system, use
059 * {@link Platform#isUnix()}
060 * </li><li>To open files and URLs in a platform-specific way, use
061 * {@link Platform#open(File)}, {@link Platform#open(URL)}, {@link Platform#show(File)}
062 * </li></ul>
063 *
064 * @author ochafik
065 */
066public class Platform {
067
068    static final String osName = System.getProperty("os.name", "");
069    private static boolean inited;
070    static final String BridJLibraryName = "bridj";
071    public static final int POINTER_SIZE,
072            WCHAR_T_SIZE,
073            SIZE_T_SIZE,
074            TIME_T_SIZE,
075            CLONG_SIZE;
076    /*interface FunInt {
077     int apply();
078     }
079     static int tryInt(FunInt f, int defaultValue) {
080     try {
081     return f.apply();
082     } catch (Throwable th) {
083     return defaultValue;
084     }
085     }*/
086    static final ClassLoader systemClassLoader;
087
088    public static ClassLoader getClassLoader() {
089        return getClassLoader(BridJ.class);
090    }
091
092    public static ClassLoader getClassLoader(Class<?> cl) {
093        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
094        if (contextClassLoader != null) {
095            return contextClassLoader;
096        }
097        ClassLoader classLoader = cl == null ? null : cl.getClassLoader();
098        return classLoader == null ? systemClassLoader : classLoader;
099    }
100
101    public static InputStream getResourceAsStream(String path) {
102        URL url = getResource(path);
103        try {
104            return url != null ? url.openStream() : null;
105        } catch (IOException ex) {
106            if (BridJ.verbose) {
107                BridJ.warning("Failed to get resource '" + path + "'", ex);
108            }
109            return null;
110        }
111    }
112
113    public static URL getResource(String path) {
114        if (!path.startsWith("/")) {
115            path = "/" + path;
116        }
117
118        URL in = BridJ.class.getResource(path);
119        if (in != null) {
120            return in;
121        }
122
123        ClassLoader[] cls = {
124            BridJ.class.getClassLoader(),
125            Thread.currentThread().getContextClassLoader(),
126            systemClassLoader
127        };
128        for (ClassLoader cl : cls) {
129            if (cl != null && (in = cl.getResource(path)) != null) {
130                return in;
131            }
132        }
133        return null;
134    }
135    /*
136     public static class utsname {
137     public final String sysname, nodename, release, version, machine;
138     public utsname(String sysname, String nodename, String release, String version, String machine) {
139     this.sysname = sysname;
140     this.nodename = nodename;
141     this.release = release;
142     this.version = version;
143     this.machine = machine;
144     }
145     public String toString() {
146     StringBuilder b = new StringBuilder("{\n");
147     b.append("\tsysname: \"").append(sysname).append("\",\n");
148     b.append("\tnodename: \"").append(nodename).append("\",\n");
149     b.append("\trelease: \"").append(release).append("\",\n");
150     b.append("\tversion: \"").append(version).append("\",\n");
151     b.append("\tmachine: \"").append(machine).append("\"\n");
152     return b.append("}").toString();
153     }
154     }
155     public static native utsname uname();
156     */
157    static final List<String> embeddedLibraryResourceRoots = new ArrayList<String>();
158
159    /**
160     * BridJ is able to automatically extract native binaries bundled in the
161     * application's JARs, using a customizable root path and a predefined
162     * architecture-dependent subpath. This method adds an alternative root path
163     * to the search list.<br>
164     * For instance, if you want to load library "mylib" and call
165     * <code>addEmbeddedLibraryResourceRoot("my/company/lib/")</code>, BridJ
166     * will look for library in the following locations :
167     * <ul>
168     * <li>"my/company/lib/darwin_universal/libmylib.dylib" on MacOS X (or
169     * darwin_x86, darwin_x64, darwin_ppc if the binary is not universal)</li>
170     * <li>"my/company/lib/win32/mylib.dll" on Windows (use win64 on 64 bits
171     * architectures)</li>
172     * <li>"my/company/lib/linux_x86/libmylib.so" on Linux (use linux_x64 on 64
173     * bits architectures)</li>
174     * <li>"my/company/lib/sunos_x86/libmylib.so" on Solaris (use sunos_x64 /
175     * sunos_sparc on other architectures)</li>
176     * <li>"lib/armeabi/libmylib.so" on Android (for Android-specific reasons,
177     * only the "lib" sub-path can effectively contain loadable binaries)</li>
178     * </ul>
179     * For other platforms or for an updated list of supported platforms, please
180     * have a look at BridJ's JAR contents (under "org/bridj/lib") and/or to its
181     * source tree, browsable online.
182     */
183    public static synchronized void addEmbeddedLibraryResourceRoot(String root) {
184        embeddedLibraryResourceRoots.add(0, root);
185    }
186    static Set<File> temporaryExtractedLibraryCanonicalFiles = Collections.synchronizedSet(new LinkedHashSet<File>());
187
188    static void addTemporaryExtractedLibraryFileToDeleteOnExit(File file) throws IOException {
189        File canonicalFile = file.getCanonicalFile();
190
191        // Give a chance to NativeLibrary.release() to delete the file :
192        temporaryExtractedLibraryCanonicalFiles.add(canonicalFile);
193
194        // Ask Java to delete the file upon exit if it still exists
195        canonicalFile.deleteOnExit();
196    }
197    private static final String arch;
198    private static boolean is64Bits;
199    private static File extractedLibrariesTempDir;
200
201    static {
202        arch = System.getProperty("os.arch");
203        {
204            String dataModel = System.getProperty("sun.arch.data.model", System.getProperty("com.ibm.vm.bitmode"));
205            if ("32".equals(dataModel)) {
206                is64Bits = false;
207            } else if ("64".equals(dataModel)) {
208                is64Bits = true;
209            } else {
210                is64Bits =
211                        arch.contains("64")
212                        || arch.equalsIgnoreCase("sparcv9");
213            }
214        }
215        systemClassLoader = createClassLoader();
216
217        addEmbeddedLibraryResourceRoot("libs/");
218        if (!isAndroid()) {
219            addEmbeddedLibraryResourceRoot("lib/");
220            addEmbeddedLibraryResourceRoot("org/bridj/lib/");
221            if (!Version.VERSION_SPECIFIC_SUB_PACKAGE.equals("")) {
222                addEmbeddedLibraryResourceRoot("org/bridj/" + Version.VERSION_SPECIFIC_SUB_PACKAGE + "/lib/");
223            }
224        }
225
226        try {
227            extractedLibrariesTempDir = createTempDir("BridJExtractedLibraries");
228            initLibrary();
229        } catch (Throwable th) {
230            th.printStackTrace();
231        }
232        POINTER_SIZE = sizeOf_ptrdiff_t();
233        WCHAR_T_SIZE = sizeOf_wchar_t();
234        SIZE_T_SIZE = sizeOf_size_t();
235        TIME_T_SIZE = sizeOf_time_t();
236        CLONG_SIZE = sizeOf_long();
237
238        is64Bits = POINTER_SIZE == 8;
239
240        Runtime.getRuntime().addShutdownHook(new Thread() {
241            public void run() {
242                shutdown();
243            }
244        });
245    }
246    private static List<NativeLibrary> nativeLibraries = new ArrayList<NativeLibrary>();
247
248    static void addNativeLibrary(NativeLibrary library) {
249        synchronized (nativeLibraries) {
250            nativeLibraries.add(library);
251        }
252    }
253
254    private static void shutdown() {
255        //releaseNativeLibraries();
256        deleteTemporaryExtractedLibraryFiles();
257    }
258
259    private static void releaseNativeLibraries() {
260        synchronized (nativeLibraries) {
261            // Release libraries in reverse order :
262            for (int iLibrary = nativeLibraries.size(); iLibrary-- != 0;) {
263                NativeLibrary lib = nativeLibraries.get(iLibrary);
264                try {
265                    lib.release();
266                } catch (Throwable th) {
267                    BridJ.error("Failed to release library '" + lib.path + "' : " + th, th);
268                }
269            }
270        }
271    }
272
273    private static void deleteTemporaryExtractedLibraryFiles() {
274        synchronized (temporaryExtractedLibraryCanonicalFiles) {
275            temporaryExtractedLibraryCanonicalFiles.add(extractedLibrariesTempDir);
276
277            // Release libraries in reverse order :
278            List<File> filesToDeleteAfterExit = new ArrayList<File>();
279            for (File tempFile : Platform.temporaryExtractedLibraryCanonicalFiles) {
280                if (tempFile.delete()) {
281                    if (BridJ.verbose) {
282                        BridJ.info("Deleted temporary library file '" + tempFile + "'");
283                    }
284                } else {
285                    filesToDeleteAfterExit.add(tempFile);
286                }
287            }
288            if (!filesToDeleteAfterExit.isEmpty()) {
289                if (BridJ.verbose) {
290                    BridJ.info("Attempting to delete " + filesToDeleteAfterExit.size() + " files after JVM exit : " + StringUtils.implode(filesToDeleteAfterExit, ", "));
291                }
292
293                try {
294                    ProcessUtils.startJavaProcess(DeleteFiles.class, filesToDeleteAfterExit);
295                } catch (Throwable ex) {
296                    BridJ.error("Failed to launch process to delete files after JVM exit : " + ex, ex);
297                }
298            }
299        }
300    }
301
302    public static class DeleteFiles {
303
304        static boolean delete(List<File> files) {
305            for (Iterator<File> it = files.iterator(); it.hasNext();) {
306                File file = it.next();
307                if (file.delete()) {
308                    it.remove();
309                }
310            }
311            return files.isEmpty();
312        }
313        final static long TRY_DELETE_EVERY_MILLIS = 50,
314                FAIL_AFTER_MILLIS = 10000;
315
316        public static void main(String[] args) {
317            try {
318                List<File> files = new LinkedList<File>();
319                for (String arg : args) {
320                    files.add(new File(arg));
321                }
322
323                long start = System.currentTimeMillis();
324                while (!delete(files)) {
325                    long elapsed = System.currentTimeMillis() - start;
326                    if (elapsed > FAIL_AFTER_MILLIS) {
327                        BridJ.error("Failed to delete the following files : " + StringUtils.implode(files));
328                        System.exit(1);
329                    }
330
331                    Thread.sleep(TRY_DELETE_EVERY_MILLIS);
332                }
333            } catch (Throwable th) {
334                th.printStackTrace();
335            } finally {
336                System.exit(0);
337            }
338        }
339    }
340
341    static ClassLoader createClassLoader() {
342        List<URL> urls = new ArrayList<URL>();
343        for (String propName : new String[]{"java.class.path", "sun.boot.class.path"}) {
344            String prop = System.getProperty(propName);
345            if (prop == null) {
346                continue;
347            }
348
349            for (String path : prop.split(File.pathSeparator)) {
350                path = path.trim();
351                if (path.length() == 0) {
352                    continue;
353                }
354
355                URL url;
356                try {
357                    url = new URL(path);
358                } catch (MalformedURLException ex) {
359                    try {
360                        url = new File(path).toURI().toURL();
361                    } catch (MalformedURLException ex2) {
362                        url = null;
363                    }
364                }
365                if (url != null) {
366                    urls.add(url);
367                }
368            }
369        }
370        //System.out.println("URLs for synthetic class loader :");
371        //for (URL url : urls)
372        //      System.out.println("\t" + url);
373        return new URLClassLoader(urls.toArray(new URL[urls.size()]));
374    }
375
376    static String getenvOrProperty(String envName, String javaName, String defaultValue) {
377        String value = System.getenv(envName);
378        if (value == null) {
379            value = System.getProperty(javaName);
380        }
381        if (value == null) {
382            value = defaultValue;
383        }
384        return value;
385    }
386
387    public static synchronized void initLibrary() {
388        if (inited) {
389            return;
390        }
391        inited = true;
392
393        try {
394            boolean loaded = false;
395
396            String forceLibFile = getenvOrProperty("BRIDJ_LIBRARY", "bridj.library", null);
397
398            String lib = null;
399
400            if (forceLibFile != null) {
401                try {
402                    System.load(lib = forceLibFile);
403                    loaded = true;
404                } catch (Throwable ex) {
405                    BridJ.error("Failed to load forced library " + forceLibFile, ex);
406                }
407            }
408
409            if (!loaded) {
410                if (!Platform.isAndroid()) {
411                    try {
412                        File libFile = extractEmbeddedLibraryResource(BridJLibraryName);
413                        if (libFile == null) {
414                            throw new FileNotFoundException("Failed to extract embedded library '" + BridJLibraryName + "' (could be a classloader issue, or missing binary in resource path " + StringUtils.implode(embeddedLibraryResourceRoots, ", ") + ")");
415                        }
416
417                        if (BridJ.veryVerbose) {
418                            BridJ.info("Loading library " + libFile);
419                        }
420                        System.load(lib = libFile.toString());
421                        BridJ.setNativeLibraryFile(BridJLibraryName, libFile);
422                        loaded = true;
423                    } catch (IOException ex) {
424                        BridJ.error("Failed to load '" + BridJLibraryName + "'", ex);
425                    }
426                }
427                if (!loaded) {
428                    System.loadLibrary("bridj");
429                }
430            }
431            if (BridJ.veryVerbose) {
432                BridJ.info("Loaded library " + lib);
433            }
434
435            init();
436
437            //if (BridJ.protectedMode)
438            //          BridJ.info("Protected mode enabled");
439            if (BridJ.logCalls) {
440                BridJ.info("Calls logs enabled");
441            }
442
443        } catch (Throwable ex) {
444            throw new RuntimeException("Failed to initialize " + BridJ.class.getSimpleName() + " (" + ex + ")", ex);
445        }
446    }
447
448    private static native void init();
449
450    public static boolean isLinux() {
451        return isUnix() && osName.toLowerCase().contains("linux");
452    }
453
454    public static boolean isMacOSX() {
455        return isUnix() && (osName.startsWith("Mac") || osName.startsWith("Darwin"));
456    }
457
458    public static boolean isSolaris() {
459        return isUnix() && (osName.startsWith("SunOS") || osName.startsWith("Solaris"));
460    }
461
462    public static boolean isBSD() {
463        return isUnix() && (osName.contains("BSD") || isMacOSX());
464    }
465
466    public static boolean isUnix() {
467        return File.separatorChar == '/';
468    }
469
470    public static boolean isWindows() {
471        return File.separatorChar == '\\';
472    }
473
474    public static boolean isWindows7() {
475        return osName.equals("Windows 7");
476    }
477    /**
478     * Whether to use Unicode versions of Windows APIs rather than ANSI versions
479     * (for functions that haven't been bound yet : has no effect on functions
480     * that have already been bound).<br>
481     * Some Windows APIs such as SendMessage have two versions :
482     * <ul>
483     * <li>one that uses single-byte character strings (SendMessageA, with 'A'
484     * for ANSI strings)</li>
485     * <li>one that uses unicode character strings (SendMessageW, with 'W' for
486     * Wide strings).</li>
487     * </ul>
488     * <br>
489     * In a C/C++ program, this behaviour is controlled by the UNICODE macro
490     * definition.<br>
491     * By default, BridJ will use the Unicode versions. Set this field to false,
492     * set the bridj.useUnicodeVersionOfWindowsAPIs property to "false" or the
493     * BRIDJ_USE_UNICODE_VERSION_OF_WINDOWS_APIS environment variable to "0" to
494     * use the ANSI string version instead.
495     */
496    public static boolean useUnicodeVersionOfWindowsAPIs = !("false".equals(System.getProperty("bridj.useUnicodeVersionOfWindowsAPIs"))
497            || "0".equals(System.getenv("BRIDJ_USE_UNICODE_VERSION_OF_WINDOWS_APIS")));
498
499    private static String getArch() {
500        return arch;
501    }
502
503    /**
504     * Machine (as returned by `uname -m`, except for i686 which is actually
505     * i386), adjusted to the JVM platform (32 or 64 bits)
506     */
507    public static String getMachine() {
508        String arch = getArch();
509        if (arch.equals("amd64") || arch.equals("x86_64")) {
510            if (is64Bits()) {
511                return "x86_64";
512            } else {
513                return "i386"; // we are running a 32 bits JVM on a 64 bits platform
514            }
515        }
516        return arch;
517    }
518
519    public static boolean isAndroid() {
520        return "dalvik".equalsIgnoreCase(System.getProperty("java.vm.name")) && isLinux();
521    }
522
523    public static boolean isArm() {
524        String arch = getArch();
525        return "arm".equals(arch);
526    }
527
528    public static boolean isSparc() {
529        String arch = getArch();
530        return "sparc".equals(arch)
531                || "sparcv9".equals(arch);
532    }
533
534    public static boolean is64Bits() {
535        return is64Bits;
536    }
537
538    public static boolean isAmd64Arch() {
539        String arch = getArch();
540        return arch.equals("x86_64");
541    }
542
543    static List<String> getPossibleFileNames(String name) {
544        List<String> fileNames = new ArrayList<String>(1);
545        if (isWindows()) {
546            fileNames.add(name + ".dll");
547            fileNames.add(name + ".drv");
548        } else {
549            String jniName = "lib" + name + ".jnilib";
550            if (isMacOSX()) {
551                fileNames.add("lib" + name + ".dylib");
552                fileNames.add(jniName);
553            } else {
554                fileNames.add("lib" + name + ".so");
555                fileNames.add(name + ".so");
556                fileNames.add(jniName);
557            }
558        }
559
560        assert !fileNames.isEmpty();
561        if (name.contains(".")) {
562            fileNames.add(name);
563        }
564        return fileNames;
565    }
566
567    static synchronized List<String> getEmbeddedLibraryPaths(String name) {
568        List<String> paths = new ArrayList<String>(embeddedLibraryResourceRoots.size());
569        for (String root : embeddedLibraryResourceRoots) {
570            if (root == null) {
571                continue;
572            }
573
574            if (isWindows()) {
575                paths.add(root + (is64Bits() ? "win64/" : "win32/"));
576            } else if (isMacOSX()) {
577                if (isArm()) {
578                    paths.add(root + "iphoneos_arm32_arm/");
579                } else {
580                    paths.add(root + "darwin_universal/");
581                    if (isAmd64Arch()) {
582                        paths.add(root + "darwin_x64/");
583                    }
584                }
585            } else {
586                if (isAndroid()) {
587                    assert root.equals("libs/");
588                    paths.add(root + "armeabi/"); // Android SDK + NDK-style .so embedding = lib/armeabi/libTest.so
589                } else if (isLinux()) {
590                    if (isArm()) {
591                        paths.add(root + "linux_armhf/");
592                        // To discriminate between hard-float and soft-float, we used to test new File("/lib/arm-linux-gnueabihf").isDirectory().
593                    } else {
594                        paths.add(root + (is64Bits() ? "linux_x64/" : "linux_x86/"));
595                    }
596                } else if (isSolaris()) {
597                    if (isSparc()) {
598                        paths.add(root + (is64Bits() ? "sunos_sparc64/" : "sunos_sparc/"));
599                    } else {
600                        paths.add(root + (is64Bits() ? "sunos_x64/" : "sunos_x86/"));
601                    }
602                }
603            }
604        }
605
606        if (paths.isEmpty()) {
607            throw new RuntimeException("Platform not supported ! (os.name='" + osName + "', os.arch='" + System.getProperty("os.arch") + "')");
608        }
609        return paths;
610    }
611
612    static synchronized List<String> getEmbeddedLibraryResource(String name) {
613        List<String> paths = getEmbeddedLibraryPaths(name);
614        List<String> fileNames = getPossibleFileNames(name);
615        List<String> ret = new ArrayList<String>(paths.size() * fileNames.size());
616        for (String path : paths) {
617            for (String fileName : fileNames) {
618                ret.add(path + fileName);
619            }
620        }
621
622        if (BridJ.veryVerbose) {
623            BridJ.info("Embedded resource paths for library '" + name + "': " + ret);
624        }
625        return ret;
626    }
627
628    static void tryDeleteFilesInSameDirectory(final File legitFile, final Pattern fileNamePattern, long atLeastOlderThanMillis) {
629        final long maxModifiedDateForDeletion = System.currentTimeMillis() - atLeastOlderThanMillis;
630        new Thread(new Runnable() {
631            public void run() {
632                File dir = legitFile.getParentFile();
633                String legitFileName = legitFile.getName();
634                try {
635                    for (String name : dir.list()) {
636                        if (name.equals(legitFileName)) {
637                            continue;
638                        }
639
640                        if (!fileNamePattern.matcher(name).matches()) {
641                            continue;
642                        }
643
644                        File file = new File(dir, name);
645                        if (file.lastModified() > maxModifiedDateForDeletion) {
646                            continue;
647                        }
648
649                        if (file.delete() && BridJ.verbose) {
650                            BridJ.info("Deleted old binary file '" + file + "'");
651                        }
652                    }
653                } catch (SecurityException ex) {
654                    // no right to delete files in that directory
655                    BridJ.warning("Failed to delete files matching '" + fileNamePattern + "' in directory '" + dir + "'", ex);
656                } catch (Throwable ex) {
657                    BridJ.error("Unexpected error : " + ex, ex);
658                }
659            }
660        }).start();
661    }
662    static final long DELETE_OLD_BINARIES_AFTER_MILLIS = 24 * 60 * 60 * 1000; // 24 hours
663
664    static File extractEmbeddedLibraryResource(String name) throws IOException {
665        String firstLibraryResource = null;
666
667        List<String> libraryResources = getEmbeddedLibraryResource(name);
668        if (BridJ.veryVerbose) {
669            BridJ.info("Library resources for " + name + ": " + libraryResources);
670        }               
671        for (String libraryResource : libraryResources) {
672            if (firstLibraryResource == null) {
673                firstLibraryResource = libraryResource;
674            }
675            int i = libraryResource.lastIndexOf('.');
676            int len;
677            byte[] b = new byte[8196];
678            InputStream in = getResourceAsStream(libraryResource);
679            if (in == null) {
680                File f = new File(libraryResource);
681                if (!f.exists()) {
682                    f = new File(f.getName());
683                }
684                if (f.exists()) {
685                    return f.getCanonicalFile();
686                }
687                continue;
688            }
689            String fileName = new File(libraryResource).getName();
690            File libFile = new File(extractedLibrariesTempDir, fileName);
691            OutputStream out = new BufferedOutputStream(new FileOutputStream(libFile));
692            while ((len = in.read(b)) > 0) {
693                out.write(b, 0, len);
694            }
695            out.close();
696            in.close();
697
698            addTemporaryExtractedLibraryFileToDeleteOnExit(libFile);
699            addTemporaryExtractedLibraryFileToDeleteOnExit(libFile.getParentFile());
700
701            return libFile;
702        }
703        return null;
704    }
705    static final int maxTempFileAttempts = 20;
706
707    static File createTempDir(String prefix) throws IOException {
708        File dir;
709        for (int i = 0; i < maxTempFileAttempts; i++) {
710            dir = File.createTempFile(prefix, "");
711            if (dir.delete() && dir.mkdirs()) {
712                return dir;
713            }
714        }
715        throw new RuntimeException("Failed to create temp dir with prefix '" + prefix + "' despite " + maxTempFileAttempts + " attempts!");
716    }
717
718    /**
719     * Opens an URL with the default system action.
720     *
721     * @param url url to open
722     * @throws NoSuchMethodException if opening an URL on the current platform
723     * is not supported
724     */
725    public static final void open(URL url) throws NoSuchMethodException {
726        if (url.getProtocol().equals("file")) {
727            open(new File(url.getFile()));
728        } else {
729            if (Platform.isMacOSX()) {
730                execArgs("open", url.toString());
731            } else if (Platform.isWindows()) {
732                execArgs("rundll32", "url.dll,FileProtocolHandler", url.toString());
733            } else if (Platform.isUnix() && hasUnixCommand("gnome-open")) {
734                execArgs("gnome-open", url.toString());
735            } else if (Platform.isUnix() && hasUnixCommand("konqueror")) {
736                execArgs("konqueror", url.toString());
737            } else if (Platform.isUnix() && hasUnixCommand("mozilla")) {
738                execArgs("mozilla", url.toString());
739            } else {
740                throw new NoSuchMethodException("Cannot open urls on this platform");
741            }
742        }
743    }
744
745    /**
746     * Opens a file with the default system action.
747     *
748     * @param file file to open
749     * @throws NoSuchMethodException if opening a file on the current platform
750     * is not supported
751     */
752    public static final void open(File file) throws NoSuchMethodException {
753        if (Platform.isMacOSX()) {
754            execArgs("open", file.getAbsolutePath());
755        } else if (Platform.isWindows()) {
756            if (file.isDirectory()) {
757                execArgs("explorer", file.getAbsolutePath());
758            } else {
759                execArgs("start", file.getAbsolutePath());
760            }
761        } else if (Platform.isUnix() && hasUnixCommand("gnome-open")) {
762            execArgs("gnome-open", file.toString());
763        } else if (Platform.isUnix() && hasUnixCommand("konqueror")) {
764            execArgs("konqueror", file.toString());
765        } else if (Platform.isSolaris() && file.isDirectory()) {
766            execArgs("/usr/dt/bin/dtfile", "-folder", file.getAbsolutePath());
767        } else {
768            throw new NoSuchMethodException("Cannot open files on this platform");
769        }
770    }
771
772    /**
773     * Show a file in its parent directory, if possible selecting the file (not
774     * possible on all platforms).
775     *
776     * @param file file to show in the system's default file navigator
777     * @throws NoSuchMethodException if showing a file on the current platform
778     * is not supported
779     */
780    public static final void show(File file) throws NoSuchMethodException, IOException {
781        if (Platform.isWindows()) {
782            exec("explorer /e,/select,\"" + file.getCanonicalPath() + "\"");
783        } else {
784            open(file.getAbsoluteFile().getParentFile());
785        }
786    }
787
788    static final void execArgs(String... cmd) throws NoSuchMethodException {
789        try {
790            Runtime.getRuntime().exec(cmd);
791        } catch (Exception ex) {
792            ex.printStackTrace();
793            throw new NoSuchMethodException(ex.toString());
794        }
795    }
796
797    static final void exec(String cmd) throws NoSuchMethodException {
798        try {
799            Runtime.getRuntime().exec(cmd).waitFor();
800        } catch (Exception ex) {
801            ex.printStackTrace();
802            throw new NoSuchMethodException(ex.toString());
803        }
804    }
805
806    static final boolean hasUnixCommand(String name) {
807        try {
808            Process p = Runtime.getRuntime().exec(new String[]{"which", name});
809            return p.waitFor() == 0;
810        } catch (Exception ex) {
811            ex.printStackTrace();
812            return false;
813        }
814    }
815
816    static native int sizeOf_size_t();
817
818    static native int sizeOf_time_t();
819
820    static native int sizeOf_wchar_t();
821
822    static native int sizeOf_ptrdiff_t();
823
824    static native int sizeOf_long();
825
826    static native int getMaxDirectMappingArgCount();
827}