From 3ad74f0fb120a94eb816e42dff0f9a106398be4e Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Fri, 9 Jul 2021 00:04:54 +0200 Subject: [PATCH] [DEV] set it work for imutable and basic records --- .../atriasoft/exml/annotation/XmlName.java | 2 +- .../builder/IntrospectionModelComplex.java | 670 ++++++++---------- .../exml/builder/IntrospectionProperty.java | 8 + .../builder/IntrospectionPropertyField.java | 5 +- .../builder/IntrospectionPropertyMethod.java | 2 +- .../atriasoft/exml/reflect/ReflectTools.java | 299 ++++++++ .../atriasoft/exml/ExmlTestIntrospection.java | 2 +- ...xmlTestIntrospectionObjectConstructor.java | 51 ++ .../exml/ExmlTestIntrospectionRecord.java | 13 +- 9 files changed, 679 insertions(+), 373 deletions(-) create mode 100644 src/org/atriasoft/exml/reflect/ReflectTools.java diff --git a/src/org/atriasoft/exml/annotation/XmlName.java b/src/org/atriasoft/exml/annotation/XmlName.java index cc7b4cd..941c6d4 100644 --- a/src/org/atriasoft/exml/annotation/XmlName.java +++ b/src/org/atriasoft/exml/annotation/XmlName.java @@ -9,7 +9,7 @@ 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 }) +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @ExmlAnnotation public @interface XmlName { diff --git a/src/org/atriasoft/exml/builder/IntrospectionModelComplex.java b/src/org/atriasoft/exml/builder/IntrospectionModelComplex.java index ca4d123..c3ff33d 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionModelComplex.java +++ b/src/org/atriasoft/exml/builder/IntrospectionModelComplex.java @@ -1,14 +1,16 @@ 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; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -16,21 +18,16 @@ import java.util.stream.Collectors; import org.atriasoft.eStringSerialize.StringSerializer; import org.atriasoft.etk.util.ArraysTools; -import org.atriasoft.exml.annotation.XmlAttribute; -import org.atriasoft.exml.annotation.XmlCaseSensitive; -import org.atriasoft.exml.annotation.XmlDefaultAttibute; -import org.atriasoft.exml.annotation.XmlDefaultCaseSensitive; -import org.atriasoft.exml.annotation.XmlDefaultManaged; -import org.atriasoft.exml.annotation.XmlDefaultOptional; -import org.atriasoft.exml.annotation.XmlList; -import org.atriasoft.exml.annotation.XmlManaged; -import org.atriasoft.exml.annotation.XmlName; -import org.atriasoft.exml.annotation.XmlOptional; import org.atriasoft.exml.exception.ExmlBuilderException; import org.atriasoft.exml.internal.Log; import org.atriasoft.exml.parser.Tools; +import org.atriasoft.exml.reflect.ReflectTools; +record ConstructorModel(String[] values, + Constructor constructor) { +} + public class IntrospectionModelComplex extends IntrospectionModel { // TODO Optimize this with external object for basic types.... @@ -38,6 +35,7 @@ public class IntrospectionModelComplex extends IntrospectionModel { private final Method tostring; // used for the set Text if the object is an end point... private final boolean isSubClass; // if true, the constructor must be called with a null first object. private final Constructor constructorEmpty; + private final List constructors = new ArrayList<>(); private final List nodes = new ArrayList<>(); private final List attributes = new ArrayList<>(); @@ -62,11 +60,18 @@ public class IntrospectionModelComplex extends IntrospectionModel { } else { this.isSubClass = false; } + final boolean isRecord = Record.class.isAssignableFrom(classType); + if (isRecord) { + Log.error("Detect record !!! "); + } + if (classType.isPrimitive()) { + Log.critical("Detect primitive ==> impossible case !!! "); + } - final Boolean isDefaultAttribute = getIsDefaultAttribute(classType, IntrospectionModel.DEFAULT_ATTRIBUTE); - final Boolean isDefaultManaged = getIsDefaultManaged(classType, IntrospectionModel.DEFAULT_MANAGED); - final Boolean isDefaultOptional = getIsDefaultOptional(classType, IntrospectionModel.DEFAULT_OPTIONAL); - final Boolean isDefaultCaseSensitive = getIsDefaultCaseSensitive(classType, IntrospectionModel.DEFAULT_CASE_SENSITIVE); + final Boolean isDefaultAttribute = ReflectTools.getIsDefaultAttribute(classType, IntrospectionModel.DEFAULT_ATTRIBUTE); + final Boolean isDefaultManaged = ReflectTools.getIsDefaultManaged(classType, IntrospectionModel.DEFAULT_MANAGED); + final Boolean isDefaultOptional = ReflectTools.getIsDefaultOptional(classType, IntrospectionModel.DEFAULT_OPTIONAL); + final Boolean isDefaultCaseSensitive = ReflectTools.getIsDefaultCaseSensitive(classType, IntrospectionModel.DEFAULT_CASE_SENSITIVE); Log.verbose("Introspect class: '" + classType.getCanonicalName() + "'"); final Constructor[] constructors = this.classType.getConstructors(); Log.verbose(" Constructors: (" + constructors.length + ")"); @@ -81,16 +86,113 @@ public class IntrospectionModelComplex extends IntrospectionModel { emptyConstructorTmp = elem; Log.verbose(" >>> " + elem.toGenericString()); } else { - Log.verbose(" - " + elem.toGenericString()); + String[] names = ReflectTools.getNames(elem, null); + if (names == null) { + // Search in the parameters ... + List restoredElementNames = new ArrayList<>(); + Parameter[] params = elem.getParameters(); + for (int iii=1; iii unmanaged"); + } else if (restoredElementNames.size() != params.length-1) { + throw new ExmlBuilderException("the @XmlName in constructor parameter must set for every one"); + } else { + this.constructors.add(new ConstructorModel(restoredElementNames.toArray(new String[restoredElementNames.size()]), elem)); + } + } else { + if (elem.getParameterCount() != names.length+1) { + throw new ExmlBuilderException("Wrong number of parameter in constructor with ne number declared in the @XmlName"); + } + this.constructors.add(new ConstructorModel(names, elem)); + } } } else if (elem.getParameterCount() == 0) { emptyConstructorTmp = elem; Log.verbose(" >>> " + elem.toGenericString()); } else { - Log.verbose(" - " + elem.toGenericString()); + String[] names = ReflectTools.getNames(elem, null); + if (names == null) { + // Search in the parameters ... + List restoredElementNames = new ArrayList<>(); + Parameter[] params = elem.getParameters(); + for (int iii=0; iii unmanaged"); + } else if (restoredElementNames.size() != params.length) { + throw new ExmlBuilderException("the @XmlName in constructor parameter must set for every one"); + } else { + this.constructors.add(new ConstructorModel(restoredElementNames.toArray(new String[restoredElementNames.size()]), elem)); + } + } else { + if (elem.getParameterCount() != names.length) { + throw new ExmlBuilderException("Wrong number of parameter in constructor with ne number declared in the @XmlName"); + } + this.constructors.add(new ConstructorModel(names, elem)); + } } } this.constructorEmpty = emptyConstructorTmp; + + // Order the constructor from the bigger number of element to the lowest... + Collections.sort(this.constructors, new Comparator() { + @Override + public + int compare(final ConstructorModel a, final ConstructorModel b) { + return a.values().length - b.values().length; + } + }); + for (ConstructorModel elem : this.constructors) { + Log.verbose(" * " + elem.constructor().toGenericString()); + StringBuilder tmpPrint = new StringBuilder(" ==> ("); + if (this.isSubClass) { + tmpPrint.append("null, "); + } + for (int iii=0; iii recordAllPossibleValues = new ArrayList<>(); + final List recordAllPossibleValuesFiltered = new ArrayList<>(); + if (isRecord) { + for (ConstructorModel elem : this.constructors) { + for (int iii=0; iii methods = List.of(methodsTmp).stream().filter(o -> { if (o.getName().contentEquals("getClass")) { @@ -140,46 +242,52 @@ public class IntrospectionModelComplex extends IntrospectionModel { } return true; } - 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') { + if (isRecord) { + if (recordAllPossibleValues.contains(o.getName())) { + // This list is the real list of record members + recordAllPossibleValuesFiltered.add(o.getName()); return true; } - return false; - } - if (o.getName().startsWith("set")) { - if (o.getReturnType() != void.class || o.getParameterCount() != 1) { + } else { + 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; } - // check name format - if (o.getName().length() == 3) { + 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().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) { + 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; } - // 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()); @@ -198,9 +306,15 @@ public class IntrospectionModelComplex extends IntrospectionModel { 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()); + if (isRecord) { + methodsGet = methods.stream().filter(o -> recordAllPossibleValues.contains(o.getName())).collect(Collectors.toList()); + methodsSet = new ArrayList<>(); + methodsIs = new ArrayList<>(); + } else { + 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<>(); @@ -225,58 +339,40 @@ public class IntrospectionModelComplex extends IntrospectionModel { // associate methods by pair. final List elements = new ArrayList<>(); for (final Method method : methodsGet) { - final String name = method.getName().substring(3); + final String name = isRecord?method.getName():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; - } - } - /* - 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()); - } + if (!isRecord) { + 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 properties, final Map> nodes) throws ExmlBuilderException { - Object tmp; - if (this.constructorEmpty == null) { - throw new ExmlBuilderException("No constructor accessible for class: " + this.classType.getCanonicalName()); - } - try { - if (this.isSubClass) { - Object tmp2 = null; - tmp = this.constructorEmpty.newInstance(tmp2); - } else { - tmp = this.constructorEmpty.newInstance(); + Object tmp = null; + // STEP 1: try to create the object with provided parameter (if a constructor exist....) + if (!this.constructors.isEmpty()) { + // try to find the constructor that fit with parameters ... + for (ConstructorModel elem : this.constructors) { + int offset = this.isSubClass?1:0; + Object[] inputs = new Object[elem.values().length+offset]; + inputs[0] = null; + for (int iii=0; iii tmppp = nodes.get(elem.values()[iii]); + if (tmppp != null && tmppp.size() >=1) { + valueToInject = tmppp.get(0); + } + } + if (valueToInject == null) { + inputs = null; + break; + } + inputs[iii+offset] = valueToInject; + } + if (inputs != null) { + // here we find our constructor... + try { + switch(inputs.length) { + case 0: tmp = elem.constructor().newInstance(); break; + case 1: tmp = elem.constructor().newInstance(inputs[0]); break; + case 2: tmp = elem.constructor().newInstance(inputs[0],inputs[1]); break; + case 3: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2]); break; + case 4: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3]); break; + case 5: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4]); break; + case 6: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5]); break; + case 7: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5],inputs[6]); break; + case 8: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5],inputs[6],inputs[7]); break; + case 9: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5],inputs[6],inputs[7],inputs[8]); break; + case 10: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5],inputs[6],inputs[7],inputs[8],inputs[9]); break; + case 11: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5],inputs[6],inputs[7],inputs[8],inputs[9],inputs[10]); break; + case 12: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5],inputs[6],inputs[7],inputs[8],inputs[9],inputs[10],inputs[11]); break; + case 13: tmp = elem.constructor().newInstance(inputs[0],inputs[1],inputs[2],inputs[3],inputs[4],inputs[5],inputs[6],inputs[7],inputs[8],inputs[9],inputs[10],inputs[11],inputs[12]); break; + default: + throw new ExmlBuilderException("to much parameter in the constructor... " + inputs.length); + } + //tmp = elem.constructor().newInstance(inputs); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new ExmlBuilderException("Error when creating the Object ..." + this.classType.getCanonicalName()); + } + // Remove all previously added parameters + for (int iii=0; iii try with empty constructor... + if (tmp == null) { + if (this.constructorEmpty == null) { + throw new ExmlBuilderException("No constructor accessible for class: " + this.classType.getCanonicalName()); + } + try { + if (this.isSubClass) { + Object tmp2 = null; + tmp = this.constructorEmpty.newInstance(tmp2); + } else { + tmp = this.constructorEmpty.newInstance(); + } + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + // STEP 3: set the rest if the parameters... for (Entry elem : properties.entrySet()) { setValue(tmp, elem.getKey(), elem.getValue()); } @@ -429,224 +593,6 @@ public class IntrospectionModelComplex extends IntrospectionModel { return null; } - protected Boolean getIsCaseSensitive(final Field element, final Boolean defaultValue) throws ExmlBuilderException { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlCaseSensitive.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlCaseSensitive) annotation[0]).value(); - } - - protected Boolean getIsCaseSensitive(final Method element, final Boolean defaultValue) throws ExmlBuilderException { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlCaseSensitive.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlCaseSensitive) annotation[0]).value(); - } - - private Boolean getIsDefaultCaseSensitive(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { - final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultCaseSensitive.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlDefaultCaseSensitive) annotation[0]).value(); - } - - private Boolean getIsDefaultAttribute(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { - final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultAttibute.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlDefaultAttibute) annotation[0]).value(); - } - - private Boolean getIsDefaultManaged(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { - final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultManaged.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlDefaultManaged) annotation[0]).value(); - } - - private Boolean getIsDefaultOptional(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { - final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultOptional.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlDefaultOptional) annotation[0]).value(); - } - - protected Boolean getIsAttribute(final Field element, final Boolean parentValue) throws ExmlBuilderException { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlAttribute.class); - if (annotation.length == 0) { - return parentValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlAttribute) annotation[0]).value(); - } - - protected Boolean getIsAttribute(final Method element, final Boolean parentValue) throws ExmlBuilderException { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlAttribute.class); - if (annotation.length == 0) { - return parentValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlAttribute) annotation[0]).value(); - } - - protected Boolean getIsManaged(final Field element, final Boolean parentValue) throws ExmlBuilderException { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlManaged.class); - if (annotation.length == 0) { - return parentValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have more that "); - } - return ((XmlManaged) annotation[0]).value(); - } - - protected Boolean getIsManaged(final Method element, final Boolean parentValue) throws ExmlBuilderException { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlManaged.class); - if (annotation.length == 0) { - return parentValue; - } - if (annotation.length > 1) { - throw new ExmlBuilderException("Must not have 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 have 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 have 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; - } - return new String[] { defaultValue }; - } - if (annotation.length > 1) { - throw new Exception("Must not have 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()) { - 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; - } - return new String[] { defaultValue }; - } - if (annotation.length > 1) { - throw new Exception("Must not have 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()) { - throw new Exception("Set empty String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); - } - } - return tmp; - } - - protected String getListName(final Field element, final String defaultValue) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlList.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new Exception("Must not have more that "); - } - final String tmp = ((XmlList) annotation[0]).value(); - if (tmp == null) { - throw new Exception("Set null value in decorator @XmlList is not availlable on: " + element.toGenericString()); - } - return tmp; - } - protected String getListName(final Method element, final String defaultValue) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlList.class); - if (annotation.length == 0) { - return defaultValue; - } - if (annotation.length > 1) { - throw new Exception("Must not have more that "); - } - final String tmp = ((XmlList) annotation[0]).value(); - if (tmp == null) { - throw new Exception("Set null value in decorator @XmlList is not availlable on: " + element.toGenericString()); - } - return tmp; - } @SuppressWarnings("unchecked") private T[] autoCast(final Class clazz, final List data) { T[] out = (T[]) java.lang.reflect.Array.newInstance(clazz, data.size());// T[data.size()]; diff --git a/src/org/atriasoft/exml/builder/IntrospectionProperty.java b/src/org/atriasoft/exml/builder/IntrospectionProperty.java index 4cd082a..4c5f24f 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionProperty.java +++ b/src/org/atriasoft/exml/builder/IntrospectionProperty.java @@ -17,7 +17,15 @@ public abstract class IntrospectionProperty { public String getListName() { return this.listName; } + protected boolean canBeSetByConstructor = false; + public boolean isCanBeSetByConstructor() { + return this.canBeSetByConstructor; + } + + public void setCanBeSetByConstructor(final boolean canBeSetByConstructor) { + this.canBeSetByConstructor = canBeSetByConstructor; + } protected final Class type; protected final Class subType; diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java index f19ffca..e785fe9 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyField.java @@ -1,6 +1,7 @@ package org.atriasoft.exml.builder; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -8,6 +9,7 @@ import org.atriasoft.exml.exception.ExmlBuilderException; public class IntrospectionPropertyField extends IntrospectionProperty { private final Field fieldDescription; + private final boolean finalValue; private static Class[] getTypeField(final Field fieldDescription) { @@ -31,6 +33,7 @@ public class IntrospectionPropertyField extends IntrospectionProperty { public IntrospectionPropertyField(final Field fieldDescription, final String[] names, final String listName, final Boolean caseSensitive, final Boolean isOptionnal) { super(IntrospectionPropertyField.getTypeField(fieldDescription), names, listName, caseSensitive, isOptionnal); this.fieldDescription = fieldDescription; + this.finalValue = Modifier.isFinal(fieldDescription.getModifiers()); } @Override @@ -40,7 +43,7 @@ public class IntrospectionPropertyField extends IntrospectionProperty { @Override public boolean canSetValue() { - return true; + return this.canBeSetByConstructor || !this.finalValue; } @Override public Object getValue(final Object object) throws ExmlBuilderException { diff --git a/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java index bae1aa5..6a31aa4 100644 --- a/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java +++ b/src/org/atriasoft/exml/builder/IntrospectionPropertyMethod.java @@ -74,7 +74,7 @@ public class IntrospectionPropertyMethod extends IntrospectionProperty { @Override public boolean canSetValue() { - return this.setter != null; + return this.canBeSetByConstructor || this.setter != null; } @Override diff --git a/src/org/atriasoft/exml/reflect/ReflectTools.java b/src/org/atriasoft/exml/reflect/ReflectTools.java new file mode 100644 index 0000000..d99bd68 --- /dev/null +++ b/src/org/atriasoft/exml/reflect/ReflectTools.java @@ -0,0 +1,299 @@ +package org.atriasoft.exml.reflect; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import org.atriasoft.exml.annotation.XmlAttribute; +import org.atriasoft.exml.annotation.XmlCaseSensitive; +import org.atriasoft.exml.annotation.XmlDefaultAttibute; +import org.atriasoft.exml.annotation.XmlDefaultCaseSensitive; +import org.atriasoft.exml.annotation.XmlDefaultManaged; +import org.atriasoft.exml.annotation.XmlDefaultOptional; +import org.atriasoft.exml.annotation.XmlList; +import org.atriasoft.exml.annotation.XmlManaged; +import org.atriasoft.exml.annotation.XmlName; +import org.atriasoft.exml.annotation.XmlOptional; +import org.atriasoft.exml.exception.ExmlBuilderException; + +public class ReflectTools { + private ReflectTools() {} + + public static Boolean getIsCaseSensitive(final Field element, final Boolean defaultValue) throws ExmlBuilderException { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlCaseSensitive.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlCaseSensitive) annotation[0]).value(); + } + + public static Boolean getIsCaseSensitive(final Method element, final Boolean defaultValue) throws ExmlBuilderException { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlCaseSensitive.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlCaseSensitive) annotation[0]).value(); + } + + public static Boolean getIsDefaultCaseSensitive(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { + final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultCaseSensitive.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlDefaultCaseSensitive) annotation[0]).value(); + } + + public static Boolean getIsDefaultAttribute(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { + final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultAttibute.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlDefaultAttibute) annotation[0]).value(); + } + + public static Boolean getIsDefaultManaged(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { + final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultManaged.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlDefaultManaged) annotation[0]).value(); + } + + public static Boolean getIsDefaultOptional(final Class classType, final Boolean defaultValue) throws ExmlBuilderException { + final Annotation[] annotation = classType.getDeclaredAnnotationsByType(XmlDefaultOptional.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlDefaultOptional) annotation[0]).value(); + } + + public static Boolean getIsAttribute(final Field element, final Boolean parentValue) throws ExmlBuilderException { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlAttribute.class); + if (annotation.length == 0) { + return parentValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlAttribute) annotation[0]).value(); + } + + public static Boolean getIsAttribute(final Method element, final Boolean parentValue) throws ExmlBuilderException { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlAttribute.class); + if (annotation.length == 0) { + return parentValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlAttribute) annotation[0]).value(); + } + + public static Boolean getIsManaged(final Field element, final Boolean parentValue) throws ExmlBuilderException { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlManaged.class); + if (annotation.length == 0) { + return parentValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlManaged) annotation[0]).value(); + } + + public static Boolean getIsManaged(final Method element, final Boolean parentValue) throws ExmlBuilderException { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlManaged.class); + if (annotation.length == 0) { + return parentValue; + } + if (annotation.length > 1) { + throw new ExmlBuilderException("Must not have more that "); + } + return ((XmlManaged) annotation[0]).value(); + } + + public static 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 have more that "); + } + return ((XmlOptional) annotation[0]).value(); + } + + public static 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 have more that "); + } + return ((XmlOptional) annotation[0]).value(); + } + + public static 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; + } + return new String[] { defaultValue }; + } + if (annotation.length > 1) { + throw new Exception("Must not have 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()) { + throw new Exception("Set empty String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + } + return tmp; + } + + public static 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; + } + return new String[] { defaultValue }; + } + if (annotation.length > 1) { + throw new Exception("Must not have 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()) { + throw new Exception("Set empty String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + } + return tmp; + } + public static String[] getNames(final Constructor element, final String defaultValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlName.class); + if (annotation.length == 0) { + if (defaultValue == null) { + return null; + } + return new String[] { defaultValue }; + } + if (annotation.length > 1) { + throw new Exception("Must not have 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()) { + throw new Exception("Set empty String in list of value in decorator @XmlName is not availlable on: " + element.toGenericString()); + } + } + return tmp; + } + public static String[] getNames(final Constructor constructor, final Parameter element, final String defaultValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlName.class); + if (annotation.length == 0) { + if (defaultValue == null) { + return null; + } + return new String[] { defaultValue }; + } + if (annotation.length > 1) { + throw new Exception("Must not have 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: " + constructor.toGenericString()); + } + if (tmp.length == 0) { + throw new Exception("Set empty list value in decorator @XmlName is not availlable on: " + constructor.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: " + constructor.toGenericString()); + } + if (elem.isEmpty()) { + throw new Exception("Set empty String in list of value in decorator @XmlName is not availlable on: " + constructor.toGenericString()); + } + } + return tmp; + } + + + public static String getListName(final Field element, final String defaultValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlList.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new Exception("Must not have more that "); + } + final String tmp = ((XmlList) annotation[0]).value(); + if (tmp == null) { + throw new Exception("Set null value in decorator @XmlList is not availlable on: " + element.toGenericString()); + } + return tmp; + } + public static String getListName(final Method element, final String defaultValue) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(XmlList.class); + if (annotation.length == 0) { + return defaultValue; + } + if (annotation.length > 1) { + throw new Exception("Must not have more that "); + } + final String tmp = ((XmlList) annotation[0]).value(); + if (tmp == null) { + throw new Exception("Set null value in decorator @XmlList is not availlable on: " + element.toGenericString()); + } + return tmp; + } +} diff --git a/test/src/test/atriasoft/exml/ExmlTestIntrospection.java b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java index 3193d36..2a78160 100644 --- a/test/src/test/atriasoft/exml/ExmlTestIntrospection.java +++ b/test/src/test/atriasoft/exml/ExmlTestIntrospection.java @@ -340,7 +340,7 @@ public class ExmlTestIntrospection { //@formatter:on final ClassPublicMethodeStructured[] root = Assertions.assertDoesNotThrow(() -> Exml.parse(dataToParse, ClassPublicMethodeStructured.class, "elem")); Assertions.assertEquals(1, root.length); - + final ClassPublicMethodeStructured elem = root[0]; Assertions.assertEquals(5, elem.getMemberArrayByte().length); Assertions.assertEquals((byte) 12, elem.getMemberArrayByte()[0]); diff --git a/test/src/test/atriasoft/exml/ExmlTestIntrospectionObjectConstructor.java b/test/src/test/atriasoft/exml/ExmlTestIntrospectionObjectConstructor.java index 1a36c07..3fd8f96 100644 --- a/test/src/test/atriasoft/exml/ExmlTestIntrospectionObjectConstructor.java +++ b/test/src/test/atriasoft/exml/ExmlTestIntrospectionObjectConstructor.java @@ -7,6 +7,7 @@ package test.atriasoft.exml; import org.atriasoft.exml.Exml; import org.atriasoft.exml.annotation.XmlAttribute; +import org.atriasoft.exml.annotation.XmlName; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -45,5 +46,55 @@ public class ExmlTestIntrospectionObjectConstructor { Assertions.assertEquals(18523.0f, root.valueB); } + public class TestConstructorSpecificParameter { + @XmlAttribute + public Integer valueA; + public double valueB; + public TestConstructorSpecificParameter(@XmlName("valueA") final Integer valueA, @XmlName("valueB") final double valueB) { + this.valueA = valueA; + this.valueB = valueB; + } + } + @Test + public void testModelConstructorSpecificParameter() { + TestConstructorSpecificParameter elem = new TestConstructorSpecificParameter(66, 18523.0); + StringBuilder builder = new StringBuilder(); + Assertions.assertDoesNotThrow(() -> Exml.generate(elem, ExmlTestIntrospectionObject.NODE_NAME, builder)); + String dataTest = builder.toString(); + Log.warning("data generated: " + builder.toString()); + Assertions.assertEquals("\n" + + " 18523.0\n" + + "", dataTest); + + final TestConstructorSpecificParameter root = Assertions.assertDoesNotThrow(() -> Exml.parseOne(dataTest, TestConstructorSpecificParameter.class, ExmlTestIntrospectionObject.NODE_NAME)); + Assertions.assertEquals(66, root.valueA); + Assertions.assertEquals(18523.0f, root.valueB); + } + + public class TestConstructorSpecific { + @XmlAttribute + public Integer valueA; + public double valueB; + @XmlName({"valueA", "valueB"}) + public TestConstructorSpecific(final Integer valueA, final double valueB) { + this.valueA = valueA; + this.valueB = valueB; + } + } + @Test + public void testModelConstructorSpecific() { + TestConstructorSpecific elem = new TestConstructorSpecific(66, 18523.0); + StringBuilder builder = new StringBuilder(); + Assertions.assertDoesNotThrow(() -> Exml.generate(elem, ExmlTestIntrospectionObject.NODE_NAME, builder)); + String dataTest = builder.toString(); + Log.warning("data generated: " + builder.toString()); + Assertions.assertEquals("\n" + + " 18523.0\n" + + "", dataTest); + + final TestConstructorSpecific root = Assertions.assertDoesNotThrow(() -> Exml.parseOne(dataTest, TestConstructorSpecific.class, ExmlTestIntrospectionObject.NODE_NAME)); + Assertions.assertEquals(66, root.valueA); + Assertions.assertEquals(18523.0f, root.valueB); + } } diff --git a/test/src/test/atriasoft/exml/ExmlTestIntrospectionRecord.java b/test/src/test/atriasoft/exml/ExmlTestIntrospectionRecord.java index 0b4a174..4bb1782 100644 --- a/test/src/test/atriasoft/exml/ExmlTestIntrospectionRecord.java +++ b/test/src/test/atriasoft/exml/ExmlTestIntrospectionRecord.java @@ -6,6 +6,7 @@ package test.atriasoft.exml; import org.atriasoft.exml.Exml; +import org.atriasoft.exml.annotation.XmlName; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -18,12 +19,8 @@ public class ExmlTestIntrospectionRecord { Log.warning("================================================================"); } public record TestRecord( - Integer valueA, - double valueB) { - public TestRecord(final Integer valueA, final double valueB) { - this.valueA = valueA; - this.valueB = valueB; - } + @XmlName("valueA") Integer valueA, + @XmlName("valueB") double valueB) { } @Test public void testModelRecord() { @@ -32,7 +29,8 @@ public class ExmlTestIntrospectionRecord { Assertions.assertDoesNotThrow(() -> Exml.generate(elem, ExmlTestIntrospectionObject.NODE_NAME, builder)); String dataTest = builder.toString(); Log.warning("data generated: " + builder.toString()); - Assertions.assertEquals("\n" + Assertions.assertEquals("\n" + + " 66\n" + " 18523.0\n" + "", dataTest); @@ -40,6 +38,7 @@ public class ExmlTestIntrospectionRecord { Assertions.assertEquals(66, root.valueA); Assertions.assertEquals(18523.0f, root.valueB); } + }