
org.freedesktop.dbus.messages.ExportedObject Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dbus-java-osgi Show documentation
Show all versions of dbus-java-osgi Show documentation
Improved version of the DBus-Java library provided by freedesktop.org (https://dbus.freedesktop.org/doc/dbus-java/).
This is the OSGi compliant bundle of all required libraries in one bundle.
package org.freedesktop.dbus.messages;
import org.freedesktop.dbus.*;
import org.freedesktop.dbus.annotations.*;
import org.freedesktop.dbus.annotations.DBusProperty.Access;
import org.freedesktop.dbus.connections.AbstractConnection;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.interfaces.Introspectable;
import org.freedesktop.dbus.interfaces.Peer;
import org.freedesktop.dbus.interfaces.Properties;
import org.freedesktop.dbus.propertyref.PropertyRef;
import org.freedesktop.dbus.utils.DBusNamingUtil;
import org.freedesktop.dbus.utils.Util;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.*;
import java.text.MessageFormat;
import java.util.*;
public class ExportedObject {
private final Map methods;
private final Map propertyMethods;
private final String introspectionData;
private final Reference object;
private final Set> implementedInterfaces;
public ExportedObject(DBusInterface _object, boolean _weakreferences) throws DBusException {
object = _weakreferences ? new WeakReference<>(_object) : new StrongReference<>(_object);
methods = new HashMap<>();
propertyMethods = new HashMap<>();
implementedInterfaces = getDBusInterfaces(_object.getClass());
implementedInterfaces.add(Introspectable.class);
implementedInterfaces.add(Peer.class);
this.introspectionData = generateIntrospectionXml(implementedInterfaces);
}
/**
* Generates the introspection data xml for annotations
*
* @param _c input interface/method/signal
* @return xml with annotation definition
*/
protected String generateAnnotationsXml(AnnotatedElement _c) {
StringBuilder ans = new StringBuilder();
for (Annotation a : _c.getDeclaredAnnotations()) {
if (!a.annotationType().isAnnotationPresent(DBusInterfaceName.class)) {
// skip all interfaces not compatible with
// DBusInterface (mother of all DBus
// related interfaces)
continue;
}
Class extends Annotation> t = a.annotationType();
String value = "";
try {
Method m = t.getMethod("value");
if (m != null) {
value = m.invoke(a).toString();
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException _ex) {
LoggerFactory.getLogger(getClass()).trace("Could not find value", _ex);
}
String name = DBusNamingUtil.getAnnotationName(t);
ans.append(" \n");
}
return ans.toString();
}
/**
* Generates the introspection data for the single property.
*
* @param _property input property annotation
* @return xml with property definition
* @throws DBusException in case of unknown data types
*/
protected String generatePropertyXml(DBusProperty _property) throws DBusException {
return generatePropertyXml(_property.name(), _property.type(), _property.access());
}
/**
* Generates the introspection data for the single property.
*
* @param _propertyName name
* @param _propertyTypeClass type class
* @param _access type access
* @return xml with property definition
* @throws DBusException in case of unknown data types
*/
protected String generatePropertyXml(String _propertyName, Class> _propertyTypeClass, Access _access) throws DBusException {
String propertyTypeString;
if (TypeRef.class.isAssignableFrom(_propertyTypeClass)) {
Type actualType = Optional.ofNullable(Util.unwrapTypeRef(_propertyTypeClass))
.orElseThrow(() ->
new DBusException("Could not read TypeRef type for property '" + _propertyName + "'")
);
propertyTypeString = Marshalling.getDBusType(new Type[]{actualType});
} else if (List.class.equals(_propertyTypeClass)) {
// default non generic list types
propertyTypeString = "av";
} else if (Map.class.equals(_propertyTypeClass)) {
// default non generic map type
propertyTypeString = "a{vv}";
} else {
propertyTypeString = Marshalling.getDBusType(new Type[]{_propertyTypeClass});
}
String access = _access.getAccessName();
return " ";
}
/**
* Generates the introspection data for the input interface properties.
*
* @param _clz input interface
* @return xml with property definitions
* @throws DBusException in case of unknown data types
*/
protected String generatePropertiesXml(Class> _clz) throws DBusException {
StringBuilder xml = new StringBuilder();
Map map = new HashMap<>();
DBusProperties properties = _clz.getAnnotation(DBusProperties.class);
if (properties != null) {
for (DBusProperty property : properties.value()) {
if (map.containsKey(property.name())) {
throw new DBusException(MessageFormat.format(
"Property ''{0}'' defined multiple times.", property.name()));
}
map.put(property.name(), new PropertyRef(property));
}
}
DBusProperty property = _clz.getAnnotation(DBusProperty.class);
if (property != null) {
if (map.containsKey(property.name())) {
throw new DBusException(MessageFormat.format(
"Property ''{0}'' defined multiple times.", property.name()));
}
map.put(property.name(), new PropertyRef(property));
}
for (Method method : _clz.getDeclaredMethods()) {
DBusBoundProperty propertyAnnot = method.getAnnotation(DBusBoundProperty.class);
if (propertyAnnot != null) {
String name = DBusNamingUtil.getPropertyName(method);
Access access = PropertyRef.accessForMethod(method);
PropertyRef.checkMethod(method);
Class> type = PropertyRef.typeForMethod(method);
PropertyRef ref = new PropertyRef(name, type, access);
propertyMethods.put(ref, method);
if (map.containsKey(name)) {
PropertyRef existing = map.get(name);
if (access.equals(existing.getAccess())) {
throw new DBusException(MessageFormat.format(
"Property ''{0}'' has access mode ''{1}'' defined multiple times.", name, access));
} else {
map.put(name, new PropertyRef(name, type, Access.READ_WRITE));
}
} else {
map.put(name, ref);
}
}
}
for (var ref : map.values()) {
xml.append(" ").append(generatePropertyXml(ref.getName(), ref.getType(), ref.getAccess())).append("\n");
}
return xml.toString();
}
/**
* Generates the introspection data for the input interface methods
*
* @param _clz input interface
* @return xml with method definitions
*
* @throws DBusException if marshalling fails
*/
protected String generateMethodsXml(Class> _clz) throws DBusException {
StringBuilder sb = new StringBuilder();
for (Method meth : _clz.getDeclaredMethods()) {
if (isExcluded(meth)) {
continue;
}
String methodName = DBusNamingUtil.getMethodName(meth);
if (methodName.length() > AbstractConnection.MAX_NAME_LENGTH) {
throw new DBusException(
"Introspected method name exceeds 255 characters. Cannot export objects with method "
+ methodName);
}
sb.append(" \n");
sb.append(generateAnnotationsXml(meth));
for (Class> ex : meth.getExceptionTypes()) {
if (DBusExecutionException.class.isAssignableFrom(ex)) {
sb.append(" \n");
}
}
StringBuilder ms = new StringBuilder();
for (Type pt : meth.getGenericParameterTypes()) {
for (String s : Marshalling.getDBusType(pt)) {
sb.append(" \n");
ms.append(s);
}
}
if (!Void.TYPE.equals(meth.getGenericReturnType())) {
if (Tuple.class.isAssignableFrom(meth.getReturnType())) {
ParameterizedType tc = (ParameterizedType) meth.getGenericReturnType();
Type[] ts = tc.getActualTypeArguments();
for (Type t : ts) {
if (t != null) {
for (String s : Marshalling.getDBusType(t)) {
sb.append(" \n");
}
}
}
} else if (Object[].class.equals(meth.getGenericReturnType())) {
throw new DBusException("Return type of Object[] cannot be introspected properly");
} else {
for (String s : Marshalling.getDBusType(meth.getGenericReturnType())) {
sb.append(" \n");
}
}
}
sb.append(" \n");
methods.putIfAbsent(new MethodTuple(methodName, ms.toString()), meth);
}
return sb.toString();
}
/**
* Generates the introspection data for the input interface signals
*
* @param _clz input interface
* @return xml with signal definitions
* @throws DBusException in case of invalid signal name / data types
*/
protected String generateSignalsXml(Class> _clz) throws DBusException {
StringBuilder sb = new StringBuilder();
for (Class> sig : _clz.getDeclaredClasses()) {
if (DBusSignal.class.isAssignableFrom(sig)) {
String signalName = DBusNamingUtil.getSignalName(sig);
if (sig.isAnnotationPresent(DBusMemberName.class)) {
DBusSignal.addSignalMap(sig.getSimpleName(), signalName);
}
if (signalName.length() > AbstractConnection.MAX_NAME_LENGTH) {
throw new DBusException(
"Introspected signal name exceeds 255 characters. Cannot export objects with signals of type "
+ signalName);
}
sb.append(" \n");
Constructor> con = sig.getConstructors()[0];
Type[] ts = con.getGenericParameterTypes();
for (int j = 1; j < ts.length; j++) {
for (String s : Marshalling.getDBusType(ts[j])) {
sb.append(" \n");
}
}
sb.append(generateAnnotationsXml(sig));
sb.append(" \n");
}
}
return sb.toString();
}
/**
* Get all valid DBus interfaces which are implemented in a given class.
* The search is performed without recursion taking into account object inheritance.
* A valid DBus interface must directly extend the {@link DBusInterface}.
*
* @param _inputClazz input object class
* @return set of DBus interfaces implements in the input class
*/
protected Set> getDBusInterfaces(Class> _inputClazz) {
Objects.requireNonNull(_inputClazz, "inputClazz must not be null");
Set> result = new LinkedHashSet<>();
// set of already checked classes/interfaces - used to avoid loops/redundant reflection calls
Set> checked = new LinkedHashSet<>();
// queue with classes/interfaces to check
Queue> toCheck = new LinkedList<>();
toCheck.add(_inputClazz);
while (!toCheck.isEmpty()) {
Class> clazz = toCheck.poll();
checked.add(clazz); // avoid checking this class in the next loops
// if it's class and it has super class, queue to check it later
Class> superClass = clazz.getSuperclass();
if (superClass != null && DBusInterface.class.isAssignableFrom(superClass)) {
toCheck.add(superClass);
}
List> interfaces = Arrays.asList(clazz.getInterfaces());
if (interfaces.contains(DBusInterface.class)) {
// clazz is interface and directly extends the DBusInterface
result.add(clazz);
}
// if clazz is using @DBusBoundProperty, always expose the Properties interface
Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(DBusBoundProperty.class))
.findAny()
.ifPresent(x -> toCheck.add(Properties.class));
// iterate over the sub-interfaces and select the ones that extend DBusInterface
// this is required especially for nested interfaces
interfaces.stream()
.filter(DBusInterface.class::isAssignableFrom)
.filter(i -> i != DBusInterface.class)
.filter(i -> !checked.contains(i))
.forEach(toCheck::add);
}
return result;
}
private String generateIntrospectionXml(Set> _interfaces) throws DBusException {
StringBuilder sb = new StringBuilder();
for (Class> iface : _interfaces) {
String ifaceName = DBusNamingUtil.getInterfaceName(iface);
// don't let people export things which don't have a valid D-Bus interface name
if (ifaceName.equals(iface.getSimpleName())) {
throw new DBusException("DBusInterfaces cannot be declared outside a package");
}
if (ifaceName.length() > AbstractConnection.MAX_NAME_LENGTH) {
throw new DBusException(
"Introspected interface name exceeds 255 characters. Cannot export objects of type "
+ ifaceName);
}
// add mapping between class FQCN and name used in annotation (if present)
if (iface.isAnnotationPresent(DBusInterfaceName.class)) {
DBusSignal.addInterfaceMap(iface.getName(), ifaceName);
}
sb.append(" \n");
sb.append(generateAnnotationsXml(iface));
sb.append(generateMethodsXml(iface));
sb.append(generatePropertiesXml(iface));
sb.append(generateSignalsXml(iface));
sb.append(" \n");
}
return sb.toString();
}
public Map getMethods() {
return methods;
}
public Map getPropertyMethods() {
return propertyMethods;
}
public Reference getObject() {
return object;
}
public String getIntrospectiondata() {
return introspectionData;
}
public Set> getImplementedInterfaces() {
return implementedInterfaces;
}
@Override
public String toString() {
return getClass().getSimpleName()
+ " [methodCount=" + methods.size()
+ ", propertyMethodCount=" + propertyMethods.size()
+ ", object=" + (object.get() != null ? Objects.toString(object) : "") + "]";
}
public static boolean isExcluded(Method _meth) {
return _meth == null
|| !Modifier.isPublic(_meth.getModifiers())
|| _meth.isSynthetic() // method created by compiler
|| _meth.isDefault() // method with default implementation (in interfaces), won't work anyway
|| _meth.isBridge() // bridge method created by compiler
|| _meth.getAnnotation(DBusIgnore.class) != null
|| _meth.getAnnotation(DBusBoundProperty.class) != null
|| _meth.getName().equals("getObjectPath") && _meth.getReturnType().equals(String.class)
&& _meth.getParameterCount() == 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy