diff --git a/src/org/atriasoft/exml/Exml.java b/src/org/atriasoft/exml/Exml.java index fbd0afa..b474740 100644 --- a/src/org/atriasoft/exml/Exml.java +++ b/src/org/atriasoft/exml/Exml.java @@ -58,7 +58,14 @@ public class Exml { } public static TYPE[] parse(final String data, final Class classType, final String rootNodeName) throws ExmlBuilderException, ExmlParserErrorMulti { - final Builder builder = new BuilderIntrospection(classType, rootNodeName); + Builder builder; + try { + builder = new BuilderIntrospection(classType, rootNodeName); + } catch (final Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } final ParseXml parser = new ParseXml(builder); final ParsingProperty property = new ParsingProperty(); property.setDisplayError(true); diff --git a/src/org/atriasoft/exml/annotation/XmlCaseSensitive.java b/src/org/atriasoft/exml/annotation/XmlCaseSensitive.java new file mode 100644 index 0000000..cc6edb4 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlCaseSensitive.java @@ -0,0 +1,21 @@ +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 set the element is case sensitive or not. + * + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlCaseSensitive { + /** + * Set if the element is is case sensitive. + * @return true if case sensitive + */ + boolean value() default true; +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlDefaultAttibute.java b/src/org/atriasoft/exml/annotation/XmlDefaultAttibute.java new file mode 100644 index 0000000..5093424 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlDefaultAttibute.java @@ -0,0 +1,20 @@ +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 set the default parsing as attributes. + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlDefaultAttibute { + /** + * Set this to false to select the attribute as default. + * @return true select default attribute, false select default element. + */ + boolean value() default true; +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlDefaultCaseSensitive.java b/src/org/atriasoft/exml/annotation/XmlDefaultCaseSensitive.java new file mode 100644 index 0000000..b075c75 --- /dev/null +++ b/src/org/atriasoft/exml/annotation/XmlDefaultCaseSensitive.java @@ -0,0 +1,20 @@ +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 select if the parsing is case sensitive or not. + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@ExmlAnnotation +public @interface XmlDefaultCaseSensitive { + /** + * Set this at true to set all the element are case sensitive. + * @return true if the element are by default case_sensitive. + */ + boolean value() default true; +} \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlDefaultNotManaged.java b/src/org/atriasoft/exml/annotation/XmlDefaultManaged.java similarity index 59% rename from src/org/atriasoft/exml/annotation/XmlDefaultNotManaged.java rename to src/org/atriasoft/exml/annotation/XmlDefaultManaged.java index e60700b..d31bad2 100644 --- a/src/org/atriasoft/exml/annotation/XmlDefaultNotManaged.java +++ b/src/org/atriasoft/exml/annotation/XmlDefaultManaged.java @@ -9,8 +9,13 @@ 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 }) +@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExmlAnnotation -public @interface XmlDefaultNotManaged { +public @interface XmlDefaultManaged { + /** + * Set this at false to remove all the field and the function from Xml introspection + * @return true if the element are by default managed. + */ + boolean value() default true; } \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlDefaultOptional.java b/src/org/atriasoft/exml/annotation/XmlDefaultOptional.java index 828f126..bd7c878 100644 --- a/src/org/atriasoft/exml/annotation/XmlDefaultOptional.java +++ b/src/org/atriasoft/exml/annotation/XmlDefaultOptional.java @@ -9,8 +9,13 @@ import java.lang.annotation.Target; * Marker annotation that set the element not found are ignored. * */ -@Target({ ElementType.MODULE }) +@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExmlAnnotation public @interface XmlDefaultOptional { + /** + * Set this at true to set all the element optional. + * @return true if the element are by default optional. + */ + boolean value() default false; } \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlManaged.java b/src/org/atriasoft/exml/annotation/XmlManaged.java index 48fdef5..f467388 100644 --- a/src/org/atriasoft/exml/annotation/XmlManaged.java +++ b/src/org/atriasoft/exml/annotation/XmlManaged.java @@ -13,4 +13,9 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @ExmlAnnotation public @interface XmlManaged { + /** + * Set this at false to remove this function or this field form the XML parsing system + * @return true if the element is managed. + */ + boolean value() default true; } \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlName.java b/src/org/atriasoft/exml/annotation/XmlName.java index e3db7e3..6990d71 100644 --- a/src/org/atriasoft/exml/annotation/XmlName.java +++ b/src/org/atriasoft/exml/annotation/XmlName.java @@ -13,16 +13,11 @@ import java.lang.annotation.Target; @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(); + String[] value(); } \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlOptional.java b/src/org/atriasoft/exml/annotation/XmlOptional.java index 8d62ba2..b73e24b 100644 --- a/src/org/atriasoft/exml/annotation/XmlOptional.java +++ b/src/org/atriasoft/exml/annotation/XmlOptional.java @@ -13,4 +13,9 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @ExmlAnnotation public @interface XmlOptional { + /** + * Set if the element is optional or not. If optional, the parser does not throw error if the element is not declared. + * @return thru if optional + */ + boolean value() default true; } \ No newline at end of file diff --git a/src/org/atriasoft/exml/annotation/XmlNotManaged.java b/src/org/atriasoft/exml/annotation/XmlProperty.java similarity index 56% rename from src/org/atriasoft/exml/annotation/XmlNotManaged.java rename to src/org/atriasoft/exml/annotation/XmlProperty.java index 158712a..5948efc 100644 --- a/src/org/atriasoft/exml/annotation/XmlNotManaged.java +++ b/src/org/atriasoft/exml/annotation/XmlProperty.java @@ -6,11 +6,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Marker annotation that Permit to ignore the function or the attribute. - * + * Marker annotation that set the Xml element seen as a property. */ @Target({ ElementType.FIELD, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @ExmlAnnotation -public @interface XmlNotManaged { +public @interface XmlProperty { + /** + * Set at true to set the element managed as a property of the Xml node + * @return property management. + */ + boolean value() default true; } \ No newline at end of file diff --git a/src/org/atriasoft/exml/builder/Builder.java b/src/org/atriasoft/exml/builder/Builder.java index c32f419..410c966 100644 --- a/src/org/atriasoft/exml/builder/Builder.java +++ b/src/org/atriasoft/exml/builder/Builder.java @@ -33,7 +33,7 @@ public interface Builder { * @return the object representing the Element. * @throws ExmlBuilderException Error with this node or element. */ - Object newElement(Object parent, String nodeName) throws ExmlBuilderException; + Object newElement(Object parent, String nodeName) throws ExmlBuilderException, Exception; /** * Add a property on the Element. @@ -42,7 +42,7 @@ public interface Builder { * @param propertyValue Value of the property * @throws ExmlBuilderException Error with this node or element. */ - void newProperty(Object element, String propertyName, String propertyValue) throws ExmlBuilderException; + void newProperty(Object element, String propertyName, String propertyValue) throws ExmlBuilderException, Exception; /** * Create or get the root element of the document diff --git a/src/org/atriasoft/exml/builder/BuilderIntrospection.java b/src/org/atriasoft/exml/builder/BuilderIntrospection.java index d8da91a..c556118 100644 --- a/src/org/atriasoft/exml/builder/BuilderIntrospection.java +++ b/src/org/atriasoft/exml/builder/BuilderIntrospection.java @@ -14,13 +14,13 @@ public class BuilderIntrospection implements Builder { final Class rootClassType; final String rootNodeName; - public BuilderIntrospection(final Class classType, final String rootNodeName) { + public BuilderIntrospection(final Class classType, final String rootNodeName) throws Exception { this.rootNodeName = rootNodeName; this.rootClassType = classType; this.elements.put(classType, new IntrospectionData(classType)); } - IntrospectionData findOrCreate(final Class classType) { + IntrospectionData findOrCreate(final Class classType) throws Exception { IntrospectionData out = this.elements.get(classType); if (out != null) { return out; @@ -43,7 +43,7 @@ public class BuilderIntrospection implements Builder { } @Override - public Object newElement(final Object parent, final String nodeName) throws ExmlBuilderException { + public Object newElement(final Object parent, final String nodeName) throws ExmlBuilderException, Exception { final IntrospectionObject introspectionObject = (IntrospectionObject) parent; if (introspectionObject.getDataInterface() == null) { final Object previousData = introspectionObject.getData(); @@ -72,7 +72,7 @@ public class BuilderIntrospection implements Builder { } @Override - public void newProperty(final Object element, final String propertyName, final String propertyValue) throws ExmlBuilderException { + public void newProperty(final Object element, final String propertyName, final String propertyValue) throws ExmlBuilderException, Exception { final IntrospectionObject introspectionObject = (IntrospectionObject) element; if (introspectionObject.getDataInterface() == null) { // property on nothing ??? diff --git a/src/org/atriasoft/exml/builder/IntrospectionData.java b/src/org/atriasoft/exml/builder/IntrospectionData.java index 4317a02..0ee4f52 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionData.java +++ b/src/org/atriasoft/exml/builder/IntrospectionData.java @@ -1,5 +1,6 @@ package org.atriasoft.exml.builder; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -8,34 +9,110 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import org.atriasoft.exml.annotation.XmlCaseSensitive; +import org.atriasoft.exml.annotation.XmlDefaultCaseSensitive; +import org.atriasoft.exml.annotation.XmlDefaultManaged; +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.internal.Log; +import org.atriasoft.exml.parser.Tools; public class IntrospectionData { + private static final Boolean DEFAULT_MANAGED = true; + private static final Boolean DEFAULT_OPTIONAL = false; + private static final Boolean DEFAULT_CASE_SENSITIVE = true; final Class classType; private final List properties = new ArrayList<>(); - public IntrospectionData(final Class classType) { + private final List methods = new ArrayList<>(); + + public IntrospectionData(final Class classType) throws Exception { this.classType = classType; - Log.info("Introspect class: '" + classType.getCanonicalName() + "'"); + final Boolean isDefaultManaged = getIsDefaultManaged(classType, DEFAULT_MANAGED); + final Boolean isDefaultOptional = getIsDefaultOptional(classType, DEFAULT_OPTIONAL); + final Boolean isDefaultCaseSensitive = getIsDefaultCaseSensitive(classType, DEFAULT_CASE_SENSITIVE); + Log.verbose("Introspect class: '" + classType.getCanonicalName() + "'"); final Constructor[] constructors = this.classType.getConstructors(); - Log.info(" Constructors: (" + constructors.length + ")"); + Log.verbose(" Constructors: (" + constructors.length + ")"); for (final Constructor elem : constructors) { - Log.info(" - " + elem.toGenericString()); + Log.verbose(" - " + elem.toGenericString()); } final Field[] fields = this.classType.getFields(); - Log.info(" Fields: (" + fields.length + ")"); + 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); // TODO: check if property does not already exist ... - this.properties.add(new IntrospectionPropertyField(elem)); - Log.info(" - " + elem.toGenericString()); + if (isManaged == true) { + this.properties.add(new IntrospectionPropertyField(elem, names, caseSensitive, isOptionnal)); + } + Log.verbose(" - " + elem.toGenericString()); } final Method[] methodsTmp = this.classType.getMethods(); - + // filter getX setX isX 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"); + if (o.getName().contentEquals("getClass") == 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()); - // separate the methods... + + 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(); + final List methodsGet = methods.stream().filter(o -> { return o.getName().startsWith("get"); }).collect(Collectors.toList()); @@ -47,12 +124,106 @@ public class IntrospectionData { }).collect(Collectors.toList()); // associate methods by pair. - // nameProperty, getter, setter - - Log.info(" Methods: (" + methods.size() + ")"); - for (final Method elem : methods) { - Log.info(" - " + elem.toGenericString()); + 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"); + } + } + 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; + } + } + if (tmp == null) { + tmp = new OrderData(name); + tmp.setter = method; + elements.add(tmp); + } else { + tmp.setter = method; + } + } + // Add it in the engine + for (final OrderData elem : elements) { + Log.info("find methode : '" + elem.name + "' :"); + if (elem.setter != null && elem.getter != null) { + Log.info(" setter: " + elem.setter.toGenericString()); + Log.info(" getter: " + elem.getter.toGenericString()); + final Boolean isManagedSet = getIsManaged(elem.setter, null); + final Boolean isManagedGet = getIsManaged(elem.getter, null); + if (isManagedSet != null && isManagedGet != null && isManagedSet != isManagedGet) { + throw new Exception("Can net set oposite information on getter and setter"); + } + final Boolean isManaged = isManagedSet != null ? isManagedSet : isManagedGet != null ? isManagedGet : isDefaultManaged; + + final Boolean isOptionnalSet = getIsOptional(elem.setter, null); + final Boolean isOptionnalGet = getIsOptional(elem.getter, null); + if (isOptionnalSet != null && isOptionnalGet != null && isOptionnalSet != isOptionnalGet) { + throw new Exception("Can net set oposite information on getter and setter"); + } + final Boolean isOptional = isOptionnalSet != null ? isOptionnalSet : isOptionnalGet != null ? isOptionnalGet : isDefaultOptional; + + final Boolean caseSensitiveSet = getIsCaseSensitive(elem.setter, null); + final Boolean caseSensitiveGet = getIsCaseSensitive(elem.getter, null); + if (caseSensitiveSet != null && caseSensitiveGet != null && caseSensitiveSet != caseSensitiveGet) { + throw new Exception("Can net set oposite information on getter and setter"); + } + + final Boolean isCaseSensitive = caseSensitiveSet != null ? caseSensitiveSet : caseSensitiveGet != null ? caseSensitiveGet : isDefaultCaseSensitive; + + final String[] namesSet = getNames(elem.setter, null); + final String[] namesGet = getNames(elem.getter, null); + if (namesSet != null && namesGet != null && namesSet.equals(namesGet)) { + throw new Exception("Can net set oposite information on getter and setter"); + } + final String[] names = namesSet != null ? namesSet : namesGet != null ? namesGet : new String[] { Tools.decapitalizeFirst(elem.name) }; + this.methods.add(new IntrospectionPropertyMethod(elem.setter, elem.getter, names, isCaseSensitive, isOptional)); + } else { + Boolean isManaged = null; + Boolean isOptionnal = null; + String[] names = null; + Boolean isCaseSensitive = null; + if (elem.setter != null) { + Log.info(" setter: " + elem.setter.toGenericString()); + isManaged = getIsManaged(elem.setter, isDefaultManaged); + isOptionnal = getIsOptional(elem.setter, isDefaultOptional); + names = getNames(elem.setter, Tools.decapitalizeFirst(elem.name)); + isCaseSensitive = getIsCaseSensitive(elem.setter, isDefaultCaseSensitive); + } else { + Log.info(" setter: null"); + } + if (elem.getter != null) { + Log.info(" getter: " + elem.getter.toGenericString()); + isManaged = getIsManaged(elem.getter, isDefaultManaged); + isOptionnal = getIsOptional(elem.getter, isDefaultOptional); + names = getNames(elem.getter, Tools.decapitalizeFirst(elem.name)); + isCaseSensitive = getIsCaseSensitive(elem.getter, isDefaultCaseSensitive); + } else { + Log.info(" getter: null"); + } + if (isManaged == true) { + this.methods.add(new IntrospectionPropertyMethod(elem.setter, elem.getter, names, isCaseSensitive, isOptionnal)); + } + } + } + } Object createObject() { @@ -65,25 +236,210 @@ public class IntrospectionData { } } + protected IntrospectionProperty findMethodDescription(final String propertyName) throws Exception { + for (final IntrospectionProperty prop : this.methods) { + if (prop.isCompatible(propertyName) == true) { + return prop; + } + } + 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"); + return null; } - 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(); - + protected Boolean getIsCaseSensitive(final Field element, final Boolean defaultValue) throws Exception { + 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 "); + } + return ((XmlCaseSensitive) annotation[0]).value(); + } + + protected Boolean getIsCaseSensitive(final Method element, final Boolean defaultValue) throws Exception { + 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 "); + } + return ((XmlCaseSensitive) annotation[0]).value(); + } + + private Boolean getIsDefaultCaseSensitive(final Class classType, final Boolean defaultValue) throws Exception { + 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 "); + } + return ((XmlDefaultCaseSensitive) annotation[0]).value(); + } + + private Boolean getIsDefaultManaged(final Class classType, final Boolean defaultValue) throws Exception { + 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 "); + } + return ((XmlDefaultManaged) annotation[0]).value(); + } + + private Boolean getIsDefaultOptional(final Class classType, final Boolean defaultValue) throws Exception { + 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 "); + } + return ((XmlDefaultOptional) annotation[0]).value(); + } + + protected Boolean getIsManaged(final Field element, final Boolean parentValue) throws Exception { + 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 "); + } + return ((XmlManaged) annotation[0]).value(); + } + + protected Boolean getIsManaged(final Method element, final Boolean parentValue) throws Exception { + 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 "); + } + return ((XmlManaged) annotation[0]).value(); + } + + protected Boolean getIsOptional(final Field element, final Boolean parentValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlOptional.class); + if (annotation.length == 0) { + return parentValue; + } + if (annotation.length > 1) { + throw new Exception("Must not hame more that "); + } + return ((XmlOptional) annotation[0]).value(); + } + + protected Boolean getIsOptional(final Method element, final Boolean parentValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlOptional.class); + if (annotation.length == 0) { + return parentValue; + } + if (annotation.length > 1) { + throw new Exception("Must not hame more that "); + } + return ((XmlOptional) annotation[0]).value(); + } + + protected String[] getNames(final Field element, final String defaultValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlName.class); + if (annotation.length == 0) { + if (defaultValue == null) { + return null; + } else { + return new String[] { defaultValue }; + } + } + if (annotation.length > 1) { + throw new Exception("Must not hame more that "); + } + final String[] tmp = ((XmlName) annotation[0]).value(); + if (tmp == null) { + throw new Exception("Set null value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + if (tmp.length == 0) { + throw new Exception("Set empty list value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + for (final String elem : tmp) { + if (elem == null) { + throw new Exception("Set null String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + if (elem.isEmpty() == true) { + throw new Exception("Set empty String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + } + return tmp; + } + + protected String[] getNames(final Method element, final String defaultValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlName.class); + if (annotation.length == 0) { + if (defaultValue == null) { + return null; + } else { + return new String[] { defaultValue }; + } + } + if (annotation.length > 1) { + throw new Exception("Must not hame more that "); + } + final String[] tmp = ((XmlName) annotation[0]).value(); + if (tmp == null) { + throw new Exception("Set null value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + if (tmp.length == 0) { + throw new Exception("Set empty list value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + for (final String elem : tmp) { + if (elem == null) { + throw new Exception("Set null String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + if (elem.isEmpty() == true) { + throw new Exception("Set empty String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + } + return tmp; + } + + public void setProperty(final Object data, final String propertyName, final String propertyValue) throws Exception { + //Log.error(" propertyName='" + propertyName + "' propertyValue='" + propertyValue + "' "); + // by default use setter to set the property + final IntrospectionProperty propMethode = findMethodDescription(propertyName); + if (propMethode != null && propMethode.cansetValue() == true) { + //Log.error(" ==> find '" + propMethode.getNames()); + propMethode.setValue(data, propertyValue); + return; + } + // try with direct field + final IntrospectionProperty propField = findPropertyDescription(propertyName); + if (propField != null && propField.cansetValue() == true) { + //Log.error(" ==> find '" + propField.getNames()); + propField.setValue(data, propertyValue); + return; + } + throw new Exception("can not find the field '" + propertyName + "'"); } } + +class OrderData { + public final String name; + public Method setter = null; + public Method getter = null; + + public OrderData(final String name) { + this.name = name; + } +} diff --git a/src/org/atriasoft/exml/builder/IntrospectionObject.java b/src/org/atriasoft/exml/builder/IntrospectionObject.java index c8e1bf0..78c0bf5 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionObject.java +++ b/src/org/atriasoft/exml/builder/IntrospectionObject.java @@ -27,7 +27,7 @@ public class IntrospectionObject { return this.dataInterface; } - public void setProperty(final String propertyName, final String propertyValue) { + public void setProperty(final String propertyName, final String propertyValue) throws Exception { 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 index 8a78921..34e48b2 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionProperty.java +++ b/src/org/atriasoft/exml/builder/IntrospectionProperty.java @@ -1,18 +1,23 @@ 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; + protected final String[] names; + protected final Boolean caseSensitive; + protected final Boolean isOptionnal; - public IntrospectionProperty(final Class type) { + public IntrospectionProperty(final Class type, final String[] names, final Boolean caseSensitive, final Boolean isOptionnal) { this.type = type; + this.names = names; + this.caseSensitive = caseSensitive; + this.isOptionnal = isOptionnal; } - public List getNames() { + public abstract boolean canGetValue(); + + public abstract boolean cansetValue(); + + public String[] getNames() { return this.names; } @@ -20,14 +25,14 @@ public abstract class IntrospectionProperty { return this.type; } - public abstract String getValue(Object object); + public abstract String getValue(Object object) throws Exception; public boolean isCaseSensitive() { return this.caseSensitive; } public boolean isCompatible(String name) { - if (this.caseSensitive == false) { + if (this.caseSensitive == true) { for (final String elem : this.names) { if (elem.contentEquals(name) == true) { return true; @@ -44,13 +49,5 @@ public abstract class IntrospectionProperty { 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); + public abstract void setValue(Object object, String value) throws Exception; } \ 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 6f859f3..e46d384 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java @@ -8,10 +8,19 @@ import org.atriasoft.exml.parser.Tools; public class IntrospectionPropertyField extends IntrospectionProperty { private final Field fieldDescription; - public IntrospectionPropertyField(final Field fieldDescription) { - super(fieldDescription.getType()); + public IntrospectionPropertyField(final Field fieldDescription, final String[] names, final Boolean caseSensitive, final Boolean isOptionnal) { + super(fieldDescription.getType(), names, caseSensitive, isOptionnal); this.fieldDescription = fieldDescription; - this.names.add(this.fieldDescription.getName()); + } + + @Override + public boolean canGetValue() { + return true; + } + + @Override + public boolean cansetValue() { + return true; } @Override diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java new file mode 100644 index 0000000..5d4b42a --- /dev/null +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java @@ -0,0 +1,124 @@ +package org.atriasoft.exml.builder; + +import java.lang.reflect.Method; + +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 { + Class type = null; + if (setter == null && getter == null) { + // impossible case... + throw new Exception("kjhkhjkj"); + } + if (getter != null) { + type = getter.getReturnType(); + } + if (setter != null) { + if (type != null && setter.getParameters()[0].getType() != type) { + throw new Exception("The type of the setter ands the type return by the getter are not the same ..."); + } else { + type = setter.getParameters()[0].getType(); + } + } + return type; + } + + protected Method setter; + + protected Method getter; + + public IntrospectionPropertyMethod(final Method setter, final Method getter, final String[] names, final Boolean isCaseSensitive, final Boolean isOptional) throws Exception { + super(getTypefunction(setter, getter), names, isCaseSensitive, isOptional); + this.setter = setter; + this.getter = getter; + } + + @Override + public boolean canGetValue() { + return this.getter != null; + } + + @Override + public boolean cansetValue() { + return this.setter != null; + } + + @Override + public String getValue(final Object object) throws Exception { + if (this.getter == null) { + throw new Exception("no getter availlable"); + } + // TODO Auto-generated method stub + return null; + } + + @Override + public void setValue(final Object object, final String value) throws Exception { + if (this.setter == null) { + throw new Exception("no setter availlable"); + } + try { + if (this.type == byte.class) { + final byte data = Byte.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == short.class) { + final short data = Short.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == int.class) { + final int data = Integer.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == long.class) { + final long data = Long.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == boolean.class) { + final boolean data = Boolean.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == String.class) { + this.setter.invoke(object, value); + } else if (this.type == Byte.class) { + final Byte data = Byte.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == Short.class) { + final Short data = Short.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == Integer.class) { + final Integer data = Integer.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == Long.class) { + final Long data = Long.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == Boolean.class) { + final Boolean data = Boolean.valueOf(value); + this.setter.invoke(object, data); + } else if (this.type == byte[].class) { + this.setter.invoke(object, Tools.parseByteStringList(value)); + } else if (this.type == Byte[].class) { + this.setter.invoke(object, (Object) Tools.parseByteClassStringList(value)); + } else if (this.type == short[].class) { + this.setter.invoke(object, Tools.parseShortStringList(value)); + } else if (this.type == Short[].class) { + this.setter.invoke(object, (Object) Tools.parseShortClassStringList(value)); + } else if (this.type == int[].class) { + this.setter.invoke(object, Tools.parseIntegerStringList(value)); + } else if (this.type == Integer[].class) { + this.setter.invoke(object, (Object) Tools.parseIntegerClassStringList(value)); + } else if (this.type == long[].class) { + this.setter.invoke(object, Tools.parseLongStringList(value)); + } else if (this.type == Long[].class) { + this.setter.invoke(object, (Object) Tools.parseLongClassStringList(value)); + } else if (this.type == boolean[].class) { + this.setter.invoke(object, Tools.parseBooleanStringList(value)); + } 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);'"); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } +} diff --git a/src/org/atriasoft/exml/parser/ParseXml.java b/src/org/atriasoft/exml/parser/ParseXml.java index ec2f3a2..a621373 100644 --- a/src/org/atriasoft/exml/parser/ParseXml.java +++ b/src/org/atriasoft/exml/parser/ParseXml.java @@ -82,7 +82,12 @@ public class ParseXml { //EXML_PARSE_ATTRIBUTE(pos << " attribute : " << name << "=\"" << value << "\""); _pos.value = lastAttributePos - 1; - this.builder.newProperty(parent, name, value); + try { + this.builder.newProperty(parent, name, value); + } catch (final Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } return true; } int lastAttributePos = lastElementName + white + 3; @@ -100,7 +105,12 @@ public class ParseXml { //EXML_PARSE_ATTRIBUTE(pos << " attribute : " << name << "=\"" << value << "\""); _pos.value = lastAttributePos; - this.builder.newProperty(parent, name, value); + try { + this.builder.newProperty(parent, name, value); + } catch (final Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } return true; } @@ -495,7 +505,13 @@ public class ParseXml { Log.debug("find node named : '" + tmpname + "'"); // find text: - final Object element = this.builder.newElement(parent, tmpname); + Object element = null; + try { + element = this.builder.newElement(parent, tmpname); + } catch (final Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } _pos.value = endPosName + 1; _filePos.add(tmpPos); Log.verbose("start parse : 'element' named='" + tmpname + "'"); diff --git a/src/org/atriasoft/exml/parser/Tools.java b/src/org/atriasoft/exml/parser/Tools.java index 2e44b1d..fe47658 100644 --- a/src/org/atriasoft/exml/parser/Tools.java +++ b/src/org/atriasoft/exml/parser/Tools.java @@ -1,7 +1,5 @@ package org.atriasoft.exml.parser; -import org.atriasoft.exml.internal.Log; - public class Tools { /** * add indentation of the string input. @@ -79,19 +77,29 @@ public class Tools { return out; } + // based on this: https://stackoverflow.com/questions/4052840/most-efficient-way-to-make-the-first-character-of-a-string-lower-case + public static String decapitalizeFirst(final String string) { + if (string == null || string.length() == 0) { + return string; + } + final char c[] = string.toCharArray(); + c[0] = Character.toLowerCase(c[0]); + return new String(c); + } + /** * Display the cuurent element that is curently parse. * @param[in] _val Char that is parsed. * @param[in] _filePos Position of the char in the file. */ public static void drawElementParsed(final Character _val, final FilePos _filePos) { - if (_val == '\n') { - Log.debug(_filePos + " parse '\\n'"); - } else if (_val == '\t') { - Log.debug(_filePos + " parse '\\t'"); - } else { - Log.debug(_filePos + " parse '" + _val + "'"); - } + // if (_val == '\n') { + // Log.debug(_filePos + " parse '\\n'"); + // } else if (_val == '\t') { + // Log.debug(_filePos + " parse '\\t'"); + // } else { + // Log.debug(_filePos + " parse '" + _val + "'"); + // } } public static String extractLine(final String data, final int _pos) { diff --git a/test/src/test/atriasoft/exml/ExmlTestIntrospection.java b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java index 831b47a..1dc0fac 100644 --- a/test/src/test/atriasoft/exml/ExmlTestIntrospection.java +++ b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java @@ -122,10 +122,72 @@ public class ExmlTestIntrospection { + " memberIntegerClass = \"4654654\" \n" + " memberLongClass = \"545645645454\"\n" + " memberBooleanClass = \"true\" \n" - + " memberStringClass = \"sdfgsdkjfglksqjéé\"\n/>\n"; + + " memberStringClass = \"sdfgsdkjfglksqjéé\"\n" + + " memberArrayByte=\"12, 15,123, 100, 2\"\n" + + " memberArrayByteClass=\"\t\t\r\n 12,1, 100,122\"\n" + + " memberArrayShort=\"1245,1894, -100,-12542\"\n" + + " memberArrayShortClass=\"-1245,-1894, 0,2542,15615\"\n" + + " memberArrayInteger=\"123456,-654987\"\n" + + " memberArrayIntegerClass=\"1567845,45621354,-5646544\"\n" + + " memberArrayLong=\"1651324654,65421351685,-5\"\n" + + " memberArrayLongClass=\"6746541351,546546546,564654654,654654654654,-45546\"\n" + + " memberArrayBoolean=\"true, true, false\"\n" + + " memberArrayBooleanClass=\"false, false, true, true\"\n" + + "/>\n"; //@formatter:on final ClassPublicMethodOnly[] root = Assertions.assertDoesNotThrow(() -> Exml.parse(dataToParse, ClassPublicMethodOnly.class, "elem")); Assertions.assertEquals(1, root.length); + + final ClassPublicMethodOnly elem = root[0]; + Assertions.assertEquals((byte) 12, elem.getMemberByte()); + Assertions.assertEquals((short) 1223, elem.getMemberShort()); + Assertions.assertEquals(4541542, elem.getMemberInteger()); + Assertions.assertEquals(4564654654L, elem.getMemberLong()); + Assertions.assertEquals(true, elem.isMemberBoolean()); + Assertions.assertEquals((byte) 55, elem.getMemberByteClass()); + Assertions.assertEquals((short) 1523, elem.getMemberShortClass()); + Assertions.assertEquals(4654654, elem.getMemberIntegerClass()); + Assertions.assertEquals(545645645454L, elem.getMemberLongClass()); + Assertions.assertEquals(true, elem.isMemberBooleanClass()); + Assertions.assertEquals("sdfgsdkjfglksqjéé", elem.getMemberStringClass()); + Assertions.assertEquals(5, elem.getMemberArrayByte().length); + Assertions.assertEquals((byte) 12, elem.getMemberArrayByte()[0]); + Assertions.assertEquals((byte) 15, elem.getMemberArrayByte()[1]); + Assertions.assertEquals((byte) 123, elem.getMemberArrayByte()[2]); + Assertions.assertEquals((byte) 100, elem.getMemberArrayByte()[3]); + Assertions.assertEquals(2, elem.getMemberArrayByte()[4]); + Assertions.assertEquals(4, elem.getMemberArrayByteClass().length); + Assertions.assertEquals((byte) 12, elem.getMemberArrayByteClass()[0]); + Assertions.assertEquals((byte) 1, elem.getMemberArrayByteClass()[1]); + Assertions.assertEquals((byte) 100, elem.getMemberArrayByteClass()[2]); + Assertions.assertEquals((byte) 122, elem.getMemberArrayByteClass()[3]); + + Assertions.assertEquals(4, elem.getMemberArrayShort().length); + Assertions.assertEquals((short) 1245, elem.getMemberArrayShort()[0]); + Assertions.assertEquals((short) 1894, elem.getMemberArrayShort()[1]); + Assertions.assertEquals((short) -100, elem.getMemberArrayShort()[2]); + Assertions.assertEquals((short) -12542, elem.getMemberArrayShort()[3]); + + Assertions.assertEquals(5, elem.getMemberArrayShortClass().length); + Assertions.assertEquals((short) -1245, elem.getMemberArrayShortClass()[0]); + Assertions.assertEquals((short) -1894, elem.getMemberArrayShortClass()[1]); + Assertions.assertEquals((short) 0, elem.getMemberArrayShortClass()[2]); + Assertions.assertEquals((short) 2542, elem.getMemberArrayShortClass()[3]); + Assertions.assertEquals((short) 15615, elem.getMemberArrayShortClass()[4]); + + Assertions.assertEquals(2, elem.getMemberArrayInteger().length); + //Assertions.assertArrayEquals(Arrays.asList(123456, -654987).toArray(), elem.getMemberArrayInteger()); + + Assertions.assertEquals(3, elem.getMemberArrayIntegerClass().length); + Assertions.assertArrayEquals(Arrays.asList(1567845, 45621354, -5646544).toArray(), elem.getMemberArrayIntegerClass()); + + Assertions.assertEquals(3, elem.getMemberArrayLong().length); + //Assertions.assertArrayEquals(Arrays.asList(1651324654L, 65421351685L, -5L).toArray(), elem.getMemberArrayLong()); + Assertions.assertEquals(5, elem.getMemberArrayLongClass().length); + Assertions.assertArrayEquals(Arrays.asList(6746541351L, 546546546L, 564654654L, 654654654654L, -45546L).toArray(), elem.getMemberArrayLongClass()); + Assertions.assertEquals(3, elem.getMemberArrayBoolean().length); + //Assertions.assertArrayEquals(Arrays.asList(true, true, false).toArray(), elem.getMemberArrayBoolean()); + Assertions.assertEquals(4, elem.getMemberArrayBooleanClass().length); + Assertions.assertArrayEquals(Arrays.asList(false, false, true, true).toArray(), elem.getMemberArrayBooleanClass()); } - } diff --git a/test/src/test/atriasoft/exml/introspection/ClassInversion.java b/test/src/test/atriasoft/exml/introspection/ClassInversion.java new file mode 100644 index 0000000..55efa65 --- /dev/null +++ b/test/src/test/atriasoft/exml/introspection/ClassInversion.java @@ -0,0 +1,8 @@ +package test.atriasoft.exml.introspection; + +import org.atriasoft.exml.annotation.XmlDefaultManaged; + +@XmlDefaultManaged(value = false) +public class ClassInversion { + +} diff --git a/test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java b/test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java index dfceec8..78b6385 100644 --- a/test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java +++ b/test/src/test/atriasoft/exml/introspection/ClassPublicMemberOnly.java @@ -1,6 +1,9 @@ package test.atriasoft.exml.introspection; +import org.atriasoft.exml.annotation.XmlName; + public class ClassPublicMemberOnly { + @XmlName(value = { "jhkjhhkj" }) public byte memberByte; public short memberShort; public int memberInteger; diff --git a/test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java b/test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java index 5ad949c..15d6a6a 100644 --- a/test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java +++ b/test/src/test/atriasoft/exml/introspection/ClassPublicMethodOnly.java @@ -63,10 +63,6 @@ public class ClassPublicMethodOnly { return this.memberArrayShortClass; } - public Boolean getMemberBooleanClass() { - return this.memberBooleanClass; - } - public byte getMemberByte() { return this.memberByte; } @@ -107,6 +103,10 @@ public class ClassPublicMethodOnly { return this.memberBoolean; } + public Boolean isMemberBooleanClass() { + return this.memberBooleanClass; + } + public void setMemberArrayBoolean(final boolean[] memberArrayBoolean) { this.memberArrayBoolean = memberArrayBoolean; }