Écriture du configurator, et tests avec murderator

This commit is contained in:
Mysaa 2021-07-06 19:46:37 +02:00
parent a369ab678f
commit ef20b6f897
7 changed files with 302 additions and 13 deletions

View File

@ -1,43 +1,126 @@
package com.bernard.configurator; package com.bernard.configurator;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; 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.HashMap;
import java.util.List;
import java.util.Map; 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.Yaml;
import com.amihaiemil.eoyaml.YamlInput; import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder; import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.amihaiemil.eoyaml.YamlNode; import com.amihaiemil.eoyaml.YamlNode;
import com.amihaiemil.eoyaml.exceptions.YamlReadingException;
import com.bernard.configurator.annotations.ConfigClass; import com.bernard.configurator.annotations.ConfigClass;
import com.bernard.configurator.annotations.Option; import com.bernard.configurator.annotations.Option;
import com.bernard.util.ParseUtils; import com.bernard.util.ParseUtils;
import com.bernard.util.YamlUtils;
public class Configurator { 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() { 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) { public static YamlNode objectToNode(Object o) {
Class<?> theClass = o.getClass(); Class<?> theClass = o.getClass();
if(yamlInterfaces.containsKey(theClass)) { Optional<Predicate<Type>> tt = yamlInterfaces.keySet().stream().filter(p -> p.test(theClass)).findAny();
return yamlInterfaces.get(theClass).writeToNode(o); System.out.println("La classe est "+theClass);
if(tt.isPresent()) {
return yamlInterfaces.get(tt.get()).writeToNode(o);
}else { }else {
ConfigClass configClass = theClass.getAnnotation(ConfigClass.class); ConfigClass configClass = theClass.getAnnotation(ConfigClass.class);
if(configClass!=null) { if(configClass!=null) {
// On fait un mapping // On fait un mapping
YamlMappingBuilder ymb = Yaml.createYamlMappingBuilder(); YamlMappingBuilder ymb = Yaml.createYamlMappingBuilder();
ymb = ymb.add("class", theClass.getName()); ymb = ymb.add("class", theClass.getName());
Field[] fields = theClass.getFields(); //Seulement les accéssibles Field[] fields = theClass.getFields(); // Seulement les accéssibles
for(Field f : fields) { for(Field f : fields) {
int fm = f.getModifiers();
if(!Modifier.isPublic(fm) || Modifier.isStatic(fm) || Modifier.isTransient(fm))
continue;
try { try {
Option option = f.getAnnotation(Option.class); Option option = f.getAnnotation(Option.class);
if(option!=null) { if(option!=null) {
@ -59,8 +142,10 @@ public class Configurator {
continue; continue;
} }
//Traitement récursif //Traitement récursif
ymb = ymb.add(f.getName(), objectToNode(f.get(o))); 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 } catch (InstantiationException
| IllegalAccessException | IllegalAccessException
@ -68,8 +153,6 @@ public class Configurator {
| InvocationTargetException | InvocationTargetException
| NoSuchMethodException | NoSuchMethodException
| SecurityException e) { | SecurityException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -86,15 +169,192 @@ public class Configurator {
.buildFoldedBlockScalar("Objet écrit en base 64"); .buildFoldedBlockScalar("Objet écrit en base 64");
}else { }else {
System.err.println("Impossible de traduire cette classe "+o.getClass());
//XXX: prévenir peut-être //XXX: prévenir peut-être
} }
} }
return null; 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; 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;
}
}
} }

View File

@ -1,11 +1,13 @@
package com.bernard.configurator; package com.bernard.configurator;
import java.lang.reflect.Type;
import com.amihaiemil.eoyaml.YamlNode; import com.amihaiemil.eoyaml.YamlNode;
public interface YamlInterface { public interface YamlInterface {
YamlNode writeToNode(Object o); YamlNode writeToNode(Object o);
Object readNode(YamlNode node); Object readNode(YamlNode node,Type theType);
} }

View File

@ -2,8 +2,11 @@ package com.bernard.configurator.annotations;
import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE) @Target(TYPE)
public @interface ConfigClass { public @interface ConfigClass {

View File

@ -2,9 +2,12 @@ package com.bernard.configurator.annotations;
import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.FIELD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.function.Predicate; import java.util.function.Predicate;
@Retention(RetentionPolicy.RUNTIME)
@Target(FIELD) @Target(FIELD)
public @interface Contrainte { public @interface Contrainte {

View File

@ -2,10 +2,13 @@ package com.bernard.configurator.annotations;
import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.FIELD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import com.bernard.configurator.YamlInterface; import com.bernard.configurator.YamlInterface;
@Retention(RetentionPolicy.RUNTIME)
@Target(FIELD) @Target(FIELD)
public @interface Option { public @interface Option {

View File

@ -8,5 +8,15 @@ public class FuncUtils {
return f.compose(g); return f.compose(g);
} }
public static Class<?> unprimitive(Class<?> clz){
if(clz==byte.class)return Byte.class;
if(clz==short.class)return Short.class;
if(clz==int.class)return Integer.class;
if(clz==long.class)return Long.class;
if(clz==float.class)return Float.class;
if(clz==double.class)return Double.class;
if(clz==boolean.class)return Boolean.class;
return clz;
}
} }

View File

@ -4,11 +4,15 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collector; import java.util.stream.Collector;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.amihaiemil.eoyaml.Yaml; import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping; import com.amihaiemil.eoyaml.YamlMapping;
@ -61,6 +65,10 @@ public class YamlUtils {
return ysbb; return ysbb;
} }
public static final Stream<YamlNode> stream(YamlSequence seq) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(seq.iterator(), Spliterator.ORDERED), false);
}
public static final CollectorImpl<YamlNode, YamlSequenceBuilder, YamlSequence> sequenceCollector = public static final CollectorImpl<YamlNode, YamlSequenceBuilder, YamlSequence> sequenceCollector =
new CollectorImpl<>( new CollectorImpl<>(
Yaml::createYamlSequenceBuilder, Yaml::createYamlSequenceBuilder,