From 240b71805e51e8209418d7477359db1d38eb3645 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Sat, 27 Feb 2021 01:29:38 +0100 Subject: [PATCH] [DEV] add basic annitation and first introspection with attribute works. --- src/module-info.java | 3 + src/org/atriasoft/exml/Exml.java | 38 +++- .../exml/annotation/ExmlAnnotation.java | 19 ++ .../exml/annotation/XmlDefaultNotManaged.java | 16 ++ .../exml/annotation/XmlDefaultOptional.java | 16 ++ .../atriasoft/exml/annotation/XmlManaged.java | 16 ++ .../atriasoft/exml/annotation/XmlName.java | 28 +++ .../exml/annotation/XmlNotManaged.java | 16 ++ .../exml/annotation/XmlOptional.java | 16 ++ .../exml/builder/BuilderIntrospection.java | 96 +++++++++ .../exml/builder/IntrospectionData.java | 89 ++++++++ .../exml/builder/IntrospectionObject.java | 34 +++ .../exml/builder/IntrospectionProperty.java | 56 +++++ .../builder/IntrospectionPropertyField.java | 87 ++++++++ .../atriasoft/exml/model/XmlDeclaration.java | 2 +- src/org/atriasoft/exml/model/XmlNode.java | 14 +- src/org/atriasoft/exml/parser/Tools.java | 114 +++++++++++ .../atriasoft/exml/ExmlTestIntrospection.java | 131 ++++++++++++ .../introspection/ClassPublicMemberOnly.java | 25 +++ .../introspection/ClassPublicMethodOnly.java | 193 ++++++++++++++++++ 20 files changed, 996 insertions(+), 13 deletions(-) create mode 100644 src/org/atriasoft/exml/annotation/ExmlAnnotation.java create mode 100644 src/org/atriasoft/exml/annotation/XmlDefaultNotManaged.java create mode 100644 src/org/atriasoft/exml/annotation/XmlDefaultOptional.java create mode 100644 src/org/atriasoft/exml/annotation/XmlManaged.java create mode 100644 src/org/atriasoft/exml/annotation/XmlName.java create mode 100644 src/org/atriasoft/exml/annotation/XmlNotManaged.java create mode 100644 src/org/atriasoft/exml/annotation/XmlOptional.java create mode 100644 src/org/atriasoft/exml/builder/BuilderIntrospection.java create mode 100644 src/org/atriasoft/exml/builder/IntrospectionData.java create mode 100644 src/org/atriasoft/exml/builder/IntrospectionObject.java create mode 100644 src/org/atriasoft/exml/builder/IntrospectionProperty.java create mode 100644 src/org/atriasoft/exml/builder/IntrospectionPropertyField.java create mode 100644 test/src/test/atriasoft/exml/ExmlTestIntrospection.java create mode 100644 test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java create mode 100644 test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java diff --git a/src/module-info.java b/src/module-info.java index 1b9f739..ada1f63 100644 --- a/src/module-info.java +++ b/src/module-info.java @@ -10,7 +10,10 @@ open module org.atriasoft.exml { exports org.atriasoft.exml.exception; exports org.atriasoft.exml.builder; exports org.atriasoft.exml.parser; + exports org.atriasoft.exml.annotation; requires transitive org.atriasoft.etk; requires transitive io.scenarium.logger; + requires java.base; + } diff --git a/src/org/atriasoft/exml/Exml.java b/src/org/atriasoft/exml/Exml.java index 23a8af7..fbd0afa 100644 --- a/src/org/atriasoft/exml/Exml.java +++ b/src/org/atriasoft/exml/Exml.java @@ -6,8 +6,13 @@ package org.atriasoft.exml; +import java.lang.reflect.Array; +import java.util.List; + import org.atriasoft.exml.builder.Builder; import org.atriasoft.exml.builder.BuilderGeneric; +import org.atriasoft.exml.builder.BuilderIntrospection; +import org.atriasoft.exml.builder.IntrospectionObject; import org.atriasoft.exml.exception.ExmlBuilderException; import org.atriasoft.exml.exception.ExmlParserErrorMulti; import org.atriasoft.exml.internal.Log; @@ -27,17 +32,19 @@ public class Exml { Log.info("Generated XML : \n" + tmpp.toString()); } + public static void generate(final Object root, final StringBuilder data) { + // TODO: ... + } + /** - * generate a string that contain the created XML - * @param[out] _data Data where the xml is stored - * @return false : An error occured - * @return true : Parsing is OK + * Generate a string that contain the created XML + * @param data Data where the xml is stored */ - public static void generate(final XmlNode root, final StringBuilder _data) { + public static void generate(final XmlNode root, final StringBuilder data) { if (root.isElement() == false || ((XmlElement) root).getValue().isEmpty() == false) { - SerializerXml.serialize(root, _data, 0); + SerializerXml.serialize(root, data, 0); } else { - SerializerXml.serializeRoot((XmlElement) root, _data); + SerializerXml.serializeRoot((XmlElement) root, data); } //return iGenerate(_data, 0); } @@ -50,6 +57,23 @@ public class Exml { return (XmlElement) parser.parse(data, property); } + public static TYPE[] parse(final String data, final Class classType, final String rootNodeName) throws ExmlBuilderException, ExmlParserErrorMulti { + final Builder builder = new BuilderIntrospection(classType, rootNodeName); + final ParseXml parser = new ParseXml(builder); + final ParsingProperty property = new ParsingProperty(); + property.setDisplayError(true); + + final IntrospectionObject introspectionObject = (IntrospectionObject) parser.parse(data, property); + final Object listRet = introspectionObject.getData(); + if (listRet != null && listRet instanceof List) { + final List rootList = (List) listRet; + final TYPE[] strarr = (TYPE[]) Array.newInstance(classType, 0); + return rootList.toArray(strarr); + } else { + return null; + } + } + /** * Load the file that might contain the xml * @param[in] _uri URI of the xml diff --git a/src/org/atriasoft/exml/annotation/ExmlAnnotation.java b/src/org/atriasoft/exml/annotation/ExmlAnnotation.java new file mode 100644 index 0000000..815a5d8 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/ExmlAnnotation.java @@ -0,0 +1,19 @@ +package org.atriasoft.exml.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta-annotation (annotations used on other annotations) + * used for marking all annotations that are + * part of Exml package. Can be used for recognizing all + * Exml annotations generically, and in future also for + * passing other generic annotation configuration. + */ +@Target({ ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExmlAnnotation { + // for now, a pure tag annotation, no parameters +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlDefaultNotManaged.java b/src/org/atriasoft/exml/annotation/XmlDefaultNotManaged.java new file mode 100644 index 0000000..e60700b --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlDefaultNotManaged.java @@ -0,0 +1,16 @@ +package org.atriasoft.exml.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that set the element are not managed by default. Need to add @XmlManaged to be enable. + * + */ +@Target({ ElementType.MODULE }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlDefaultNotManaged { +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlDefaultOptional.java b/src/org/atriasoft/exml/annotation/XmlDefaultOptional.java new file mode 100644 index 0000000..828f126 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlDefaultOptional.java @@ -0,0 +1,16 @@ +package org.atriasoft.exml.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that set the element not found are ignored. + * + */ +@Target({ ElementType.MODULE }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlDefaultOptional { +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlManaged.java b/src/org/atriasoft/exml/annotation/XmlManaged.java new file mode 100644 index 0000000..48fdef5 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlManaged.java @@ -0,0 +1,16 @@ +package org.atriasoft.exml.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that force the xml Parser to manage this element (used when the class is mark as @XmldefaultNotManaged). + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlManaged { +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlName.java b/src/org/atriasoft/exml/annotation/XmlName.java new file mode 100644 index 0000000..e3db7e3 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlName.java @@ -0,0 +1,28 @@ +package org.atriasoft.exml.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that can be used to define an other name of the attribute or the Element name. + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlName { + /** + * Set if the element has a specific case sensitivity (if the full parsing is case insensitive, this change nothing) + * @return true if the element is case insensitive. + */ + boolean caseSensitive() default true; + + /** + * Names of the property of the Element name + * @note The first name if the default generated in serialization. + * @return The list the the possible names + */ + String[] names(); +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlNotManaged.java b/src/org/atriasoft/exml/annotation/XmlNotManaged.java new file mode 100644 index 0000000..158712a --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlNotManaged.java @@ -0,0 +1,16 @@ +package org.atriasoft.exml.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that Permit to ignore the function or the attribute. + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlNotManaged { +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlOptional.java b/src/org/atriasoft/exml/annotation/XmlOptional.java new file mode 100644 index 0000000..8d62ba2 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlOptional.java @@ -0,0 +1,16 @@ +package org.atriasoft.exml.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation that to ignore the element if not present in the XML, the default case the parser throw a missing error. + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlOptional { +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/builder/BuilderIntrospection.java b/src/org/atriasoft/exml/builder/BuilderIntrospection.java new file mode 100644 index 0000000..d8da91a --- /dev/null +++ b/src/org/atriasoft/exml/builder/BuilderIntrospection.java @@ -0,0 +1,96 @@ +package org.atriasoft.exml.builder; + +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 BuilderIntrospection implements Builder { + // Keep in cach all the object alredy parsed ==> optimize CPU + final Map, IntrospectionData> elements = new HashMap<>(); + // The root class (need to keep it if we use 2 time the builder, the root class is no more accessible). + final Class rootClassType; + final String rootNodeName; + + public BuilderIntrospection(final Class classType, final String rootNodeName) { + this.rootNodeName = rootNodeName; + this.rootClassType = classType; + this.elements.put(classType, new IntrospectionData(classType)); + } + + IntrospectionData findOrCreate(final Class classType) { + IntrospectionData out = this.elements.get(classType); + if (out != null) { + return out; + } + out = new IntrospectionData(classType); + this.elements.put(classType, out); + return out; + } + + @Override + public void newComment(final Object element, final String comment) throws ExmlBuilderException { + // we drop all comment, we have no need of it. + return; + } + + @Override + public Object newDeclaration(final Object parent, final String text) throws ExmlBuilderException { + // we drop all declaration, no need of it too. + return null; + } + + @Override + public Object newElement(final Object parent, final String nodeName) throws ExmlBuilderException { + final IntrospectionObject introspectionObject = (IntrospectionObject) parent; + if (introspectionObject.getDataInterface() == null) { + final Object previousData = introspectionObject.getData(); + if (previousData != null && previousData instanceof List) { + final List rootList = (List) previousData; + // detect root node... + 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); + } else { + // need to add a throw on the node... + return null; // ==> disable the parsing.. + } + } else { + // throw an error... + return null; + } + } else { + + } + + return null; + } + + @Override + public void newProperty(final Object element, final String propertyName, final String propertyValue) throws ExmlBuilderException { + final IntrospectionObject introspectionObject = (IntrospectionObject) element; + if (introspectionObject.getDataInterface() == null) { + // property on nothing ??? + return; + } + introspectionObject.setProperty(propertyName, propertyValue); + + } + + @Override + public Object newRoot() throws ExmlBuilderException { + return new IntrospectionObject(); + } + + @Override + public void newText(final Object parent, final String text) throws ExmlBuilderException { + final IntrospectionObject introspectionObject = (IntrospectionObject) parent; + + } + +} diff --git a/src/org/atriasoft/exml/builder/IntrospectionData.java b/src/org/atriasoft/exml/builder/IntrospectionData.java new file mode 100644 index 0000000..4317a02 --- /dev/null +++ b/src/org/atriasoft/exml/builder/IntrospectionData.java @@ -0,0 +1,89 @@ +package org.atriasoft.exml.builder; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.atriasoft.exml.internal.Log; + +public class IntrospectionData { + + final Class classType; + private final List properties = new ArrayList<>(); + + public IntrospectionData(final Class classType) { + this.classType = classType; + Log.info("Introspect class: '" + classType.getCanonicalName() + "'"); + final Constructor[] constructors = this.classType.getConstructors(); + Log.info(" Constructors: (" + constructors.length + ")"); + for (final Constructor elem : constructors) { + Log.info(" - " + elem.toGenericString()); + } + final Field[] fields = this.classType.getFields(); + Log.info(" Fields: (" + fields.length + ")"); + for (final Field elem : fields) { + // TODO: check if property does not already exist ... + this.properties.add(new IntrospectionPropertyField(elem)); + Log.info(" - " + elem.toGenericString()); + } + final Method[] methodsTmp = this.classType.getMethods(); + + final List methods = List.of(methodsTmp).stream().filter(o -> { + return (o.getName().startsWith("get") || o.getName().startsWith("set") || o.getName().startsWith("is")) && !o.getName().contentEquals("getClass"); + }).collect(Collectors.toList()); + // separate the methods... + final List methodsGet = methods.stream().filter(o -> { + return o.getName().startsWith("get"); + }).collect(Collectors.toList()); + final List methodsSet = methods.stream().filter(o -> { + return o.getName().startsWith("set"); + }).collect(Collectors.toList()); + final List methodsIs = methods.stream().filter(o -> { + return o.getName().startsWith("is"); + }).collect(Collectors.toList()); + + // associate methods by pair. + // nameProperty, getter, setter + + Log.info(" Methods: (" + methods.size() + ")"); + for (final Method elem : methods) { + Log.info(" - " + elem.toGenericString()); + } + } + + Object createObject() { + try { + return this.classType.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + protected IntrospectionProperty findPropertyDescription(final String propertyName) throws Exception { + for (final IntrospectionProperty prop : this.properties) { + if (prop.isCompatible(propertyName) == true) { + return prop; + } + } + Log.error("lkjlkjlkjlkj"); + throw new Exception("lkjlkjlk"); + } + + public void setProperty(final Object data, final String propertyName, final String propertyValue) { + try { + final IntrospectionProperty prop = findPropertyDescription(propertyName); + prop.setValue(data, propertyValue); + } catch (final Exception e) { + Log.error("can not find the field '" + propertyName + "' " + e.getMessage()); + e.printStackTrace(); + + } + } + +} diff --git a/src/org/atriasoft/exml/builder/IntrospectionObject.java b/src/org/atriasoft/exml/builder/IntrospectionObject.java new file mode 100644 index 0000000..c8e1bf0 --- /dev/null +++ b/src/org/atriasoft/exml/builder/IntrospectionObject.java @@ -0,0 +1,34 @@ +package org.atriasoft.exml.builder; + +import java.util.ArrayList; + +public class IntrospectionObject { + private final IntrospectionData dataInterface; + private final Object data; + + /** + * 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 ... + */ + public IntrospectionObject() { + this.dataInterface = null; + this.data = new ArrayList<>(); + } + + public IntrospectionObject(final IntrospectionData dataInterface, final Object data) { + this.dataInterface = dataInterface; + this.data = data; + } + + public Object getData() { + return this.data; + } + + public IntrospectionData getDataInterface() { + return this.dataInterface; + } + + public void setProperty(final String propertyName, final String propertyValue) { + this.dataInterface.setProperty(this.data, propertyName, propertyValue); + + } +} diff --git a/src/org/atriasoft/exml/builder/IntrospectionProperty.java b/src/org/atriasoft/exml/builder/IntrospectionProperty.java new file mode 100644 index 0000000..8a78921 --- /dev/null +++ b/src/org/atriasoft/exml/builder/IntrospectionProperty.java @@ -0,0 +1,56 @@ +package org.atriasoft.exml.builder; + +import java.util.ArrayList; +import java.util.List; + +public abstract class IntrospectionProperty { + protected List names = new ArrayList<>(); + protected boolean caseSensitive = true; + protected final Class type; + + public IntrospectionProperty(final Class type) { + this.type = type; + } + + public List getNames() { + return this.names; + } + + public Class getType() { + return this.type; + } + + public abstract String getValue(Object object); + + public boolean isCaseSensitive() { + return this.caseSensitive; + } + + public boolean isCompatible(String name) { + if (this.caseSensitive == false) { + for (final String elem : this.names) { + if (elem.contentEquals(name) == true) { + return true; + } + } + } else { + name = name.toLowerCase(); + for (final String elem : this.names) { + if (elem.toLowerCase().contentEquals(name) == true) { + return true; + } + } + } + return false; + } + + public void setCaseSensitive(final boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + public void setNames(final List names) { + this.names = names; + } + + public abstract void setValue(Object object, String value); +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java new file mode 100644 index 0000000..6f859f3 --- /dev/null +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java @@ -0,0 +1,87 @@ +package org.atriasoft.exml.builder; + +import java.lang.reflect.Field; + +import org.atriasoft.exml.internal.Log; +import org.atriasoft.exml.parser.Tools; + +public class IntrospectionPropertyField extends IntrospectionProperty { + private final Field fieldDescription; + + public IntrospectionPropertyField(final Field fieldDescription) { + super(fieldDescription.getType()); + this.fieldDescription = fieldDescription; + this.names.add(this.fieldDescription.getName()); + } + + @Override + public String getValue(final Object object) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setValue(final Object object, final String value) { + try { + if (this.type == byte.class) { + final byte data = Byte.valueOf(value); + this.fieldDescription.setByte(object, data); + } else if (this.type == short.class) { + final short data = Short.valueOf(value); + this.fieldDescription.setShort(object, data); + } else if (this.type == int.class) { + final int data = Integer.valueOf(value); + this.fieldDescription.setInt(object, data); + } else if (this.type == long.class) { + final long data = Long.valueOf(value); + this.fieldDescription.setLong(object, data); + } else if (this.type == boolean.class) { + final boolean data = Boolean.valueOf(value); + this.fieldDescription.setBoolean(object, data); + } else if (this.type == String.class) { + this.fieldDescription.set(object, value); + } else if (this.type == Byte.class) { + final Byte data = Byte.valueOf(value); + this.fieldDescription.set(object, data); + } else if (this.type == Short.class) { + final Short data = Short.valueOf(value); + this.fieldDescription.set(object, data); + } else if (this.type == Integer.class) { + final Integer data = Integer.valueOf(value); + this.fieldDescription.set(object, data); + } else if (this.type == Long.class) { + final Long data = Long.valueOf(value); + this.fieldDescription.set(object, data); + } else if (this.type == Boolean.class) { + final Boolean data = Boolean.valueOf(value); + this.fieldDescription.set(object, data); + } else if (this.type == byte[].class) { + this.fieldDescription.set(object, Tools.parseByteStringList(value)); + } else if (this.type == Byte[].class) { + this.fieldDescription.set(object, Tools.parseByteClassStringList(value)); + } else if (this.type == short[].class) { + this.fieldDescription.set(object, Tools.parseShortStringList(value)); + } else if (this.type == Short[].class) { + this.fieldDescription.set(object, Tools.parseShortClassStringList(value)); + } else if (this.type == int[].class) { + this.fieldDescription.set(object, Tools.parseIntegerStringList(value)); + } else if (this.type == Integer[].class) { + this.fieldDescription.set(object, Tools.parseIntegerClassStringList(value)); + } else if (this.type == long[].class) { + this.fieldDescription.set(object, Tools.parseLongStringList(value)); + } else if (this.type == Long[].class) { + this.fieldDescription.set(object, Tools.parseLongClassStringList(value)); + } else if (this.type == boolean[].class) { + this.fieldDescription.set(object, Tools.parseBooleanStringList(value)); + } 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);'"); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/src/org/atriasoft/exml/model/XmlDeclaration.java b/src/org/atriasoft/exml/model/XmlDeclaration.java index 1bc28ce..46fb3e9 100644 --- a/src/org/atriasoft/exml/model/XmlDeclaration.java +++ b/src/org/atriasoft/exml/model/XmlDeclaration.java @@ -16,7 +16,7 @@ public class XmlDeclaration extends XmlAttributeList { }; /** - * Constructor + * Constructor * @param[in] _name name of the declaration (xml, xml:xxxx ...) */ public XmlDeclaration(final String _name) { diff --git a/src/org/atriasoft/exml/model/XmlNode.java b/src/org/atriasoft/exml/model/XmlNode.java index 271322a..8002532 100644 --- a/src/org/atriasoft/exml/model/XmlNode.java +++ b/src/org/atriasoft/exml/model/XmlNode.java @@ -9,8 +9,8 @@ package org.atriasoft.exml.model; * Basic main object of all xml elements. */ public abstract class XmlNode { - /// Value of the node (for element this is the name, for text it is the inside text ...); - protected String value = ""; + /// Value of the node (for element this is the name, for text it is the inside text ...)(null for the root element); + protected String value = null; /** * basic element of a xml structure @@ -22,14 +22,14 @@ public abstract class XmlNode { * @param[in] _value value of the node */ public XmlNode(final String _value) { - this.value = _value; + setValue(_value); } /** * Clear the Node */ public void clear() { - this.value = ""; + this.value = null; } @Override @@ -88,7 +88,11 @@ public abstract class XmlNode { * @param[in] _value New value of the node. */ public final void setValue(final String _value) { - this.value = _value; + if (_value.isEmpty() == true || _value.isBlank() == true) { + this.value = null; + } else { + this.value = _value; + } } /** diff --git a/src/org/atriasoft/exml/parser/Tools.java b/src/org/atriasoft/exml/parser/Tools.java index 5e05397..2e44b1d 100644 --- a/src/org/atriasoft/exml/parser/Tools.java +++ b/src/org/atriasoft/exml/parser/Tools.java @@ -36,6 +36,10 @@ public class Tools { return true; } + public static String cleanNumberList(final String data) { + return data.replaceAll("[ \t\n\r]", "").replaceAll(",", ";"); + } + /** * count the number of white char in the string from the specify position (stop at the first element that is not a white char) * @param[in] _data Data to parse. @@ -128,6 +132,116 @@ public class Tools { return false; } + public static Boolean[] parseBooleanClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Boolean[] out = new Boolean[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Boolean.valueOf(str); + } + return out; + } + + public static boolean[] parseBooleanStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final boolean[] out = new boolean[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Boolean.valueOf(str); + } + return out; + } + + public static Byte[] parseByteClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Byte[] out = new Byte[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Byte.parseByte(str); + } + return out; + } + + public static byte[] parseByteStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final byte[] out = new byte[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Byte.parseByte(str); + } + return out; + } + + public static Integer[] parseIntegerClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Integer[] out = new Integer[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Integer.parseInt(str); + } + return out; + } + + public static int[] parseIntegerStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final int[] out = new int[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Integer.parseInt(str); + } + return out; + } + + public static Long[] parseLongClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Long[] out = new Long[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Long.parseLong(str); + } + return out; + } + + public static long[] parseLongStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final long[] out = new long[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Long.parseLong(str); + } + return out; + } + + public static Short[] parseShortClassStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final Short[] out = new Short[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Short.parseShort(str); + } + return out; + } + + public static short[] parseShortStringList(String data) { // throws NumberFormatException + data = cleanNumberList(data); + final String dataArray[] = data.split(";"); + final short[] out = new short[dataArray.length]; + int count = 0; + for (final String str : dataArray) { + out[count++] = Short.parseShort(str); + } + return out; + } + // transform the Text with : // "<" == "<" // ">" == ">" diff --git a/test/src/test/atriasoft/exml/ExmlTestIntrospection.java b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java new file mode 100644 index 0000000..831b47a --- /dev/null +++ b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java @@ -0,0 +1,131 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2021, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +package test.atriasoft.exml; + +import java.util.Arrays; + +import org.atriasoft.exml.Exml; +import org.atriasoft.exml.exception.ExmlBuilderException; +import org.atriasoft.exml.exception.ExmlParserErrorMulti; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import test.atriasoft.exml.introspection.ClassPublicMemberOnly; +import test.atriasoft.exml.introspection.ClassPublicMethodOnly; + +public class ExmlTestIntrospection { + @BeforeAll + public static void beforeClass() { + Log.verbose("----------------------------------------------------------------"); + } + + @Test + public void test1() throws ExmlParserErrorMulti, ExmlBuilderException { + + //@formatter:off + final String dataToParse = "\n"; + //@formatter:on + + final ClassPublicMemberOnly[] root = Assertions.assertDoesNotThrow(() -> Exml.parse(dataToParse, ClassPublicMemberOnly.class, "elem")); + Assertions.assertEquals(1, root.length); + final ClassPublicMemberOnly elem = root[0]; + Assertions.assertEquals((byte) 12, elem.memberByte); + Assertions.assertEquals((short) 1223, elem.memberShort); + Assertions.assertEquals(4541542, elem.memberInteger); + Assertions.assertEquals(4564654654L, elem.memberLong); + Assertions.assertEquals(true, elem.memberBoolean); + Assertions.assertEquals((byte) 55, elem.memberByteClass); + Assertions.assertEquals((short) 1523, elem.memberShortClass); + Assertions.assertEquals(4654654, elem.memberIntegerClass); + Assertions.assertEquals(545645645454L, elem.memberLongClass); + Assertions.assertEquals(true, elem.memberBooleanClass); + Assertions.assertEquals("sdfgsdkjfglksqjéé", elem.memberStringClass); + Assertions.assertEquals(5, elem.memberArrayByte.length); + Assertions.assertEquals((byte) 12, elem.memberArrayByte[0]); + Assertions.assertEquals((byte) 15, elem.memberArrayByte[1]); + Assertions.assertEquals((byte) 123, elem.memberArrayByte[2]); + Assertions.assertEquals((byte) 100, elem.memberArrayByte[3]); + Assertions.assertEquals(2, elem.memberArrayByte[4]); + Assertions.assertEquals(4, elem.memberArrayByteClass.length); + Assertions.assertEquals((byte) 12, elem.memberArrayByteClass[0]); + Assertions.assertEquals((byte) 1, elem.memberArrayByteClass[1]); + Assertions.assertEquals((byte) 100, elem.memberArrayByteClass[2]); + Assertions.assertEquals((byte) 122, elem.memberArrayByteClass[3]); + + Assertions.assertEquals(4, elem.memberArrayShort.length); + Assertions.assertEquals((short) 1245, elem.memberArrayShort[0]); + Assertions.assertEquals((short) 1894, elem.memberArrayShort[1]); + Assertions.assertEquals((short) -100, elem.memberArrayShort[2]); + Assertions.assertEquals((short) -12542, elem.memberArrayShort[3]); + + Assertions.assertEquals(5, elem.memberArrayShortClass.length); + Assertions.assertEquals((short) -1245, elem.memberArrayShortClass[0]); + Assertions.assertEquals((short) -1894, elem.memberArrayShortClass[1]); + Assertions.assertEquals((short) 0, elem.memberArrayShortClass[2]); + Assertions.assertEquals((short) 2542, elem.memberArrayShortClass[3]); + Assertions.assertEquals((short) 15615, elem.memberArrayShortClass[4]); + + Assertions.assertEquals(2, elem.memberArrayInteger.length); + //Assertions.assertArrayEquals(Arrays.asList(123456, -654987).toArray(), elem.memberArrayInteger); + + Assertions.assertEquals(3, elem.memberArrayIntegerClass.length); + Assertions.assertArrayEquals(Arrays.asList(1567845, 45621354, -5646544).toArray(), elem.memberArrayIntegerClass); + + Assertions.assertEquals(3, elem.memberArrayLong.length); + //Assertions.assertArrayEquals(Arrays.asList(1651324654L, 65421351685L, -5L).toArray(), elem.memberArrayLong); + Assertions.assertEquals(5, elem.memberArrayLongClass.length); + Assertions.assertArrayEquals(Arrays.asList(6746541351L, 546546546L, 564654654L, 654654654654L, -45546L).toArray(), elem.memberArrayLongClass); + Assertions.assertEquals(3, elem.memberArrayBoolean.length); + //Assertions.assertArrayEquals(Arrays.asList(true, true, false).toArray(), elem.memberArrayBoolean); + Assertions.assertEquals(4, elem.memberArrayBooleanClass.length); + Assertions.assertArrayEquals(Arrays.asList(false, false, true, true).toArray(), elem.memberArrayBooleanClass); + + } + + @Test + public void test2() { + //@formatter:off + final String dataToParse = "\n"; + //@formatter:on + final ClassPublicMethodOnly[] root = Assertions.assertDoesNotThrow(() -> Exml.parse(dataToParse, ClassPublicMethodOnly.class, "elem")); + Assertions.assertEquals(1, root.length); + } + +} diff --git a/test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java b/test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java new file mode 100644 index 0000000..dfceec8 --- /dev/null +++ b/test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java @@ -0,0 +1,25 @@ +package test.atriasoft.exml.introspection; + +public class ClassPublicMemberOnly { + public byte memberByte; + public short memberShort; + public int memberInteger; + public long memberLong; + public boolean memberBoolean; + public Byte memberByteClass; + public Short memberShortClass; + public Integer memberIntegerClass; + public Long memberLongClass; + public Boolean memberBooleanClass; + public String memberStringClass; + public byte[] memberArrayByte; + public short[] memberArrayShort; + public int[] memberArrayInteger; + public long[] memberArrayLong; + public boolean[] memberArrayBoolean; + public Byte[] memberArrayByteClass; + public Short[] memberArrayShortClass; + public Integer[] memberArrayIntegerClass; + public Long[] memberArrayLongClass; + public Boolean[] memberArrayBooleanClass; +} diff --git a/test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java b/test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java new file mode 100644 index 0000000..5ad949c --- /dev/null +++ b/test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java @@ -0,0 +1,193 @@ +package test.atriasoft.exml.introspection; + +public class ClassPublicMethodOnly { + private byte memberByte; + private short memberShort; + private int memberInteger; + private long memberLong; + private boolean memberBoolean; + private Byte memberByteClass; + private Short memberShortClass; + private Integer memberIntegerClass; + private Long memberLongClass; + private Boolean memberBooleanClass; + private String memberStringClass; + private byte[] memberArrayByte; + private short[] memberArrayShort; + private int[] memberArrayInteger; + private long[] memberArrayLong; + private boolean[] memberArrayBoolean; + private Byte[] memberArrayByteClass; + private Short[] memberArrayShortClass; + private Integer[] memberArrayIntegerClass; + private Long[] memberArrayLongClass; + private Boolean[] memberArrayBooleanClass; + + public boolean[] getMemberArrayBoolean() { + return this.memberArrayBoolean; + } + + public Boolean[] getMemberArrayBooleanClass() { + return this.memberArrayBooleanClass; + } + + public byte[] getMemberArrayByte() { + return this.memberArrayByte; + } + + public Byte[] getMemberArrayByteClass() { + return this.memberArrayByteClass; + } + + public int[] getMemberArrayInteger() { + return this.memberArrayInteger; + } + + public Integer[] getMemberArrayIntegerClass() { + return this.memberArrayIntegerClass; + } + + public long[] getMemberArrayLong() { + return this.memberArrayLong; + } + + public Long[] getMemberArrayLongClass() { + return this.memberArrayLongClass; + } + + public short[] getMemberArrayShort() { + return this.memberArrayShort; + } + + public Short[] getMemberArrayShortClass() { + return this.memberArrayShortClass; + } + + public Boolean getMemberBooleanClass() { + return this.memberBooleanClass; + } + + public byte getMemberByte() { + return this.memberByte; + } + + public Byte getMemberByteClass() { + return this.memberByteClass; + } + + public int getMemberInteger() { + return this.memberInteger; + } + + public Integer getMemberIntegerClass() { + return this.memberIntegerClass; + } + + public long getMemberLong() { + return this.memberLong; + } + + public Long getMemberLongClass() { + return this.memberLongClass; + } + + public short getMemberShort() { + return this.memberShort; + } + + public Short getMemberShortClass() { + return this.memberShortClass; + } + + public String getMemberStringClass() { + return this.memberStringClass; + } + + public boolean isMemberBoolean() { + return this.memberBoolean; + } + + public void setMemberArrayBoolean(final boolean[] memberArrayBoolean) { + this.memberArrayBoolean = memberArrayBoolean; + } + + public void setMemberArrayBooleanClass(final Boolean[] memberArrayBooleanClass) { + this.memberArrayBooleanClass = memberArrayBooleanClass; + } + + public void setMemberArrayByte(final byte[] memberArrayByte) { + this.memberArrayByte = memberArrayByte; + } + + public void setMemberArrayByteClass(final Byte[] memberArrayByteClass) { + this.memberArrayByteClass = memberArrayByteClass; + } + + public void setMemberArrayInteger(final int[] memberArrayInteger) { + this.memberArrayInteger = memberArrayInteger; + } + + public void setMemberArrayIntegerClass(final Integer[] memberArrayIntegerClass) { + this.memberArrayIntegerClass = memberArrayIntegerClass; + } + + public void setMemberArrayLong(final long[] memberArrayLong) { + this.memberArrayLong = memberArrayLong; + } + + public void setMemberArrayLongClass(final Long[] memberArrayLongClass) { + this.memberArrayLongClass = memberArrayLongClass; + } + + public void setMemberArrayShort(final short[] memberArrayShort) { + this.memberArrayShort = memberArrayShort; + } + + public void setMemberArrayShortClass(final Short[] memberArrayShortClass) { + this.memberArrayShortClass = memberArrayShortClass; + } + + public void setMemberBoolean(final boolean memberBoolean) { + this.memberBoolean = memberBoolean; + } + + public void setMemberBooleanClass(final Boolean memberBooleanClass) { + this.memberBooleanClass = memberBooleanClass; + } + + public void setMemberByte(final byte memberByte) { + this.memberByte = memberByte; + } + + public void setMemberByteClass(final Byte memberByteClass) { + this.memberByteClass = memberByteClass; + } + + public void setMemberInteger(final int memberInteger) { + this.memberInteger = memberInteger; + } + + public void setMemberIntegerClass(final Integer memberIntegerClass) { + this.memberIntegerClass = memberIntegerClass; + } + + public void setMemberLong(final long memberLong) { + this.memberLong = memberLong; + } + + public void setMemberLongClass(final Long memberLongClass) { + this.memberLongClass = memberLongClass; + } + + public void setMemberShort(final short memberShort) { + this.memberShort = memberShort; + } + + public void setMemberShortClass(final Short memberShortClass) { + this.memberShortClass = memberShortClass; + } + + public void setMemberStringClass(final String memberStringClass) { + this.memberStringClass = memberStringClass; + } +}