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; + } + + } +}