1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.jnigen; 18 19 import java.io.File; 20 import java.io.FileInputStream; 21 import java.io.FileNotFoundException; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.util.HashSet; 26 import java.util.Set; 27 import java.util.zip.CRC32; 28 import java.util.zip.ZipEntry; 29 import java.util.zip.ZipFile; 30 31 /** Loads shared libraries from a natives jar file (desktop) or arm folders (Android). For desktop projects, have the natives jar 32 * in the classpath, for Android projects put the shared libraries in the libs/armeabi and libs/armeabi-v7a folders. 33 * 34 * See {@link AntScriptGenerator}. 35 * 36 * @author mzechner */ 37 public class JniGenSharedLibraryLoader { 38 private static Set<String> loadedLibraries = new HashSet<String>(); 39 private String nativesJar; 40 private SharedLibraryFinder libraryFinder; 41 42 private ZipFile nativesZip = null; 43 JniGenSharedLibraryLoader()44 public JniGenSharedLibraryLoader () { 45 } 46 47 /** Fetches the natives from the given natives jar file. Used for testing a shared lib on the fly, see MyJniClass. 48 * @param nativesJar */ JniGenSharedLibraryLoader(String nativesJar)49 public JniGenSharedLibraryLoader (String nativesJar) { 50 this.nativesJar = nativesJar; 51 } 52 53 /** Fetches the natives from the given natives jar file. Used for testing a shared lib on the fly, see MyJniClass. 54 * @param nativesJar 55 * @param libraryFinder A custom libraryfinder, which enables the use of different dynamic libs naming. */ JniGenSharedLibraryLoader(String nativesJar, SharedLibraryFinder libraryFinder)56 public JniGenSharedLibraryLoader (String nativesJar, SharedLibraryFinder libraryFinder) { 57 this.nativesJar = nativesJar; 58 this.libraryFinder = libraryFinder; 59 if (nativesJar != null) { 60 try { 61 nativesZip = new ZipFile(nativesJar); 62 } catch (IOException e) { 63 nativesZip = null; 64 } 65 } 66 } 67 68 /** Setting a SharedLibraryFinder enables you to load libraries according to a nondefault natives jar layout or library names. 69 * @param libraryFinder */ setSharedLibraryFinder(SharedLibraryFinder libraryFinder)70 public void setSharedLibraryFinder (SharedLibraryFinder libraryFinder) { 71 this.libraryFinder = libraryFinder; 72 if (nativesJar != null) { 73 try { 74 nativesZip = new ZipFile(nativesJar); 75 } catch (IOException e) { 76 nativesZip = null; 77 } 78 } 79 } 80 81 /** Returns a CRC of the remaining bytes in the stream. */ crc(InputStream input)82 public String crc (InputStream input) { 83 if (input == null) return "" + System.nanoTime(); // fallback 84 CRC32 crc = new CRC32(); 85 byte[] buffer = new byte[4096]; 86 try { 87 while (true) { 88 int length = input.read(buffer); 89 if (length == -1) break; 90 crc.update(buffer, 0, length); 91 } 92 } catch (Exception ex) { 93 try { 94 input.close(); 95 } catch (Exception ignored) { 96 } 97 } 98 return Long.toString(crc.getValue()); 99 } 100 loadLibrary(String sharedLibName)101 private boolean loadLibrary (String sharedLibName) { 102 if (sharedLibName == null) return false; 103 104 String path = extractLibrary(sharedLibName); 105 if (path != null) System.load(path); 106 return path != null; 107 } 108 extractLibrary(String sharedLibName)109 private String extractLibrary (String sharedLibName) { 110 String srcCrc = crc(JniGenSharedLibraryLoader.class.getResourceAsStream("/" + sharedLibName)); 111 File nativesDir = new File(System.getProperty("java.io.tmpdir") + "/jnigen/" + srcCrc); 112 File nativeFile = new File(nativesDir, sharedLibName); 113 114 String extractedCrc = null; 115 if (nativeFile.exists()) { 116 try { 117 extractedCrc = crc(new FileInputStream(nativeFile)); 118 } catch (FileNotFoundException ignored) { 119 } 120 } 121 122 if (extractedCrc == null || !extractedCrc.equals(srcCrc)) { 123 try { 124 // Extract native from classpath to temp dir. 125 InputStream input = null; 126 if (nativesJar == null) 127 input = JniGenSharedLibraryLoader.class.getResourceAsStream("/" + sharedLibName); 128 else 129 input = getFromJar(nativesJar, sharedLibName); 130 if (input == null) return null; 131 nativeFile.getParentFile().mkdirs(); 132 FileOutputStream output = new FileOutputStream(nativeFile); 133 byte[] buffer = new byte[4096]; 134 while (true) { 135 int length = input.read(buffer); 136 if (length == -1) break; 137 output.write(buffer, 0, length); 138 } 139 input.close(); 140 output.close(); 141 } catch (IOException ex) { 142 ex.printStackTrace(); 143 throw new RuntimeException(ex); 144 } 145 } 146 return nativeFile.exists() ? nativeFile.getAbsolutePath() : null; 147 } 148 getFromJar(String jarFile, String sharedLibrary)149 private InputStream getFromJar (String jarFile, String sharedLibrary) throws IOException { 150 ZipFile file = new ZipFile(nativesJar); 151 ZipEntry entry = file.getEntry(sharedLibrary); 152 return file.getInputStream(entry); 153 } 154 155 /** Loads a shared library with the given name for the platform the application is running on. The name should not contain a 156 * prefix (e.g. 'lib') or suffix (e.g. '.dll). 157 * @param sharedLibName */ load(String sharedLibName)158 public synchronized void load (String sharedLibName) { 159 if (loadedLibraries.contains(sharedLibName)) return; 160 161 boolean isWindows = System.getProperty("os.name").contains("Windows"); 162 boolean isLinux = System.getProperty("os.name").contains("Linux"); 163 boolean isMac = System.getProperty("os.name").contains("Mac"); 164 boolean isAndroid = false; 165 boolean is64Bit = System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64"); 166 boolean isArm = System.getProperty("os.arch").equals("arm"); 167 168 String vm = System.getProperty("java.vm.name"); 169 if (vm != null && vm.contains("Dalvik")) { 170 isAndroid = true; 171 isWindows = false; 172 isLinux = false; 173 isMac = false; 174 is64Bit = false; 175 } 176 177 boolean loaded = false; 178 if (isWindows) { 179 if (libraryFinder != null) 180 loaded = loadLibrary(libraryFinder.getSharedLibraryNameWindows(sharedLibName, is64Bit, nativesZip)); 181 else if (!is64Bit) 182 loaded = loadLibrary(sharedLibName + ".dll"); 183 else 184 loaded = loadLibrary(sharedLibName + "64.dll"); 185 } 186 if (isLinux) { 187 if (libraryFinder != null) 188 loaded = loadLibrary(libraryFinder.getSharedLibraryNameLinux(sharedLibName, is64Bit, isArm, nativesZip)); 189 else if (!is64Bit) { 190 if (isArm) 191 loaded = loadLibrary("lib" + sharedLibName + "Arm.so"); 192 else 193 loaded = loadLibrary("lib" + sharedLibName + ".so"); 194 } else { 195 if (isArm) 196 loaded = loadLibrary("lib" + sharedLibName + "Arm64.so"); 197 else 198 loaded = loadLibrary("lib" + sharedLibName + "64.so"); 199 } 200 } 201 if (isMac) { 202 if (libraryFinder != null) 203 loaded = loadLibrary(libraryFinder.getSharedLibraryNameMac(sharedLibName, is64Bit, nativesZip)); 204 else if (!is64Bit) 205 loaded = loadLibrary("lib" + sharedLibName + ".dylib"); 206 else 207 loaded = loadLibrary("lib" + sharedLibName + "64.dylib"); 208 } 209 if (isAndroid) { 210 if (libraryFinder != null) 211 System.loadLibrary(libraryFinder.getSharedLibraryNameAndroid(sharedLibName, nativesZip)); 212 else 213 System.loadLibrary(sharedLibName); 214 loaded = true; 215 } 216 if (loaded) loadedLibraries.add(sharedLibName); 217 } 218 } 219