jaulib v1.3.0
Jau Support Library (C++, Java, ..)
JNILibrary.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 * Copyright (c) 2011 Gothel Software e.K.
5 * Copyright (c) 2011 JogAmp Community.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 */
26
27package org.jau.sys;
28
29import java.io.File;
30import java.io.IOException;
31import java.net.URISyntaxException;
32import java.security.PrivilegedAction;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.HashSet;
36import java.util.Iterator;
37import java.util.List;
38import java.util.StringTokenizer;
39
40import org.jau.io.IOUtil;
41import org.jau.lang.ReflectionUtil;
42import org.jau.sec.SecurityUtil;
43
44/**
45 * Static JNI Native Libraries handler.
46 */
47public class JNILibrary {
48 public static final boolean DEBUG;
49 protected static final boolean PERF;
50
51 private static final String[] prefixes;
52 private static final String[] suffixes;
53 private static final boolean isOSX;
54 private static String sys_env_lib_path_varname;
55
56 private static final String tjc_name = "org.jau.pkg.cache.TempJarCache";
57 private static final ReflectionUtil.MethodAccessor tjcIsInit;
58 private static final ReflectionUtil.MethodAccessor tjcFindLib;
59 private static final boolean tjcAvail;
60
61 static {
63 DEBUG = Debug.debug("JNILibrary");
64 PERF = DEBUG || PropertyAccess.isPropertyDefined("jau.debug.JNILibrary.Perf", true);
65
66 switch (PlatformProps.OS) {
67 case WINDOWS:
68 prefixes = new String[] { "" };
69 suffixes = new String[] { ".dll" };
70 sys_env_lib_path_varname = "PATH";
71 isOSX = false;
72 break;
73
74 case MACOS:
75 case IOS:
76 prefixes = new String[] { "lib" };
77 suffixes = new String[] { ".dylib" };
78 sys_env_lib_path_varname = "DYLD_LIBRARY_PATH";
79 isOSX = true;
80 break;
81
82 /*
83 case ANDROID:
84 case FREEBSD:
85 case SUNOS:
86 case HPUX:
87 case OPENKODE:
88 case LINUX: */
89 default:
90 prefixes = new String[] { "lib" };
91 suffixes = new String[] { ".so" };
92 sys_env_lib_path_varname = "LD_LIBRARY_PATH";
93 isOSX = false;
94 break;
95 }
96
97 Class<?> tjc = null;
98 try {
99 tjc = ReflectionUtil.getClass(tjc_name, false /* initializeClazz */, JNILibrary.class.getClassLoader());
100 } catch (final Throwable t) {}
101 if( null != tjc ) {
102 tjcIsInit = new ReflectionUtil.MethodAccessor(tjc, "isInitialized", boolean.class);
103 tjcFindLib = new ReflectionUtil.MethodAccessor(tjc, "findLibrary", String.class);
104 tjcAvail = tjcIsInit.available() && tjcFindLib.available();
105 if( DEBUG ) {
106 System.err.println("JNILibrary: Available <"+tjc_name+">, fully avail "+tjcAvail+" (a "+tjcIsInit.available()+", b "+tjcFindLib.available()+")");
107 }
108 } else {
109 tjcIsInit = null;
110 tjcFindLib = null;
111 tjcAvail = false;
112 if( DEBUG ) {
113 System.err.println("JNILibrary: Not available <"+tjc_name+">");
114 }
115 }
116 }
117
118 protected static final Object perfSync = new Object();
119 protected static long perfTotal = 0;
120 protected static long perfCount = 0;
121
122 private static final HashSet<String> loaded = new HashSet<String>();
123
124 /**
125 * Returns the system's environment variable name used for the dynamic linker to resolve library locations, e.g.
126 * - Windows: PATH
127 * - MacOS: DYLD_LIBRARY_PATH
128 * - Unix: LD_LIBRARY_PATH
129 */
130 public static final String getSystemEnvLibraryPathVarname() { return sys_env_lib_path_varname; }
131
132 /**
133 * Returns a list of system paths, from the {@link #getSystemEnvLibraryPathVarname()} variable.
134 */
135 public static final List<String> getSystemEnvLibraryPaths() {
136 final String paths =
137 SecurityUtil.doPrivileged(new PrivilegedAction<String>() {
138 @Override
139 public String run() {
140 return System.getenv(getSystemEnvLibraryPathVarname());
141 }
142 });
143 final List<String> res = new ArrayList<String>();
144 if( null != paths && paths.length() > 0 ) {
145 final StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
146 while (st.hasMoreTokens()) {
147 res.add(st.nextToken());
148 }
149 }
150 return res;
151 }
152
153 public static synchronized boolean isLoaded(final String libName) {
154 return loaded.contains(libName);
155 }
156
157 private static synchronized void addLoaded(final String libName) {
158 loaded.add(libName);
159 if(DEBUG) {
160 System.err.println("JNILibrary: Loaded Native Library: "+libName);
161 }
162 }
163
164 /**
165 * Loads the library specified by libname.<br>
166 * The implementation should ignore, if the library has been loaded already.<br>
167 * @param libname the library to load
168 * @param ignoreError if true, errors during loading the library should be ignored
169 * @param cl optional ClassLoader, used to locate the library
170 * @return true if library loaded successful
171 */
172 public static synchronized boolean loadLibrary(final String libname, final boolean ignoreError, final ClassLoader cl)
173 throws SecurityException, UnsatisfiedLinkError
174 {
175 boolean res = true;
176 if(!isLoaded(libname)) {
177 try {
178 loadLibraryImpl(libname, cl);
179 addLoaded(libname);
180 if(DEBUG) {
181 System.err.println("JNILibrary: loaded "+libname);
182 }
183 } catch (final UnsatisfiedLinkError e) {
184 res = false;
185 if(DEBUG) {
186 e.printStackTrace();
187 }
188 if (!ignoreError && e.getMessage().indexOf("already loaded") < 0) {
189 throw e;
190 }
191 }
192 }
193 return res;
194 }
195
196 /**
197 * Loads the library specified by libname.<br>
198 * Optionally preloads the libraries specified by preload.<br>
199 * The implementation should ignore, if any library has been loaded already.<br>
200 * @param libname the library to load
201 * @param preload the libraries to load before loading the main library if not null
202 * @param preloadIgnoreError if true, errors during loading the preload-libraries should be ignored
203 * @param cl optional ClassLoader, used to locate the library
204 */
205 public static synchronized void loadLibrary(final String libname, final String[] preload, final boolean preloadIgnoreError, final ClassLoader cl)
206 throws SecurityException, UnsatisfiedLinkError
207 {
208 if(!isLoaded(libname)) {
209 if (null!=preload) {
210 for (int i=0; i<preload.length; i++) {
211 loadLibrary(preload[i], preloadIgnoreError, cl);
212 }
213 }
214 loadLibrary(libname, false, cl);
215 }
216 }
217
218 private static void loadLibraryImpl(final String libraryName, final ClassLoader cl) throws SecurityException, UnsatisfiedLinkError {
219 // Note: special-casing JAWT which is built in to the JDK
220 int mode = 0; // 2 - System.load( TempJarCache ), 3 - System.loadLibrary( name ), 4 - System.load( enumLibNames )
221 // System.err.println("sun.boot.library.path=" + Debug.getProperty("sun.boot.library.path", false));
222 final String libraryPath = findLibrary(libraryName, cl); // implicit TempJarCache usage if used/initialized
223 if(DEBUG) {
224 System.err.println("JNILibrary: loadLibraryImpl("+libraryName+"), TempJarCache: "+libraryPath);
225 }
226 if(null != libraryPath) {
227 if(DEBUG) {
228 System.err.println("JNILibrary: System.load("+libraryPath+") - mode 2");
229 }
230 System.load(libraryPath);
231 mode = 2;
232 } else {
233 if(DEBUG) {
234 System.err.println("JNILibrary: System.loadLibrary("+libraryName+") - mode 3: SystemEnvLibraryPaths: "+getSystemEnvLibraryPaths());
235 }
236 try {
237 System.loadLibrary(libraryName);
238 mode = 3;
239 } catch (final UnsatisfiedLinkError ex1) {
240 if(DEBUG) {
241 System.err.println("ERROR mode 3 - "+ex1.getMessage());
242 }
243 final List<String> possiblePaths = enumerateLibraryPaths(libraryName, false /* sys */, false /* sys */, cl);
244 // Iterate down these and see which one if any we can actually find.
245 for (final Iterator<String> iter = possiblePaths.iterator(); 0 == mode && iter.hasNext(); ) {
246 final String path = iter.next();
247 if (DEBUG) {
248 System.err.println("JNILibrary: System.load("+path+") - mode 4");
249 }
250 try {
251 System.load(path);
252 mode = 4;
253 } catch (final UnsatisfiedLinkError ex2) {
254 if(DEBUG) {
255 System.err.println("n/a - "+ex2.getMessage());
256 }
257 if(!iter.hasNext()) {
258 // Avoid misleading final exception, use our own
259 throw new UnsatisfiedLinkError("Couldn't load library '"+libraryName+
260 "' generically including "+getSystemEnvLibraryPaths()+ // mode 3
261 ", nor as "+possiblePaths); // mode 4
262 }
263 }
264 }
265 }
266 }
267 if(DEBUG) {
268 System.err.println("JNILibrary: loadLibraryImpl("+libraryName+"): OK - mode "+mode);
269 }
270 }
271
272 public static final String findLibrary(final String libName, final ClassLoader loader) {
273 String res = null;
274 if( tjcAvail ) { // TempJarCache ..
275 final boolean _tjcIsInit = tjcIsInit.callStaticMethod(true);
276 if( _tjcIsInit ) {
277 res = tjcFindLib.callStaticMethod(libName);
278 if (DEBUG) {
279 System.err.println("JNILibrary.findLibrary(<"+libName+">) (TempJarCache): "+res);
280 }
281 }
282 }
283 return res;
284 }
285
286 /**
287 * Comparison of prefix and suffix of the given libName's basename
288 * is performed case insensitive <br>
289 *
290 * @param libName the full path library name with prefix and suffix
291 * @param isLowerCaseAlready indicates if libName is already lower-case
292 *
293 * @return basename of libName w/o path, ie. /usr/lib/libDrinkBeer.so -> DrinkBeer on Unix systems, but null on Windows.
294 */
295 public static final String isValidNativeLibraryName(final String libName, final boolean isLowerCaseAlready) {
296 final String libBaseName;
297 try {
298 libBaseName = IOUtil.getBasename(libName);
299 } catch (final URISyntaxException uriEx) {
300 throw new IllegalArgumentException(uriEx);
301 }
302 final String libBaseNameLC = isLowerCaseAlready ? libBaseName : libBaseName.toLowerCase();
303 int prefixIdx = -1;
304 for(int i=0; i < prefixes.length && 0 > prefixIdx; i++) {
305 if ( libBaseNameLC.startsWith( prefixes[i] ) ) {
306 prefixIdx = i;
307 }
308 }
309 if( 0 <= prefixIdx ) {
310 for(int i=0; i < suffixes.length; i++) {
311 if ( libBaseNameLC.endsWith( suffixes[i] ) ) {
312 final int s = prefixes[prefixIdx].length();
313 final int e = suffixes[i].length();
314 return libBaseName.substring(s, libBaseName.length()-e);
315 }
316 }
317 }
318 return null;
319 }
320
321 /** Given the base library names (no prefixes/suffixes) for the
322 various platforms, enumerate the possible locations and names of
323 the indicated native library on the system using the system path. */
324 public static final List<String> enumerateLibraryPaths(final String libName,
325 final boolean searchSystemPath,
326 final boolean searchSystemPathFirst,
327 final ClassLoader loader) {
328 return enumerateLibraryPaths(libName, libName, libName,
329 searchSystemPath, searchSystemPathFirst,
330 loader);
331 }
332
333 /** Given the base library names (no prefixes/suffixes) for the
334 various platforms, enumerate the possible locations and names of
335 the indicated native library on the system using the system path. */
336 public static final List<String> enumerateLibraryPaths(final String windowsLibName,
337 final String unixLibName,
338 final String macOSXLibName,
339 final boolean searchSystemPath,
340 final boolean searchSystemPathFirst,
341 final ClassLoader loader) {
342 final List<String> paths = new ArrayList<String>();
343 final String libName = selectName(windowsLibName, unixLibName, macOSXLibName);
344 if (libName == null) {
345 if (DEBUG) {
346 System.err.println("JNILibrary.enumerateLibraryPaths: empty, no libName selected");
347 }
348 return paths;
349 }
350 if (DEBUG) {
351 System.err.println("JNILibrary.enumerateLibraryPaths: libName '"+libName+"'");
352 }
353
354 // Allow user's full path specification to override our building of paths
355 final File file = new File(libName);
356 if (file.isAbsolute()) {
357 paths.add(libName);
358 if (DEBUG) {
359 System.err.println("JNILibrary.enumerateLibraryPaths: done, absolute path found '"+libName+"'");
360 }
361 return paths;
362 }
363
364 final String[] baseNames = buildNames(libName);
365 if (DEBUG) {
366 System.err.println("JNILibrary.enumerateLibraryPaths: baseNames: "+Arrays.toString(baseNames));
367 }
368
369 if( searchSystemPath && searchSystemPathFirst ) {
370 // Add just the library names to use the OS's search algorithm
371 for (int i = 0; i < baseNames.length; i++) {
372 if (DEBUG) {
373 System.err.println("JNILibrary.enumerateLibraryPaths: add.ssp_1st: "+baseNames[i]);
374 }
375 paths.add(baseNames[i]);
376 }
377 // Add probable Mac OS X-specific paths
378 if ( isOSX ) {
379 // Add historical location
380 addAbsPaths("add.ssp_1st_macos_old", "/Library/Frameworks/" + libName + ".framework", baseNames, paths);
381 // Add current location
382 addAbsPaths("add.ssp_1st_macos_cur", "/System/Library/Frameworks/" + libName + ".framework", baseNames, paths);
383 }
384 }
385
386 final String clPath = findLibrary(libName, loader);
387 if (clPath != null) {
388 if (DEBUG) {
389 System.err.println("JNILibrary.enumerateLibraryPaths: add.clp: "+clPath);
390 }
391 paths.add(clPath);
392 }
393
394 // Add entries from java.library.path
395 final String[] javaLibraryPaths =
396 SecurityUtil.doPrivileged(new PrivilegedAction<String[]>() {
397 @Override
398 public String[] run() {
399 int count = 0;
400 final String usrPath = System.getProperty("java.library.path");
401 if(null != usrPath) {
402 count++;
403 }
404 final String sysPath;
405 if( searchSystemPath ) {
406 sysPath = System.getProperty("sun.boot.library.path");
407 if(null != sysPath) {
408 count++;
409 }
410 } else {
411 sysPath = null;
412 }
413 final String[] res = new String[count];
414 int i=0;
415 if( null != sysPath && searchSystemPathFirst ) {
416 res[i++] = sysPath;
417 }
418 if(null != usrPath) {
419 res[i++] = usrPath;
420 }
421 if( null != sysPath && !searchSystemPathFirst ) {
422 res[i++] = sysPath;
423 }
424 return res;
425 }
426 });
427 if ( null != javaLibraryPaths ) {
428 for( int i=0; i < javaLibraryPaths.length; i++ ) {
429 final StringTokenizer tokenizer = new StringTokenizer(javaLibraryPaths[i], File.pathSeparator);
430 while (tokenizer.hasMoreTokens()) {
431 addRelPaths("add.java.library.path", tokenizer.nextToken(), baseNames, paths);
432 }
433 }
434 }
435
436 // Add current working directory
437 final String userDir =
438 SecurityUtil.doPrivileged(new PrivilegedAction<String>() {
439 @Override
440 public String run() {
441 return System.getProperty("user.dir");
442 }
443 });
444 addAbsPaths("add.user.dir.std", userDir, baseNames, paths);
445
446 // Add current working directory + natives/os-arch/ + library names
447 // to handle Bug 1145 cc1 using an unpacked fat-jar
448 addAbsPaths("add.user.dir.fat", userDir+File.separator+"natives"+File.separator+PlatformProps.os_and_arch, baseNames, paths);
449
450 if( searchSystemPath && !searchSystemPathFirst ) {
451 // Add just the library names to use the OS's search algorithm
452 for (int i = 0; i < baseNames.length; i++) {
453 if (DEBUG) {
454 System.err.println("JNILibrary.enumerateLibraryPaths: add.ssp_lst: "+baseNames[i]);
455 }
456 paths.add(baseNames[i]);
457 }
458 // Add probable Mac OS X-specific paths
459 if ( isOSX ) {
460 // Add historical location
461 addAbsPaths("add.ssp_lst_macos_old", "/Library/Frameworks/" + libName + ".Framework", baseNames, paths);
462 // Add current location
463 addAbsPaths("add.ssp_lst_macos_cur", "/System/Library/Frameworks/" + libName + ".Framework", baseNames, paths);
464 }
465 }
466 if (DEBUG) {
467 System.err.println("JNILibrary.enumerateLibraryPaths: done: "+paths.toString());
468 }
469 return paths;
470 }
471
472
473 private static final String selectName(final String windowsLibName,
474 final String unixLibName,
475 final String macOSXLibName) {
476 switch (PlatformProps.OS) {
477 case WINDOWS:
478 return windowsLibName;
479
480 case MACOS:
481 case IOS:
482 return macOSXLibName;
483
484 default:
485 return unixLibName;
486 }
487 }
488
489 private static final String[] buildNames(final String libName) {
490 // If the library name already has the prefix / suffix added
491 // (principally because we want to force a version number on Unix
492 // operating systems) then just return the library name.
493 final String libBaseNameLC;
494 try {
495 libBaseNameLC = IOUtil.getBasename(libName).toLowerCase();
496 } catch (final URISyntaxException uriEx) {
497 throw new IllegalArgumentException(uriEx);
498 }
499
500 int prefixIdx = -1;
501 for(int i=0; i<prefixes.length && 0 > prefixIdx; i++) {
502 if (libBaseNameLC.startsWith(prefixes[i])) {
503 prefixIdx = i;
504 }
505 }
506 if( 0 <= prefixIdx ) {
507 for(int i=0; i<suffixes.length; i++) {
508 if (libBaseNameLC.endsWith(suffixes[i])) {
509 return new String[] { libName };
510 }
511 }
512 int suffixIdx = -1;
513 for(int i=0; i<suffixes.length && 0 > suffixIdx; i++) {
514 suffixIdx = libBaseNameLC.indexOf(suffixes[i]);
515 }
516 boolean ok = true;
517 if (suffixIdx >= 0) {
518 // Check to see if everything after it is a Unix version number
519 for (int i = suffixIdx + suffixes[0].length();
520 i < libName.length();
521 i++) {
522 final char c = libName.charAt(i);
523 if (!(c == '.' || (c >= '0' && c <= '9'))) {
524 ok = false;
525 break;
526 }
527 }
528 if (ok) {
529 return new String[] { libName };
530 }
531 }
532 }
533
534 final String[] res = new String[prefixes.length * suffixes.length + ( isOSX ? 1 : 0 )];
535 int idx = 0;
536 for (int i = 0; i < prefixes.length; i++) {
537 for (int j = 0; j < suffixes.length; j++) {
538 res[idx++] = prefixes[i] + libName + suffixes[j];
539 }
540 }
541 if ( isOSX ) {
542 // Plain library-base-name in Framework folder
543 res[idx++] = libName;
544 }
545 return res;
546 }
547
548 private static final void addRelPaths(final String cause, final String path, final String[] baseNames, final List<String> paths) {
549 final String abs_path;
550 try {
551 final File fpath = new File(path);
552 abs_path = fpath.getCanonicalPath();
553 } catch( final IOException ioe ) {
554 if (DEBUG) {
555 System.err.println("JNILibrary.enumerateLibraryPaths: "+cause+": Exception "+ioe.getMessage()+", from path "+path);
556 }
557 return;
558 }
559 addAbsPaths(cause, abs_path, baseNames, paths);
560 }
561 private static final void addAbsPaths(final String cause, final String abs_path, final String[] baseNames, final List<String> paths) {
562 for (int j = 0; j < baseNames.length; j++) {
563 final String p = abs_path + File.separator + baseNames[j];
564 if (DEBUG) {
565 System.err.println("JNILibrary.enumerateLibraryPaths: "+cause+": "+p+", from path "+abs_path);
566 }
567 paths.add(p);
568 }
569 }
570}
static String getBasename(String fname)
Returns the basename of the given fname w/o directory part.
Definition: IOUtil.java:420
boolean available()
Returns true if method is available, otherwise false.
Utility methods to simplify reflection access.
static final Class<?> getClass(final String clazzName, final boolean initializeClazz, final ClassLoader cl)
Loads and returns the class or null.
static< T > T doPrivileged(final PrivilegedAction< T > o)
Call wrapper for java.security.AccessController#doPrivileged(PrivilegedAction).
Helper routines for logging and debugging.
Definition: Debug.java:35
static final boolean debug(final String subcomponent)
Definition: Debug.java:63
static final void initSingleton()
Ensures static init block has been issues, i.e.
Definition: Debug.java:53
Static JNI Native Libraries handler.
Definition: JNILibrary.java:47
static final Object perfSync
static final String isValidNativeLibraryName(final String libName, final boolean isLowerCaseAlready)
Comparison of prefix and suffix of the given libName's basename is performed case insensitive
static synchronized void loadLibrary(final String libname, final String[] preload, final boolean preloadIgnoreError, final ClassLoader cl)
Loads the library specified by libname.
static final String getSystemEnvLibraryPathVarname()
Returns the system's environment variable name used for the dynamic linker to resolve library locatio...
static final List< String > getSystemEnvLibraryPaths()
Returns a list of system paths, from the getSystemEnvLibraryPathVarname() variable.
static final List< String > enumerateLibraryPaths(final String windowsLibName, final String unixLibName, final String macOSXLibName, final boolean searchSystemPath, final boolean searchSystemPathFirst, final ClassLoader loader)
Given the base library names (no prefixes/suffixes) for the various platforms, enumerate the possible...
static final boolean DEBUG
Definition: JNILibrary.java:48
static final boolean PERF
Definition: JNILibrary.java:49
static synchronized boolean isLoaded(final String libName)
static synchronized boolean loadLibrary(final String libname, final boolean ignoreError, final ClassLoader cl)
Loads the library specified by libname.
static final String findLibrary(final String libName, final ClassLoader loader)
static final List< String > enumerateLibraryPaths(final String libName, final boolean searchSystemPath, final boolean searchSystemPathFirst, final ClassLoader loader)
Given the base library names (no prefixes/suffixes) for the various platforms, enumerate the possible...
Platform Properties derived from Java properties.
static final String os_and_arch
Unique platform denominator composed as 'os_name' + '-' + 'os_arch'.
static final OSType OS
Helper routines for accessing properties.
static final boolean isPropertyDefined(final String property, final boolean jnlpAlias)