From 4fdcb5cf7c931a72572589245024b9c4ac3cab24 Mon Sep 17 00:00:00 2001
From: David M
Date: Fri, 16 Aug 2024 15:29:25 +0200
Subject: [PATCH] Added new Helper VariantBuilder
---
README.md | 2 +-
.../org/freedesktop/dbus/Marshalling.java | 7 +-
.../dbus/types/VariantBuilder.java | 107 ++++++++++++++++++
.../dbus/types/VariantBuilderTest.java | 54 +++++++++
4 files changed, 166 insertions(+), 4 deletions(-)
create mode 100644 dbus-java-core/src/main/java/org/freedesktop/dbus/types/VariantBuilder.java
create mode 100644 dbus-java-tests/src/test/java/org/freedesktop/dbus/types/VariantBuilderTest.java
diff --git a/README.md b/README.md
index 81eadd32..afe5d1a6 100644
--- a/README.md
+++ b/README.md
@@ -125,7 +125,7 @@ The library will remain open source and MIT licensed and can still be used, fork
#### Changes
##### Changes in 5.1.1 (not released yet):
- - Nothing yet
+ - Added new Helper class `VariantBuilder` to allow creating Variants which contain Maps or Collections without messing with the required DBus type arguments
##### Changes in 5.1.0 (2024-08-01):
- Use Junit BOM thanks to [spannm](https://github.com/spannm) ([PR#248](https://github.com/hypfvieh/dbus-java/issues/248))
diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/Marshalling.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/Marshalling.java
index c290be69..30ecbf98 100644
--- a/dbus-java-core/src/main/java/org/freedesktop/dbus/Marshalling.java
+++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/Marshalling.java
@@ -240,7 +240,8 @@ private static String[] recursiveGetDBusType(StringBuffer[] _out, Type _dataType
return sigs;
} else if (_dataType instanceof ParameterizedType p) {
if (p.getRawType().equals(Map.class)) {
- _out[_level].append("a{");
+ _out[_level].append(ArgumentType.ARRAY_STRING)
+ .append(ArgumentType.DICT_ENTRY1_STRING);
Type[] t = p.getActualTypeArguments();
try {
String[] s = recursiveGetDBusType(_out, t[0], true, _level + 1);
@@ -257,7 +258,7 @@ private static String[] recursiveGetDBusType(StringBuffer[] _out, Type _dataType
LOGGER.debug("", _ex);
throw new DBusException("Map must have 2 parameters");
}
- _out[_level].append('}');
+ _out[_level].append(ArgumentType.DICT_ENTRY2_STRING);
} else if (List.class.isAssignableFrom((Class>) p.getRawType())) {
for (Type t : p.getActualTypeArguments()) {
if (Type.class.equals(t)) {
@@ -323,7 +324,7 @@ private static String[] recursiveGetDBusType(StringBuffer[] _out, Type _dataType
}
}
}
- _out[_level].append(')');
+ _out[_level].append(ArgumentType.STRUCT2_STRING);
} else if (Enum.class.isAssignableFrom(dataTypeClazz)) {
_out[_level].append((char) ArgumentType.STRING);
diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/types/VariantBuilder.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/types/VariantBuilder.java
new file mode 100644
index 00000000..29e98f76
--- /dev/null
+++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/types/VariantBuilder.java
@@ -0,0 +1,107 @@
+package org.freedesktop.dbus.types;
+
+import org.freedesktop.dbus.Marshalling;
+import org.freedesktop.dbus.messages.constants.ArgumentType;
+
+import java.util.*;
+
+/**
+ * Builder to create Variants for parameterized types like Collections or Maps more easily.
+ *
+ * When working with Variants you can use the default constructor which is taking your
+ * object, but that will not work if you use Maps/Collections because the actual type(s) used inside
+ * of those objects are not known on runtime (due to Java type erasure).
+ *
+ * In this case you can use this builder providing the Class type you want to have inside of your
+ * Variant (e.g. Map.class) and the generic class types used inside of the Map.
+ *
+ *
+ * Example:
+ *
+ * VariantBuilder.of(Map.class).withGenericTypes(String.class, Integer.class).create(myMap);
+ *
+ *
+ *
+ * @since 5.1.1 - 2024-08-16
+ * @author hypfvieh
+ */
+public final class VariantBuilder {
+ private final Class> baseClass;
+
+ private final List> genericTypes = new ArrayList<>();
+
+ private VariantBuilder(Class> _baseClass) {
+ baseClass = _baseClass;
+ }
+
+ /**
+ * Create a new instance using the given class as starting point.
+ *
+ * If you want to create Variant containing a Map or List, this would be Map/List.class.
+ *
+ * @param _clz class to use, never null
+ * @return new instance
+ */
+ public static VariantBuilder of(Class> _clz) {
+ return new VariantBuilder(Objects.requireNonNull(_clz, "Class required"));
+ }
+
+ /**
+ * Add one or more generic types.
+ *
+ * Use this if you want to create a Variant containing a Map, Collection.
+ * You have to provide the data types used inside of your Map/Collection to this method.
+ * E.g. you have Map>Integer,String< than you have to provide Integer.class and String.class to this method.
+ *
+ *
+ * @param _clz generic classes to add
+ * @return this
+ */
+ public VariantBuilder withGenericTypes(Class>... _clz) {
+ if (_clz == null || _clz.length == 0) {
+ return this;
+ }
+ genericTypes.addAll(Arrays.asList(_clz));
+ return this;
+ }
+
+ /**
+ * Create the Variant instance using the provided data object.
+ *
+ * @param Type inside of the Variant
+ * @param _data data to store in Variant
+ *
+ * @return Variant
+ *
+ * @throws IllegalArgumentException when provided data object is not compatible with class given in constuctor
+ * @throws NullPointerException when null is given
+ */
+ public Variant create(X _data) {
+ Objects.requireNonNull(_data, "No data given");
+
+ if (!baseClass.isAssignableFrom(_data.getClass())) {
+ throw new IllegalArgumentException("Given data is not compatible with defined Variant base class");
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ boolean isMap = false;
+ if (Map.class.isAssignableFrom(baseClass)) {
+ sb.append(ArgumentType.ARRAY_STRING).append(ArgumentType.DICT_ENTRY1_STRING);
+ isMap = true;
+ } else {
+ sb.append(Marshalling.convertJavaClassesToSignature(baseClass));
+ }
+
+ genericTypes.stream()
+ .map(Marshalling::convertJavaClassesToSignature)
+ .forEach(sb::append);
+
+ if (isMap) {
+ sb.append(ArgumentType.DICT_ENTRY2_STRING);
+ }
+
+ return new Variant<>(_data, sb.toString());
+ }
+
+}
diff --git a/dbus-java-tests/src/test/java/org/freedesktop/dbus/types/VariantBuilderTest.java b/dbus-java-tests/src/test/java/org/freedesktop/dbus/types/VariantBuilderTest.java
new file mode 100644
index 00000000..cf0bc9b6
--- /dev/null
+++ b/dbus-java-tests/src/test/java/org/freedesktop/dbus/types/VariantBuilderTest.java
@@ -0,0 +1,54 @@
+package org.freedesktop.dbus.types;
+
+import org.freedesktop.dbus.Struct;
+import org.freedesktop.dbus.annotations.Position;
+import org.freedesktop.dbus.test.AbstractBaseTest;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+public class VariantBuilderTest extends AbstractBaseTest {
+
+ @Test
+ void testBuilder() {
+
+ assertEquals(new Variant<>(List.of("str"), "as"),
+ VariantBuilder.of(List.class)
+ .withGenericTypes(String.class)
+ .create(List.of("str")));
+
+ assertEquals(new Variant<>("foo"),
+ VariantBuilder.of(String.class)
+ .create("foo"));
+
+ assertEquals(new Variant<>(Map.of(1, "str"), "a{is}"),
+ VariantBuilder.of(Map.class)
+ .withGenericTypes(Integer.class, String.class)
+ .create(Map.of(1, "str")));
+
+ assertEquals(new Variant<>(List.of(new VbStruct("test", false)), "a(sb)"),
+ VariantBuilder.of(List.class)
+ .withGenericTypes(VbStruct.class)
+ .create(List.of(new VbStruct("test", false))));
+
+ assertThrows(NullPointerException.class, () -> VariantBuilder.of(String.class).create(null));
+ assertThrows(IllegalArgumentException.class, () -> VariantBuilder.of(String.class).create(1));
+ assertThrows(NullPointerException.class, () -> VariantBuilder.of(null));
+
+ }
+
+ public static class VbStruct extends Struct {
+ @Position(0)
+ private String val1;
+ @Position(1)
+ private boolean val2;
+
+ public VbStruct(String _val1, boolean _val2) {
+ super();
+ val1 = _val1;
+ val2 = _val2;
+ }
+
+ }
+}