Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
PlatformToolkit.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2020 Gothel Software e.K.
4 * Copyright (c) 2020 ZAFENA AB
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25package org.direct_bt;
26
27import java.io.File;
28import java.nio.ByteBuffer;
29import java.nio.ByteOrder;
30import java.nio.IntBuffer;
31import java.nio.ShortBuffer;
32import java.security.PrivilegedAction;
33import java.util.ArrayList;
34import java.util.Iterator;
35import java.util.List;
36import java.util.StringTokenizer;
37
38/**
39 * Miscellaneous platform utilities, allowed to be used within same Java package.
40 */
41final class PlatformToolkit {
42 public enum OSType {
43 UNIX, MACOS, IOS, WINDOWS;
44 }
45 public enum CPUFamily {
46 /** AMD/Intel */
48 /** ARM 32bit */
50 /** ARM 64bit */
52 /** Power PC */
54 /** SPARC */
56 /** Mips */
58 /** PA RISC */
60 /** Itanium */
62 /** Hitachi SuperH */
64 }
65 public enum CPUType {
66 /** ARM 32bit default, usually little endian */
68 /** ARM7EJ, ARM9E, ARM10E, XScale, usually little endian */
70 /** ARM11, usually little endian */
72 /** ARM Cortex, usually little endian */
74 // 4
75
76 /** X86 32bit, little endian */
78 /** PPC 32bit default, usually big endian */
79 PPC( CPUFamily.PPC, true),
80 /** MIPS 32bit, big endian (mips) or little endian (mipsel) */
82 /** Hitachi SuperH 32bit default, ??? endian */
84 /** SPARC 32bit, big endian */
86 // 9
87
88 /** ARM64 default (64bit), usually little endian */
90 /** ARM AArch64 (64bit), usually little endian */
92 /** X86 64bit, little endian */
94 /** PPC 64bit default, usually big endian */
96 /** MIPS 64bit, big endian (mips64) or little endian (mipsel64) ? */
98 /** Itanium 64bit default, little endian */
100 /** SPARC 64bit, big endian */
102 /** PA_RISC2_0 64bit, ??? endian */
104 // 17
105
106 public final CPUFamily family;
107 public final boolean is32Bit;
108
109 CPUType(final CPUFamily type, final boolean is32Bit){
110 this.family = type;
111 this.is32Bit = is32Bit;
112 }
113
114 /**
115 * Returns {@code true} if the given {@link CPUType} is compatible
116 * w/ this one, i.e. at least {@link #family} and {@link #is32Bit} is equal.
117 */
118 public final boolean isCompatible(final CPUType other) {
119 if( null == other ) {
120 return false;
121 } else if( other == this ) {
122 return true;
123 } else {
124 return this.family == other.family &&
125 this.is32Bit == other.is32Bit;
126 }
127 }
128
129 public static final CPUType query(final String cpuABILower) {
130 if( null == cpuABILower ) {
131 throw new IllegalArgumentException("Null cpuABILower arg");
132 }
133 if( cpuABILower.equals("x86") ||
134 cpuABILower.equals("i386") ||
135 cpuABILower.equals("i486") ||
136 cpuABILower.equals("i586") ||
137 cpuABILower.equals("i686") ) {
138 return X86_32;
139 } else if( cpuABILower.equals("x86_64") ||
140 cpuABILower.equals("amd64") ) {
141 return X86_64;
142 } else if( cpuABILower.equals("ia64") ) {
143 return IA64;
144 } else if( cpuABILower.equals("aarch64") ) {
145 return ARM64;
146 } else if( cpuABILower.startsWith("arm") ) {
147 if( cpuABILower.equals("armv8-a") ||
148 cpuABILower.equals("arm-v8-a") ||
149 cpuABILower.equals("arm-8-a") ||
150 cpuABILower.equals("arm64-v8a") ) {
151 return ARMv8_A;
152 } else if( cpuABILower.startsWith("arm64") ) {
153 return ARM64;
154 } else if( cpuABILower.startsWith("armv7") ||
155 cpuABILower.startsWith("arm-v7") ||
156 cpuABILower.startsWith("arm-7") ||
157 cpuABILower.startsWith("armeabi-v7") ) {
158 return ARMv7;
159 } else if( cpuABILower.startsWith("armv5") ||
160 cpuABILower.startsWith("arm-v5") ||
161 cpuABILower.startsWith("arm-5") ) {
162 return ARMv5;
163 } else if( cpuABILower.startsWith("armv6") ||
164 cpuABILower.startsWith("arm-v6") ||
165 cpuABILower.startsWith("arm-6") ) {
166 return ARMv6;
167 } else {
168 return ARM;
169 }
170 } else if( cpuABILower.equals("sparcv9") ) {
171 return SPARCV9_64;
172 } else if( cpuABILower.equals("sparc") ) {
173 return SPARC_32;
174 } else if( cpuABILower.equals("pa_risc2.0") ) {
175 return PA_RISC2_0;
176 } else if( cpuABILower.startsWith("ppc64") ) {
177 return PPC64;
178 } else if( cpuABILower.startsWith("ppc") ) {
179 return PPC;
180 } else if( cpuABILower.startsWith("mips64") ) {
181 return MIPS_64;
182 } else if( cpuABILower.startsWith("mips") ) {
183 return MIPS_32;
184 } else if( cpuABILower.startsWith("superh") ) {
185 return SuperH;
186 } else {
187 throw new RuntimeException("Please port CPUType detection to your platform (CPU_ABI string '" + cpuABILower + "')");
188 }
189 }
190 }
191 public enum ABIType {
192 GENERIC_ABI ( 0x00 ),
193 /** ARM GNU-EABI ARMEL -mfloat-abi=softfp */
195 /** ARM GNU-EABI ARMHF -mfloat-abi=hard */
197 /** ARM EABI AARCH64 (64bit) */
198 EABI_AARCH64 ( 0x03 );
199
200 public final int id;
201
202 ABIType(final int id){
203 this.id = id;
204 }
205
206 /**
207 * Returns {@code true} if the given {@link ABIType} is compatible
208 * w/ this one, i.e. they are equal.
209 */
210 public final boolean isCompatible(final ABIType other) {
211 if( null == other ) {
212 return false;
213 } else {
214 return other == this;
215 }
216 }
217
218 public static final ABIType query(final CPUType cpuType, final String cpuABILower) {
219 if( null == cpuType ) {
220 throw new IllegalArgumentException("Null cpuType");
221 } else if( null == cpuABILower ) {
222 throw new IllegalArgumentException("Null cpuABILower");
223 } else if( CPUFamily.ARM64 == cpuType.family ) {
224 return EABI_AARCH64;
225 } else if( CPUFamily.ARM32 == cpuType.family ) {
226 // FIXME: We only support EABI_GNU_ARMHF on ARM 32bit for now!
227 return EABI_GNU_ARMHF;
228 } else {
229 return GENERIC_ABI;
230 }
231 }
232 }
233
234 /** Lower case system property '{@code os.name}'. */
235 static final String os_name;
236 /** Lower case system property '{@code os.arch}' */
237 static final String os_arch;
238 private static final String user_dir;
239 private static final String java_user_lib_path;
240 private static final String java_boot_lib_path;
241
242 /**
243 * Unique platform denominator composed as '{@link #os_name}' + '-' + '{@link #os_arch}'.
244 */
245 static final String os_and_arch;
246 static final OSType OS_TYPE;
247 private static final boolean isOSX;
248
249 private static final String prefix;
250 private static final String suffix;
251
252 static {
253 {
254 final String[] props =
255 BTFactory.doPrivileged(new PrivilegedAction<String[]>() {
256 @Override
257 public String[] run() {
258 final String[] props = new String[5];
259 int i=0;
260 props[i++] = System.getProperty("os.name").toLowerCase(); // 0
261 props[i++] = System.getProperty("os.arch").toLowerCase(); // 1
262 props[i++] = System.getProperty("user.dir"); // 2
263 props[i++] = System.getProperty("java.library.path"); // 3
264 props[i++] = System.getProperty("sun.boot.library.path"); // 4
265 return props;
266 }
267 });
268 int i=0;
269 os_name = props[i++];
270 final String _os_arch1 = props[i++];
271 user_dir = props[i++];
272 java_user_lib_path = props[i++];
273 java_boot_lib_path = props[i++];
274
275 final boolean LITTLE_ENDIAN = queryIsLittleEndianImpl();
276 final CPUType CPU_TYPE = CPUType.query(_os_arch1);
277 final ABIType ABI_TYPE = ABIType.query(CPU_TYPE, _os_arch1);
278 final String _os_arch2 = getArchName(CPU_TYPE, ABI_TYPE, LITTLE_ENDIAN);
279 os_arch = null != _os_arch2 ? _os_arch2 : _os_arch1;
280 os_and_arch = os_name+"-"+os_arch;
281 if( BTFactory.DEBUG ) {
282 System.err.println("PlatformToolkit: os_name "+os_name+", os_arch ("+_os_arch1+" -> "+_os_arch2+" ->) "+os_arch+" (final), "+
283 "CPU_TYPE "+CPU_TYPE+", ABI_TYPE "+ABI_TYPE+", LITTLE_ENDIAN "+LITTLE_ENDIAN);
284 }
285 }
286
287 if ( os_name.startsWith("mac os x") ||
288 os_name.startsWith("darwin") ) {
289 OS_TYPE = OSType.MACOS;
290 isOSX = true;
291 } else if ( os_name.startsWith("ios") ) {
292 OS_TYPE = OSType.IOS;
293 isOSX = true;
294 } else if ( os_name.startsWith("windows") ) {
295 OS_TYPE = OSType.WINDOWS;
296 isOSX = false;
297 } else {
298 OS_TYPE = OSType.UNIX;
299 isOSX = false;
300 }
301
302 switch (OS_TYPE) {
303 case WINDOWS:
304 prefix = "";
305 suffix = ".dll";
306 break;
307
308 case MACOS:
309 case IOS:
310 prefix = "lib";
311 suffix = ".dylib";
312 break;
313
314 case UNIX:
315 default:
316 prefix = "lib";
317 suffix = ".so";
318 break;
319 }
320 }
321
322 private static final boolean queryIsLittleEndianImpl() {
323 final ByteBuffer tst_b = ByteBuffer.allocateDirect(4 /* SIZEOF_INT */).order(ByteOrder.nativeOrder()); // 32bit in native order
324 final IntBuffer tst_i = tst_b.asIntBuffer();
325 final ShortBuffer tst_s = tst_b.asShortBuffer();
326 tst_i.put(0, 0x0A0B0C0D);
327 return 0x0C0D == tst_s.get(0);
328 }
329 private static final String getArchName(final CPUType cpuType, final ABIType abiType, final boolean littleEndian) {
330 switch( abiType ) {
331 case EABI_GNU_ARMEL:
332 return "arm"; // actually not supported!
333 case EABI_GNU_ARMHF:
334 return "armhf";
335 case EABI_AARCH64:
336 return "arm64";
337 default:
338 break;
339 }
340
341 switch( cpuType ) {
342 case X86_32:
343 return "i386";
344 case PPC:
345 return "ppc";
346 case MIPS_32:
347 return littleEndian ? "mipsel" : "mips";
348 case SuperH:
349 return "superh";
350 case SPARC_32:
351 return "sparc";
352
353 case X86_64:
354 return "amd64";
355 case PPC64:
356 return littleEndian ? "ppc64le" : "ppc64";
357 case MIPS_64:
358 return "mips64";
359 case IA64:
360 return "ia64";
361 case SPARCV9_64:
362 return "sparcv9";
363 case PA_RISC2_0:
364 return "risc2.0";
365 default:
366 return null;
367 }
368 }
369
370 /**
371 * Produces a list of potential full native library pathnames, denoted by its {@code libBaseName}.
372 *
373 * <p>
374 * Basic order of library locations
375 * <pre>
376 * User locations:
377 * - current working directory + {@link #os_and_arch}
378 * - iterate through paths within 'java.library.path', adding {@link #os_and_arch} to each
379 * - current working directory
380 * - iterate through paths within 'java.library.path'
381 *
382 * System locations:
383 * - optional OSX path
384 * - iterate through paths within 'sun.boot.library.path'
385 * </pre>
386 * </p>
387 *
388 * <p>
389 * Example:
390 * <pre>
391 /usr/local/projects/direct_bt/dist-amd64/linux-amd64/libdirect_bt.so (addPath cwd.os_and_arch)
392 /usr/local/projects/direct_bt/dist-amd64/lib/linux-amd64/libdirect_bt.so (addPath java-user-libpath.os_and_arch:0)
393 /usr/local/projects/direct_bt/dist-amd64/libdirect_bt.so (addPath cwd)
394 /usr/local/projects/direct_bt/dist-amd64/lib/libdirect_bt.so (addPath java-user-libpath:0)
395 /usr/lib/jvm/java-14-openjdk-amd64/lib/libdirect_bt.so (addPath java-boot-libpath:0)
396 * </pre>
397 *
398 * @param libBaseName library basename without prefix (like 'lib') or suffix like '.so'.
399 * @param searchSystemPath
400 * @param searchSystemPathFirst
401 * @param loader
402 * @return
403 */
404 private static final List<String> enumerateLibraryPaths(final String libBaseName,
405 final boolean searchSystemPath,
406 final boolean searchSystemPathFirst,
407 final ClassLoader loader) {
408 final List<String> paths = new ArrayList<String>();
409
410 if ( libBaseName == null || libBaseName.length() == 0 ) {
411 return paths;
412 }
413
414 final String libPlatformName = getPlatformName(libBaseName);
415
416 if( searchSystemPath && searchSystemPathFirst ) {
417 // Add probable Mac OS X-specific paths
418 if ( isOSX ) {
419 // Add historical location
420 addPath("osx-1", "/Library/Frameworks/" + libBaseName + ".framework", libPlatformName, paths);
421 // Add current location
422 addPath("osx-2", "/System/Library/Frameworks/" + libBaseName + ".framework", libPlatformName, paths);
423 }
424 addMultiPaths("java-boot-libpath", java_boot_lib_path, libPlatformName, paths);
425 }
426
427 addPath("cwd.os_and_arch", user_dir+File.separator+os_and_arch, libPlatformName, paths);
428 addMultiPaths2("java-user-libpath.os_and_arch", java_user_lib_path, os_and_arch, libPlatformName, paths);
429
430 addPath("cwd", user_dir, libPlatformName, paths);
431 addMultiPaths("java-user-libpath", java_user_lib_path, libPlatformName, paths);
432
433 if( searchSystemPath && !searchSystemPathFirst ) {
434 // Add probable Mac OS X-specific paths
435 if ( isOSX ) {
436 // Add historical location
437 addPath("osx-1", "/Library/Frameworks/" + libBaseName + ".Framework", libPlatformName, paths);
438 // Add current location
439 addPath("osx-2", "/System/Library/Frameworks/" + libBaseName + ".Framework", libPlatformName, paths);
440 }
441 addMultiPaths("java-boot-libpath", java_boot_lib_path, libPlatformName, paths);
442 }
443
444 return paths;
445 }
446
447
448 private static final String getPlatformName(final String libBaseName) {
449 return prefix + libBaseName + suffix;
450 }
451 private static final String getCanonicalPath(final String path) {
452 return BTFactory.doPrivileged(new PrivilegedAction<String>() {
453 @Override
454 public String run() {
455 try {
456 final File f = new File(path);
457 // f.getCanonicalPath() also resolved '.', '..' and symbolic links in contrast to f.getAbsolutePath()
458 return f.getCanonicalPath();
459 } catch (final Throwable t) {
460 if( BTFactory.DEBUG ) {
461 System.err.println("getAbsolutePath("+path+") failed: "+t.getMessage());
462 }
463 return null;
464 }
465 } } );
466 }
467 private static final void addPath(final String msg, final String path, final String platformName, final List<String> paths) {
468 if( null != path && path.length() > 0 ) {
469 final String fullpath = path + File.separator + platformName;
470 final String abspath = getCanonicalPath(fullpath);
471 if( null != abspath ) {
472 final boolean isDup = paths.contains(abspath);
473 if( BTFactory.DEBUG ) {
474 System.err.println(" "+abspath+" (addPath "+msg+", dropped duplicate "+isDup+")");
475 }
476 if( !isDup ) {
477 paths.add(abspath);
478 }
479 }
480 }
481 }
482 private static final void addMultiPaths(final String msg, final String pathList, final String platformName, final List<String> paths) {
483 if( null != pathList && pathList.length() > 0 ) {
484 final StringTokenizer tokenizer = new StringTokenizer(pathList, File.pathSeparator);
485 int i=0;
486 while (tokenizer.hasMoreTokens()) {
487 addPath(msg+":"+i, tokenizer.nextToken(), platformName, paths);
488 i++;
489 }
490 }
491 }
492 private static final void addMultiPaths2(final String msg, final String pathList, final String subDir, final String platformName, final List<String> paths) {
493 if( null != pathList && pathList.length() > 0 && null != subDir && subDir.length() > 0 ) {
494 final StringTokenizer tokenizer = new StringTokenizer(pathList, File.pathSeparator);
495 int i=0;
496 while (tokenizer.hasMoreTokens()) {
497 final String path = tokenizer.nextToken() + File.separator + subDir;
498 addPath(msg+":"+i, path, platformName, paths);
499 i++;
500 }
501 }
502 }
503
504
505 /**
506 * Loads a native library, denoted by its {@code libBaseName}.
507 *
508 * <p>
509 * Basic order of library locations
510 * <pre>
511 * User locations:
512 * - current working directory + {@link #os_and_arch}
513 * - iterate through paths within 'java.library.path', adding {@link #os_and_arch} to each
514 * - current working directory
515 * - iterate through paths within 'java.library.path'
516 *
517 * System locations:
518 * - optional OSX path
519 * - iterate through paths within 'sun.boot.library.path'
520 * </pre>
521 * </p>
522 *
523 * <p>
524 * If the above fails, {@link System#loadLibrary(String)} is called using the plain {@code libBaseName},
525 * exhausting all simple locations and methods.
526 * </p>
527 *
528 * <p>
529 * Example:
530 * <pre>
531 /usr/local/projects/direct_bt/dist-amd64/linux-amd64/libdirect_bt.so (addPath cwd.os_and_arch)
532 /usr/local/projects/direct_bt/dist-amd64/lib/linux-amd64/libdirect_bt.so (addPath java-user-libpath.os_and_arch:0)
533 /usr/local/projects/direct_bt/dist-amd64/libdirect_bt.so (addPath cwd)
534 /usr/local/projects/direct_bt/dist-amd64/lib/libdirect_bt.so (addPath java-user-libpath:0)
535 /usr/lib/jvm/java-14-openjdk-amd64/lib/libdirect_bt.so (addPath java-boot-libpath:0)
536 * </pre>
537 *
538 * @param libBaseName library basename without prefix (like 'lib') or suffix like '.so'.
539 * @param cl
540 * @param t holder to store the last Throwable, if any
541 * @return {@code true} if successful, otherwise {@code false}.
542 */
543 static boolean loadLibrary(final String libBaseName, final ClassLoader cl, final Throwable[] t) {
544 if( BTFactory.DEBUG ) {
545 System.err.println();
546 System.err.println("PlatformToolkit.loadLibrary: libBaseName "+libBaseName+":");
547 }
548 final List<String> possiblePaths = enumerateLibraryPaths(libBaseName, true /* searchSystemPath */, false /* searchSystemPathFirst */, cl);
549 if( BTFactory.DEBUG ) {
550 System.err.println();
551 }
552
553 // Iterate down these and see which one if any we can actually find.
554 for (final Iterator<String> iter = possiblePaths.iterator(); iter.hasNext(); ) {
555 final String path = iter.next();
556 try {
557 System.load(path);
558 if( BTFactory.DEBUG ) {
559 System.err.println(" "+path+" success");
560 }
561 return true;
562 } catch (final Throwable t0) {
563 if( BTFactory.DEBUG ) {
564 System.err.println(" "+path+" failed: "+t0.getMessage());
565 }
566 t[0] = t0;
567 }
568 }
569
570 // Fall back to loadLibrary
571 try {
572 System.loadLibrary(libBaseName);
573 if( BTFactory.DEBUG ) {
574 System.err.println(" "+libBaseName+" success");
575 }
576 return true;
577 } catch (final Throwable t0) {
578 if( BTFactory.DEBUG ) {
579 System.err.println(" "+libBaseName+" failed: "+t0.getMessage());
580 }
581 t[0] = t0;
582 }
583 return false;
584 }
585}
One stop BTManager API entry point.
Definition: BTFactory.java:52
static< T > T doPrivileged(final PrivilegedAction< T > o)
Deprecated call to java.security.AccessController#doPrivileged(PrivilegedAction) w/o warnings.
Definition: BTFactory.java:167
EABI_GNU_ARMHF
ARM GNU-EABI ARMHF -mfloat-abi=hard.
static final ABIType query(final CPUType cpuType, final String cpuABILower)
final boolean isCompatible(final ABIType other)
Returns true if the given ABIType is compatible w/ this one, i.e.
EABI_GNU_ARMEL
ARM GNU-EABI ARMEL -mfloat-abi=softfp.
ARMv6
ARM11, usually little endian.
static final CPUType query(final String cpuABILower)
PPC
PPC 32bit default, usually big endian.
ARM64
ARM64 default (64bit), usually little endian.
ARM
ARM 32bit default, usually little endian.
MIPS_64
MIPS 64bit, big endian (mips64) or little endian (mipsel64) ?
CPUType(final CPUFamily type, final boolean is32Bit)
final boolean isCompatible(final CPUType other)
Returns true if the given CPUType is compatible w/ this one, i.e.
ARMv7
ARM Cortex, usually little endian.
IA64
Itanium 64bit default, little endian.
ARMv8_A
ARM AArch64 (64bit), usually little endian.
SuperH
Hitachi SuperH 32bit default, ??? endian.
PA_RISC2_0
PA_RISC2_0 64bit, ??? endian.
ARMv5
ARM7EJ, ARM9E, ARM10E, XScale, usually little endian.
PPC64
PPC 64bit default, usually big endian.
MIPS_32
MIPS 32bit, big endian (mips) or little endian (mipsel)