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.");
}
}
+
}