From ef20b6f897c5ede4b975f83a2567d5b9c58f2466 Mon Sep 17 00:00:00 2001 From: Mysaa Date: Tue, 6 Jul 2021 19:46:37 +0200 Subject: [PATCH] =?UTF-8?q?=C3=89criture=20du=20configurator,=20et=20tests?= =?UTF-8?q?=20avec=20murderator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bernard/configurator/Configurator.java | 284 +++++++++++++++++- .../bernard/configurator/YamlInterface.java | 4 +- .../configurator/annotations/ConfigClass.java | 3 + .../configurator/annotations/Contrainte.java | 3 + .../configurator/annotations/Option.java | 3 + src/com/bernard/util/FuncUtils.java | 10 + src/com/bernard/util/YamlUtils.java | 8 + 7 files changed, 302 insertions(+), 13 deletions(-) diff --git a/src/com/bernard/configurator/Configurator.java b/src/com/bernard/configurator/Configurator.java index f47d297..e8b598e 100644 --- a/src/com/bernard/configurator/Configurator.java +++ b/src/com/bernard/configurator/Configurator.java @@ -1,43 +1,126 @@ 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,YamlInterface> yamlInterfaces = new HashMap<>(); + public static Map,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 void addInterface(Class clz, + Function writer, + Function reader) { + yamlInterfaces.put(t -> ((Class)t).isAssignableFrom(clz), new YamlInterfaceImpl<>(writer, (n,c)->reader.apply(n))); + } + + public static void addStringedInterface(Class clz, + Function writer, + Function 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> 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 YamlMappingBuilder ymb = Yaml.createYamlMappingBuilder(); 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) { + 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 - 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 | 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 readYaml(YamlInput in, Class outClass) { + @SuppressWarnings("unchecked") + public static T readYaml(YamlNode in, Type theType) { + + Optional> 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 implements YamlInterface{ + + Function writer; + BiFunction,T> reader; + + + + public YamlInterfaceImpl(Function writer, BiFunction, 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 keys = node.asMapping().keys(); + + Map 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; + } + + } } diff --git a/src/com/bernard/configurator/YamlInterface.java b/src/com/bernard/configurator/YamlInterface.java index c1f9956..51ab7eb 100644 --- a/src/com/bernard/configurator/YamlInterface.java +++ b/src/com/bernard/configurator/YamlInterface.java @@ -1,11 +1,13 @@ package com.bernard.configurator; +import java.lang.reflect.Type; + import com.amihaiemil.eoyaml.YamlNode; public interface YamlInterface { YamlNode writeToNode(Object o); - Object readNode(YamlNode node); + Object readNode(YamlNode node,Type theType); } diff --git a/src/com/bernard/configurator/annotations/ConfigClass.java b/src/com/bernard/configurator/annotations/ConfigClass.java index 02cb2bf..9b332c6 100644 --- a/src/com/bernard/configurator/annotations/ConfigClass.java +++ b/src/com/bernard/configurator/annotations/ConfigClass.java @@ -2,8 +2,11 @@ package com.bernard.configurator.annotations; import static java.lang.annotation.ElementType.TYPE; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +@Retention(RetentionPolicy.RUNTIME) @Target(TYPE) public @interface ConfigClass { diff --git a/src/com/bernard/configurator/annotations/Contrainte.java b/src/com/bernard/configurator/annotations/Contrainte.java index c7a13ed..02926a0 100644 --- a/src/com/bernard/configurator/annotations/Contrainte.java +++ b/src/com/bernard/configurator/annotations/Contrainte.java @@ -2,9 +2,12 @@ package com.bernard.configurator.annotations; import static java.lang.annotation.ElementType.FIELD; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.function.Predicate; +@Retention(RetentionPolicy.RUNTIME) @Target(FIELD) public @interface Contrainte { diff --git a/src/com/bernard/configurator/annotations/Option.java b/src/com/bernard/configurator/annotations/Option.java index 44456f6..2e9025c 100644 --- a/src/com/bernard/configurator/annotations/Option.java +++ b/src/com/bernard/configurator/annotations/Option.java @@ -2,10 +2,13 @@ package com.bernard.configurator.annotations; import static java.lang.annotation.ElementType.FIELD; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.bernard.configurator.YamlInterface; +@Retention(RetentionPolicy.RUNTIME) @Target(FIELD) public @interface Option { diff --git a/src/com/bernard/util/FuncUtils.java b/src/com/bernard/util/FuncUtils.java index 6b83210..1fc20d9 100644 --- a/src/com/bernard/util/FuncUtils.java +++ b/src/com/bernard/util/FuncUtils.java @@ -8,5 +8,15 @@ public class FuncUtils { 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; + } } diff --git a/src/com/bernard/util/YamlUtils.java b/src/com/bernard/util/YamlUtils.java index c4e68b4..be3e0c2 100755 --- a/src/com/bernard/util/YamlUtils.java +++ b/src/com/bernard/util/YamlUtils.java @@ -4,11 +4,15 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import com.amihaiemil.eoyaml.Yaml; import com.amihaiemil.eoyaml.YamlMapping; @@ -61,6 +65,10 @@ public class YamlUtils { return ysbb; } + public static final Stream stream(YamlSequence seq) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(seq.iterator(), Spliterator.ORDERED), false); + } + public static final CollectorImpl sequenceCollector = new CollectorImpl<>( Yaml::createYamlSequenceBuilder,