diff --git a/features/record/pom.xml b/features/record/pom.xml new file mode 100644 index 0000000..4374b9c --- /dev/null +++ b/features/record/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + cc.carm.lib + configured-parent + 4.1.6 + ../../pom.xml + + + configured-feature-record + jar + + + 16 + 16 + UTF-8 + + + + + + cc.carm.lib + configured-core + ${project.version} + + + + cc.carm.lib + configured-temp + ${project.version} + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + UTF-8 + -parameters + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + diff --git a/features/record/src/main/java/cc/carm/lib/configured/adapter/record/RecordAdapter.java b/features/record/src/main/java/cc/carm/lib/configured/adapter/record/RecordAdapter.java new file mode 100644 index 0000000..dabc43e --- /dev/null +++ b/features/record/src/main/java/cc/carm/lib/configured/adapter/record/RecordAdapter.java @@ -0,0 +1,108 @@ +package cc.carm.lib.configured.adapter.record; + +import cc.carm.lib.configuration.adapter.ValueAdapter; +import cc.carm.lib.configuration.adapter.ValueParser; +import cc.carm.lib.configuration.adapter.ValueSerializer; +import cc.carm.lib.configuration.adapter.ValueType; +import cc.carm.lib.configuration.source.ConfigurationHolder; +import cc.carm.lib.configuration.source.section.ConfigureSection; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.RecordComponent; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +public class RecordAdapter extends ValueAdapter { + + public static void register(ConfigurationHolder holder) { + holder.adapters().register(of(Record.class)); + } + + public static RecordAdapter of(@NotNull Class type) { + return of(ValueType.of(type)); + } + + public static RecordAdapter of(@NotNull ValueType type) { + return new RecordAdapter<>(type); + } + + public RecordAdapter(@NotNull ValueType type) { + super(type, serializer(type), parser(type)); + } + + public static ValueSerializer serializer(@NotNull ValueType type) { + return (holder, type1, r) -> toMap(holder, r); + } + + public static ValueParser parser(@NotNull ValueType type) { + return (holder, valueType, value) -> { + if (!(value instanceof ConfigureSection section)) return null; + return fromMap(holder, type.getRawType(), section.asMap()); + }; + } + + public static Map toMap( + @NotNull ConfigurationHolder holder, @NotNull R record + ) throws Exception { + Map map = new LinkedHashMap<>(); + Class recordClass = record.getClass(); + if (!recordClass.isRecord()) { + throw new IllegalArgumentException("Object is not a record"); + } + try { + for (RecordComponent component : recordClass.getRecordComponents()) { + String name = component.getName(); + Method accessor = component.getAccessor(); + Object value = accessor.invoke(record); + map.put(name, serializeValue(holder, value)); + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to convert record to map", e); + } + return map; + } + + public static R fromMap( + @NotNull ConfigurationHolder holder, + @NotNull Class type, @NotNull Map data + ) throws Exception { + RecordComponent[] components = type.getRecordComponents(); + Object[] args = new Object[components.length]; + for (int i = 0; i < components.length; i++) { + RecordComponent component = components[i]; + args[i] = parseValue(holder, component.getType(), data.get(component.getName())); + } + return createInstance(type, args); + } + + @SuppressWarnings("unchecked") + private static Object parseValue(ConfigurationHolder holder, Class targetType, Object value) throws Exception { + if (value == null) return null; + if (targetType.isRecord()) { + return fromMap(holder, targetType.asSubclass(Record.class), (Map) value); + } + return holder.deserialize(targetType, value); + } + + private static Object serializeValue(ConfigurationHolder holder, Object value) throws Exception { + if (value == null) return null; + if (value.getClass().isRecord()) { + return toMap(holder, (Record) value); + } + return holder.serialize(value); + } + + private static T createInstance(Class t, Object[] args) throws Exception { + Class[] paramTypes = Arrays.stream(t.getRecordComponents()) + .map(RecordComponent::getType).toArray(Class[]::new); + Constructor constructor = t.getDeclaredConstructor(paramTypes); + constructor.setAccessible(true); // Make sure the constructor is accessible + return constructor.newInstance(args); + } + + +} diff --git a/features/record/src/test/java/RecordTest.java b/features/record/src/test/java/RecordTest.java new file mode 100644 index 0000000..421c5ef --- /dev/null +++ b/features/record/src/test/java/RecordTest.java @@ -0,0 +1,76 @@ +import cc.carm.lib.configuration.Configuration; +import cc.carm.lib.configuration.source.ConfigurationHolder; +import cc.carm.lib.configuration.source.temp.TempConfigFactory; +import cc.carm.lib.configuration.value.standard.ConfiguredValue; +import cc.carm.lib.configured.adapter.record.RecordAdapter; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class RecordTest { + + interface Config extends Configuration { + + ConfiguredValue VAL = ConfiguredValue.of(new Device( + "device1", + "My Device", + UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), + new Chip("chip1", "SN123456") + )); + + } + + @Test + public void test() { + + ConfigurationHolder holder = TempConfigFactory.create().build(); + RecordAdapter.register(holder); + holder.initialize(Config.class); + + System.out.println("Device ID: " + Config.VAL.resolve().id()); + System.out.println("Device Name: " + Config.VAL.resolve().name()); + System.out.println("Device Serial: " + Config.VAL.resolve().serial()); + System.out.println("Chip ID: " + Config.VAL.resolve().chip().id()); + System.out.println("Chip Serial: " + Config.VAL.resolve().chip().serialNumber()); + + try { + holder.save(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + printMap(holder.config().asMap(), 0); + + } + + + public record Device(String id, String name, UUID serial, Chip chip) { + } + + public record Chip(String id, String serialNumber) { + } + + static void printMap(Map map, int indent) { + String indentStr = " ".repeat(indent); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof Map subMap) { + System.out.println(indentStr + entry.getKey() + ":"); + printMap((Map) subMap, indent + 2); + } else if (entry.getValue() instanceof List subList) { + System.out.println(indentStr + entry.getKey() + ":"); + for (Object item : subList) { + if (item instanceof Map itemMap) { + printMap((Map) itemMap, indent + 2); + } else { + System.out.println(indentStr + " - " + item); + } + } + } else { + System.out.println(indentStr + entry.getKey() + ": " + entry.getValue()); + } + } + } + +}