1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32package com.jme3.system;
33
34import java.io.*;
35import java.net.MalformedURLException;
36import java.net.URL;
37import java.net.URLConnection;
38import java.util.logging.Level;
39import java.util.logging.Logger;
40
41/**
42 * Helper class for extracting the natives (dll, so) from the jars.
43 * This class should only be used internally.
44 */
45public final class Natives {
46
47    private static final Logger logger = Logger.getLogger(Natives.class.getName());
48    private static final byte[] buf = new byte[1024];
49    private static File extractionDirOverride = null;
50    private static File extractionDir = null;
51
52    public static void setExtractionDir(String name) {
53        extractionDirOverride = new File(name).getAbsoluteFile();
54    }
55
56    public static File getExtractionDir() {
57        if (extractionDirOverride != null) {
58            return extractionDirOverride;
59        }
60        if (extractionDir == null) {
61            File workingFolder = new File("").getAbsoluteFile();
62            if (!workingFolder.canWrite()) {
63                setStorageExtractionDir();
64            } else {
65                try {
66                    File file = new File(workingFolder.getAbsolutePath() + File.separator + ".jmetestwrite");
67                    file.createNewFile();
68                    file.delete();
69                    extractionDir = workingFolder;
70                } catch (Exception e) {
71                    setStorageExtractionDir();
72                }
73            }
74        }
75        return extractionDir;
76    }
77
78    private static void setStorageExtractionDir() {
79        logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead.");
80        extractionDir = new File(JmeSystem.getStorageFolder(),
81                "natives_" + Integer.toHexString(computeNativesHash()));
82        if (!extractionDir.exists()) {
83            extractionDir.mkdir();
84        }
85    }
86
87    private static int computeNativesHash() {
88        try {
89            String classpath = System.getProperty("java.class.path");
90            URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/Natives.class");
91
92            StringBuilder sb = new StringBuilder(url.toString());
93            if (sb.indexOf("jar:") == 0) {
94                sb.delete(0, 4);
95                sb.delete(sb.indexOf("!"), sb.length());
96                sb.delete(sb.lastIndexOf("/") + 1, sb.length());
97            }
98            try {
99                url = new URL(sb.toString());
100            } catch (MalformedURLException ex) {
101                throw new UnsupportedOperationException(ex);
102            }
103
104            URLConnection conn = url.openConnection();
105            int hash = classpath.hashCode() ^ (int) conn.getLastModified();
106            return hash;
107        } catch (IOException ex) {
108            throw new UnsupportedOperationException(ex);
109        }
110    }
111
112    public static void extractNativeLib(String sysName, String name) throws IOException {
113        extractNativeLib(sysName, name, false, true);
114    }
115
116    public static void extractNativeLib(String sysName, String name, boolean load) throws IOException {
117        extractNativeLib(sysName, name, load, true);
118    }
119
120    public static void extractNativeLib(String sysName, String name, boolean load, boolean warning) throws IOException {
121        String fullname = System.mapLibraryName(name);
122
123        String path = "native/" + sysName + "/" + fullname;
124        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
125
126        if (url == null) {
127            if (!warning) {
128                logger.log(Level.WARNING, "Cannot locate native library: {0}/{1}",
129                        new String[]{sysName, fullname});
130            }
131            return;
132        }
133
134        URLConnection conn = url.openConnection();
135        InputStream in = conn.getInputStream();
136        File targetFile = new File(getExtractionDir(), fullname);
137        OutputStream out = null;
138        try {
139            if (targetFile.exists()) {
140                // OK, compare last modified date of this file to
141                // file in jar
142                long targetLastModified = targetFile.lastModified();
143                long sourceLastModified = conn.getLastModified();
144
145                // Allow ~1 second range for OSes that only support low precision
146                if (targetLastModified + 1000 > sourceLastModified) {
147                    logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", fullname);
148                    return;
149                }
150            }
151
152            out = new FileOutputStream(targetFile);
153            int len;
154            while ((len = in.read(buf)) > 0) {
155                out.write(buf, 0, len);
156            }
157            in.close();
158            in = null;
159            out.close();
160            out = null;
161
162            // NOTE: On OSes that support "Date Created" property,
163            // this will cause the last modified date to be lower than
164            // date created which makes no sense
165            targetFile.setLastModified(conn.getLastModified());
166        } catch (FileNotFoundException ex) {
167            if (ex.getMessage().contains("used by another process")) {
168                return;
169            }
170
171            throw ex;
172        } finally {
173            if (load) {
174                System.load(targetFile.getAbsolutePath());
175            }
176            if(in != null){
177                in.close();
178            }
179            if(out != null){
180                out.close();
181            }
182        }
183        logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile});
184    }
185
186    protected static boolean isUsingNativeBullet() {
187        try {
188            Class clazz = Class.forName("com.jme3.bullet.util.NativeMeshUtil");
189            return clazz != null;
190        } catch (ClassNotFoundException ex) {
191            return false;
192        }
193    }
194
195    public static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException {
196        String renderer = settings.getRenderer();
197        String audioRenderer = settings.getAudioRenderer();
198        boolean needLWJGL = false;
199        boolean needOAL = false;
200        boolean needJInput = false;
201        boolean needNativeBullet = isUsingNativeBullet();
202
203        if (renderer != null) {
204            if (renderer.startsWith("LWJGL")) {
205                needLWJGL = true;
206            }
207        }
208        if (audioRenderer != null) {
209            if (audioRenderer.equals("LWJGL")) {
210                needLWJGL = true;
211                needOAL = true;
212            }
213        }
214        needJInput = settings.useJoysticks();
215
216        String libraryPath = getExtractionDir().toString();
217        if (needLWJGL) {
218            logger.log(Level.INFO, "Extraction Directory: {0}", getExtractionDir().toString());
219
220            // LWJGL supports this feature where
221            // it can load libraries from this path.
222            System.setProperty("org.lwjgl.librarypath", libraryPath);
223        }
224        if (needJInput) {
225            // AND Luckily enough JInput supports the same feature.
226            System.setProperty("net.java.games.input.librarypath", libraryPath);
227        }
228
229        switch (platform) {
230            case Windows64:
231                if (needLWJGL) {
232                    extractNativeLib("windows", "lwjgl64");
233                }
234                if (needOAL) {
235                    extractNativeLib("windows", "OpenAL64");
236                }
237                if (needJInput) {
238                    extractNativeLib("windows", "jinput-dx8_64");
239                    extractNativeLib("windows", "jinput-raw_64");
240                }
241                if (needNativeBullet) {
242                    extractNativeLib("windows", "bulletjme64", true, false);
243                }
244                break;
245            case Windows32:
246                if (needLWJGL) {
247                    extractNativeLib("windows", "lwjgl");
248                }
249                if (needOAL) {
250                    extractNativeLib("windows", "OpenAL32");
251                }
252                if (needJInput) {
253                    extractNativeLib("windows", "jinput-dx8");
254                    extractNativeLib("windows", "jinput-raw");
255                }
256                if (needNativeBullet) {
257                    extractNativeLib("windows", "bulletjme", true, false);
258                }
259                break;
260            case Linux64:
261                if (needLWJGL) {
262                    extractNativeLib("linux", "lwjgl64");
263                }
264                if (needJInput) {
265                    extractNativeLib("linux", "jinput-linux64");
266                }
267                if (needOAL) {
268                    extractNativeLib("linux", "openal64");
269                }
270                if (needNativeBullet) {
271                    extractNativeLib("linux", "bulletjme64", true, false);
272                }
273                break;
274            case Linux32:
275                if (needLWJGL) {
276                    extractNativeLib("linux", "lwjgl");
277                }
278                if (needJInput) {
279                    extractNativeLib("linux", "jinput-linux");
280                }
281                if (needOAL) {
282                    extractNativeLib("linux", "openal");
283                }
284                if (needNativeBullet) {
285                    extractNativeLib("linux", "bulletjme", true, false);
286                }
287                break;
288            case MacOSX_PPC32:
289            case MacOSX32:
290            case MacOSX_PPC64:
291            case MacOSX64:
292                if (needLWJGL) {
293                    extractNativeLib("macosx", "lwjgl");
294                }
295//                if (needOAL)
296//                    extractNativeLib("macosx", "openal");
297                if (needJInput) {
298                    extractNativeLib("macosx", "jinput-osx");
299                }
300                if (needNativeBullet) {
301                    extractNativeLib("macosx", "bulletjme", true, false);
302                }
303                break;
304        }
305    }
306}
307