From 7017468e461adf2edda0c383c1d8cd74f1ee771f Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Thu, 24 Jun 2021 21:48:10 +0200 Subject: [PATCH] [DEV] continue integration of generated object --- src/org/atriasoft/exml/builder/Builder.java | 17 +- .../exml/builder/BuilderGeneric.java | 10 + .../exml/builder/BuilderIntrospection.java | 46 ++- .../exml/builder/IntrospectionData.java | 373 ++++++++++++++++-- .../exml/builder/IntrospectionObject.java | 62 ++- .../exml/builder/IntrospectionProperty.java | 37 +- .../builder/IntrospectionPropertyField.java | 97 ++++- .../builder/IntrospectionPropertyMethod.java | 121 +++++- src/org/atriasoft/exml/parser/ParseXml.java | 5 + .../atriasoft/exml/ExmlTestIntrospection.java | 12 + .../introspection/ClassPublicMethodeNode.java | 51 +++ 11 files changed, 772 insertions(+), 59 deletions(-) diff --git a/src/org/atriasoft/exml/builder/Builder.java b/src/org/atriasoft/exml/builder/Builder.java index 410c966..dfa6e0c 100644 --- a/src/org/atriasoft/exml/builder/Builder.java +++ b/src/org/atriasoft/exml/builder/Builder.java @@ -27,7 +27,7 @@ public interface Builder { Object newDeclaration(Object parent, String text) throws ExmlBuilderException; /** - * Add a new sub-element on the current parent element + * Add a new sub-element on the current parent element: {@code} * @param parent Element representing the node of the Element is added * @param nodeName New element name. * @return the object representing the Element. @@ -59,4 +59,19 @@ public interface Builder { */ void newText(Object parent, String text) throws ExmlBuilderException; + /** + * Detect the end of the element : {@code} + * @param element Element that is finished + * @throws ExmlBuilderException + */ + void endElement(Object element) throws ExmlBuilderException; + + /** + * The sub-element is finish (creation done) + * @param parent Parent node of the element that is created + * @param tmpname Name of the node + * @param element Element builder that has been created + */ + void newElementFinished(Object parent, String tmpname, Object element); + } diff --git a/src/org/atriasoft/exml/builder/BuilderGeneric.java b/src/org/atriasoft/exml/builder/BuilderGeneric.java index 09302b0..f37ae3a 100644 --- a/src/org/atriasoft/exml/builder/BuilderGeneric.java +++ b/src/org/atriasoft/exml/builder/BuilderGeneric.java @@ -66,5 +66,15 @@ public class BuilderGeneric implements Builder { } throw new ExmlBuilderException("can not add Text on something else than Element or Declaration"); } + + @Override + public void endElement(Object element) { + // Nothing to do... + } + + @Override + public void newElementFinished(Object parent, String tmpname, Object element) { + // Nothing to do... + } } diff --git a/src/org/atriasoft/exml/builder/BuilderIntrospection.java b/src/org/atriasoft/exml/builder/BuilderIntrospection.java index 2082992..b9c4de9 100644 --- a/src/org/atriasoft/exml/builder/BuilderIntrospection.java +++ b/src/org/atriasoft/exml/builder/BuilderIntrospection.java @@ -56,19 +56,26 @@ public class BuilderIntrospection implements Builder { if (nodeName.contentEquals(this.rootNodeName)) { Log.verbose("Create new class: " + this.rootClassType.getCanonicalName()); final IntrospectionData inferData = findOrCreate(this.rootClassType); - final Object newElement = inferData.createObject(); - rootList.add(newElement); - return new IntrospectionObject(inferData, newElement); + //final Object newElement = inferData.createObject(); + //rootList.add(newElement); + return new IntrospectionObject(inferData);//, newElement); } // need to add a throw on the node... return null; // ==> disable the parsing.. } Class typeClass = introspectionObject.getTypeOfSubNode(nodeName); if (typeClass != null) { - Log.verbose("Create new class: '" + typeClass.getCanonicalName() + "' for node '" + nodeName + "'"); - final IntrospectionData inferData = findOrCreate(typeClass); + if (!List.class.isAssignableFrom(typeClass)) { + Log.verbose("Create new class: '" + typeClass.getCanonicalName() + "' for node '" + nodeName + "'"); + final IntrospectionData inferData = findOrCreate(typeClass); + // Create the data when object is ended created... + return new IntrospectionObject(inferData); + } + Class subTypeClass = introspectionObject.getTypeOfSubNodeSubType(nodeName); + Log.verbose("Create new 'SUB' class: '" + typeClass.getCanonicalName() + "' for node '" + nodeName + "'"); + final IntrospectionData inferData = findOrCreate(subTypeClass); // Create the data when object is ended created... - return new IntrospectionObject(inferData, null); + return new IntrospectionObject(inferData); } return null; } @@ -98,5 +105,30 @@ public class BuilderIntrospection implements Builder { } introspectionObject.setText(text); } - + + @Override + public void endElement(Object element) throws ExmlBuilderException { + + final IntrospectionObject introspectionObject = (IntrospectionObject) element; + if (introspectionObject.getDataInterface() == null) { + // property on nothing ??? + return; + } + introspectionObject.generateTheObject(); + } + + @Override + public void newElementFinished(Object parent, String tmpname, Object element) { + final IntrospectionObject introspectionElementObject = (IntrospectionObject) element; + if (introspectionElementObject.getDataInterface() == null) { + // property on nothing ??? + return; + } + final IntrospectionObject introspectionParentObject = (IntrospectionObject) parent; + if (introspectionParentObject.getDataInterface() == null) { + // property on nothing ??? + return; + } + introspectionParentObject.addObject(tmpname, introspectionElementObject.getData()); + } } diff --git a/src/org/atriasoft/exml/builder/IntrospectionData.java b/src/org/atriasoft/exml/builder/IntrospectionData.java index fb31e03..9ce8e70 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionData.java +++ b/src/org/atriasoft/exml/builder/IntrospectionData.java @@ -5,10 +5,20 @@ 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.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.stream.Collectors; +import org.atriasoft.etk.util.ArraysTools; import org.atriasoft.exml.annotation.XmlCaseSensitive; import org.atriasoft.exml.annotation.XmlDefaultCaseSensitive; import org.atriasoft.exml.annotation.XmlDefaultManaged; @@ -16,21 +26,140 @@ import org.atriasoft.exml.annotation.XmlDefaultOptional; import org.atriasoft.exml.annotation.XmlManaged; import org.atriasoft.exml.annotation.XmlName; import org.atriasoft.exml.annotation.XmlOptional; +import org.atriasoft.exml.exception.ExmlBuilderException; import org.atriasoft.exml.internal.Log; import org.atriasoft.exml.parser.Tools; + public class IntrospectionData { private static final Boolean DEFAULT_CASE_SENSITIVE = true; private static final Boolean DEFAULT_MANAGED = true; private static final Boolean DEFAULT_OPTIONAL = false; - + public interface Converter { + Object valueOf(String value); + } + private static final Map, Converter> VALUES_OF = new HashMap<>(); + static { + VALUES_OF.put(byte.class, new Converter() { + public Object valueOf(String value) { + return Byte.valueOf(value); + } + }); + VALUES_OF.put(Byte.class, new Converter() { + public Object valueOf(String value) { + return Byte.valueOf(value); + } + }); + VALUES_OF.put(int.class, new Converter() { + public Object valueOf(String value) { + return Integer.valueOf(value); + } + }); + VALUES_OF.put(Integer.class, new Converter() { + public Object valueOf(String value) { + return Integer.valueOf(value); + } + }); + VALUES_OF.put(long.class, new Converter() { + public Object valueOf(String value) { + return Long.valueOf(value); + } + }); + VALUES_OF.put(Long.class, new Converter() { + public Object valueOf(String value) { + return Long.valueOf(value); + } + }); + VALUES_OF.put(short.class, new Converter() { + public Object valueOf(String value) { + return Short.valueOf(value); + } + }); + VALUES_OF.put(Short.class, new Converter() { + public Object valueOf(String value) { + return Short.valueOf(value); + } + }); + VALUES_OF.put(boolean.class, new Converter() { + public Object valueOf(String value) { + return Boolean.valueOf(value); + } + }); + VALUES_OF.put(Boolean.class, new Converter() { + public Object valueOf(String value) { + return Boolean.valueOf(value); + } + }); + VALUES_OF.put(byte[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseByteStringList(value); + } + }); + VALUES_OF.put(Byte[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseByteClassStringList(value); + } + }); + VALUES_OF.put(short[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseShortStringList(value); + } + }); + VALUES_OF.put(Short[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseShortClassStringList(value); + } + }); + VALUES_OF.put(int[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseIntegerStringList(value); + } + }); + VALUES_OF.put(Integer[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseIntegerClassStringList(value); + } + }); + VALUES_OF.put(long[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseLongStringList(value); + } + }); + VALUES_OF.put(Long[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseLongClassStringList(value); + } + }); + VALUES_OF.put(boolean[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseBooleanStringList(value); + } + }); + VALUES_OF.put(Boolean[].class, new Converter() { + public Object valueOf(String value) { + return Tools.parseBooleanClassStringList(value); + } + }); + VALUES_OF.put(String.class, new Converter() { + public Object valueOf(String value) { + return value; + } + }); + } + final Class classType; + final Class subClassType; + private final Method valueof; // used for the set Text if the object is an end point... private final List methods = new ArrayList<>(); private final List properties = new ArrayList<>(); - + public IntrospectionData(final Class classType) throws Exception { + this(classType, null); + } + public IntrospectionData(final Class classType, final Class subClassType) throws Exception { this.classType = classType; + this.subClassType = subClassType; final Boolean isDefaultManaged = getIsDefaultManaged(classType, IntrospectionData.DEFAULT_MANAGED); final Boolean isDefaultOptional = getIsDefaultOptional(classType, IntrospectionData.DEFAULT_OPTIONAL); final Boolean isDefaultCaseSensitive = getIsDefaultCaseSensitive(classType, IntrospectionData.DEFAULT_CASE_SENSITIVE); @@ -59,6 +188,12 @@ public class IntrospectionData { if (o.getName().contentEquals("getClass")) { return false; } + if (Modifier.isStatic(o.getModifiers())) { + if (o.getName().contentEquals("valueOf") && o.getParameterCount() == 1 && o.getParameters()[0].getType() == String.class) { + return true; + } + return false; + } if (o.getName().startsWith("get")) { if (o.getParameterCount() != 0 || o.getReturnType() == void.class || o.getReturnType() == Boolean.class || o.getReturnType() == boolean.class) { return false; @@ -102,12 +237,12 @@ public class IntrospectionData { } return false; }).collect(Collectors.toList()); - 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); @@ -116,7 +251,14 @@ public class IntrospectionData { final List methodsGet = methods.stream().filter(o -> o.getName().startsWith("get")).collect(Collectors.toList()); final List methodsSet = methods.stream().filter(o -> o.getName().startsWith("set")).collect(Collectors.toList()); final List methodsIs = methods.stream().filter(o -> o.getName().startsWith("is")).collect(Collectors.toList()); - + final List valueOfString = methods.stream().filter(o -> o.getName().startsWith("valueOf")).collect(Collectors.toList()); + if (valueOfString.size() == 1) { + valueof = valueOfString.get(0); + } else { + // some specific model: + + valueof = null; + } // associate methods by pair. final List elements = new ArrayList<>(); for (final Method method : methodsGet) { @@ -146,6 +288,26 @@ public class IntrospectionData { break; } } + /* + Class internalModelClass = null; + Class[] tmppp = method.getParameterTypes(); + if (tmppp.length > 0 && (List.class.isAssignableFrom(tmppp[0]))) { + Log.warning(" * " + method.getName()); + Type[] empppe = method.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()); + } + } + } + for (int iii=0; iii properties, Map> nodes) throws ExmlBuilderException { + Object tmp; try { - return this.classType.getConstructor().newInstance(); + tmp = this.classType.getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } + for (Entry elem : properties.entrySet()) { + setValue(tmp, elem.getKey(), elem.getValue()); + } + for (Entry> elem : nodes.entrySet()) { + setValue(tmp, elem.getKey(), elem.getValue()); + } + return tmp; } - protected IntrospectionProperty findMethodDescription(final String propertyName) throws Exception { + protected IntrospectionProperty findMethodDescription(final String propertyName) throws ExmlBuilderException { for (final IntrospectionProperty prop : this.methods) { if (prop.isCompatible(propertyName)) { return prop; @@ -239,7 +409,7 @@ public class IntrospectionData { return null; } - protected IntrospectionProperty findPropertyDescription(final String propertyName) throws Exception { + protected IntrospectionProperty findPropertyDescription(final String propertyName) throws ExmlBuilderException { for (final IntrospectionProperty prop : this.properties) { if (prop.isCompatible(propertyName)) { return prop; @@ -248,79 +418,79 @@ public class IntrospectionData { return null; } - protected Boolean getIsCaseSensitive(final Field element, final Boolean defaultValue) throws Exception { + protected Boolean getIsCaseSensitive(final Field element, final Boolean defaultValue) throws ExmlBuilderException { final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlCaseSensitive.class); if (annotation.length == 0) { return defaultValue; } if (annotation.length > 1) { - throw new Exception("Must not hame more that "); + throw new ExmlBuilderException("Must not hame more that "); } return ((XmlCaseSensitive) annotation[0]).value(); } - protected Boolean getIsCaseSensitive(final Method element, final Boolean defaultValue) throws Exception { + protected Boolean getIsCaseSensitive(final Method element, final Boolean defaultValue) throws ExmlBuilderException { final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlCaseSensitive.class); if (annotation.length == 0) { return defaultValue; } if (annotation.length > 1) { - throw new Exception("Must not hame more that "); + throw new ExmlBuilderException("Must not hame more that "); } return ((XmlCaseSensitive) annotation[0]).value(); } - private Boolean getIsDefaultCaseSensitive(final Class classType, final Boolean defaultValue) throws Exception { + private Boolean getIsDefaultCaseSensitive(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultCaseSensitive.class); if (annotation.length == 0) { return defaultValue; } if (annotation.length > 1) { - throw new Exception("Must not hame more that "); + throw new ExmlBuilderException("Must not hame more that "); } return ((XmlDefaultCaseSensitive) annotation[0]).value(); } - private Boolean getIsDefaultManaged(final Class classType, final Boolean defaultValue) throws Exception { + private Boolean getIsDefaultManaged(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultManaged.class); if (annotation.length == 0) { return defaultValue; } if (annotation.length > 1) { - throw new Exception("Must not hame more that "); + throw new ExmlBuilderException("Must not hame more that "); } return ((XmlDefaultManaged) annotation[0]).value(); } - private Boolean getIsDefaultOptional(final Class classType, final Boolean defaultValue) throws Exception { + private Boolean getIsDefaultOptional(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultOptional.class); if (annotation.length == 0) { return defaultValue; } if (annotation.length > 1) { - throw new Exception("Must not hame more that "); + throw new ExmlBuilderException("Must not hame more that "); } return ((XmlDefaultOptional) annotation[0]).value(); } - protected Boolean getIsManaged(final Field element, final Boolean parentValue) throws Exception { + protected Boolean getIsManaged(final Field element, final Boolean parentValue) throws ExmlBuilderException { final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlManaged.class); if (annotation.length == 0) { return parentValue; } if (annotation.length > 1) { - throw new Exception("Must not hame more that "); + throw new ExmlBuilderException("Must not hame more that "); } return ((XmlManaged) annotation[0]).value(); } - protected Boolean getIsManaged(final Method element, final Boolean parentValue) throws Exception { + protected Boolean getIsManaged(final Method element, final Boolean parentValue) throws ExmlBuilderException { final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlManaged.class); if (annotation.length == 0) { return parentValue; } if (annotation.length > 1) { - throw new Exception("Must not hame more that "); + throw new ExmlBuilderException("Must not hame more that "); } return ((XmlManaged) annotation[0]).value(); } @@ -404,8 +574,75 @@ public class IntrospectionData { } return tmp; } - - public void setProperty(final Object data, final String propertyName, final String propertyValue) throws Exception { + @SuppressWarnings("unchecked") + private T[] autoCast(Class clazz, List data) { + T[] out = (T[]) java.lang.reflect.Array.newInstance(clazz, data.size());// T[data.size()]; + + for(int iii=0; iii find '" + Arrays.toString(propMethode.getNames()) + " type=" + propMethode.getType() + " sub-type=" + propMethode.getSubType() ); + if (propMethode.getType().isAssignableFrom(value.getClass())) { + propMethode.setExistingValue(data, value); + } else { + @SuppressWarnings("unchecked") + List tmpp = (List)value; + if (propMethode.getType().isArray()) { + if (propMethode.getType().componentType() == byte.class) { + byte[] datas = ArraysTools.listByteToPrimitive(tmpp); + propMethode.setExistingValue(data, datas); + } else if (propMethode.getType().componentType() == short.class) { + short[] datas = ArraysTools.listShortToPrimitive(tmpp); + propMethode.setExistingValue(data, datas); + } else if (propMethode.getType().componentType() == int.class) { + int[] datas = ArraysTools.listIntegerToPrimitive(tmpp); + propMethode.setExistingValue(data, datas); + } else if (propMethode.getType().componentType() == long.class) { + long[] datas = ArraysTools.listLongToPrimitive(tmpp); + propMethode.setExistingValue(data, datas); + } else if (propMethode.getType().componentType() == boolean.class) { + boolean[] datas = ArraysTools.listBooleanToPrimitive(tmpp); + propMethode.setExistingValue(data, datas); + } 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 ... + } + } + //Log.verbose(" ==> TODO...."); + } + return; + } + // try with direct field + final IntrospectionProperty propField = findPropertyDescription(name); + if (propField != null && propField.canSetValue()) { + Log.verbose(" ==> find '" + Arrays.toString(propField.getNames()) + " type=" + propMethode.getType() + " sub-type=" + propMethode.getSubType() ); + if (propField.getType().isAssignableFrom(value.getClass())) { + propField.setExistingValue(data, value); + } else { + Log.verbose(" ==> TODO...."); + } + return; + } + throw new ExmlBuilderException("can not find the field '" + name + "'"); + + } + public void setProperty(final Object data, final String propertyName, final String propertyValue) throws ExmlBuilderException { Log.error(" propertyName='" + propertyName + "' propertyValue='" + propertyValue + "' "); // by default use setter to set the property final IntrospectionProperty propMethode = findMethodDescription(propertyName); @@ -421,7 +658,7 @@ public class IntrospectionData { propField.setValue(data, propertyValue); return; } - throw new Exception("can not find the field '" + propertyName + "'"); + throw new ExmlBuilderException("can not find the field '" + propertyName + "'"); } /** @@ -429,7 +666,7 @@ public class IntrospectionData { * @param nodeName Name of the node * @return Class of the node to create */ - public Class getTypeOfSubNode(final Object data, final String nodeName) throws Exception { + public Class getTypeOfSubNode(final Object data, final String nodeName) throws ExmlBuilderException { Log.error(" nodeType='" + nodeName + "'"); // by default use setter to set the property final IntrospectionProperty propMethode = findMethodDescription(nodeName); @@ -443,11 +680,89 @@ public class IntrospectionData { Log.error(" ==> find '" + propField.getNames()); return propMethode.getType(); } - throw new Exception("can not find the field '" + nodeName + "'"); + throw new ExmlBuilderException("can not find the field '" + nodeName + "'"); + } + public Class getTypeOfSubNodeList(Object data, String nodeName) throws ExmlBuilderException { + Log.error(" nodeType='" + nodeName + "'"); + // by default use setter to set the property + final IntrospectionProperty propMethode = findMethodDescription(nodeName); + if (propMethode != null && propMethode.canSetValue()) { + Log.error(" ==> find '" + propMethode.getNames()); + return propMethode.getSubType(); + } + // try with direct field + final IntrospectionProperty propField = findPropertyDescription(nodeName); + if (propField != null && propField.canSetValue()) { + Log.error(" ==> find '" + propField.getNames()); + return propMethode.getSubType(); + } + throw new ExmlBuilderException("can not find the field '" + nodeName + "'"); } - public void setText(final Object data, final String text) { - Log.todo("add text to element ... '" + text + "' for type : " + this.classType.getCanonicalName()); + public Object getValueFromText(final String text) throws ExmlBuilderException { + // 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.warning("======>>>>>>> Get input type : " + this.classType.getCanonicalName()); + //Log.warning("======>>>>>>> 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 (subClassType != null) { + classTypeLocal = 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.warning("======>>>>>>> subElement input type : " + classTypeLocal.getCanonicalName()); + + if (this.valueof == null) { + Converter con = VALUES_OF.get(classTypeLocal); + if (con == null) { + throw new ExmlBuilderException("function 'valueOf' for '" + classTypeLocal.getCanonicalName() + "' is not defined and not registered for specific type"); + } else { + return con.valueOf(text); + } + } + try { + return this.valueof.invoke(null, text); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new ExmlBuilderException("Error in call 'valueOf(String ...)' for '" + classTypeLocal.getCanonicalName() + "' " + e.getMessage()); + } + } + + public Object getValue(String propertyName, String propertyValue) throws ExmlBuilderException { + Log.error(" propertyName='" + propertyName + "' propertyValue='" + propertyValue + "' "); + // by default use setter to set the property + final IntrospectionProperty propMethode = findMethodDescription(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 ExmlBuilderException("can not find the field '" + propertyName + "'"); } } diff --git a/src/org/atriasoft/exml/builder/IntrospectionObject.java b/src/org/atriasoft/exml/builder/IntrospectionObject.java index 20208ce..6e9ee78 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionObject.java +++ b/src/org/atriasoft/exml/builder/IntrospectionObject.java @@ -1,10 +1,18 @@ package org.atriasoft.exml.builder; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.atriasoft.exml.exception.ExmlBuilderException; +import org.atriasoft.exml.internal.Log; public class IntrospectionObject { private final IntrospectionData dataInterface; - private final Object data; + private Object data = null; + private final Map properties = new HashMap<>(); + private final Map> nodes = new HashMap<>(); /** * Create and empty element that have nothing. this is for the root node ==> not capable of knowing if only one element is created or many ... @@ -13,12 +21,17 @@ public class IntrospectionObject { this.dataInterface = null; this.data = new ArrayList<>(); } - + public IntrospectionObject(final IntrospectionData dataInterface, final Object data) { this.dataInterface = dataInterface; this.data = data; } + public IntrospectionObject(final IntrospectionData dataInterface) { + this.dataInterface = dataInterface; + this.data = null; + } + public Object getData() { return this.data; } @@ -29,7 +42,13 @@ public class IntrospectionObject { public void setProperty(final String propertyName, final String propertyValue) throws Exception { this.dataInterface.setProperty(this.data, propertyName, propertyValue); - + Object value = this.dataInterface.getValue(propertyName, propertyValue); + Object property = properties.get(propertyName); + if (property == null) { + properties.put(propertyName, value); + } else { + throw new ExmlBuilderException("Property have multiple values ==> impossible case; A Node must contain only 1 attibutes"); + } } /** @@ -37,11 +56,40 @@ public class IntrospectionObject { * @param nodeName Name of the node * @return Class of the node to create */ - public Class getTypeOfSubNode(final String nodeName) throws Exception { + public Class getTypeOfSubNode(final String nodeName) throws ExmlBuilderException { return this.dataInterface.getTypeOfSubNode(this.data, nodeName); } - - public void setText(final String text) { - this.dataInterface.setText(this.data, text); + public Class getTypeOfSubNodeSubType(String nodeName) throws ExmlBuilderException { + return this.dataInterface.getTypeOfSubNodeList(this.data, nodeName); } + + public void setText(final String text) throws ExmlBuilderException { + if (this.data != null) { + throw new ExmlBuilderException("Can not set multiple text value in a single NODE ..."); + } + this.data = this.dataInterface.getValueFromText(text); + } + + public void addObject(String nodeName, Object value) { + List node = nodes.get(nodeName); + if (node == null) { + node = new ArrayList<>(); + node.add(value); + nodes.put(nodeName, node); + } else { + node.add(value); + } + } + + public void generateTheObject() throws ExmlBuilderException { + if (data != null) { + // nothing to do ... ==> element already created + return; + } + Log.info("Create the element for the Specific node ... type = " + dataInterface.classType.getCanonicalName()); + Log.info(" Properties : " + this.properties.keySet()); + Log.info(" Nodes : " + this.nodes.keySet()); + this.data = dataInterface.createObject(this.properties, this.nodes); + } + } diff --git a/src/org/atriasoft/exml/builder/IntrospectionProperty.java b/src/org/atriasoft/exml/builder/IntrospectionProperty.java index 8506c9f..a8a4396 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionProperty.java +++ b/src/org/atriasoft/exml/builder/IntrospectionProperty.java @@ -1,13 +1,17 @@ package org.atriasoft.exml.builder; +import org.atriasoft.exml.exception.ExmlBuilderException; + public abstract class IntrospectionProperty { protected final Boolean caseSensitive; protected final Boolean isOptionnal; protected final String[] names; protected final Class type; + protected final Class subType; - public IntrospectionProperty(final Class type, final String[] names, final Boolean caseSensitive, final Boolean isOptionnal) { - this.type = type; + public IntrospectionProperty(final Class[] type, final String[] names, final Boolean caseSensitive, final Boolean isOptionnal) { + this.type = type[0]; + this.subType = type[1]; this.names = names; this.caseSensitive = caseSensitive; this.isOptionnal = isOptionnal; @@ -25,6 +29,10 @@ public abstract class IntrospectionProperty { return this.type; } + public Class getSubType() { + return this.subType; + } + public abstract String getValue(Object object) throws Exception; public boolean isCaseSensitive() { @@ -48,6 +56,27 @@ public abstract class IntrospectionProperty { } return false; } - - public abstract void setValue(Object object, String value) throws Exception; + /** + * 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 abstract void setValue(Object object, String value) throws ExmlBuilderException; + /** + * 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 abstract void setExistingValue(Object object, Object value) throws ExmlBuilderException; + + /** + * 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 + */ + protected abstract Object createValue(String value) throws ExmlBuilderException; } \ No newline at end of file diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java index d62b021..a4614bd 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java @@ -1,15 +1,39 @@ package org.atriasoft.exml.builder; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import org.atriasoft.exml.exception.ExmlBuilderException; import org.atriasoft.exml.internal.Log; import org.atriasoft.exml.parser.Tools; public class IntrospectionPropertyField extends IntrospectionProperty { private final Field fieldDescription; + + private static Class[] getTypeField(final Field fieldDescription) { + Class type = fieldDescription.getType(); + Class subType = null; + Type empppe = fieldDescription.getGenericType(); + if (empppe instanceof ParameterizedType plopppppp) { + Type[] realType = plopppppp.getActualTypeArguments(); + if (realType.length > 0) { + try { + subType = Class.forName(realType[0].getTypeName()); + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + return new Class[] {type, subType}; + } + public IntrospectionPropertyField(final Field fieldDescription, final String[] names, final Boolean caseSensitive, final Boolean isOptionnal) { - super(fieldDescription.getType(), names, caseSensitive, isOptionnal); + super(getTypeField(fieldDescription), names, caseSensitive, isOptionnal); this.fieldDescription = fieldDescription; } @@ -30,7 +54,7 @@ public class IntrospectionPropertyField extends IntrospectionProperty { } @Override - public void setValue(final Object object, final String value) { + public void setValue(final Object object, final String value) throws ExmlBuilderException { try { if (this.type == byte.class) { final byte data = Byte.valueOf(value); @@ -85,11 +109,78 @@ public class IntrospectionPropertyField extends IntrospectionProperty { } else if (this.type == Boolean[].class) { this.fieldDescription.set(object, Tools.parseBooleanClassStringList(value)); } else { - Log.error("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); + throw new ExmlBuilderException("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); } } catch (IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); + throw new ExmlBuilderException("Error in parsing the property value ... " + e.getMessage()); + } + } + + @Override + protected Object createValue(String value) throws ExmlBuilderException { + try { + if (this.type == byte.class) { + return Byte.valueOf(value); + } else if (this.type == short.class) { + return Short.valueOf(value); + } else if (this.type == int.class) { + return Integer.valueOf(value); + } else if (this.type == long.class) { + return Long.valueOf(value); + } else if (this.type == boolean.class) { + return Boolean.valueOf(value); + } else if (this.type == String.class) { + return value; + } else if (this.type == Byte.class) { + return Byte.valueOf(value); + } else if (this.type == Short.class) { + return Short.valueOf(value); + } else if (this.type == Integer.class) { + return Integer.valueOf(value); + } else if (this.type == Long.class) { + return Long.valueOf(value); + } else if (this.type == Boolean.class) { + return Boolean.valueOf(value); + } else if (this.type == byte[].class) { + return Tools.parseByteStringList(value); + } else if (this.type == Byte[].class) { + return Tools.parseByteClassStringList(value); + } else if (this.type == short[].class) { + return Tools.parseShortStringList(value); + } else if (this.type == Short[].class) { + return Tools.parseShortClassStringList(value); + } else if (this.type == int[].class) { + return Tools.parseIntegerStringList(value); + } else if (this.type == Integer[].class) { + return Tools.parseIntegerClassStringList(value); + } else if (this.type == long[].class) { + return Tools.parseLongStringList(value); + } else if (this.type == Long[].class) { + return Tools.parseLongClassStringList(value); + } else if (this.type == boolean[].class) { + return Tools.parseBooleanStringList(value); + } else if (this.type == Boolean[].class) { + return Tools.parseBooleanClassStringList(value); + } else { + throw new ExmlBuilderException("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); + } + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new ExmlBuilderException("Error in parsing the property value ... " + e.getMessage()); + } + } + + @Override + public void setExistingValue(Object object, Object value) throws ExmlBuilderException { + try { + this.fieldDescription.set(object, value); + } catch (IllegalArgumentException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new ExmlBuilderException("Can not set value ... " + e.getMessage()); } } diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java index 93f917a..1ac442a 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java @@ -1,19 +1,33 @@ package org.atriasoft.exml.builder; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import org.atriasoft.exml.exception.ExmlBuilderException; import org.atriasoft.exml.internal.Log; import org.atriasoft.exml.parser.Tools; public class IntrospectionPropertyMethod extends IntrospectionProperty { - private static Class getTypefunction(final Method setter, final Method getter) throws Exception { + 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(); + 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) { @@ -21,8 +35,26 @@ public class IntrospectionPropertyMethod extends IntrospectionProperty { } else { type = setter.getParameters()[0].getType(); } + // specific for the list: + 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 type; + return new Class[] {type, subType}; } protected Method setter; @@ -46,18 +78,18 @@ public class IntrospectionPropertyMethod extends IntrospectionProperty { } @Override - public String getValue(final Object object) throws Exception { + public String getValue(final Object object) throws ExmlBuilderException { if (this.getter == null) { - throw new Exception("no getter availlable"); + throw new ExmlBuilderException("no getter availlable"); } // TODO Auto-generated method stub return null; } @Override - public void setValue(final Object object, final String value) throws Exception { + public void setValue(final Object object, final String value) throws ExmlBuilderException { if (this.setter == null) { - throw new Exception("no setter availlable"); + throw new ExmlBuilderException("no setter availlable"); } try { if (this.type == byte.class) { @@ -113,12 +145,85 @@ public class IntrospectionPropertyMethod extends IntrospectionProperty { } else if (this.type == Boolean[].class) { this.setter.invoke(object, (Object) Tools.parseBooleanClassStringList(value)); } else { - Log.error("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); + throw new ExmlBuilderException("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); } } catch (IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); + throw new ExmlBuilderException("Error in parsing the property value ... " + e.getMessage()); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new ExmlBuilderException("Error in invoque setter of the Object ... " + e.getMessage()); + } + } + + @Override + protected Object createValue(String value) throws ExmlBuilderException { + try { + if (this.type == byte.class) { + return Byte.valueOf(value); + } else if (this.type == short.class) { + return Short.valueOf(value); + } else if (this.type == int.class) { + return Integer.valueOf(value); + } else if (this.type == long.class) { + return Long.valueOf(value); + } else if (this.type == boolean.class) { + return Boolean.valueOf(value); + } else if (this.type == String.class) { + return value; + } else if (this.type == Byte.class) { + return Byte.valueOf(value); + } else if (this.type == Short.class) { + return Short.valueOf(value); + } else if (this.type == Integer.class) { + return Integer.valueOf(value); + } else if (this.type == Long.class) { + return Long.valueOf(value); + } else if (this.type == Boolean.class) { + return Boolean.valueOf(value); + } else if (this.type == byte[].class) { + return Tools.parseByteStringList(value); + } else if (this.type == Byte[].class) { + return Tools.parseByteClassStringList(value); + } else if (this.type == short[].class) { + return Tools.parseShortStringList(value); + } else if (this.type == Short[].class) { + return Tools.parseShortClassStringList(value); + } else if (this.type == int[].class) { + return Tools.parseIntegerStringList(value); + } else if (this.type == Integer[].class) { + return Tools.parseIntegerClassStringList(value); + } else if (this.type == long[].class) { + return Tools.parseLongStringList(value); + } else if (this.type == Long[].class) { + return Tools.parseLongClassStringList(value); + } else if (this.type == boolean[].class) { + return Tools.parseBooleanStringList(value); + } else if (this.type == Boolean[].class) { + return Tools.parseBooleanClassStringList(value); + } else { + throw new ExmlBuilderException("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); + } + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new ExmlBuilderException("Error in parsing the property value ... " + e.getMessage()); + } + } + + @Override + public void setExistingValue(Object object, Object value) throws ExmlBuilderException { + if (this.setter == null) { + throw new ExmlBuilderException("no setter availlable"); + } + try { + this.setter.invoke(object, value); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new ExmlBuilderException("Can not set value ... " + e.getMessage()); } - } } diff --git a/src/org/atriasoft/exml/parser/ParseXml.java b/src/org/atriasoft/exml/parser/ParseXml.java index 1a8b98f..c700dc3 100644 --- a/src/org/atriasoft/exml/parser/ParseXml.java +++ b/src/org/atriasoft/exml/parser/ParseXml.java @@ -489,6 +489,8 @@ public class ParseXml { if (data.charAt(jjj) == '>') { pos.value = jjj; filePos.add(tmpPos); + Log.todo("End of node: '" + nameElement + "' ==> need add build result to parent and create object ..."); + this.builder.endElement(parent); return true; } if (data.charAt(jjj) != '\r' && data.charAt(jjj) != ' ' && data.charAt(jjj) != '\t') { @@ -498,6 +500,7 @@ public class ParseXml { return false; } } + } if (data.charAt(iii + white + 1) == '>') { // end of something == > this is really bad @@ -541,6 +544,8 @@ public class ParseXml { if (!iParseElement(element, tmpnameOriginal, data, pos, filePos, parsingProperty)) { return false; } + // TODO : Add the element in the parent ... + this.builder.newElementFinished(parent, tmpname, element); iii = pos.value; continue; } diff --git a/test/src/test/atriasoft/exml/ExmlTestIntrospection.java b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java index f378cf7..b937b3a 100644 --- a/test/src/test/atriasoft/exml/ExmlTestIntrospection.java +++ b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java @@ -6,6 +6,7 @@ package test.atriasoft.exml; import java.util.Arrays; +import java.util.List; import org.atriasoft.exml.Exml; import org.atriasoft.exml.exception.ExmlBuilderException; @@ -247,6 +248,17 @@ public class ExmlTestIntrospection { + " false\n" + " true\n" + " true\n" + + " 55 \n" + + " -53 \n" + + " 31632\n" + + " -32100 \n" + + " 15612 \n" + + " 542 \n" + + " 16521 \n" + + " 4654 \n" + + " 9875546 \n" + + " true\n" + + " false \n" + "\n"; //@formatter:on final ClassPublicMethodeNode[] root = Assertions.assertDoesNotThrow(() -> Exml.parse(dataToParse, ClassPublicMethodeNode.class, "elem")); diff --git a/test/src/test/atriasoft/exml/introspection/ClassPublicMethodeNode.java b/test/src/test/atriasoft/exml/introspection/ClassPublicMethodeNode.java index 0c36a6b..27fc70e 100644 --- a/test/src/test/atriasoft/exml/introspection/ClassPublicMethodeNode.java +++ b/test/src/test/atriasoft/exml/introspection/ClassPublicMethodeNode.java @@ -1,5 +1,7 @@ package test.atriasoft.exml.introspection; +import java.util.List; + import org.atriasoft.exml.annotation.XmlDefaultManaged; @XmlDefaultManaged(value = false) @@ -15,6 +17,11 @@ public class ClassPublicMethodeNode { private Long[] memberArrayLongClass; private short[] memberArrayShort; private Short[] memberArrayShortClass; + private List memberListBooleanClass; + private List memberListByteClass; + private List memberListIntegerClass; + private List memberListLongClass; + private List memberListShortClass; private boolean memberBoolean; private Boolean memberBooleanClass; private byte memberByte; @@ -27,6 +34,50 @@ public class ClassPublicMethodeNode { private Short memberShortClass; private String memberStringClass; + public List getMemberListBooleanClass() { + return memberListBooleanClass; + } + + public void setMemberListBooleanClass(List memberListBooleanClass) { + this.memberListBooleanClass = memberListBooleanClass; + } + + public List getMemberListByteClass() { + return memberListByteClass; + } + + public void setMemberListByteClass(List memberListByteClass) { + this.memberListByteClass = memberListByteClass; + } + + public List getMemberListIntegerClass() { + return memberListIntegerClass; + } + + public void setMemberListIntegerClass(List memberListIntegerClass) { + this.memberListIntegerClass = memberListIntegerClass; + } + + public List getMemberListLongClass() { + return memberListLongClass; + } + + public void setMemberListLongClass(List memberListLongClass) { + this.memberListLongClass = memberListLongClass; + } + + public List getMemberListShortClass() { + return memberListShortClass; + } + + public void setMemberListShortClass(List memberListShortClass) { + this.memberListShortClass = memberListShortClass; + } + + public Boolean getMemberBooleanClass() { + return memberBooleanClass; + } + public boolean[] getMemberArrayBoolean() { return this.memberArrayBoolean; }