diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusBoundProperty.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusBoundProperty.java index 52fbbd6d..dd31eb28 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusBoundProperty.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusBoundProperty.java @@ -1,6 +1,7 @@ package org.freedesktop.dbus.annotations; import org.freedesktop.dbus.annotations.DBusProperty.Access; +import org.freedesktop.dbus.annotations.PropertiesEmitsChangedSignal.EmitChangeSignal; import org.freedesktop.dbus.interfaces.Properties; import java.lang.annotation.ElementType; @@ -79,4 +80,26 @@ */ Access access() default Access.READ_WRITE; + /** + * Annotation org.freedesktop.DBus.Property.EmitsChangedSignal. + * From DBUS Specification:
+ * If set to false, the org.freedesktop.DBus.Properties.PropertiesChanged signal,
+ * see the section called “org.freedesktop.DBus.Properties” is not guaranteed to be emitted if the property changes.
+ *
+ * If set to const the property never changes value during the lifetime of the object it belongs to,
+ * and hence the signal is never emitted for it.
+ *
+ * If set to invalidates the signal is emitted but the value is not included in the signal.
+ *
+ * If set to true the signal is emitted with the value included.
+ * The value for the annotation defaults to true if the enclosing interface element does not specify the annotation. + * Otherwise it defaults to the value specified in the enclosing interface element.
+ *
+ * This annotation is intended to be used by code generators to implement client-side caching of property values.
+ * For all properties for which the annotation is set to const, invalidates or true the client may unconditionally
+ * cache the values as the properties don't change or notifications are generated for them if they do. + * + * @return emitChangeSignal + */ + EmitChangeSignal emitChangeSignal() default EmitChangeSignal.TRUE; } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusProperty.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusProperty.java index 16a09558..f4335306 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusProperty.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/DBusProperty.java @@ -1,12 +1,9 @@ package org.freedesktop.dbus.annotations; +import org.freedesktop.dbus.annotations.PropertiesEmitsChangedSignal.EmitChangeSignal; import org.freedesktop.dbus.types.Variant; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** * Appends information about properties in the interface. The annotated properties are added to the introspection data. @@ -46,16 +43,16 @@ String name(); /** - * type of the property, in case of complex types please create custom interface that extends {@link org.freedesktop.dbus.TypeRef} + * type of the property, in case of complex types please create custom interface that extends {@link org.freedesktop.dbus.TypeRef}. * * @return type */ Class type() default Variant.class; /** - * Property access type + * Specifies the access type of this property. * - * @return access + * @return access type, never null */ Access access() default Access.READ_WRITE; @@ -74,4 +71,12 @@ public String getAccessName() { return accessName; } } + + /** + * Property which defines if a signal is emitted when the annotated property was changed. + * + * @return emitChangeSignal, never null + */ + EmitChangeSignal emitChangeSignal() default EmitChangeSignal.TRUE; + } diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/PropertiesEmitsChangedSignal.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/PropertiesEmitsChangedSignal.java index d6ea994b..32b7f279 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/PropertiesEmitsChangedSignal.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/annotations/PropertiesEmitsChangedSignal.java @@ -27,10 +27,21 @@ @Retention(RetentionPolicy.RUNTIME) @DBusInterfaceName("org.freedesktop.DBus.Property.EmitsChangedSignal") public @interface PropertiesEmitsChangedSignal { + EmitChangeSignal value(); + /** + * The different values which are supported for emitting a signal. + */ enum EmitChangeSignal { - TRUE, INVALIDATES, CONST, FALSE; + /** The signal is emitted with the value included. */ + TRUE, + /** The signal is emitted but the value is not included in the signal. */ + INVALIDATES, + /** The property never changes value during the lifetime of the object it belongs to, and hence the signal is never emitted for it. */ + CONST, + /** It is not guaranteed to emit a signal if the property changes. */ + FALSE; @Override public String toString() { diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/messages/ExportedObject.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/messages/ExportedObject.java index c02d71e1..2b417f41 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/messages/ExportedObject.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/messages/ExportedObject.java @@ -3,6 +3,7 @@ import org.freedesktop.dbus.*; import org.freedesktop.dbus.annotations.*; import org.freedesktop.dbus.annotations.DBusProperty.Access; +import org.freedesktop.dbus.annotations.PropertiesEmitsChangedSignal.EmitChangeSignal; import org.freedesktop.dbus.connections.AbstractConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; @@ -62,9 +63,7 @@ protected String generateAnnotationsXml(AnnotatedElement _c) { String value = ""; try { Method m = t.getMethod("value"); - if (m != null) { - value = m.invoke(a).toString(); - } + value = m.invoke(a).toString(); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException _ex) { LoggerFactory.getLogger(getClass()).trace("Could not find value", _ex); } @@ -84,7 +83,7 @@ protected String generateAnnotationsXml(AnnotatedElement _c) { * @throws DBusException in case of unknown data types */ protected String generatePropertyXml(DBusProperty _property) throws DBusException { - return generatePropertyXml(_property.name(), _property.type(), _property.access()); + return generatePropertyXml(_property.name(), _property.type(), _property.access(), _property.emitChangeSignal()); } /** @@ -96,7 +95,7 @@ protected String generatePropertyXml(DBusProperty _property) throws DBusExceptio * @return xml with property definition * @throws DBusException in case of unknown data types */ - protected String generatePropertyXml(String _propertyName, Class _propertyTypeClass, Access _access) throws DBusException { + protected String generatePropertyXml(String _propertyName, Class _propertyTypeClass, Access _access, EmitChangeSignal _emitChangeSignal) throws DBusException { String propertyTypeString; if (TypeRef.class.isAssignableFrom(_propertyTypeClass)) { Type actualType = Optional.ofNullable(Util.unwrapTypeRef(_propertyTypeClass)) @@ -115,6 +114,11 @@ protected String generatePropertyXml(String _propertyName, Class _propertyTyp } String access = _access.getAccessName(); + if (_emitChangeSignal != null && _emitChangeSignal != EmitChangeSignal.TRUE) { + return "" + + "\n \n \n"; + } return ""; } @@ -126,6 +130,11 @@ protected String generatePropertyXml(String _propertyName, Class _propertyTyp * @throws DBusException in case of unknown data types */ protected String generatePropertiesXml(Class _clz) throws DBusException { + + EmitChangeSignal globalChangeSignal = Optional.ofNullable(_clz.getAnnotation(PropertiesEmitsChangedSignal.class)) + .map(e -> e.value()) + .orElse(EmitChangeSignal.TRUE); + StringBuilder xml = new StringBuilder(); Map map = new HashMap<>(); DBusProperties properties = _clz.getAnnotation(DBusProperties.class); @@ -163,7 +172,14 @@ protected String generatePropertiesXml(Class _clz) throws DBusException { 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)); + // if the signal of the annotation is null or the same as the default (TRUE), + // use whatever the global annotation defines + // this means DBusBoundProperty has precedence over the global annotation + EmitChangeSignal emitSignal = Optional.ofNullable(propertyAnnot.emitChangeSignal()) + .filter(s -> s == globalChangeSignal) + .orElse(globalChangeSignal); + + map.put(name, new PropertyRef(name, type, Access.READ_WRITE, emitSignal)); } } else { map.put(name, ref); @@ -171,8 +187,8 @@ protected String generatePropertiesXml(Class _clz) throws DBusException { } } - for (var ref : map.values()) { - xml.append(" ").append(generatePropertyXml(ref.getName(), ref.getType(), ref.getAccess())).append("\n"); + for (PropertyRef ref : map.values()) { + xml.append(" ").append(generatePropertyXml(ref.getName(), ref.getType(), ref.getAccess(), ref.getEmitChangeSignal())).append("\n"); } return xml.toString(); diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropertyRef.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropertyRef.java index 127a2931..65962da2 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropertyRef.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/propertyref/PropertyRef.java @@ -3,6 +3,7 @@ import org.freedesktop.dbus.annotations.DBusBoundProperty; import org.freedesktop.dbus.annotations.DBusProperty; import org.freedesktop.dbus.annotations.DBusProperty.Access; +import org.freedesktop.dbus.annotations.PropertiesEmitsChangedSignal.EmitChangeSignal; import java.lang.reflect.Method; import java.util.Objects; @@ -15,21 +16,31 @@ * @author Brett Smith * @since 5.0.0 - 2023-10-20 */ -public final class PropertyRef { +public final class PropertyRef { private final String name; private final Class type; private final DBusProperty.Access access; + private final EmitChangeSignal emitChangeSignal; public PropertyRef(String _name, Class _type, Access _access) { super(); this.name = _name; this.type = _type; this.access = _access; + this.emitChangeSignal = EmitChangeSignal.TRUE; + } + + public PropertyRef(String _name, Class _type, Access _access, EmitChangeSignal _emitChangeSignal) { + super(); + this.name = _name; + this.type = _type; + this.access = _access; + this.emitChangeSignal = _emitChangeSignal; } public PropertyRef(DBusProperty _property) { - this(_property.name(), _property.type(), _property.access()); + this(_property.name(), _property.type(), _property.access(), _property.emitChangeSignal()); } @Override @@ -64,6 +75,10 @@ public DBusProperty.Access getAccess() { return access; } + public EmitChangeSignal getEmitChangeSignal() { + return emitChangeSignal; + } + public static Access accessForMethod(Method _method) { DBusBoundProperty annotation = _method.getAnnotation(DBusBoundProperty.class); Access access = _method.getName().toLowerCase().startsWith("set") ? Access.WRITE : Access.READ; @@ -95,4 +110,5 @@ public static void checkMethod(Method _method) { throw new IllegalArgumentException("WRITE properties must have exactly 1 parameter, and return void."); } } + }