|
|
|
@@ -1,35 +1,115 @@
|
|
|
|
|
package com.bernard.configurator;
|
|
|
|
|
|
|
|
|
|
import java.awt.Color;
|
|
|
|
|
import java.awt.Dimension;
|
|
|
|
|
import java.awt.Font;
|
|
|
|
|
import java.io.Serializable;
|
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
|
|
|
import java.lang.reflect.Modifier;
|
|
|
|
|
import java.lang.reflect.ParameterizedType;
|
|
|
|
|
import java.lang.reflect.Type;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Map.Entry;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.function.BiFunction;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
import java.util.function.Predicate;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
import com.amihaiemil.eoyaml.Yaml;
|
|
|
|
|
import com.amihaiemil.eoyaml.YamlInput;
|
|
|
|
|
import com.amihaiemil.eoyaml.YamlMapping;
|
|
|
|
|
import com.amihaiemil.eoyaml.YamlMappingBuilder;
|
|
|
|
|
import com.amihaiemil.eoyaml.YamlNode;
|
|
|
|
|
import com.amihaiemil.eoyaml.exceptions.YamlReadingException;
|
|
|
|
|
import com.bernard.configurator.annotations.ConfigClass;
|
|
|
|
|
import com.bernard.configurator.annotations.Option;
|
|
|
|
|
import com.bernard.util.ParseUtils;
|
|
|
|
|
import com.bernard.util.YamlUtils;
|
|
|
|
|
|
|
|
|
|
public class Configurator {
|
|
|
|
|
|
|
|
|
|
private static Map<Class<?>,YamlInterface> yamlInterfaces = new HashMap<>();
|
|
|
|
|
public static Map<Predicate<Type>,YamlInterface> yamlInterfaces = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
//TODO: Ajouter le support des @Contrainte
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
genInterfaces();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void genInterfaces() {
|
|
|
|
|
yamlInterfaces.put(null, null);
|
|
|
|
|
addStringedInterface(byte.class, b -> b.toString(), Byte::parseByte);
|
|
|
|
|
addStringedInterface(short.class, s -> s.toString(), Short::parseShort);
|
|
|
|
|
addStringedInterface(int.class,i -> i.toString(), Integer::parseInt);
|
|
|
|
|
addStringedInterface(long.class, l -> l.toString(), Long::parseLong);
|
|
|
|
|
addStringedInterface(float.class, f -> f.toString(), Float::parseFloat);
|
|
|
|
|
addStringedInterface(double.class, d -> d.toString(), Double::parseDouble);
|
|
|
|
|
addStringedInterface(boolean.class, b -> b.toString(), s -> s.equalsIgnoreCase("true"));
|
|
|
|
|
|
|
|
|
|
addStringedInterface(Byte.class, b -> b.toString(), Byte::parseByte);
|
|
|
|
|
addStringedInterface(Short.class, s -> s.toString(), Short::parseShort);
|
|
|
|
|
addStringedInterface(Integer.class,i -> i.toString(), Integer::parseInt);
|
|
|
|
|
addStringedInterface(Long.class, l -> l.toString(), Long::parseLong);
|
|
|
|
|
addStringedInterface(Float.class, f -> f.toString(), Float::parseFloat);
|
|
|
|
|
addStringedInterface(Double.class, d -> d.toString(), Double::parseDouble);
|
|
|
|
|
addStringedInterface(Boolean.class, b -> b.toString(), s -> s.equalsIgnoreCase("true"));
|
|
|
|
|
|
|
|
|
|
addStringedInterface(String.class, Function.identity(), Function.identity());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addStringedInterface(Color.class, c -> String.format("%#08x", c.getRGB()) , s -> new Color(Integer.parseUnsignedInt(s.substring(2),16)));
|
|
|
|
|
|
|
|
|
|
addStringedInterface(Dimension.class,
|
|
|
|
|
d -> d.width+"x"+d.height,
|
|
|
|
|
s -> {String[] spt = s.split("x");
|
|
|
|
|
return new Dimension(Integer.parseInt(spt[0]), Integer.parseUnsignedInt(spt[1]));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
addInterface(Font.class,
|
|
|
|
|
f -> Yaml.createYamlMappingBuilder()
|
|
|
|
|
.add("name", f.getName())
|
|
|
|
|
.add("style", Integer.toString(f.getStyle()))
|
|
|
|
|
.add("size", Integer.toString(f.getSize()))
|
|
|
|
|
.build(),
|
|
|
|
|
n -> new Font(n.asMapping().string("name"),
|
|
|
|
|
n.asMapping().integer("style"),
|
|
|
|
|
n.asMapping().integer("size")));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Types composés
|
|
|
|
|
// On est obligés de faire des prédicats plus compliqués
|
|
|
|
|
yamlInterfaces.put(t -> List.class.isAssignableFrom(t.getClass()),new ListYamlInterface());
|
|
|
|
|
yamlInterfaces.put(t -> Map.class.isAssignableFrom(t.getClass()),new MapYamlInterface());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static <T> void addInterface(Class<T> clz,
|
|
|
|
|
Function<T, YamlNode> writer,
|
|
|
|
|
Function<YamlNode, T> reader) {
|
|
|
|
|
yamlInterfaces.put(t -> ((Class<?>)t).isAssignableFrom(clz), new YamlInterfaceImpl<>(writer, (n,c)->reader.apply(n)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static <T> void addStringedInterface(Class<T> clz,
|
|
|
|
|
Function<T, String> writer,
|
|
|
|
|
Function<String, T> reader) {
|
|
|
|
|
yamlInterfaces.put(t -> ((Class<?>)t).isAssignableFrom(clz), new YamlInterfaceImpl<>(
|
|
|
|
|
o -> Yaml.createYamlScalarBuilder().addLine(writer.apply(o)).buildPlainScalar(),
|
|
|
|
|
(n,c) -> reader.apply(n.asScalar().value())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static YamlNode objectToNode(Object o) {
|
|
|
|
|
Class<?> theClass = o.getClass();
|
|
|
|
|
if(yamlInterfaces.containsKey(theClass)) {
|
|
|
|
|
return yamlInterfaces.get(theClass).writeToNode(o);
|
|
|
|
|
Optional<Predicate<Type>> tt = yamlInterfaces.keySet().stream().filter(p -> p.test(theClass)).findAny();
|
|
|
|
|
System.out.println("La classe est "+theClass);
|
|
|
|
|
if(tt.isPresent()) {
|
|
|
|
|
return yamlInterfaces.get(tt.get()).writeToNode(o);
|
|
|
|
|
}else {
|
|
|
|
|
|
|
|
|
|
ConfigClass configClass = theClass.getAnnotation(ConfigClass.class);
|
|
|
|
|
|
|
|
|
|
if(configClass!=null) {
|
|
|
|
|
|
|
|
|
|
// On fait un mapping
|
|
|
|
@@ -38,6 +118,9 @@ public class Configurator {
|
|
|
|
|
|
|
|
|
|
Field[] fields = theClass.getFields(); // Seulement les accéssibles
|
|
|
|
|
for(Field f : fields) {
|
|
|
|
|
int fm = f.getModifiers();
|
|
|
|
|
if(!Modifier.isPublic(fm) || Modifier.isStatic(fm) || Modifier.isTransient(fm))
|
|
|
|
|
continue;
|
|
|
|
|
try {
|
|
|
|
|
Option option = f.getAnnotation(Option.class);
|
|
|
|
|
if(option!=null) {
|
|
|
|
@@ -59,8 +142,10 @@ public class Configurator {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//Traitement récursif
|
|
|
|
|
Object of = f.get(o);
|
|
|
|
|
if(of != null)
|
|
|
|
|
ymb = ymb.add(f.getName(), objectToNode(f.get(o)));
|
|
|
|
|
|
|
|
|
|
//Sinon, on ne met pas le champ et c'est la valeur par défaut
|
|
|
|
|
|
|
|
|
|
} catch (InstantiationException
|
|
|
|
|
| IllegalAccessException
|
|
|
|
@@ -68,8 +153,6 @@ public class Configurator {
|
|
|
|
|
| InvocationTargetException
|
|
|
|
|
| NoSuchMethodException
|
|
|
|
|
| SecurityException e) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -86,15 +169,192 @@ public class Configurator {
|
|
|
|
|
.buildFoldedBlockScalar("Objet écrit en base 64");
|
|
|
|
|
|
|
|
|
|
}else {
|
|
|
|
|
System.err.println("Impossible de traduire cette classe "+o.getClass());
|
|
|
|
|
//XXX: prévenir peut-être
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static <T> T readYaml(YamlInput in, Class<? extends T> outClass) {
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
public static <T> T readYaml(YamlNode in, Type theType) {
|
|
|
|
|
|
|
|
|
|
Optional<Predicate<Type>> tt = yamlInterfaces.keySet().stream().filter(p -> p.test(theType)).findAny();
|
|
|
|
|
System.out.println("La classe est "+theType);
|
|
|
|
|
if(tt.isPresent()) {
|
|
|
|
|
try {
|
|
|
|
|
return (T) yamlInterfaces.get(tt.get()).readNode(in,theType);
|
|
|
|
|
}catch(ClassCastException e) {
|
|
|
|
|
System.err.println("La classe "+theType.getTypeName()+" est associée à une mauvaise interface !!!");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}else {
|
|
|
|
|
|
|
|
|
|
Class<?> theClass = (Class<?>)theType;
|
|
|
|
|
|
|
|
|
|
ConfigClass configClass = theClass.getAnnotation(ConfigClass.class);
|
|
|
|
|
|
|
|
|
|
if(configClass!=null) {
|
|
|
|
|
|
|
|
|
|
// On fait un mapping
|
|
|
|
|
YamlMapping ym;
|
|
|
|
|
try {
|
|
|
|
|
ym = in.asMapping();
|
|
|
|
|
}catch (YamlReadingException e){
|
|
|
|
|
System.err.println("Une classe étant une @ConfigClass ne peut être représentée que par un mapping !");
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Field[] fields = theClass.getFields(); //Seulement les accéssibles
|
|
|
|
|
T theObject = null;
|
|
|
|
|
try {
|
|
|
|
|
theObject = (T) theClass.getConstructor().newInstance();
|
|
|
|
|
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
|
|
|
|
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
|
|
|
|
System.err.println("Impossible de créer l'objet de base, il est censé nous fournir"
|
|
|
|
|
+ "un constructeur vide utilisable.");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
for(Field f : fields) {
|
|
|
|
|
int fm = f.getModifiers();
|
|
|
|
|
if(!Modifier.isPublic(fm) || Modifier.isStatic(fm) || Modifier.isTransient(fm))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Option option = f.getAnnotation(Option.class);
|
|
|
|
|
if(option!=null) {
|
|
|
|
|
|
|
|
|
|
if(option.yamlInterface() != YamlInterface.class) {
|
|
|
|
|
YamlInterface theInterface;
|
|
|
|
|
theInterface = option.yamlInterface().getConstructor().newInstance();
|
|
|
|
|
String name;
|
|
|
|
|
if(option.name().isBlank())
|
|
|
|
|
name = f.getName();
|
|
|
|
|
else
|
|
|
|
|
name = option.name();
|
|
|
|
|
YamlNode theNode = ym.value(name);
|
|
|
|
|
Object of = theInterface.readNode(theNode,f.getDeclaringClass());
|
|
|
|
|
f.set(theObject, of);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//On ne continue pas, même traitement que sans option
|
|
|
|
|
}else {
|
|
|
|
|
if(configClass.requireOption())
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//Traitement récursif
|
|
|
|
|
Object of = readYaml(ym.value(f.getName()),f.getType());
|
|
|
|
|
f.set(theObject,of);
|
|
|
|
|
|
|
|
|
|
} catch (InstantiationException
|
|
|
|
|
| IllegalAccessException
|
|
|
|
|
| IllegalArgumentException
|
|
|
|
|
| InvocationTargetException
|
|
|
|
|
| NoSuchMethodException
|
|
|
|
|
| SecurityException e) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return theObject;
|
|
|
|
|
|
|
|
|
|
}else if(Serializable.class.isAssignableFrom(theClass)) {
|
|
|
|
|
|
|
|
|
|
// On doit récupérer un dump en Base64
|
|
|
|
|
String b64desc = in.asScalar().value();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return (T) ParseUtils.fromBase64(b64desc);
|
|
|
|
|
} catch (ClassNotFoundException e) {
|
|
|
|
|
System.err.println("Ce dump n'a pas la bonne version");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}else {
|
|
|
|
|
System.err.println("Impossible de comprendre ce type de classe !");
|
|
|
|
|
//XXX: prévenir peut-être
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class YamlInterfaceImpl<T> implements YamlInterface{
|
|
|
|
|
|
|
|
|
|
Function<T,YamlNode> writer;
|
|
|
|
|
BiFunction<YamlNode,Class<?>,T> reader;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public YamlInterfaceImpl(Function<T, YamlNode> writer, BiFunction<YamlNode,Class<?>, T> reader) {
|
|
|
|
|
this.writer = writer;
|
|
|
|
|
this.reader = reader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
@Override
|
|
|
|
|
public YamlNode writeToNode(Object o) {
|
|
|
|
|
return writer.apply((T) o);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object readNode(YamlNode node, Type clazz) {
|
|
|
|
|
return reader.apply(node,clazz.getClass());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
private static class ListYamlInterface implements YamlInterface{
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public YamlNode writeToNode(Object o) {
|
|
|
|
|
List<?> l = (List<?>) o;
|
|
|
|
|
return l.stream().map(Configurator::objectToNode).collect(YamlUtils.sequenceCollector);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object readNode(YamlNode node, Type theClass) {
|
|
|
|
|
if(node.asSequence().size()==0)
|
|
|
|
|
return new ArrayList<>();
|
|
|
|
|
return YamlUtils.stream(node.asSequence())
|
|
|
|
|
.map(nn -> Configurator.readYaml(nn, ((ParameterizedType)theClass).getActualTypeArguments()[0]))
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class MapYamlInterface implements YamlInterface{
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public YamlNode writeToNode(Object o) {
|
|
|
|
|
Map<?,?> m = (Map<?,?>) o;
|
|
|
|
|
|
|
|
|
|
YamlMappingBuilder ymb = Yaml.createYamlMappingBuilder();
|
|
|
|
|
for(Entry<?,?> e : m.entrySet()) {
|
|
|
|
|
ymb = ymb.add(
|
|
|
|
|
Configurator.objectToNode(e.getKey()),
|
|
|
|
|
Configurator.objectToNode(e.getValue())
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return ymb.build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object readNode(YamlNode node, Type theClass) {
|
|
|
|
|
Set<YamlNode> keys = node.asMapping().keys();
|
|
|
|
|
|
|
|
|
|
Map<? extends Object,? extends Object> theMap = new HashMap<>();
|
|
|
|
|
for(YamlNode key : keys) {
|
|
|
|
|
theMap.put(
|
|
|
|
|
Configurator.readYaml(key, ((ParameterizedType)theClass).getActualTypeArguments()[0]) ,
|
|
|
|
|
Configurator.readYaml(node.asMapping().value(key),
|
|
|
|
|
((ParameterizedType)theClass).getActualTypeArguments()[1])
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return theMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|