/**
* Author: Sven Gothel
* Copyright (c) 2020 Gothel Software e.K.
* Copyright (c) 2020 ZAFENA AB
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.direct_bt;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.jau.util.VersionUtil;
/**
* One stop {@link BTManager} API entry point.
*
* Further provides access to certain property settings,
* see {@link #DEBUG}, {@link #VERBOSE}, {@link #DEFAULT_BTMODE} and {@link BTManager.Settings}.
*
*/
public class BTFactory {
/**
* Identifier names, allowing {@link BTFactory#getBTManager(ImplementationIdentifier)}
* to initialize the required native libraries and to instantiate the root {@link BTManager} instance.
*
* The implementation class must provide the static factory method
*
*
*/
public final String BluetoothManagerClassName;
/** Native library basename for the implementation native library */
public final String ImplementationNativeLibraryBasename;
/** Native library basename for the Java binding native library */
public final String JavaNativeLibraryBasename;
public ImplementationIdentifier(final String BluetoothManagerClassName,
final String ImplementationNativeLibraryBasename,
final String JavaNativeLibraryBasename) {
this.BluetoothManagerClassName = BluetoothManagerClassName;
this.ImplementationNativeLibraryBasename = ImplementationNativeLibraryBasename;
this.JavaNativeLibraryBasename = JavaNativeLibraryBasename;
}
/**
*
* Implementation compares {@link #BluetoothManagerClassName} only for equality.
*
* {@inheritDoc}
*/
@Override
public boolean equals(final Object other) {
if( null == other || !(other instanceof ImplementationIdentifier) ) {
return false;
}
final ImplementationIdentifier o = (ImplementationIdentifier)other;
return BluetoothManagerClassName.equals( o.BluetoothManagerClassName );
}
@Override
public String toString() {
return "ImplementationIdentifier[class "+BluetoothManagerClassName+
", implLib "+ImplementationNativeLibraryBasename+
", javaLib "+JavaNativeLibraryBasename+"]";
}
}
/**
* {@link ImplementationIdentifier} for direct_bt implementation: {@value}
*
* This value is exposed for convenience, user implementations are welcome.
*
*/
public static final ImplementationIdentifier DirectBTImplementationID = new ImplementationIdentifier("jau.direct_bt.DBTManager", "direct_bt", "javadirect_bt");
private static final List implIDs = new ArrayList();
/**
* Manifest's {@link Attributes.Name#SPECIFICATION_VERSION} or {@code null} if not available.
*/
public static final String getAPIVersion() { return APIVersion; }
private static String APIVersion;
/**
* Manifest's {@link Attributes.Name#IMPLEMENTATION_VERSION} or {@code null} if not available.
*/
public static final String getImplVersion() { return ImplVersion; }
private static String ImplVersion;
/**
* Verbose logging enabled or disabled.
*
* System property {@code org.direct_bt.verbose}, boolean, default {@code false}.
*
*/
public static final boolean VERBOSE;
/**
* Debug logging enabled or disabled.
*
* System property {@code org.direct_bt.debug}, boolean, default {@code false}.
*
*/
public static final boolean DEBUG;
/**
* True if jaulib {@link org.jau.sys.PlatformProps} has been detected.
*/
public static final boolean JAULIB_AVAILABLE;
/**
* True if jaulib {@link #JAULIB_AVAILABLE} and its {@link org.jau.sys.PlatformProps#USE_TEMP_JAR_CACHE} is true,
* i.e. the jar cache is available, enabled and in use.
*/
public static final boolean JAULIB_JARCACHE_USED;
static {
{
final String v = System.getProperty("org.direct_bt.debug", "false");
DEBUG = Boolean.valueOf(v);
}
if( DEBUG ) {
VERBOSE = true;
} else {
final String v = System.getProperty("org.direct_bt.verbose", "false");
VERBOSE = Boolean.valueOf(v);
}
implIDs.add(DirectBTImplementationID);
boolean isJaulibAvail = false;
try {
isJaulibAvail = null != Class.forName("org.jau.sys.PlatformProps", true /* initializeClazz */, BTFactory.class.getClassLoader());
} catch( final Throwable t ) {
if( DEBUG ) {
System.err.println("BTFactory Caught: "+t.getMessage());
t.printStackTrace();
}
}
JAULIB_AVAILABLE = isJaulibAvail;
if( isJaulibAvail ) {
JAULIB_JARCACHE_USED = org.jau.sys.PlatformProps.USE_TEMP_JAR_CACHE;
} else {
JAULIB_JARCACHE_USED = false;
}
if( VERBOSE ) {
System.err.println("Jaulib available: "+JAULIB_AVAILABLE+", JarCache in use: "+JAULIB_JARCACHE_USED);
}
}
private static ImplementationIdentifier initializedID = null;
public static synchronized void checkInitialized() {
if( null == initializedID ) {
throw new IllegalStateException("BluetoothFactory not initialized.");
}
}
public static synchronized boolean isInitialized() {
return null == initializedID;
}
private static synchronized void initLibrary(final ImplementationIdentifier id) {
if( null != initializedID ) {
if( id != initializedID ) {
throw new IllegalStateException("BluetoothFactory already initialized with "+initializedID+", can't override by "+id);
}
return;
}
final ClassLoader cl = BTFactory.class.getClassLoader();
boolean libsLoaded = false;
if( JAULIB_AVAILABLE ) {
if( JAULIB_JARCACHE_USED ) {
try {
org.jau.pkg.JNIJarLibrary.addNativeJarLibs(new Class>[] { BTFactory.class }, null);
} catch (final Exception e0) {
System.err.println("BTFactory Caught "+e0.getClass().getSimpleName()+": "+e0.getMessage()+", while JNILibLoaderBase.addNativeJarLibs(..)");
if( DEBUG ) {
e0.printStackTrace();
}
}
}
try {
if( null != org.jau.sys.dl.NativeLibrary.open(id.ImplementationNativeLibraryBasename,
true /* searchSystemPath */, false /* searchSystemPathFirst */, cl, true /* global */) )
{
org.jau.sys.JNILibrary.loadLibrary(id.JavaNativeLibraryBasename, false, cl);
libsLoaded = true;
}
} catch (final Throwable t) {
System.err.println("Caught "+t.getClass().getSimpleName()+": "+t.getMessage()+", while loading libs..");
if( DEBUG ) {
t.printStackTrace();
}
}
if( DEBUG ) {
System.err.println("Jaulib: Native libs loaded: "+ libsLoaded);
}
}
if( !libsLoaded ) {
try {
final Throwable[] t = { null };
if( !PlatformToolkit.loadLibrary(id.ImplementationNativeLibraryBasename, cl, t) ) {
throw new RuntimeException("Couldn't load native library with basename <"+id.ImplementationNativeLibraryBasename+">", t[0]);
}
if( !PlatformToolkit.loadLibrary(id.JavaNativeLibraryBasename, cl, t) ) {
throw new RuntimeException("Couldn't load native library with basename <"+id.JavaNativeLibraryBasename+">", t[0]);
}
} catch (final Throwable e) {
System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()+", while loading libs (2) ..");
if( DEBUG ) {
e.printStackTrace();
}
throw e; // fwd exception - end here
}
}
// Map all Java properties '[org.]direct_bt.*' and 'direct_bt.*' to native environment.
try {
if( DEBUG ) {
System.err.println("BlootoothFactory: Mapping '[org.|jau.]direct_bt.*' properties to native environment");
}
final Properties props = AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Properties run() {
return System.getProperties();
} });
final Enumeration> enums = props.propertyNames();
while (enums.hasMoreElements()) {
final String key = (String) enums.nextElement();
if( key.startsWith("org.direct_bt.") || key.startsWith("jau.direct_bt.") ||
key.startsWith("direct_bt.") )
{
final String value = props.getProperty(key);
if( DEBUG ) {
System.err.println(" <"+key+"> := <"+value+">");
}
setenv(key, value, true /* overwrite */);
}
}
} catch (final Throwable e) {
System.err.println("Caught exception while forwarding system properties: "+e.getMessage());
e.printStackTrace();
}
try {
final Manifest manifest = getManifest(cl, new String[] { "org.direct_bt" } );
final Attributes mfAttributes = null != manifest ? manifest.getMainAttributes() : null;
// major.minor must match!
final String NAPIVersion = getNativeAPIVersion();
final String JAPIVersion = null != mfAttributes ? mfAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION) : null;
if ( null != JAPIVersion && JAPIVersion.equals(NAPIVersion) == false) {
final String[] NAPIVersionCode = NAPIVersion.split("\\D");
final String[] JAPIVersionCode = JAPIVersion.split("\\D");
if (JAPIVersionCode[0].equals(NAPIVersionCode[0]) == false) {
if (Integer.valueOf(JAPIVersionCode[0]) < Integer.valueOf(NAPIVersionCode[0])) {
throw new RuntimeException("Java library "+JAPIVersion+" < native library "+NAPIVersion+". Please update the Java library.");
} else {
throw new RuntimeException("Native library "+NAPIVersion+" < java library "+JAPIVersion+". Please update the native library.");
}
} else if (JAPIVersionCode[1].equals(NAPIVersionCode[1]) == false) {
if (Integer.valueOf(JAPIVersionCode[1]) < Integer.valueOf(NAPIVersionCode[1])) {
throw new RuntimeException("Java library "+JAPIVersion+" < native library "+NAPIVersion+". Please update the Java library.");
} else {
throw new RuntimeException("Native library "+NAPIVersion+" < java library "+JAPIVersion+". Please update the native library.");
}
}
}
initializedID = id; // initialized!
APIVersion = JAPIVersion;
ImplVersion = null != mfAttributes ? mfAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION) : null;
if( VERBOSE ) {
System.err.println("direct_bt loaded "+id);
System.err.println("direct_bt java api version "+JAPIVersion);
System.err.println("direct_bt native api version "+NAPIVersion);
if( null != mfAttributes ) {
final Attributes.Name[] versionAttributeNames = new Attributes.Name[] {
Attributes.Name.SPECIFICATION_TITLE,
Attributes.Name.SPECIFICATION_VENDOR,
Attributes.Name.SPECIFICATION_VERSION,
Attributes.Name.IMPLEMENTATION_TITLE,
Attributes.Name.IMPLEMENTATION_VENDOR,
Attributes.Name.IMPLEMENTATION_VERSION,
new Attributes.Name("Implementation-Commit") };
for( final Attributes.Name an : versionAttributeNames ) {
System.err.println(" "+an+": "+mfAttributes.getValue(an));
}
} else {
System.err.println(" No Manifest available;");
}
}
} catch (final Throwable e) {
System.err.println("Error querying manifest information.");
e.printStackTrace();
throw e; // fwd exception - end here
}
}
private static synchronized BTManager getBTManager(final Class> factoryImplClass)
throws BTException, NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
final Method m = factoryImplClass.getMethod("getManager");
return (BTManager)m.invoke(null);
}
/**
* Registers a new {@link ImplementationIdentifier} to the internal list.
* The {@code id} is only added if not registered yet.
* @param id the {@link ImplementationIdentifier} to register
* @return {@code true} if the given {@link ImplementationIdentifier} has been newly added,
* otherwise {@code false}.
*/
public static synchronized boolean registerImplementationIdentifier(final ImplementationIdentifier id) {
if( null == id ) {
return false;
}
if( implIDs.contains(id) ) {
return false;
}
return implIDs.add(id);
}
/**
* Returns the matching {@link ImplementationIdentifier} from the internal list or {@code null} if not found.
* @param fqBluetoothManagerImplementationClassName fully qualified class name for the {@link BTManager} implementation
*/
public static synchronized ImplementationIdentifier getImplementationIdentifier(final String fqBluetoothManagerImplementationClassName) {
for(final ImplementationIdentifier id : implIDs) {
if( id.BluetoothManagerClassName.equals(fqBluetoothManagerImplementationClassName) ) {
return id;
}
}
return null;
}
/**
* Returns an initialized BluetoothManager instance using the given {@code fqBluetoothManagerImplementationClassName}
* to lookup a registered {@link ImplementationIdentifier}.
*
* The chosen implementation can't be changed within a running implementation, an exception is thrown if tried.
*
*
* @param fqBluetoothManagerImplementationClassName fully qualified class name for the {@link BTManager} implementation
* @throws BTException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws ClassNotFoundException
* @see {@link #DBusFactoryImplClassName}
* @see {@link #DirectBTFactoryImplClassName}
*/
public static synchronized BTManager getBTManager(final String fqBluetoothManagerImplementationClassName)
throws BTException, NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException
{
final ImplementationIdentifier id = getImplementationIdentifier(fqBluetoothManagerImplementationClassName);
if( null != id ) {
return getBTManager(id);
}
return null;
}
/**
* Returns an initialized BluetoothManager instance using the given {@link ImplementationIdentifier}.
*
* If the {@link ImplementationIdentifier} has not been {@link #registerImplementationIdentifier(ImplementationIdentifier)},
* it will be added to the list.
*
*
* The chosen implementation can't be changed within a running implementation, an exception is thrown if tried.
*
* @param id the specific {@link ImplementationIdentifier}
* @throws BTException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws ClassNotFoundException
* @see {@link #DBusFactoryImplClassName}
* @see {@link #DirectBTFactoryImplClassName}
*/
public static synchronized BTManager getBTManager(final ImplementationIdentifier id)
throws BTException, NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException
{
registerImplementationIdentifier(id);
initLibrary(id);
final Class> factoryImpl = Class.forName(id.BluetoothManagerClassName);
return getBTManager(factoryImpl);
}
/**
* Returns an initialized BluetoothManager instance using the DirectBT implementation.
*
* Issues {@link #getBTManager(ImplementationIdentifier)} using {@link #DirectBTImplementationID}.
*
*
* The chosen implementation can't be changed within a running implementation, an exception is thrown if tried.
*
* @throws BTException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws ClassNotFoundException
*/
public static synchronized BTManager getDirectBTManager()
throws BTException, NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException
{
return getBTManager(DirectBTImplementationID);
}
/**
* Preloads the DirectBT native library w/o instantiating BTManager.
*/
public static synchronized void initDirectBTLibrary() {
initLibrary(DirectBTImplementationID);
}
/**
* Helper function to retrieve a Manifest instance.
*/
public static final Manifest getManifest(final ClassLoader cl, final String[] extensions) {
final Manifest[] extManifests = new Manifest[extensions.length];
try {
final Enumeration resources = cl.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
final URL resURL = resources.nextElement();
if( DEBUG ) {
System.err.println("resource: "+resURL);
}
final InputStream is = resURL.openStream();
final Manifest manifest;
try {
manifest = new Manifest(is);
} finally {
try {
is.close();
} catch (final IOException e) {}
}
final Attributes attributes = manifest.getMainAttributes();
if(attributes != null) {
final String attributesExtName = attributes.getValue( Attributes.Name.EXTENSION_NAME );
for(int i=0; i < extensions.length && null == extManifests[i]; i++) {
final String extension = extensions[i];
if( extension.equals( attributesExtName ) ) {
if( 0 == i ) {
return manifest; // 1st one has highest prio - done
}
extManifests[i] = manifest;
}
}
}
}
} catch (final IOException ex) {
throw new RuntimeException("Unable to read manifest.", ex);
}
for(int i=1; i keys = attr.keySet();
final StringBuilder sb = new StringBuilder();
for(final Iterator