From 4badf7d29e8db4fa761d8d3269079b2aa2272781 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Wed, 11 May 2022 00:53:12 +0200 Subject: [PATCH] [DEV] add introspection interface --- src/module-info.java | 3 +- .../aknot/{reflect => }/ReflectClass.java | 19 +- .../aknot/{reflect => }/ReflectTools.java | 10 +- src/org/atriasoft/aknot/StringSerializer.java | 230 ++++ .../aknot/model/ConstructorModel.java | 8 + .../aknot/model/IntrospectionModel.java | 137 ++ .../model/IntrospectionPropertyGetter.java | 7 + .../model/IntrospectionPropertySetter.java | 13 + src/org/atriasoft/aknot/model/MapKey.java | 14 + src/org/atriasoft/aknot/model/ModelType.java | 7 + .../aknot/model/StringConverter.java | 16 + .../aknot/pojo/CacheIntrospectionModel.java | 36 + .../aknot/pojo/IntrospectionModelArray.java | 77 ++ .../pojo/IntrospectionModelBaseType.java | 47 + .../aknot/pojo/IntrospectionModelComplex.java | 1106 +++++++++++++++++ .../aknot/pojo/IntrospectionModelFactory.java | 29 + .../aknot/pojo/IntrospectionModelList.java | 55 + .../aknot/pojo/IntrospectionObject.java | 167 +++ .../aknot/pojo/IntrospectionProperty.java | 344 +++++ .../pojo/IntrospectionPropertyField.java | 44 + .../IntrospectionPropertyMethodGetter.java | 75 ++ .../IntrospectionPropertyMethodSetter.java | 29 + 22 files changed, 2466 insertions(+), 7 deletions(-) rename src/org/atriasoft/aknot/{reflect => }/ReflectClass.java (92%) rename src/org/atriasoft/aknot/{reflect => }/ReflectTools.java (98%) create mode 100644 src/org/atriasoft/aknot/StringSerializer.java create mode 100644 src/org/atriasoft/aknot/model/ConstructorModel.java create mode 100644 src/org/atriasoft/aknot/model/IntrospectionModel.java create mode 100644 src/org/atriasoft/aknot/model/IntrospectionPropertyGetter.java create mode 100644 src/org/atriasoft/aknot/model/IntrospectionPropertySetter.java create mode 100644 src/org/atriasoft/aknot/model/MapKey.java create mode 100644 src/org/atriasoft/aknot/model/ModelType.java create mode 100644 src/org/atriasoft/aknot/model/StringConverter.java create mode 100644 src/org/atriasoft/aknot/pojo/CacheIntrospectionModel.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionModelArray.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionModelBaseType.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionModelComplex.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionModelFactory.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionModelList.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionObject.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionProperty.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionPropertyField.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodGetter.java create mode 100644 src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodSetter.java diff --git a/src/module-info.java b/src/module-info.java index 4a91a3c..820c1d2 100644 --- a/src/module-info.java +++ b/src/module-info.java @@ -5,7 +5,8 @@ */ open module org.atriasoft.aknot { - exports org.atriasoft.aknot.reflect; + exports org.atriasoft.aknot; + exports org.atriasoft.aknot.pojo; exports org.atriasoft.aknot.annotation; exports org.atriasoft.aknot.model; exports org.atriasoft.aknot.exception; diff --git a/src/org/atriasoft/aknot/reflect/ReflectClass.java b/src/org/atriasoft/aknot/ReflectClass.java similarity index 92% rename from src/org/atriasoft/aknot/reflect/ReflectClass.java rename to src/org/atriasoft/aknot/ReflectClass.java index 95f60fa..813aab1 100644 --- a/src/org/atriasoft/aknot/reflect/ReflectClass.java +++ b/src/org/atriasoft/aknot/ReflectClass.java @@ -1,5 +1,6 @@ -package org.atriasoft.aknot.reflect; +package org.atriasoft.aknot; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -7,6 +8,22 @@ import java.util.List; import java.util.stream.Collectors; public class ReflectClass { + // TODO ploàp ... + public class PojoModelData { + public final String name; + public final Class typeData; + public Field field = null; + public Method getMethod = null; + public Method setMethod = null; + public Method isMethod = null; + + public PojoModelData(final String name, final Class typeData) { + this.name = name; + this.typeData = typeData; + } + + } + // Separate the methods and filer as: // - XXX GetXxx(); & XXX != boolean // - void setXxx(XXX elem); diff --git a/src/org/atriasoft/aknot/reflect/ReflectTools.java b/src/org/atriasoft/aknot/ReflectTools.java similarity index 98% rename from src/org/atriasoft/aknot/reflect/ReflectTools.java rename to src/org/atriasoft/aknot/ReflectTools.java index 084782c..ab3de9b 100644 --- a/src/org/atriasoft/aknot/reflect/ReflectTools.java +++ b/src/org/atriasoft/aknot/ReflectTools.java @@ -1,4 +1,4 @@ -package org.atriasoft.aknot.reflect; +package org.atriasoft.aknot; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; @@ -59,10 +59,10 @@ public class ReflectTools { return tmp; } - public static String getDescription(final Field element) throws Exception { + public static String getDescription(final Field element, final String parentValue) throws Exception { final Annotation[] annotation = element.getDeclaredAnnotationsByType(AknotDescription.class); if (annotation.length == 0) { - return ""; + return parentValue; } if (annotation.length > 1) { throw new Exception("Must not have more than 1 element @AknotDescription on " + element.getClass().getCanonicalName()); @@ -74,10 +74,10 @@ public class ReflectTools { return tmp; } - public static String getDescription(final Method element) throws Exception { + public static String getDescription(final Method element, final String parentValue) throws Exception { final Annotation[] annotation = element.getDeclaredAnnotationsByType(AknotDescription.class); if (annotation.length == 0) { - return ""; + return parentValue; } if (annotation.length > 1) { throw new Exception("Must not have more than 1 element @AknotDescription on " + element.getClass().getCanonicalName()); diff --git a/src/org/atriasoft/aknot/StringSerializer.java b/src/org/atriasoft/aknot/StringSerializer.java new file mode 100644 index 0000000..c4dd342 --- /dev/null +++ b/src/org/atriasoft/aknot/StringSerializer.java @@ -0,0 +1,230 @@ +package org.atriasoft.aknot; + +import java.util.HashMap; +import java.util.Map; + +import org.atriasoft.aknot.model.StringConverter; + +public class StringSerializer { + private static final Map, StringConverter> VALUES_OF = new HashMap<>(); + static { + StringSerializer.VALUES_OF.put(byte.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Byte.toString((Byte) data); + } + + @Override + public Object valueOf(final String value) { + return Byte.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(Byte.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Byte.toString((Byte) data); + } + + @Override + public Object valueOf(final String value) { + return Byte.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(int.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Integer.toString((Integer) data); + } + + @Override + public Object valueOf(final String value) { + return Integer.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(Integer.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Integer.toString((Integer) data); + } + + @Override + public Object valueOf(final String value) { + return Integer.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(long.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Long.toString((Long) data); + } + + @Override + public Object valueOf(final String value) { + return Long.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(Long.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Long.toString((Long) data); + } + + @Override + public Object valueOf(final String value) { + return Long.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(short.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Short.toString((Short) data); + } + + @Override + public Object valueOf(final String value) { + return Short.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(Short.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Short.toString((Short) data); + } + + @Override + public Object valueOf(final String value) { + return Short.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(float.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Float.toString((Float) data); + } + + @Override + public Object valueOf(final String value) { + return Float.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(Float.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Float.toString((Float) data); + } + + @Override + public Object valueOf(final String value) { + return Float.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(double.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Double.toString((Double) data); + } + + @Override + public Object valueOf(final String value) { + return Double.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(Double.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Double.toString((Double) data); + } + + @Override + public Object valueOf(final String value) { + return Double.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(boolean.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Boolean.toString((Boolean) data); + } + + @Override + public Object valueOf(final String value) { + return Boolean.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(Boolean.class, new StringConverter() { + @Override + public String toString(final Object data) { + return Boolean.toString((Boolean) data); + } + + @Override + public Object valueOf(final String value) { + return Boolean.valueOf(value.trim()); + } + }); + StringSerializer.VALUES_OF.put(String.class, new StringConverter() { + @Override + public String toString(final Object data) { + return (String) data; + } + + @Override + public Object valueOf(final String value) { + return value; + } + }); + } + + public static boolean contains(final Class clazz) { + return StringSerializer.VALUES_OF.containsKey(clazz); + } + + public static String toString(final boolean data) { + return Boolean.toString(data); + } + + public static String toString(final byte data) { + return Byte.toString(data); + } + + public static String toString(final int data) { + return Integer.toString(data); + } + + public static String toString(final long data) { + return Long.toString(data); + } + + /** + * Serialize in a string the require data + * @param data Object to serialize + * @return The new data... + */ + public static String toString(final Object data) { + if (data == null) { + return null; + } + final Class clazz = data.getClass(); + final StringConverter conv = StringSerializer.VALUES_OF.get(clazz); + return conv.toString(data); + } + + public static String toString(final short data) { + return Short.toString(data); + } + + /** + * Un-serialize a String in a specified Object + * @param value String to parse + * @return Data generated or Null + */ + public static Object valueOf(final Class clazz, final String value) { + if (value == null) { + return null; + } + final StringConverter conv = StringSerializer.VALUES_OF.get(clazz); + return conv.valueOf(value); + } + + private StringSerializer() {} +} diff --git a/src/org/atriasoft/aknot/model/ConstructorModel.java b/src/org/atriasoft/aknot/model/ConstructorModel.java new file mode 100644 index 0000000..fe3f305 --- /dev/null +++ b/src/org/atriasoft/aknot/model/ConstructorModel.java @@ -0,0 +1,8 @@ +package org.atriasoft.aknot.model; + +import java.lang.reflect.Constructor; + +public record ConstructorModel( + String[] values, + Boolean[] isAttributes, + Constructor constructor) {} diff --git a/src/org/atriasoft/aknot/model/IntrospectionModel.java b/src/org/atriasoft/aknot/model/IntrospectionModel.java new file mode 100644 index 0000000..90798aa --- /dev/null +++ b/src/org/atriasoft/aknot/model/IntrospectionModel.java @@ -0,0 +1,137 @@ +package org.atriasoft.aknot.model; + +import java.util.List; +import java.util.Map; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.pojo.IntrospectionProperty; + +public abstract class IntrospectionModel { + protected static final Boolean DEFAULT_ATTRIBUTE = false; + protected static final Boolean DEFAULT_IGNORE_UNBKNOWN = false; + protected static final Boolean DEFAULT_DEFAULT_NULL_VALUE = false; + protected static final Boolean DEFAULT_CASE_SENSITIVE = true; + protected static final Boolean DEFAULT_MANAGED = true; + protected static final Boolean DEFAULT_OPTIONAL = false; + + protected boolean defaultNullValue = false; + protected boolean ignoreUnknown = false; + + protected final Class classType; + + public IntrospectionModel(final Class classType) { + this.classType = classType; + } + + public Object createObject(final Map properties, final Map> nodes) throws AknotException { + return null; + } + + public List getAttributes() { + return null; + } + + public String getBeanName(final String nodeName) { + return nodeName; + } + + public String getBeanNameModel(final String nodeName) { + return getBeanName(nodeName); + } + + public Class getClassType() { + return this.classType; + } + + public List getNodeAvaillable() { + return null; + } + + public List getNodes() { + return null; + } + + public List getSignals() { + return null; + } + + public String getTextBeanName() { + // TODO Auto-generated method stub + return null; + } + + public String getTreeNameOfSubNode(final String nodeName) throws AknotException { + return null; + } + + public Class getTypeOfProperty(final String nodeName) throws AknotException { + return null; + } + + public Class getTypeOfSubNode(final String nodeName) throws AknotException { + return null; + } + + public Class getTypeOfSubNodeList(final String nodeName) throws AknotException { + return null; + } + + public Class getTypeOfSubProperty(final String nodeName) throws AknotException { + return null; + } + + public Class getTypeOfText() { + return null; + } + + public Object getValue(final String propertyName, final String propertyValue) throws AknotException { + return null; + } + + public Object getValueFromText(final String text) throws AknotException { + return null; + } + + /** + * This permit to know if an element in the property is able to manage the Whole text under (this remove all parsing of xml inside the model...) Annotation @XmlText + * @return true if a parameter manage the text (otherwise the text is sended to the fromString() function. + */ + public boolean hasTextModel() { + return false; + } + + public boolean isArray() { + return false; + } + + protected boolean isDefaultNullValue() { + return this.defaultNullValue; + } + + public boolean isEnum() { + return this.classType.isEnum(); + } + + public boolean isIgnoreUnknown() { + return this.ignoreUnknown; + } + + public boolean isList() { + return false; + } + + public boolean isNative() { + return false; + } + + public boolean isObject(final String propertyName) { + return Object.class.isAssignableFrom(this.classType); + } + + public boolean isRecord(final String propertyName) { + return Record.class.isAssignableFrom(this.classType); + } + + public abstract String toString(final Object data) throws AknotException; + +} diff --git a/src/org/atriasoft/aknot/model/IntrospectionPropertyGetter.java b/src/org/atriasoft/aknot/model/IntrospectionPropertyGetter.java new file mode 100644 index 0000000..217d6ca --- /dev/null +++ b/src/org/atriasoft/aknot/model/IntrospectionPropertyGetter.java @@ -0,0 +1,7 @@ +package org.atriasoft.aknot.model; + +import org.atriasoft.aknot.exception.AknotException; + +public interface IntrospectionPropertyGetter { + Object getValue(Object object) throws AknotException; +} diff --git a/src/org/atriasoft/aknot/model/IntrospectionPropertySetter.java b/src/org/atriasoft/aknot/model/IntrospectionPropertySetter.java new file mode 100644 index 0000000..62e0bb2 --- /dev/null +++ b/src/org/atriasoft/aknot/model/IntrospectionPropertySetter.java @@ -0,0 +1,13 @@ +package org.atriasoft.aknot.model; + +import org.atriasoft.aknot.exception.AknotException; + +public interface IntrospectionPropertySetter { + /** + * set the specific string value in the specified object + * @param object Object to add the value + * @param value Value to set in the Object + * @throws Exception An error occurred + */ + void setValue(Object object, Object value) throws AknotException; +} diff --git a/src/org/atriasoft/aknot/model/MapKey.java b/src/org/atriasoft/aknot/model/MapKey.java new file mode 100644 index 0000000..e0af6b9 --- /dev/null +++ b/src/org/atriasoft/aknot/model/MapKey.java @@ -0,0 +1,14 @@ +package org.atriasoft.aknot.model; + +public record MapKey (ModelType model, + String nodeName, + Class type) { + public MapKey(final ModelType model, final String nodeName, final Class type) { + this.model = model; + this.nodeName = nodeName; + this.type = type; + } + public MapKey(final ModelType model, final Class type) { + this(model, null, type); + } +} \ No newline at end of file diff --git a/src/org/atriasoft/aknot/model/ModelType.java b/src/org/atriasoft/aknot/model/ModelType.java new file mode 100644 index 0000000..82288ab --- /dev/null +++ b/src/org/atriasoft/aknot/model/ModelType.java @@ -0,0 +1,7 @@ +package org.atriasoft.aknot.model; + +public enum ModelType { + NORMAL, + ARRAY, + LIST +} \ No newline at end of file diff --git a/src/org/atriasoft/aknot/model/StringConverter.java b/src/org/atriasoft/aknot/model/StringConverter.java new file mode 100644 index 0000000..baf0847 --- /dev/null +++ b/src/org/atriasoft/aknot/model/StringConverter.java @@ -0,0 +1,16 @@ +package org.atriasoft.aknot.model; + +public interface StringConverter { + /** + * Un-serialize a String in a specified Object + * @param value String to parse + * @return Data generated or Null + */ + Object valueOf(final String value); + /** + * Serialize in a string the require data + * @param data Object to serialize + * @return The new data... + */ + String toString(final Object data); +} \ No newline at end of file diff --git a/src/org/atriasoft/aknot/pojo/CacheIntrospectionModel.java b/src/org/atriasoft/aknot/pojo/CacheIntrospectionModel.java new file mode 100644 index 0000000..c161849 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/CacheIntrospectionModel.java @@ -0,0 +1,36 @@ +package org.atriasoft.aknot.pojo; + +import java.util.HashMap; +import java.util.Map; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionModel; +import org.atriasoft.aknot.model.MapKey; +import org.atriasoft.aknot.model.ModelType; + +public class CacheIntrospectionModel { + final Map elements = new HashMap<>(); + + public CacheIntrospectionModel() { + + } + + public IntrospectionModel findOrCreate(final ModelType model, final String name, final Class classType) throws AknotException { + final MapKey key = new MapKey(model, name, classType); + IntrospectionModel out = this.elements.get(key); + if (out != null) { + return out; + } + if (model == ModelType.ARRAY) { + out = IntrospectionModelFactory.createModelArray(key.nodeName(), key); + } else if (model == ModelType.LIST) { + out = IntrospectionModelFactory.createModelList(key.nodeName(), key); + } else if (classType.isEnum()) { + out = IntrospectionModelFactory.createModelEnum(key); + } else { + out = IntrospectionModelFactory.createModel(key); + } + this.elements.put(key, out); + return out; + } +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionModelArray.java b/src/org/atriasoft/aknot/pojo/IntrospectionModelArray.java new file mode 100644 index 0000000..aa03228 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionModelArray.java @@ -0,0 +1,77 @@ +package org.atriasoft.aknot.pojo; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionModel; +import org.atriasoft.etk.util.ArraysTools; + +public class IntrospectionModelArray extends IntrospectionModel { + @SuppressWarnings("unchecked") + public static T[] convertList(final Class classType, final List data) { + final List rootList = (List) data; + final T[] strarr = (T[]) Array.newInstance(classType, 0); + return rootList.toArray(strarr); + } + + final String nodeName; + + public IntrospectionModelArray(final String nodeName, final Class classType) { + super(classType); + this.nodeName = nodeName; + } + + @Override + public Object createObject(final Map properties, final Map> nodes) throws AknotException { + List tmp = null; + if (this.nodeName == null) { + tmp = nodes.get(IntrospectionObject.STUPID_TOCKEN); + } else { + tmp = nodes.get(this.nodeName); + } + if (tmp == null) { + return null; + } + if (this.classType.isPrimitive()) { + return ArraysTools.listToPrimitiveAuto(this.classType, tmp); + } + return IntrospectionModelArray.convertList(this.classType, tmp); + } + + @Override + public List getNodeAvaillable() { + if (this.nodeName != null) { + return Arrays.asList(this.nodeName); + } + return null; + } + + @Override + public Object getValue(final String propertyName, final String propertyValue) throws AknotException { + return null; + } + + @Override + public Object getValueFromText(final String text) throws AknotException { + return new Object[0]; + } + + @Override + public boolean hasTextModel() { + return false; + } + + @Override + public boolean isArray() { + return true; + } + + @Override + public String toString(final Object data) { + return null; + } + +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionModelBaseType.java b/src/org/atriasoft/aknot/pojo/IntrospectionModelBaseType.java new file mode 100644 index 0000000..86f474e --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionModelBaseType.java @@ -0,0 +1,47 @@ +package org.atriasoft.aknot.pojo; + +import java.util.List; +import java.util.Map; + +import org.atriasoft.aknot.StringSerializer; +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionModel; + +public class IntrospectionModelBaseType extends IntrospectionModel { + + public IntrospectionModelBaseType(final Class classType) { + super(classType); + } + + @Override + public Object createObject(final Map properties, final Map> nodes) throws AknotException { + throw new AknotException("Base type model can not have properties and nodes ... "); + } + + @Override + public List getNodeAvaillable() { + return null; + } + + @Override + public Object getValue(final String propertyName, final String propertyValue) throws AknotException { + return null; + } + + @Override + public Object getValueFromText(final String text) throws AknotException { + //il y a un bug ici ... + return StringSerializer.valueOf(this.classType, text); + } + + @Override + public boolean isNative() { + return true; + } + + @Override + public String toString(final Object data) { + return StringSerializer.toString(data); + } + +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionModelComplex.java b/src/org/atriasoft/aknot/pojo/IntrospectionModelComplex.java new file mode 100644 index 0000000..0012d8e --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionModelComplex.java @@ -0,0 +1,1106 @@ +package org.atriasoft.aknot.pojo; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.atriasoft.aknot.ReflectClass; +import org.atriasoft.aknot.ReflectTools; +import org.atriasoft.aknot.StringSerializer; +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.internal.Log; +import org.atriasoft.aknot.model.ConstructorModel; +import org.atriasoft.aknot.model.IntrospectionModel; +import org.atriasoft.etk.Tools; +import org.atriasoft.etk.util.ArraysTools; + +public class IntrospectionModelComplex extends IntrospectionModel { + private final boolean isRecord; + // TODO Optimize this with external object for basic types.... + private final Method valueof; // used for the set Text if the object is an end point... + private final Method tostring; // used for the set Text if the object is an end point... + private final boolean isSubClass; // if true, the constructor must be called with a null first object. + private final Constructor constructorEmpty; + private final List constructors = new ArrayList<>(); + private final List elements = new ArrayList<>(); + + public IntrospectionModelComplex(final Class classType) throws AknotException { + super(classType); + try { + if (classType.getNestHost() == classType) { + this.isSubClass = false; + } else if (!Modifier.isStatic(classType.getModifiers())) { + this.isSubClass = true; + } else { + this.isSubClass = false; + } + this.isRecord = Record.class.isAssignableFrom(classType); + if (classType.isPrimitive()) { + Log.critical("Detect primitive ==> impossible case !!! "); + } + // ------------------------------------------------------------------------ + // -- Parse constructor + // ------------------------------------------------------------------------ + Log.error("Introspect class: '" + classType.getCanonicalName() + "'"); + final Constructor[] constructors = this.classType.getConstructors(); + Log.verbose(" Constructors: (" + constructors.length + ")"); + Constructor emptyConstructorTmp = null; + for (final Constructor elem : constructors) { + Log.error(" Constructor ??? : {}", elem.toGenericString()); + // we does not manage private field + if (!Modifier.isPublic(elem.getModifiers())) { + continue; + } + if (elem.getParameterCount() == 0) { + emptyConstructorTmp = elem; + Log.error(" >>> " + elem.toGenericString()); + } else { + int offsetSubClass = 0; + if (this.isSubClass) { + offsetSubClass = 1; + } + if (elem.getParameterCount() == 1 && offsetSubClass == 1) { + emptyConstructorTmp = elem; + Log.error(" >>> " + elem.toGenericString()); + } else { + // Retrieve full description in constructor properties... + String[] namesBeans = ReflectTools.getNames(elem, null); + if (namesBeans == null) { + namesBeans = new String[elem.getParameterCount() - offsetSubClass]; + } else if (elem.getParameterCount() != namesBeans.length + offsetSubClass) { + throw new AknotException("Wrong number of parameter in constructor with ne number declared in the @XmlName (for bean name)"); + } + final Boolean[] isAttributes = new Boolean[elem.getParameterCount() - offsetSubClass]; + final Boolean[] isCaseSensitives = new Boolean[elem.getParameterCount() - offsetSubClass]; + final Boolean[] isOptionals = new Boolean[elem.getParameterCount() - offsetSubClass]; + final Boolean[] isManageds = new Boolean[elem.getParameterCount() - offsetSubClass]; + + final Class[][] clazz = new Class[elem.getParameterCount() - offsetSubClass][]; + final String[][] names = new String[elem.getParameterCount() - offsetSubClass][]; + + final Parameter[] params = elem.getParameters(); + for (int iii = offsetSubClass; iii < params.length; iii++) { + final Parameter paramElem = params[iii]; + isAttributes[iii - offsetSubClass] = ReflectTools.getIsAttribute(elem, paramElem, null); + isCaseSensitives[iii - offsetSubClass] = ReflectTools.getIsCaseSensitive(elem, paramElem, null); + isOptionals[iii - offsetSubClass] = ReflectTools.getIsOptional(elem, paramElem, null); + isManageds[iii - offsetSubClass] = ReflectTools.getIsManaged(elem, paramElem, null); + final String[] namesParam = ReflectTools.getNames(elem, paramElem, null); + final Class[] types = ReflectTools.getTypeParameterfunction(elem, iii); + clazz[iii - offsetSubClass] = types; + names[iii - offsetSubClass] = namesParam; + if (namesParam != null && namesParam.length != 0) { + // TODO maybe do something id name is already set ??? + namesBeans[iii - offsetSubClass] = namesParam[0]; + } + } + if (checkIfOneIsNull(namesBeans, 0)) { + Log.verbose(" - " + elem.toGenericString()); + Log.verbose(" ==> unmanaged (missing names description: " + Arrays.toString(namesBeans) + ")"); + } else { + // check default attributes in the global list ... (do it at the end to be sure the constructor is VALID ... + for (int iii = 0; iii < namesBeans.length; iii++) { + IntrospectionProperty prop = findElement(namesBeans[iii]); + if (prop == null) { + prop = new IntrospectionProperty(namesBeans[iii], clazz[iii], names[iii]); + this.elements.add(prop); + } else { + final Class curentType = prop.getType(); + final Class curentSubType = prop.getSubType(); + if (curentType != clazz[iii][0] && curentSubType != clazz[iii][1]) { + throw new AknotException("Set 'return type' with a different value previous=" + curentType.getCanonicalName() + " / " + curentSubType.getCanonicalName() + + " ==> new=" + clazz[iii][0] + " / " + clazz[iii][1] + " in " + elem.toGenericString()); + } + final String[] names1 = names[iii]; + if (names1 != null) { + final String[] curentValue = prop.getNames(); + if (curentValue != null) { + // TODO maybe set the value permissive if no change !!! like the others... + throw new AknotException("Set 'names' with a (already set!) " + elem.toGenericString()); + } + prop.setNames(names1); + } + } + // Set settable by the constructor + prop.setCanBeSetByConstructor(true); + final Boolean isAttribute = isAttributes[iii]; + if (isAttribute != null) { + final Boolean curentValue = prop.isAttribute(); + if (curentValue != null && curentValue != isAttribute) { + throw new AknotException("Set 'attribute' with a different value previous=" + curentValue + " ==> new=" + isAttribute + " in " + elem.toGenericString()); + } + prop.setAttribute(isAttribute); + } + final Boolean isCaseSensitive = isCaseSensitives[iii]; + if (isCaseSensitive != null) { + final Boolean curentValue = prop.isCaseSensitive(); + if (curentValue != null && curentValue != isCaseSensitive) { + throw new AknotException( + "Set 'caseSensitive' with a different value previous=" + curentValue + " ==> new=" + isCaseSensitive + " in " + elem.toGenericString()); + } + prop.setCaseSensitive(isCaseSensitive); + } + final Boolean isOptional = isOptionals[iii]; + if (isOptional != null) { + final Boolean curentValue = prop.isOptionnal(); + if (curentValue != null && curentValue != isOptional) { + throw new AknotException("Set 'optionnal' with a different value previous=" + curentValue + " ==> new=" + isOptional + " in " + elem.toGenericString()); + } + prop.setOptionnal(isOptional); + } + final Boolean isManaged = isManageds[iii]; + if (isManaged != null) { + final Boolean curentValue = prop.isManaged(); + if (curentValue != null && curentValue != isManaged) { + throw new AknotException("Set 'managed' with a different value previous=" + curentValue + " ==> new=" + isManaged + " in " + elem.toGenericString()); + } + prop.setManaged(isManaged); + } + } + this.constructors.add(new ConstructorModel(namesBeans, isAttributes, elem)); + } + } + } + } + this.constructorEmpty = emptyConstructorTmp; + Log.error(" ==> constructor = {}", this.constructorEmpty); + // Order the constructor from the bigger number of element to the lowest... + Collections.sort(this.constructors, (a, b) -> a.values().length - b.values().length); + for (final ConstructorModel elem : this.constructors) { + Log.verbose(" * " + elem.constructor().toGenericString()); + final StringBuilder tmpPrint = new StringBuilder(" ==> ("); + if (this.isSubClass) { + tmpPrint.append("null, "); + } + for (int iii = 0; iii < elem.values().length; iii++) { + if (iii != 0) { + tmpPrint.append(", "); + } + tmpPrint.append(elem.values()[iii]); + } + tmpPrint.append(")"); + Log.verbose(tmpPrint.toString()); + } + final List recordAllPossibleValues = new ArrayList<>(); + + if (this.isRecord) { + for (final ConstructorModel elem : this.constructors) { + for (int iii = 0; iii < elem.values().length; iii++) { + final String tmpp = elem.values()[iii]; + if (!recordAllPossibleValues.contains(tmpp)) { + recordAllPossibleValues.add(tmpp); + } + } + } + } + // ------------------------------------------------------------------------ + // -- Parse Field + // ------------------------------------------------------------------------ + final Field[] fields = this.classType.getFields(); + Log.verbose(" Fields: (" + fields.length + ")"); + for (final Field elem : fields) { + // we does not manage static field + if (Modifier.isStatic(elem.getModifiers())) { + continue; + } + // we does not manage private field + // NOTE: if the field is private I do not check the elements ==> the user want a private API !!! (maybe change ...) + if (!Modifier.isPublic(elem.getModifiers())) { + continue; + } + final String[] names = ReflectTools.getNames(elem, null); + final Class[] types = ReflectTools.getTypeField(elem); + + IntrospectionProperty prop = findElement(elem.getName()); + if (prop == null) { + prop = new IntrospectionProperty(elem.getName(), types, names); + this.elements.add(prop); + } else { + final Class curentType = prop.getType(); + final Class curentSubType = prop.getSubType(); + if (curentType != types[0] && curentSubType != types[1]) { + throw new AknotException("Set 'return type' with a different value previous=" + curentType.getCanonicalName() + " / " + curentSubType.getCanonicalName() + " ==> new=" + + types[0] + " / " + types[1] + " in " + elem.toGenericString()); + } + final String[] names1 = names; + if (names1 != null) { + final String[] curentValue = prop.getNames(); + if (curentValue != null) { + // TODO maybe set the value permissive if no change !!! like the others... + throw new AknotException("Set 'names' with a (already set!) " + elem.toGenericString()); + } + prop.setNames(names1); + } + } + + final String description = ReflectTools.getDescription(elem, null); + if (description != null) { + prop.setDescription(description); + } + final String listName = ReflectTools.getListName(elem, null); + if (listName != null) { + prop.setListName(listName); + } + final Boolean isSignal = ReflectTools.getIsSignal(elem, null); + if (isSignal != null) { + prop.setSignal(isSignal); + } + final Boolean isAttribute = ReflectTools.getIsAttribute(elem, null); + if (isAttribute != null) { + prop.setAttribute(isAttribute); + } + final Boolean isManaged = ReflectTools.getIsManaged(elem, null); + if (isManaged != null) { + prop.setManaged(isManaged); + } + final Boolean isOptionnal = ReflectTools.getIsOptional(elem, null); + if (isOptionnal != null) { + prop.setOptionnal(isOptionnal); + } + final Boolean isCaseSensitive = ReflectTools.getIsCaseSensitive(elem, null); + if (isCaseSensitive != null) { + prop.setCaseSensitive(isCaseSensitive); + } + final Boolean isText = ReflectTools.getIsText(elem, null); + if (isText != null) { + prop.setTextMode(isText); + } + final Class factory = ReflectTools.getFactory(elem); + if (factory != null) { + prop.setFactory(factory); + } + // generate getter and setter with field. + + final IntrospectionPropertyField modifier = new IntrospectionPropertyField(elem); + // if the field is final ==> we can not set the value... + if (Modifier.isFinal(elem.getModifiers())) { + prop.setGetter(modifier); + } else { + prop.setSetter(modifier); + prop.setGetter(modifier); + } + Log.verbose(" - " + elem.toGenericString()); + } + + final List methods = ReflectClass.getFilterGenericFucntion(this.classType, recordAllPossibleValues, true, true, true); + + Log.verbose(" Methods: (" + methods.size() + ")"); + for (final Method elem : methods) { + Log.verbose(" - " + elem.toGenericString()); + } + + // Separate the methods and filer as: + // - XXX GetXxx(); & XXX != boolean + // - void setXxx(XXX elem); + // - [bB]oolean isXxx(); + // for records: + // - xxx(); + + final List methodsGet = ReflectClass.extractGetMethod(classType, methods, recordAllPossibleValues); + final List methodsSet = ReflectClass.extractSetMethod(classType, methods); + final List methodsIs = ReflectClass.extractIsMethod(classType, methods); + this.valueof = ReflectClass.extractValueOf(methods); + this.tostring = ReflectClass.extractToString(methods); + // associate methods by pair. + for (final Method method : methodsGet) { + final String name = Tools.decapitalizeFirst(this.isRecord ? method.getName() : method.getName().substring(3)); + final IntrospectionProperty prop = updateForMethod(name, method, ReflectTools.getTypeReturnFunction(method)); + // generate getter and setter with field. + final IntrospectionPropertyMethodGetter modifier = new IntrospectionPropertyMethodGetter(method); + prop.setGetter(modifier); + } + if (!this.isRecord) { + for (final Method method : methodsIs) { + final String name = Tools.decapitalizeFirst(method.getName().substring(2)); + final IntrospectionProperty prop = updateForMethod(name, method, ReflectTools.getTypeReturnFunction(method)); + // generate getter and setter with field. + final IntrospectionPropertyMethodGetter modifier = new IntrospectionPropertyMethodGetter(method); + prop.setGetter(modifier); + } + for (final Method method : methodsSet) { + final String name = Tools.decapitalizeFirst(method.getName().substring(3)); + final IntrospectionProperty prop = updateForMethod(name, method, ReflectTools.getTypeParameterfunction(method)); + // generate getter and setter with field. + final IntrospectionPropertyMethodSetter modifier = new IntrospectionPropertyMethodSetter(method); + prop.setSetter(modifier); + } + } + this.ignoreUnknown = ReflectTools.getIsIgnoreUnknown(classType, IntrospectionModel.DEFAULT_IGNORE_UNBKNOWN); + + this.defaultNullValue = ReflectTools.getIsDefaultNullValue(classType, IntrospectionModel.DEFAULT_DEFAULT_NULL_VALUE); + + // Set only at the end ==> no need before... + final Boolean isDefaultAttribute = ReflectTools.getIsDefaultAttribute(classType, IntrospectionModel.DEFAULT_ATTRIBUTE); + for (final IntrospectionProperty prop : this.elements) { + if (prop.isAttribute() == null) { + prop.setAttribute(isDefaultAttribute); + } + } + final Boolean isDefaultManaged = ReflectTools.getIsDefaultManaged(classType, IntrospectionModel.DEFAULT_MANAGED); + for (final IntrospectionProperty prop : this.elements) { + if (prop.isManaged() == null) { + prop.setManaged(isDefaultManaged); + } + } + final Boolean isDefaultOptional = ReflectTools.getIsDefaultOptional(classType, IntrospectionModel.DEFAULT_OPTIONAL); + for (final IntrospectionProperty prop : this.elements) { + if (prop.isOptionnal() == null) { + prop.setOptionnal(isDefaultOptional); + } + } + final Boolean isDefaultCaseSensitive = ReflectTools.getIsDefaultCaseSensitive(classType, IntrospectionModel.DEFAULT_CASE_SENSITIVE); + for (final IntrospectionProperty prop : this.elements) { + if (prop.isCaseSensitive() == null) { + prop.setCaseSensitive(isDefaultCaseSensitive); + } + } + // set default name in the list: + for (final IntrospectionProperty prop : this.elements) { + if (prop.getNames() == null) { + prop.setNames(new String[] { prop.getBeanName() }); + } + } + + } catch (final Exception ex) { + ex.printStackTrace(); + throw new AknotException("Error in creating introspection data ... " + ex.getMessage()); + } + + // Sort the parameters to generate all time the same XML.. + Collections.sort(this.elements, (a, b) -> a.getNames()[0].compareTo(b.getNames()[0])); + + for (final IntrospectionProperty prop : this.elements) { + Log.verbose("Property/node : " + prop.getBeanName()); + Log.verbose(" names: " + Arrays.toString(prop.getNames())); + Log.verbose(" list: " + prop.getListName()); + Log.verbose(" managed: " + prop.isManaged()); + Log.verbose(" attribute: " + prop.isAttribute()); + Log.verbose(" text: " + prop.isText()); + Log.verbose(" case-sensitive: " + prop.isCaseSensitive()); + Log.verbose(" optionnal: " + prop.isOptionnal()); + Log.verbose(" constructor: " + prop.isCanBeSetByConstructor()); + Log.verbose(" get/set: " + prop.canGetValue() + " / " + prop.canSetValue()); + Log.verbose(" type: " + prop.getType().getCanonicalName()); + if (prop.getSubType() != null) { + Log.verbose(" sub-type: " + prop.getSubType().getCanonicalName()); + } else { + Log.verbose(" sub-type: null"); + } + + } + } + + @SuppressWarnings("unchecked") + private T[] autoCast(final Class clazz, final List data) { + final T[] out = (T[]) java.lang.reflect.Array.newInstance(clazz, data.size());// T[data.size()]; + + for (int iii = 0; iii < data.size(); iii++) { + out[iii] = (T) data.get(iii); + } + return out; + //return data.stream().map(clazz::cast).toArray(new T[data.size()]);//java.lang.reflect.Array.newInstance((propMethode.getSubType(), tmpp.size())); + } + + private boolean checkIdenticalArray(final String[] valA, final String[] valB) { + if (valA == valB) { + return true; + } + if (valA.length != valB.length) { + return false; + } + for (int iii = 0; iii < valA.length; iii++) { + if (!valA[iii].equals(valB[iii])) { + return false; + } + } + return true; + } + + // private boolean checkIfOneIsNull(Boolean[] values, int offset) { + // for (int iii=offset; iii properties, final Map> nodes) throws AknotException { + Object tmp = null; + // STEP 1: try to create the object with provided parameter (if a constructor exist....) + if (!this.constructors.isEmpty()) { + Object[] inputs = null; + ConstructorModel betterModel = null; + int lastEmpty = Integer.MAX_VALUE; + // try to find the constructor that fit with parameters ... + for (final ConstructorModel elem : this.constructors) { + final int offset = this.isSubClass ? 1 : 0; + final Object[] inputsTmp = new Object[elem.values().length + offset]; + inputsTmp[0] = null; + int empty = 0; + for (int iii = 0; iii < elem.values().length; iii++) { + Object valueToInject = properties.get(elem.values()[iii]); + if (valueToInject == null) { + final List tmppp = nodes.get(elem.values()[iii]); + if (tmppp != null) { + if (List.class == findBeanNodeDescription(elem.values()[iii]).getType()) { + valueToInject = tmppp; + } else if (tmppp.size() >= 1) { + valueToInject = tmppp.get(0); + } + } + } + if (valueToInject == null) { + empty++; + inputsTmp[iii + offset] = null; + continue; + } + inputsTmp[iii + offset] = valueToInject; + } + if (lastEmpty >= empty && (inputs == null || inputs.length < inputsTmp.length)) { + inputs = inputsTmp; + betterModel = elem; + lastEmpty = empty; + } + } + if (betterModel != null) { + if (isDefaultNullValue() || lastEmpty == 0) { + final ConstructorModel elem = betterModel; + if (inputs != null) { + // here we find our constructor... + try { + tmp = switch (inputs.length) { + case 0 -> elem.constructor().newInstance(); + case 1 -> elem.constructor().newInstance(inputs[0]); + case 2 -> elem.constructor().newInstance(inputs[0], inputs[1]); + case 3 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2]); + case 4 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3]); + case 5 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4]); + case 6 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5]); + case 7 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6]); + case 8 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7]); + case 9 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8]); + case 10 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9]); + case 11 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10]); + case 12 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11]); + case 13 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12]); + case 14 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13]); + case 15 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13], inputs[14]); + case 16 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13], inputs[14], inputs[15]); + case 17 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13], inputs[14], inputs[15], inputs[16]); + case 18 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13], inputs[14], inputs[15], inputs[16], inputs[17]); + case 19 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13], inputs[14], inputs[15], inputs[16], inputs[17], inputs[18]); + case 20 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13], inputs[14], inputs[15], inputs[16], inputs[17], inputs[18], inputs[19]); + case 21 -> elem.constructor().newInstance(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6], inputs[7], inputs[8], inputs[9], inputs[10], + inputs[11], inputs[12], inputs[13], inputs[14], inputs[15], inputs[16], inputs[17], inputs[18], inputs[19], inputs[20]); + default -> throw new AknotException("to much parameter in the constructor... " + inputs.length); + }; + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new AknotException("Error when creating the Object ..." + this.classType.getCanonicalName()); + } + // Remove all previously added parameters + for (int iii = 0; iii < elem.values().length; iii++) { + properties.remove(elem.values()[iii]); + nodes.remove(elem.values()[iii]); + } + } + } + } + } + // STEP 2: If we do not create the object ==> try with empty constructor... + if (tmp == null) { + if (this.constructorEmpty == null) { + throw new AknotException("No constructor accessible for class: " + this.classType.getCanonicalName()); + } + try { + Log.error("create class : {} with subClass={}", this.classType.getCanonicalName(), this.isSubClass); + Log.error(" ==> constructor = {}", this.constructorEmpty); + if (this.isSubClass) { + final Object tmp2 = null; + tmp = this.constructorEmpty.newInstance(tmp2); + } else { + tmp = this.constructorEmpty.newInstance(); + } + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) { + e.printStackTrace(); + return null; + } + } + // STEP 3: set the rest if the parameters... + for (final Entry elem : properties.entrySet()) { + setValue(tmp, elem.getKey(), elem.getValue()); + } + for (final Entry> elem : nodes.entrySet()) { + setValue(tmp, elem.getKey(), elem.getValue()); + } + return tmp; + } + + protected IntrospectionProperty findBeanNodeDescription(final String propertyBeanName) throws AknotException { + for (final IntrospectionProperty prop : this.elements) { + if (prop.isAttribute()) { + continue; + } + if (prop.getBeanName().equals(propertyBeanName)) { + return prop; + } + } + return null; + } + + protected IntrospectionProperty findBeanPropertyDescription(final String propertyBeanName) throws AknotException { + for (final IntrospectionProperty prop : this.elements) { + if (!prop.isAttribute()) { + continue; + } + if (prop.getBeanName().equals(propertyBeanName)) { + return prop; + } + } + return null; + } + + protected IntrospectionProperty findElement(final String beanName) { + for (final IntrospectionProperty elem : this.elements) { + if (elem.getBeanName().equals(beanName)) { + return elem; + } + } + return null; + } + + protected IntrospectionProperty findNodeDescription(final String propertyName) throws AknotException { + for (final IntrospectionProperty prop : this.elements) { + if (prop.isAttribute()) { + continue; + } + if (prop.isCompatible(propertyName)) { + return prop; + } + } + return null; + } + + protected IntrospectionProperty findPropertyDescription(final String propertyName) throws AknotException { + for (final IntrospectionProperty prop : this.elements) { + if (!prop.isAttribute()) { + continue; + } + if (prop.isCompatible(propertyName)) { + return prop; + } + } + return null; + } + + @Override + public List getAttributes() { + final List out = new ArrayList<>(); + for (final IntrospectionProperty elem : this.elements) { + if (elem.isAttribute()) { + out.add(elem); + } + } + return out; + } + + @Override + public String getBeanName(final String nodeName) { + for (final IntrospectionProperty elem : this.elements) { + if (elem.isCompatible(nodeName)) { + return elem.getBeanName(); + } + } + return null; + } + + @Override + public String getBeanNameModel(final String nodeName) { + for (final IntrospectionProperty elem : this.elements) { + if (elem.isCompatible(nodeName)) { + if (elem.hasFactory()) { + return elem.getBeanName() + "#" + nodeName; + } + return elem.getBeanName(); + } + } + return null; + } + + @Override + public List getNodeAvaillable() { + final List out = new ArrayList<>(); + for (final IntrospectionProperty prop : this.elements) { + if (prop.isAttribute()) { + continue; + } + out.add(prop.getNames()[0]); + } + return out; + } + + @Override + public List getNodes() { + final List out = new ArrayList<>(); + for (final IntrospectionProperty elem : this.elements) { + if (!elem.isAttribute()) { + out.add(elem); + } + } + return out; + } + + @Override + public List getSignals() { + final List out = new ArrayList<>(); + for (final IntrospectionProperty elem : this.elements) { + final Boolean signal = elem.isSignal(); + if (signal != null && signal) { + out.add(elem); + } + } + return out; + } + + @Override + public String getTextBeanName() { + for (final IntrospectionProperty prop : this.elements) { + final Boolean isText = prop.isText(); + if (isText != null && isText) { + return prop.getBeanName(); + } + } + return null; + } + + @Override + public String getTreeNameOfSubNode(final String nodeBeanName) throws AknotException { + Log.debug(" nodeType='" + nodeBeanName + "'"); + final IntrospectionProperty propMethode = findBeanNodeDescription(nodeBeanName); + if (propMethode != null && propMethode.canSetValue()) { + Log.debug(" ==> find '" + propMethode.getNames()); + return propMethode.getListName(); + } + throw new AknotException("can not find the field '" + nodeBeanName + "'"); + } + + @Override + public Class getTypeOfProperty(final String nodeName) throws AknotException { + Log.debug("nodeType='" + nodeName + "'"); + final IntrospectionProperty propField = findPropertyDescription(nodeName); + if (propField != null && propField.canSetValue()) { + Log.debug(" ==> find '" + propField.getNames()); + return propField.getType(); + } + + throw new AknotException("can not find the field '" + nodeName + "' available: " + getNodeAvaillable()); + } + + /** + * Detect a subNode, and ask the type of the node at the parent Class + * @param nodeBeanName Name of the node (bean name access ==> not the XML name) + * @return Class of the node to create + */ + @Override + public Class getTypeOfSubNode(final String nodeBeanNames) throws AknotException { + final String[] elemstNames = nodeBeanNames.split("#"); + final String nodeBeanName = elemstNames[0]; + Log.debug(" nodeType='" + nodeBeanName + "'"); + final IntrospectionProperty propMethode = findBeanNodeDescription(nodeBeanName); + if (propMethode != null && propMethode.canSetValue()) { + Log.debug(" ==> find '" + propMethode.getNames()); + if (propMethode.hasFactory()) { + return propMethode.getCompatible(elemstNames[1]); + } else { + return propMethode.getType(); + } + } + throw new AknotException("can not find the field '" + nodeBeanName + "' available: " + getNodeAvaillable()); + } + + @Override + public Class getTypeOfSubNodeList(final String nodeBeanNames) throws AknotException { + final String[] elemstNames = nodeBeanNames.split("#"); + final String nodeBeanName = elemstNames[0]; + Log.debug(" nodeType='" + nodeBeanName + "'"); + final IntrospectionProperty propMethode = findBeanNodeDescription(nodeBeanName); + if (propMethode != null && propMethode.canSetValue()) { + Log.debug(" ==> find '" + propMethode.getNames()); + if (propMethode.hasFactory()) { + return propMethode.getCompatible(elemstNames[1]); + } else { + return propMethode.getSubType(); + } + } + throw new AknotException("can not find the field '" + nodeBeanName + "' available: " + getNodeAvaillable()); + } + + @Override + public Class getTypeOfSubProperty(final String nodeName) throws AknotException { + Log.debug(" nodeType='" + nodeName + "'"); + final IntrospectionProperty propField = findPropertyDescription(nodeName); + if (propField != null && propField.canSetValue()) { + Log.debug(" ==> find '" + propField.getNames()); + return propField.getSubType(); + } + throw new AknotException("can not find the field '" + nodeName + "' available: " + getNodeAvaillable()); + } + + @Override + public Class getTypeOfText() { + for (final IntrospectionProperty prop : this.elements) { + final Boolean isText = prop.isText(); + if (isText != null && isText) { + return prop.getType(); + } + } + return null; + } + + @Override + public Object getValue(final String propertyName, final String propertyValue) throws AknotException { + Log.debug(" propertyName='" + propertyName + "' propertyValue='" + propertyValue + "' "); + // by default use setter to set the property + final IntrospectionProperty propMethode = findNodeDescription(propertyName); + if (propMethode != null && propMethode.canSetValue()) { + Log.verbose(" ==> find '" + propMethode.getNames()); + return propMethode.createValue(propertyValue); + } + // try with direct field + final IntrospectionProperty propField = findPropertyDescription(propertyName); + if (propField != null && propField.canSetValue()) { + Log.verbose(" ==> find '" + propField.getNames()); + return propField.createValue(propertyValue); + } + throw new AknotException("can not find the field '" + propertyName + "'"); + } + + @Override + public Object getValueFromText(final String text) throws AknotException { + // Note if the type is an Array<>() or a List<>() ==> we parse element by element ... then we need to keep the undertype... + Class classTypeLocal = this.classType; + Log.debug("======>>>>>>> Get input type : " + this.classType.getCanonicalName()); + //Log.debug("======>>>>>>> Get input component type : " + this.classType.getComponentType().getCanonicalName()); + if (this.classType.isArray()) { + // generic array ... + classTypeLocal = this.classType.getComponentType(); + } else if (List.class == this.classType || Collection.class.isAssignableFrom(this.classType)) { + // a generic list .... + /*if (this.subClassType != null) { + classTypeLocal = this.subClassType; + }*/ + //Type[] tmpp = this.classType.getGenericInterfaces(); + //Class[] tmpp = this.classType.getNestMembers(); + //Class[] tmpp = this.classType.getClasses(); + //Class[] tmpp = this.classType.getDeclaredClasses(); + //Class[] tmpp = this.classType.getInterfaces(); + //Class tmpp = this.classType.getDeclaringClass(); + //TypeVariable[] tmpp = this.classType.getTypeParameters(); + /* + Class persistentClass =((ParameterizedType)classType.getGenericSuperclass()).getActualTypeArguments()[0]; + */ + //Type tmpp = classType.getGenericSuperclass(); + // Class tmpp = classType.getInterfaces(); + // Log.warning("======>>>>>>> Find List '" + tmpp + "'"); + // for (int iii = 0; iii < tmpp.length; iii++) { + // Log.warning(" - " + tmpp[iii]); + // } + } + Log.debug("======>>>>>>> subElement input type : " + classTypeLocal.getCanonicalName()); + + if (this.valueof == null) { + if (StringSerializer.contains(classTypeLocal)) { + throw new AknotException("function 'valueOf' for '" + classTypeLocal.getCanonicalName() + "' is not defined and not registered for specific type"); + } + return StringSerializer.valueOf(classTypeLocal, text); + } + try { + return this.valueof.invoke(null, text); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + if (Enum.class.isAssignableFrom(this.classType)) { + throw new AknotException("Error in call 'valueOf(String ...)' for enum '" + classTypeLocal.getCanonicalName() + "' ==> '" + text + "' ... availlable list: " + + Arrays.asList(this.classType.getEnumConstants())); + } + e.printStackTrace(); + throw new AknotException("Error in call 'valueOf(String ...)' for '" + classTypeLocal.getCanonicalName() + "' " + e.getMessage()); + } + } + + @Override + public boolean hasTextModel() { + Log.warning("in {}", this.classType.getCanonicalName()); + for (final IntrospectionProperty prop : this.elements) { + Log.warning(" check {}, manage={} cas setValue={} isText={}", prop.getBeanName(), prop.isManaged(), prop.canSetValue(), prop.isText()); + final Boolean isText = prop.isText(); + if (isText != null && isText) { + return true; + } + } + return false; + } + + @Override + public boolean isNative() { + return false; + } + + private void setValue(final Object data, final String beanName, final Object value) throws AknotException { + Log.verbose(" Set value ='" + beanName + "' propertyValue='" + value + "' " + value.getClass().getCanonicalName()); + { + // by default use setter to set the property + final IntrospectionProperty propMethode = findBeanNodeDescription(beanName); + if (propMethode != null && propMethode.canSetValue()) { + Log.verbose(" ==> find '" + Arrays.toString(propMethode.getNames()) + " type=" + propMethode.getType() + " sub-type=" + propMethode.getSubType()); + if (propMethode.getType().isAssignableFrom(value.getClass())) { + propMethode.setExistingValue(data, value); + } else if (value instanceof List) { + @SuppressWarnings("unchecked") + final List tmpp = (List) value; + if (propMethode.getType().isArray()) { + if (propMethode.getType().componentType().isPrimitive()) { + final Object newData = ArraysTools.listToPrimitiveAuto(propMethode.getType().componentType(), tmpp); + propMethode.setExistingValue(data, newData); + } else { + Log.verbose(" datas type: " + autoCast(propMethode.getType().componentType(), tmpp).getClass().getCanonicalName()); + Log.verbose(" methode type: " + propMethode.getType().getCanonicalName()); + propMethode.setExistingValue(data, autoCast(propMethode.getType().componentType(), tmpp)); + } + } else if (tmpp.size() == 1) { + propMethode.setExistingValue(data, tmpp.get(0)); + } else { + // impossible case ... + } + } else if (propMethode.getType() == byte.class) { + final byte dataPrimitive = (Byte) value; + propMethode.setExistingValue(data, dataPrimitive); + } else if (propMethode.getType() == short.class) { + final short dataPrimitive = (Short) value; + propMethode.setExistingValue(data, dataPrimitive); + } else if (propMethode.getType() == int.class) { + final int dataPrimitive = (Integer) value; + propMethode.setExistingValue(data, dataPrimitive); + } else if (propMethode.getType() == long.class) { + final long dataPrimitive = (Long) value; + propMethode.setExistingValue(data, dataPrimitive); + } else if (propMethode.getType() == boolean.class) { + final boolean dataPrimitive = (Boolean) value; + propMethode.setExistingValue(data, dataPrimitive); + } else if (propMethode.getType() == float.class) { + final float dataPrimitive = (Float) value; + propMethode.setExistingValue(data, dataPrimitive); + } else if (propMethode.getType() == double.class) { + final double dataPrimitive = (Double) value; + propMethode.setExistingValue(data, dataPrimitive); + } else { + + } + return; + } + } + // try with direct field + { + final IntrospectionProperty propField = findBeanPropertyDescription(beanName); + if (propField != null && propField.canSetValue()) { + Log.verbose(" ==> find '" + Arrays.toString(propField.getNames()) + " type=" + propField.getType() + " sub-type=" + propField.getSubType()); + if (propField.getType().isAssignableFrom(value.getClass())) { + propField.setExistingValue(data, value); + // Some specific case for primitives values + } else if (propField.getType() == byte.class) { + final byte dataPrimitive = (Byte) value; + propField.setExistingValue(data, dataPrimitive); + } else if (propField.getType() == short.class) { + final short dataPrimitive = (Short) value; + propField.setExistingValue(data, dataPrimitive); + } else if (propField.getType() == int.class) { + final int dataPrimitive = (Integer) value; + propField.setExistingValue(data, dataPrimitive); + } else if (propField.getType() == long.class) { + final long dataPrimitive = (Long) value; + propField.setExistingValue(data, dataPrimitive); + } else if (propField.getType() == float.class) { + final float dataPrimitive = (Float) value; + propField.setExistingValue(data, dataPrimitive); + } else if (propField.getType() == double.class) { + final Double dataPrimitive = (Double) value; + propField.setExistingValue(data, dataPrimitive); + } else if (propField.getType() == boolean.class) { + final boolean dataPrimitive = (Boolean) value; + propField.setExistingValue(data, dataPrimitive); + } else { + @SuppressWarnings("unchecked") + final List tmpp = (List) value; + if (propField.getType().isArray()) { + if (propField.getType().componentType() == byte.class) { + final byte[] datas = ArraysTools.listByteToPrimitive(tmpp); + propField.setExistingValue(data, datas); + } else if (propField.getType().componentType() == short.class) { + final short[] datas = ArraysTools.listShortToPrimitive(tmpp); + propField.setExistingValue(data, datas); + } else if (propField.getType().componentType() == int.class) { + final int[] datas = ArraysTools.listIntegerToPrimitive(tmpp); + propField.setExistingValue(data, datas); + } else if (propField.getType().componentType() == long.class) { + final long[] datas = ArraysTools.listLongToPrimitive(tmpp); + propField.setExistingValue(data, datas); + } else if (propField.getType().componentType() == boolean.class) { + final boolean[] datas = ArraysTools.listBooleanToPrimitive(tmpp); + propField.setExistingValue(data, datas); + } else if (propField.getType().componentType() == float.class) { + final float[] datas = ArraysTools.listFloatToPrimitive(tmpp); + propField.setExistingValue(data, datas); + } else if (propField.getType().componentType() == double.class) { + final double[] datas = ArraysTools.listDoubleToPrimitive(tmpp); + propField.setExistingValue(data, datas); + } else { + Log.verbose(" datas type: " + autoCast(propField.getType().componentType(), tmpp).getClass().getCanonicalName()); + Log.verbose(" methode type: " + propField.getType().getCanonicalName()); + propField.setExistingValue(data, autoCast(propField.getType().componentType(), tmpp)); + } + } else if (tmpp.size() == 1) { + propField.setExistingValue(data, tmpp.get(0)); + } else { + // impossible case ... + } + } + return; + } + } + throw new AknotException("can not find the field '" + beanName + "'"); + } + + @Override + public String toString(final Object data) throws AknotException { + if (this.tostring == null) { + if (StringSerializer.contains(this.classType)) { + throw new AknotException("function 'toString' for '" + this.classType.getCanonicalName() + "' is not defined and not registered for specific type"); + } + return StringSerializer.toString(data); + } + try { + return (String) this.tostring.invoke(data); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + if (Enum.class.isAssignableFrom(this.classType)) { + throw new AknotException( + "Error in call 'toString()' for '" + this.classType.getCanonicalName() + "' ==> '????' ... availlable list: " + Arrays.asList(this.classType.getEnumConstants())); + } + e.printStackTrace(); + throw new AknotException("Error in call 'toString()' for '" + this.classType.getCanonicalName() + "' " + e.getMessage()); + } + } + + private IntrospectionProperty updateForMethod(final String name, final Method method, final Class[] types) throws Exception { + IntrospectionProperty prop = findElement(name); + if (prop == null) { + final String[] names = ReflectTools.getNames(method, null); + prop = new IntrospectionProperty(name, types, names); + this.elements.add(prop); + } else { + final Class curentType = prop.getType(); + final Class curentSubType = prop.getSubType(); + if (curentType != types[0] && curentSubType != types[1]) { + throw new AknotException("Set 'return type' with a different value previous=" + curentType.getCanonicalName() + " / " + curentSubType.getCanonicalName() + " ==> new=" + types[0] + + " / " + types[1] + " in " + method.toGenericString()); + } + final String[] names = ReflectTools.getNames(method, null); + if (names != null) { + final String[] curentValue = prop.getNames(); + if (curentValue != null && !checkIdenticalArray(curentValue, names)) { + // TODO maybe set the value permissive if no change !!! like the others... + throw new AknotException("Set 'names' with a (already set!) " + method.toGenericString()); + } + prop.setNames(names); + } + } + final String description = ReflectTools.getDescription(method, null); + if (description != null) { + final String curentValue = prop.getListName(); + if (curentValue != null && curentValue != description) { + throw new AknotException("Set 'description' with a different value previous=" + curentValue + " ==> new=" + description + " in " + method.toGenericString()); + } + prop.setDescription(description); + } + final String listName = ReflectTools.getListName(method, null); + if (listName != null) { + final String curentValue = prop.getListName(); + if (curentValue != null && curentValue != listName) { + throw new AknotException("Set 'listNAme' with a different value previous=" + curentValue + " ==> new=" + listName + " in " + method.toGenericString()); + } + prop.setListName(listName); + } + final Boolean isAttribute = ReflectTools.getIsAttribute(method, null); + if (isAttribute != null) { + final Boolean curentValue = prop.isAttribute(); + if (curentValue != null && curentValue != isAttribute) { + throw new AknotException("Set 'attribute' with a different value previous=" + curentValue + " ==> new=" + isAttribute + " in " + method.toGenericString()); + } + prop.setAttribute(isAttribute); + } + final Boolean isManaged = ReflectTools.getIsManaged(method, null); + if (isManaged != null) { + final Boolean curentValue = prop.isManaged(); + if (curentValue != null && curentValue != isManaged) { + throw new AknotException("Set 'managed' with a different value previous=" + curentValue + " ==> new=" + isManaged + " in " + method.toGenericString()); + } + prop.setManaged(isManaged); + } + final Boolean isOptionnal = ReflectTools.getIsOptional(method, null); + if (isOptionnal != null) { + final Boolean curentValue = prop.isOptionnal(); + if (curentValue != null && curentValue != isOptionnal) { + throw new AknotException("Set 'optionnal' with a different value previous=" + curentValue + " ==> new=" + isOptionnal + " in " + method.toGenericString()); + } + prop.setOptionnal(isOptionnal); + } + final Boolean isCaseSensitive = ReflectTools.getIsCaseSensitive(method, null); + if (isCaseSensitive != null) { + final Boolean curentValue = prop.isCaseSensitive(); + if (curentValue != null && curentValue != isCaseSensitive) { + throw new AknotException("Set 'case sensitive' with a different value previous=" + curentValue + " ==> new=" + isCaseSensitive + " in " + method.toGenericString()); + } + prop.setCaseSensitive(isCaseSensitive); + } + final Boolean isText = ReflectTools.getIsText(method, null); + if (isText != null) { + final Boolean curentValue = prop.isText(); + if (curentValue != null && curentValue != isText) { + throw new AknotException("Set 'case sensitive' with a different value previous=" + curentValue + " ==> new=" + isText + " in " + method.toGenericString()); + } + prop.setTextMode(isText); + } + final Class factory = ReflectTools.getFactory(method); + if (factory != null) { + prop.setFactory(factory); + } + return prop; + } +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionModelFactory.java b/src/org/atriasoft/aknot/pojo/IntrospectionModelFactory.java new file mode 100644 index 0000000..07c2670 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionModelFactory.java @@ -0,0 +1,29 @@ +package org.atriasoft.aknot.pojo; + +import org.atriasoft.aknot.StringSerializer; +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionModel; +import org.atriasoft.aknot.model.MapKey; + +public class IntrospectionModelFactory { + public static IntrospectionModel createModel(final MapKey modelType) throws AknotException { + if (StringSerializer.contains(modelType.type())) { + return new IntrospectionModelBaseType(modelType.type()); + } + return new IntrospectionModelComplex(modelType.type()); + } + + public static IntrospectionModel createModelArray(final String nodeName, final MapKey modelType) throws AknotException { + return new IntrospectionModelArray(nodeName, modelType.type()); + } + + public static IntrospectionModel createModelEnum(final MapKey modelType) throws AknotException { + return new IntrospectionModelComplex(modelType.type()); + } + + public static IntrospectionModel createModelList(final String nodeName, final MapKey modelType) throws AknotException { + return new IntrospectionModelList(nodeName, modelType.type()); + } + + private IntrospectionModelFactory() {} +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionModelList.java b/src/org/atriasoft/aknot/pojo/IntrospectionModelList.java new file mode 100644 index 0000000..3413bce --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionModelList.java @@ -0,0 +1,55 @@ +package org.atriasoft.aknot.pojo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionModel; + +public class IntrospectionModelList extends IntrospectionModel { + final String nodeName; + + public IntrospectionModelList(final String nodeName, final Class classType) { + super(classType); + this.nodeName = nodeName; + } + + @Override + public Object createObject(final Map properties, final Map> nodes) throws AknotException { + if (this.nodeName == null) { + return nodes.get(IntrospectionObject.STUPID_TOCKEN); + } + return nodes.get(this.nodeName); + } + + @Override + public List getNodeAvaillable() { + if (this.nodeName != null) { + return Arrays.asList(this.nodeName); + } + return null; + } + + @Override + public Object getValue(final String propertyName, final String propertyValue) throws AknotException { + return null; + } + + @Override + public Object getValueFromText(final String text) throws AknotException { + return new ArrayList<>(); + } + + @Override + public boolean isList() { + return true; + } + + @Override + public String toString(final Object data) { + return null; + } + +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionObject.java b/src/org/atriasoft/aknot/pojo/IntrospectionObject.java new file mode 100644 index 0000000..ff84998 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionObject.java @@ -0,0 +1,167 @@ +package org.atriasoft.aknot.pojo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.internal.Log; +import org.atriasoft.aknot.model.IntrospectionModel; + +public class IntrospectionObject { + public static final String PUBLIC_TEXT_NAME = "##<< ** TEXT-ZONE ** >>##"; + public static final String STUPID_TOCKEN = "___aé\"'__-è==**ù!^$:;,;AZEARTYUIOPMLKJHGFDSQW>XCVBN?"; // can not exist .... + private final IntrospectionModel modelInterface; + private Object data = null; + private final Map properties = new HashMap<>(); + private final Map> nodes = new HashMap<>(); + + public IntrospectionObject(final IntrospectionModel dataInterface) { + this.modelInterface = dataInterface; + } + + @SuppressWarnings("unchecked") + public void addObject(final String nodeName, final Object value) throws AknotException { + if (value == null) { + // specific case when a List is empty but define for a specific list ==> need no action + return; + } + final String beanName = this.modelInterface.getBeanName(nodeName); + if (beanName == null) { + throw new AknotException("The node '" + nodeName + "' Does not exist..."); + } + List node = this.nodes.get(beanName); + if (node == null) { + if (List.class.isAssignableFrom(value.getClass())) { + node = (List) value; + } else if (value.getClass().isArray()) { + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + } else { + node = new ArrayList<>(); + node.add(value); + } + this.nodes.put(beanName, node); + } else if (value.getClass().isArray()) { + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + Log.error("this is a big problem ..."); + } else if (List.class.isAssignableFrom(value.getClass())) { + final List nodeIn = (List) value; + node.addAll(nodeIn); + } else { + node.add(value); + } + } + + public void generateTheObject() throws AknotException { + if (this.data != null) { + // nothing to do ... ==> element already created + return; + } + Log.debug("Create the element for the Specific node ... type = " + this.modelInterface.getClassType().getCanonicalName() + (this.modelInterface.isArray() ? "[array]" : "") + + (this.modelInterface.isList() ? "[List]" : "")); + Log.debug(" Properties : " + this.properties.keySet()); + Log.debug(" Nodes : " + this.nodes.keySet()); + this.data = this.modelInterface.createObject(this.properties, this.nodes); + } + + public Object getData() { + return this.data; + } + + public IntrospectionModel getModelIntrospection() { + return this.modelInterface; + } + + public String getTreeNameOfSubNode(final String nodeName) throws AknotException { + final String beanName = this.modelInterface.getBeanName(nodeName); + if (beanName == null) { + throw new AknotException("The node '" + nodeName + "' Does not exist..."); + } + return this.modelInterface.getTreeNameOfSubNode(beanName); + } + + public Class getTypeOfProperty(final String nodeName) throws AknotException { + final String beanName = this.modelInterface.getBeanName(nodeName); + if (beanName == null) { + throw new AknotException("The node '" + nodeName + "' Does not exist..."); + } + return this.modelInterface.getTypeOfProperty(beanName); + } + + /** + * Detect a subNode, and ask the type of the node at the parent Class + * @param nodeName Name of the node + * @return Class of the node to create + */ + public Class getTypeOfSubNode(final String nodeName) throws AknotException { + final String beanName = this.modelInterface.getBeanNameModel(nodeName); + + if (beanName == null) { + throw new AknotException("The node '" + nodeName + "' Does not exist..."); + } + return this.modelInterface.getTypeOfSubNode(beanName); + } + + public Class getTypeOfSubNodeSubType(final String nodeName) throws AknotException { + final String beanName = this.modelInterface.getBeanNameModel(nodeName); + if (beanName == null) { + throw new AknotException("The node '" + nodeName + "' Does not exist..."); + } + return this.modelInterface.getTypeOfSubNodeList(beanName); + } + + public Class getTypeOfSubProperty(final String nodeName) throws AknotException { + final String beanName = this.modelInterface.getBeanName(nodeName); + if (beanName == null) { + throw new AknotException("The node '" + nodeName + "' Does not exist..."); + } + return this.modelInterface.getTypeOfSubProperty(beanName); + } + + public boolean isSubNodeOrPropertyExist(final String nodeName) { + final String beanName = this.modelInterface.getBeanName(nodeName); + if (beanName == null) { + return false; + } + return true; + } + + public void putProperty(final String propertyName, final Object propertyValue) throws AknotException { + String beanName = null; + if (propertyName == PUBLIC_TEXT_NAME) { + beanName = this.modelInterface.getTextBeanName(); + } else { + beanName = this.modelInterface.getBeanName(propertyName); + } + if (this.properties.containsKey(beanName)) { + throw new AknotException("Property have multiple values ==> impossible case; A Node must contain only 1 attibutes"); + } + this.properties.put(beanName, propertyValue); + } + + public void setText(final String text) throws AknotException { + if (this.data != null) { + throw new AknotException("Can not set multiple text value in a single NODE ..."); + } + this.data = this.modelInterface.getValueFromText(text); + } + +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionProperty.java b/src/org/atriasoft/aknot/pojo/IntrospectionProperty.java new file mode 100644 index 0000000..9c33d73 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionProperty.java @@ -0,0 +1,344 @@ +package org.atriasoft.aknot.pojo; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map.Entry; + +import org.atriasoft.aknot.StringSerializer; +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.internal.Log; +import org.atriasoft.aknot.model.InterfaceFactoryAccess; +import org.atriasoft.aknot.model.IntrospectionPropertyGetter; +import org.atriasoft.aknot.model.IntrospectionPropertySetter; + +public final class IntrospectionProperty { + // if the element is not managed by us (set at null while not define by a function attribute, parameter ...) + private Boolean managed = null; + // Case sensitive in parsing XML or not (set at null while not define by a function attribute, parameter ...) + private Boolean caseSensitive = null; + // Optional or not (set at null while not define by a function attribute, parameter ...) + private Boolean optionnal = null; + // Attribute or Node (set at null while not define by a function attribute, parameter ...) + private Boolean attribute = null; + // this is a Signal interface + private Boolean signal = null; + private Boolean textMode = null; + // Decription of the element @AknotDescription + private String description = null; + // name of the field or the function before renaming... + private final String beanName; + // names that can take the Node or the attibute + private String[] names = null; + // if organized in sublist (!= null) then the subNode have this value + private String listName = null; + private boolean canBeSetByConstructor = false; + private Class factory = null; + private InterfaceFactoryAccess factoryCreated = null; + private final Class type; + private final Class subType; + // can get the property, if null not gettable ... ==> TODO need to remove this property ??? + // First function call + // second field access + IntrospectionPropertyGetter getter = null; + // can get the property, if null not settable (otherwise use the constructor???) + // First constructor call + // second function call + // third field access + IntrospectionPropertySetter setter = null; + + public IntrospectionProperty(final String beanName, final Class[] type, final String[] names) { + this.beanName = beanName; + this.type = type[0]; + this.subType = type[1]; + this.names = names; + } + + public boolean canGetValue() { + return this.getter != null; + } + + public boolean canSetValue() { + return this.canBeSetByConstructor || this.setter != null; + } + + /** + * Create a value adapted to the property type. + * @apiNote generic type is transformed byte -> Byte, int -> Integer ... + * @param value Value to set in the Object + * @throws Exception An error occurred + * @return The object created + */ + public Object createValue(final String value) throws AknotException { + try { + if (StringSerializer.contains(this.type)) { + // TODO This might be a deprecated code .... + return StringSerializer.valueOf(this.type, value); + } + if (this.type.isEnum()) { + + } else if ((this.type != List.class) || !StringSerializer.contains(this.subType)) { + throw new AknotException("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); + } + final ArrayList out = new ArrayList<>(); + for (final String elem : value.split(";")) { + out.add(StringSerializer.valueOf(this.subType, elem)); + } + return out; + } catch (final IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new AknotException("Error in parsing the property value ... " + e.getMessage()); + } + } + + public String getBeanName() { + return this.beanName; + } + + public Class getCompatible(final String name) { + final InterfaceFactoryAccess factoryGenerator = getFactory(); + if (factoryGenerator != null) { + if (this.caseSensitive) { + for (final Entry> elem : factoryGenerator.getConversionMap().entrySet()) { + if (elem.getKey().contentEquals(name)) { + return elem.getValue(); + } + } + } else { + for (final Entry> elem : factoryGenerator.getConversionMap().entrySet()) { + if (elem.getKey().equalsIgnoreCase(name)) { + return elem.getValue(); + } + } + } + return null; + } + if (this.caseSensitive) { + for (final String elem : this.names) { + if (elem.contentEquals(name)) { + Log.verbose(" - '{}' ==> true", elem); + return getType(); + } + Log.verbose(" - '{}' == false", elem); + } + } else { + for (final String elem : this.names) { + if (elem.equalsIgnoreCase(name)) { + Log.verbose(" - '{}' ==> true", elem); + return getType(); + } + Log.verbose(" - '{}' == false", elem); + } + } + return null; + } + + public String getDescription() { + return this.description; + } + + public InterfaceFactoryAccess getFactory() { + if (this.factoryCreated == null && this.factory != null) { + try { + this.factoryCreated = (InterfaceFactoryAccess) this.factory.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + this.factoryCreated = null; + this.factory = null; + } + } + return this.factoryCreated; + } + + public String getListName() { + return this.listName; + } + + /** + * Get list of all names of the property/node (minimal 1 name) + * @return Array of names available + */ + public String[] getNames() { + return this.names; + } + + /** + * Get the type of the property (if list ==> need to be get on method otherwise it is an Object...) + * @return Type of the list element. + */ + public Class getSubType() { + return this.subType; + } + + /** + * Get basic type of the property + * @return property type detected. + */ + public Class getType() { + return this.type; + } + + /** + * Get the value in the object. + * @param object Object that is invoke to get the value. + * @return The generate value of the object + * @throws AknotException in an error occured + */ + public Object getValue(final Object object) throws AknotException { + if (this.getter != null) { + return this.getter.getValue(object); + } + throw new AknotException("Property: " + this.names + " have no getter"); + } + + public boolean hasFactory() { + return this.factory != null; + } + + public Boolean isAttribute() { + return this.attribute; + } + + public boolean isCanBeSetByConstructor() { + return this.canBeSetByConstructor; + } + + public Boolean isCaseSensitive() { + return this.caseSensitive; + } + + /** + * Check if the input name is compatible win an element in the list availlable (respect case sensitive if needed) + * @param name Name to check + * @return true if the element is compatible, false otherwise + */ + public boolean isCompatible(final String name) { + Log.verbose("Check compatible : '{}' in {}", name, Arrays.toString(this.names)); + final InterfaceFactoryAccess factoryGenerator = getFactory(); + if (factoryGenerator != null) { + Log.verbose(" ===> Detect factory !!!!"); + if (this.caseSensitive) { + for (final Entry> elem : factoryGenerator.getConversionMap().entrySet()) { + if (elem.getKey().contentEquals(name)) { + Log.verbose(" + '{}' ==> true", elem.getKey()); + return true; + } + Log.verbose(" + '{}' == false", elem.getKey()); + } + } else { + for (final Entry> elem : factoryGenerator.getConversionMap().entrySet()) { + if (elem.getKey().equalsIgnoreCase(name)) { + Log.verbose(" + '{}' ==> true", elem.getKey()); + return true; + } + Log.verbose(" + '{}' == false", elem.getKey()); + } + } + return false; + } + if (this.caseSensitive) { + for (final String elem : this.names) { + if (elem.contentEquals(name)) { + Log.verbose(" - '{}' ==> true", elem); + return true; + } + Log.verbose(" - '{}' == false", elem); + } + } else { + for (final String elem : this.names) { + if (elem.equalsIgnoreCase(name)) { + Log.verbose(" - '{}' ==> true", elem); + return true; + } + Log.verbose(" - '{}' == false", elem); + } + } + return false; + } + + public Boolean isManaged() { + return this.managed; + } + + public Boolean isOptionnal() { + return this.optionnal; + } + + public Boolean isSignal() { + return this.signal; + } + + public Boolean isText() { + return this.textMode; + } + + public void setAttribute(final Boolean attribute) { + this.attribute = attribute; + } + + public void setCanBeSetByConstructor(final boolean canBeSetByConstructor) { + this.canBeSetByConstructor = canBeSetByConstructor; + } + + public void setCaseSensitive(final Boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + public void setDescription(final String description) { + this.description = description; + } + + /** + * set the specific string value in the specified object + * @param object Object to add the value + * @param value Value to set in the Object + * @throws Exception An error occurred + */ + public void setExistingValue(final Object object, final Object value) throws AknotException { + if (this.setter != null) { + this.setter.setValue(object, value); + return; + } + throw new AknotException("Property: " + this.names + " have no setter"); + } + + public void setFactory(final Class factory) { + this.factory = factory; + } + + public void setGetter(final IntrospectionPropertyGetter getter) { + this.getter = getter; + } + + public void setListName(final String listName) { + this.listName = listName; + } + + public void setManaged(final Boolean managed) { + this.managed = managed; + } + + public void setNames(final String[] names) { + this.names = names; + } + + public void setOptionnal(final Boolean optionnal) { + this.optionnal = optionnal; + } + + public void setSetter(final IntrospectionPropertySetter setter) { + this.setter = setter; + } + + public void setSignal(final Boolean signal) { + this.signal = signal; + } + + public void setTextMode(final Boolean isText) { + this.textMode = isText; + } + +} \ No newline at end of file diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionPropertyField.java b/src/org/atriasoft/aknot/pojo/IntrospectionPropertyField.java new file mode 100644 index 0000000..b78c7d4 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionPropertyField.java @@ -0,0 +1,44 @@ +package org.atriasoft.aknot.pojo; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionPropertyGetter; +import org.atriasoft.aknot.model.IntrospectionPropertySetter; + +public class IntrospectionPropertyField implements IntrospectionPropertyGetter, IntrospectionPropertySetter { + private final Field fieldDescription; + private final boolean finalValue; + + public IntrospectionPropertyField(final Field fieldDescription) { + this.fieldDescription = fieldDescription; + this.finalValue = Modifier.isFinal(fieldDescription.getModifiers()); + } + + @Override + public Object getValue(final Object object) throws AknotException { + try { + return this.fieldDescription.get(object); + } catch (IllegalArgumentException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new AknotException("Can not set value ... " + e.getMessage()); + } + } + + @Override + public void setValue(final Object object, final Object value) throws AknotException { + if (this.finalValue) { + throw new AknotException("Can not set value The value is FINAL!!!"); + } + try { + this.fieldDescription.set(object, value); + } catch (IllegalArgumentException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new AknotException("Can not set value ... " + e.getMessage()); + } + } + +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodGetter.java b/src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodGetter.java new file mode 100644 index 0000000..0b99c15 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodGetter.java @@ -0,0 +1,75 @@ +package org.atriasoft.aknot.pojo; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionPropertyGetter; + +public class IntrospectionPropertyMethodGetter implements IntrospectionPropertyGetter { + // private static Class[] getTypefunction(final Method setter, final Method getter) throws Exception { + // Class type = null; + // Class subType = null; + // if (setter == null && getter == null) { + // // impossible case... + // throw new Exception("kjhkhjkj"); + // } + // if (getter != null) { + // type = getter.getReturnType(); + // if (Enum.class.isAssignableFrom(type)) { + // Log.verbose("Find an enum ..."); + // } else { + // Type empppe = getter.getGenericReturnType(); + // if (empppe instanceof ParameterizedType plopppppp) { + // Type[] realType = plopppppp.getActualTypeArguments(); + // if (realType.length > 0) { + // subType = Class.forName(realType[0].getTypeName()); + // } + // } + // } + // } + // if (setter != null) { + // if (type != null && setter.getParameters()[0].getType() != type) { + // throw new Exception("The type of the setter ands the type return by the getter are not the same ..."); + // } + // type = setter.getParameters()[0].getType(); + // if (List.class.isAssignableFrom(type)) { + // Class internalModelClass = null; + // Type[] empppe = setter.getGenericParameterTypes(); + // if (empppe.length > 0) { + // if (empppe[0] instanceof ParameterizedType plopppppp) { + // Type[] realType = plopppppp.getActualTypeArguments(); + // if (realType.length > 0) { + // Log.warning(" -->> " + realType[0]); + // internalModelClass = Class.forName(realType[0].getTypeName()); + // } + // } + // } + // if (getter!=null && internalModelClass != subType) { + // throw new Exception("The type of the setter and the type return by the getter are not the same ..."); + // } + // subType = internalModelClass; + // } + // } + // return new Class[] {type, subType}; + // } + // + protected Method getter; + + public IntrospectionPropertyMethodGetter(final Method getter) throws Exception { + this.getter = getter; + } + + @Override + public Object getValue(final Object object) throws AknotException { + if (this.getter == null) { + throw new AknotException("no getter availlable"); + } + try { + return this.getter.invoke(object); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + e.printStackTrace(); + throw new AknotException("Can not set value ... " + e.getMessage()); + } + } +} diff --git a/src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodSetter.java b/src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodSetter.java new file mode 100644 index 0000000..b88afe1 --- /dev/null +++ b/src/org/atriasoft/aknot/pojo/IntrospectionPropertyMethodSetter.java @@ -0,0 +1,29 @@ +package org.atriasoft.aknot.pojo; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.atriasoft.aknot.exception.AknotException; +import org.atriasoft.aknot.model.IntrospectionPropertySetter; + +public class IntrospectionPropertyMethodSetter implements IntrospectionPropertySetter { + protected Method setter; + + public IntrospectionPropertyMethodSetter(final Method setter) throws Exception { + this.setter = setter; + } + + @Override + public void setValue(final Object object, final Object value) throws AknotException { + if (this.setter == null) { + throw new AknotException("no setter availlable"); + } + try { + this.setter.invoke(object, value); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new AknotException("Can not set value ... " + e.getMessage()); + } + } +}