From 7ef59f2f7e4938cf13bd710caa73bb5790073944 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Fri, 2 Jul 2021 10:40:27 +0200 Subject: [PATCH] [DEV] work on introspection generation --- .classpath | 2 +- .../exml/builder/IntrospectionData.java | 454 +++++++++--------- .../exml/builder/IntrospectionProperty.java | 65 ++- .../builder/IntrospectionPropertyField.java | 60 +-- .../builder/IntrospectionPropertyMethod.java | 10 +- .../generator/GeneratorIntrospection.java | 48 +- src/org/atriasoft/exml/parser/Tools.java | 122 ++++- .../exml/ExmlTestIntrospectionGenerate.java | 65 ++- 8 files changed, 516 insertions(+), 310 deletions(-) diff --git a/.classpath b/.classpath index 79118bb..94f2fe7 100644 --- a/.classpath +++ b/.classpath @@ -7,7 +7,7 @@ - + diff --git a/src/org/atriasoft/exml/builder/IntrospectionData.java b/src/org/atriasoft/exml/builder/IntrospectionData.java index c16e063..184174e 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionData.java +++ b/src/org/atriasoft/exml/builder/IntrospectionData.java @@ -186,248 +186,264 @@ public class IntrospectionData { public Class getSubClassType() { return this.subClassType; } - public IntrospectionData(final Class classType) throws Exception { + public IntrospectionData(final Class classType) throws ExmlBuilderException { 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); - Log.verbose("Introspect class: '" + classType.getCanonicalName() + "'"); - final Constructor[] constructors = this.classType.getConstructors(); - Log.verbose(" Constructors: (" + constructors.length + ")"); - for (final Constructor elem : constructors) { - Log.verbose(" - " + elem.toGenericString()); - } - final Field[] fields = this.classType.getFields(); - Log.verbose(" Fields: (" + fields.length + ")"); - for (final Field elem : fields) { - final Boolean isManaged = getIsManaged(elem, isDefaultManaged); - final Boolean isOptionnal = getIsOptional(elem, isDefaultOptional); - final String[] names = getNames(elem, Tools.decapitalizeFirst(elem.getName())); - final Boolean caseSensitive = getIsCaseSensitive(elem, isDefaultCaseSensitive); - final String listName = getListName(elem, null); - // TODO check if property does not already exist ... - if (isManaged) { - this.properties.add(new IntrospectionPropertyField(elem, names, listName, caseSensitive, isOptionnal)); + public IntrospectionData(final Class classType, final Class subClassType) throws ExmlBuilderException { + try { + 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); + Log.verbose("Introspect class: '" + classType.getCanonicalName() + "'"); + final Constructor[] constructors = this.classType.getConstructors(); + Log.verbose(" Constructors: (" + constructors.length + ")"); + for (final Constructor elem : constructors) { + Log.verbose(" - " + elem.toGenericString()); } - Log.verbose(" - " + elem.toGenericString()); - } - final Method[] methodsTmp = this.classType.getMethods(); - // filter getX setX isX - final List methods = List.of(methodsTmp).stream().filter(o -> { - if (o.getName().contentEquals("getClass")) { - return false; + 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 + if (!Modifier.isPublic(elem.getModifiers())) { + continue; + } + final Boolean isManaged = getIsManaged(elem, isDefaultManaged); + final Boolean isOptionnal = getIsOptional(elem, isDefaultOptional); + final String[] names = getNames(elem, Tools.decapitalizeFirst(elem.getName())); + final Boolean caseSensitive = getIsCaseSensitive(elem, isDefaultCaseSensitive); + final String listName = getListName(elem, null); + // TODO check if property does not already exist ... + if (isManaged) { + this.properties.add(new IntrospectionPropertyField(elem, names, listName, caseSensitive, isOptionnal)); + } + Log.verbose(" - " + elem.toGenericString()); } - if (Modifier.isStatic(o.getModifiers())) { - if (o.getName().contentEquals("valueOf") && o.getParameterCount() == 1 && o.getParameters()[0].getType() == String.class) { - return true; + final Method[] methodsTmp = this.classType.getMethods(); + // filter getX setX isX + final List methods = List.of(methodsTmp).stream().filter(o -> { + if (o.getName().contentEquals("getClass")) { + return false; + } + // we does not manage private function + if (!Modifier.isPublic(o.getModifiers())) { + 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; + } + // check name format + if (o.getName().length() == 3) { + return false; + } + if (o.getName().charAt(3) >= 'A' && o.getName().charAt(3) <= 'Z') { + return true; + } + return false; + } + if (o.getName().startsWith("set")) { + if (o.getReturnType() != void.class || o.getParameterCount() != 1) { + return false; + } + // check name format + if (o.getName().length() == 3) { + return false; + } + if (o.getName().charAt(3) >= 'A' && o.getName().charAt(3) <= 'Z') { + return true; + } + + return false; + } + if (o.getName().startsWith("is")) { + if (!(o.getReturnType() == Boolean.class || o.getReturnType() == boolean.class) && o.getParameterCount() != 0) { + return false; + } + // check name format + if (o.getName().length() == 2) { + return false; + } + if (o.getName().charAt(2) >= 'A' && o.getName().charAt(2) <= 'Z') { + return true; + } + + return false; } return false; + }).collect(Collectors.toList()); + Log.verbose(" Methods: (" + methods.size() + ")"); + for (final Method elem : methods) { + Log.verbose(" - " + elem.toGenericString()); } - if (o.getName().startsWith("get")) { - if (o.getParameterCount() != 0 || o.getReturnType() == void.class || o.getReturnType() == Boolean.class || o.getReturnType() == boolean.class) { - return false; - } - // check name format - if (o.getName().length() == 3) { - return false; - } - if (o.getName().charAt(3) >= 'A' && o.getName().charAt(3) <= 'Z') { - return true; - } - return false; - } - if (o.getName().startsWith("set")) { - if (o.getReturnType() != void.class || o.getParameterCount() != 1) { - return false; - } - // check name format - if (o.getName().length() == 3) { - return false; - } - if (o.getName().charAt(3) >= 'A' && o.getName().charAt(3) <= 'Z') { - return true; - } - - return false; - } - if (o.getName().startsWith("is")) { - if (!(o.getReturnType() == Boolean.class || o.getReturnType() == boolean.class) && o.getParameterCount() != 0) { - return false; - } - // check name format - if (o.getName().length() == 2) { - return false; - } - if (o.getName().charAt(2) >= 'A' && o.getName().charAt(2) <= 'Z') { - return true; - } - - return false; - } - 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); - // [bB]oolean isXxx(); - - List methodsGet; - List methodsSet; - List methodsIs; - if (!Enum.class.isAssignableFrom(classType)) { - methodsGet = methods.stream().filter(o -> o.getName().startsWith("get")).collect(Collectors.toList()); - methodsSet = methods.stream().filter(o -> o.getName().startsWith("set")).collect(Collectors.toList()); - methodsIs = methods.stream().filter(o -> o.getName().startsWith("is")).collect(Collectors.toList()); - } else { - methodsGet = new ArrayList<>(); - methodsSet = new ArrayList<>(); - methodsIs = new ArrayList<>(); - } - final List valueOfString = methods.stream().filter(o -> o.getName().startsWith("valueOf")).collect(Collectors.toList()); - if (valueOfString.size() == 1) { - this.valueof = valueOfString.get(0); - } else { - // some specific model: - this.valueof = null; - } - // associate methods by pair. - final List elements = new ArrayList<>(); - for (final Method method : methodsGet) { - final String name = method.getName().substring(3); - final OrderData tmp = new OrderData(name); - tmp.getter = method; - elements.add(tmp); - } - for (final Method method : methodsIs) { - final String name = method.getName().substring(2); - for (final OrderData elem : elements) { - if (elem.name.contentEquals(name)) { - Log.error("Can not have a setXXX and isXXX with the same name ... " + method.getName()); - throw new Exception("lmkjlkjlk"); - } + + // Separate the methods and filer as: + // XXX GetXxx(); & XXX != boolean + // void setXxx(XXX elem); + // [bB]oolean isXxx(); + + List methodsGet; + List methodsSet; + List methodsIs; + if (!Enum.class.isAssignableFrom(classType)) { + methodsGet = methods.stream().filter(o -> o.getName().startsWith("get")).collect(Collectors.toList()); + methodsSet = methods.stream().filter(o -> o.getName().startsWith("set")).collect(Collectors.toList()); + methodsIs = methods.stream().filter(o -> o.getName().startsWith("is")).collect(Collectors.toList()); + } else { + methodsGet = new ArrayList<>(); + methodsSet = new ArrayList<>(); + methodsIs = new ArrayList<>(); } - final OrderData tmp = new OrderData(name); - tmp.getter = method; - elements.add(tmp); - } - for (final Method method : methodsSet) { - final String name = method.getName().substring(3); - OrderData tmp = null; - for (final OrderData elem : elements) { - if (elem.name.contentEquals(name)) { - tmp = elem; - break; - } + final List valueOfString = methods.stream().filter(o -> o.getName().startsWith("valueOf")).collect(Collectors.toList()); + if (valueOfString.size() == 1) { + this.valueof = valueOfString.get(0); + } else { + // some specific model: + + this.valueof = null; } - /* - 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()); - } + // associate methods by pair. + final List elements = new ArrayList<>(); + for (final Method method : methodsGet) { + final String name = method.getName().substring(3); + final OrderData tmp = new OrderData(name); + tmp.getter = method; + elements.add(tmp); + } + for (final Method method : methodsIs) { + final String name = method.getName().substring(2); + for (final OrderData elem : elements) { + if (elem.name.contentEquals(name)) { + Log.error("Can not have a setXXX and isXXX with the same name ... " + method.getName()); + throw new Exception("lmkjlkjlk"); } } - for (int iii=0; iii 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, final Map> nodes) throws ExmlBuilderException { diff --git a/src/org/atriasoft/exml/builder/IntrospectionProperty.java b/src/org/atriasoft/exml/builder/IntrospectionProperty.java index d546f9e..3b0a7a6 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionProperty.java +++ b/src/org/atriasoft/exml/builder/IntrospectionProperty.java @@ -1,6 +1,7 @@ package org.atriasoft.exml.builder; import org.atriasoft.exml.exception.ExmlBuilderException; +import org.atriasoft.exml.parser.Tools; public abstract class IntrospectionProperty { protected final Boolean caseSensitive; @@ -40,8 +41,68 @@ public abstract class IntrospectionProperty { public Class getSubType() { return this.subType; } - - public abstract String getValue(Object object) throws ExmlBuilderException; + + public String getValueString(final Object object) throws ExmlBuilderException { + Object value = getValue(object); + if (value == null) { + return null; + } + if (this.type == byte.class) { + return Byte.toString((byte)value); + } + if (this.type == short.class) { + return Short.toString((short)value); + } + if (this.type == int.class) { + return Integer.toString((int)value); + } + if (this.type == long.class) { + return Long.toString((long)value); + } + if (this.type == boolean.class) { + return Boolean.toString((boolean)value); + } + if (this.type == String.class) { + return (String)value; + } + if (this.type == Byte.class) { + return Byte.toString((Byte)value); + } + if (this.type == Short.class) { + return Short.toString((short)value); + } + if (this.type == Integer.class) { + return Integer.toString((Integer)value); + } else if (this.type == Long.class) { + return Long.toString((Long)value); + } else if (this.type == Boolean.class) { + return Boolean.toString((Boolean)value); + } else if (this.type == byte[].class) { + return Tools.toString((byte[])value); + } else if (this.type == Byte[].class) { + return Tools.toString((Byte[])value); + } else if (this.type == short[].class) { + return Tools.toString((short[])value); + } else if (this.type == Short[].class) { + return Tools.toString((Short[])value); + } else if (this.type == int[].class) { + return Tools.toString((int[])value); + } else if (this.type == Integer[].class) { + return Tools.toString((Integer[])value); + } else if (this.type == long[].class) { + return Tools.toString((long[])value); + } else if (this.type == Long[].class) { + return Tools.toString((Long[])value); + } else if (this.type == boolean[].class) { + return Tools.toString((boolean[])value); + } else if (this.type == Boolean[].class) { + return Tools.toString((Boolean[])value); + } else { + //throw new ExmlBuilderException("Can not parse the specific element ... need to introspect and find the 'xxx valueOf(String data);'"); + } + return value.toString(); + } + public abstract Object getValue(Object object) throws ExmlBuilderException; public boolean isCaseSensitive() { return this.caseSensitive; diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java index dd7d3be..56d6879 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java @@ -43,66 +43,10 @@ public class IntrospectionPropertyField extends IntrospectionProperty { public boolean canSetValue() { return true; } - @Override - public String getValue(final Object object) throws ExmlBuilderException { + public Object getValue(final Object object) throws ExmlBuilderException { try { - Object value = this.fieldDescription.get(object); - - if (this.type == byte.class) { - return Byte.toString((byte)value); - } - if (this.type == short.class) { - return Short.toString((short)value); - } - if (this.type == int.class) { - return Integer.toString((int)value); - } - if (this.type == long.class) { - return Long.toString((long)value); - } - if (this.type == boolean.class) { - return Boolean.toString((boolean)value); - } - if (this.type == String.class) { - return (String)value; - } - if (this.type == Byte.class) { - return Byte.toString((Byte)value); - } - if (this.type == Short.class) { - return Short.toString((short)value); - } - if (this.type == Integer.class) { - return Integer.toString((Integer)value); - } else if (this.type == Long.class) { - return Long.toString((Long)value); - } else if (this.type == Boolean.class) { - return Boolean.toString((Boolean)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);'"); - } - return value.toString(); + return this.fieldDescription.get(object); } catch (IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java index b8dbb75..df10cec 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java @@ -79,12 +79,16 @@ public class IntrospectionPropertyMethod extends IntrospectionProperty { } @Override - public String getValue(final Object object) throws ExmlBuilderException { + public Object getValue(final Object object) throws ExmlBuilderException { if (this.getter == null) { throw new ExmlBuilderException("no getter availlable"); } - // TODO Auto-generated method stub - return null; + try { + return this.getter.invoke(object); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + e.printStackTrace(); + throw new ExmlBuilderException("Can not set value ... " + e.getMessage()); + } } @Override diff --git a/src/org/atriasoft/exml/generator/GeneratorIntrospection.java b/src/org/atriasoft/exml/generator/GeneratorIntrospection.java index 3576cf1..730f1c4 100644 --- a/src/org/atriasoft/exml/generator/GeneratorIntrospection.java +++ b/src/org/atriasoft/exml/generator/GeneratorIntrospection.java @@ -6,6 +6,8 @@ import java.util.Map; import org.atriasoft.exml.builder.IntrospectionData; import org.atriasoft.exml.builder.IntrospectionProperty; +import org.atriasoft.exml.exception.ExmlBuilderException; +import org.atriasoft.exml.parser.Tools; public class GeneratorIntrospection implements Generator { // Keep in cach all the object alredy parsed ==> optimize CPU @@ -20,7 +22,7 @@ public class GeneratorIntrospection implements Generator { this.elements.put(classType, new IntrospectionData(classType, null)); } - IntrospectionData findOrCreate(final Class classType) throws Exception { + IntrospectionData findOrCreate(final Class classType) throws ExmlBuilderException { IntrospectionData out = this.elements.get(classType); if (out != null) { return out; @@ -30,40 +32,54 @@ public class GeneratorIntrospection implements Generator { return out; } - public void generateProperties(final Object node, final IntrospectionData introspection, final StringBuilder tmpp) throws Exception { + public void generateProperties(final Object data, final IntrospectionData introspection, final StringBuilder tmpp) throws ExmlBuilderException { List elements = introspection.getProperties(); for (IntrospectionProperty elem : elements) { if (!elem.canGetValue()) { continue; } String name = elem.getNames()[0]; - String data=elem.getValue(node); + String dataString=elem.getValueString(data); tmpp.append(" "); tmpp.append(name); tmpp.append("=\""); - tmpp.append(data); + tmpp.append(dataString); tmpp.append("\""); } } - public void generateSubNodes(final Object node, final IntrospectionData introspection, final StringBuilder tmpp) { + public void generateSubNodes(final Object data, final IntrospectionData introspection, final StringBuilder tmpp, int indent) throws ExmlBuilderException { List elements = introspection.getMethods(); for (IntrospectionProperty elem : elements) { + if (!elem.canGetValue()) { + continue; + } + String name = elem.getNames()[0]; + Object dataObj =elem.getValue(data); + + if (dataObj != null) { + generateNode(dataObj, name, tmpp, indent); + } } } - public void generateNode(final Object node, final String nodeName, final StringBuilder tmpp) throws Exception { - IntrospectionData introspection = findOrCreate(node.getClass()); + public void generateNode(final Object data, final String nodeName, final StringBuilder tmpp, int indent) throws ExmlBuilderException { + IntrospectionData introspection = findOrCreate(data.getClass()); + Tools.addIndent(tmpp, indent); tmpp.append("<"); tmpp.append(nodeName); - generateProperties(node, introspection, tmpp); - tmpp.append(">\n"); - generateSubNodes(node, introspection, tmpp); - tmpp.append("\n"); - + generateProperties(data, introspection, tmpp); + if (introspection.getMethods().size() != 0) { + tmpp.append(">\n"); + generateSubNodes(data, introspection, tmpp, indent + 1); + Tools.addIndent(tmpp, indent); + tmpp.append("\n"); + } else { + tmpp.append("/>\n"); + } } - public void generate(final Object root, final StringBuilder tmpp) throws Exception { - generateNode(root, this.rootNodeName, tmpp); + public void generate(final Object root, final StringBuilder tmpp) throws ExmlBuilderException { + generateNode(root, this.rootNodeName, tmpp, 0); } } diff --git a/src/org/atriasoft/exml/parser/Tools.java b/src/org/atriasoft/exml/parser/Tools.java index 1f7081e..c92c058 100644 --- a/src/org/atriasoft/exml/parser/Tools.java +++ b/src/org/atriasoft/exml/parser/Tools.java @@ -149,7 +149,7 @@ public class Tools { return false; } - public static Boolean[] parseBooleanClassStringList(String data) { // throws NumberFormatException + public static Boolean[] parseBooleanClassStringList(String data) { data = Tools.cleanNumberList(data); final String[] dataArray = data.split(";"); final Boolean[] out = new Boolean[dataArray.length]; @@ -159,8 +159,18 @@ public class Tools { } return out; } + public static String toString(Boolean[] data) { + StringBuilder out = new StringBuilder(); + for (int iii=0; iii