1
mirror of https://github.com/CarmJos/EasyConfiguration.git synced 2026-06-04 18:48:20 +08:00

feat(record): Support record type values adapter.

This commit is contained in:
2025-06-25 07:10:26 +08:00
parent ad6ab9eb36
commit 608d92f834
3 changed files with 250 additions and 0 deletions
@@ -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<T extends Record> extends ValueAdapter<T> {
public static void register(ConfigurationHolder<?> holder) {
holder.adapters().register(of(Record.class));
}
public static <R extends Record> RecordAdapter<R> of(@NotNull Class<R> type) {
return of(ValueType.of(type));
}
public static <R extends Record> RecordAdapter<R> of(@NotNull ValueType<R> type) {
return new RecordAdapter<>(type);
}
public RecordAdapter(@NotNull ValueType<T> type) {
super(type, serializer(type), parser(type));
}
public static <R extends Record> ValueSerializer<R> serializer(@NotNull ValueType<R> type) {
return (holder, type1, r) -> toMap(holder, r);
}
public static <R extends Record> ValueParser<R> parser(@NotNull ValueType<R> type) {
return (holder, valueType, value) -> {
if (!(value instanceof ConfigureSection section)) return null;
return fromMap(holder, type.getRawType(), section.asMap());
};
}
public static <R extends Record> Map<String, Object> toMap(
@NotNull ConfigurationHolder<?> holder, @NotNull R record
) throws Exception {
Map<String, Object> 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 extends Record> R fromMap(
@NotNull ConfigurationHolder<?> holder,
@NotNull Class<R> type, @NotNull Map<String, Object> 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<String, Object>) 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> T createInstance(Class<T> t, Object[] args) throws Exception {
Class<?>[] paramTypes = Arrays.stream(t.getRecordComponents())
.map(RecordComponent::getType).toArray(Class[]::new);
Constructor<T> constructor = t.getDeclaredConstructor(paramTypes);
constructor.setAccessible(true); // Make sure the constructor is accessible
return constructor.newInstance(args);
}
}
@@ -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<Device> 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<String, Object> map, int indent) {
String indentStr = " ".repeat(indent);
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Map<?, ?> subMap) {
System.out.println(indentStr + entry.getKey() + ":");
printMap((Map<String, Object>) 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<String, Object>) itemMap, indent + 2);
} else {
System.out.println(indentStr + " - " + item);
}
}
} else {
System.out.println(indentStr + entry.getKey() + ": " + entry.getValue());
}
}
}
}