diff --git a/pmml-model/src/main/java/org/jpmml/model/InvalidAttributeException.java b/pmml-model/src/main/java/org/jpmml/model/InvalidAttributeException.java index 14b0a597..b9e9243a 100644 --- a/pmml-model/src/main/java/org/jpmml/model/InvalidAttributeException.java +++ b/pmml-model/src/main/java/org/jpmml/model/InvalidAttributeException.java @@ -22,7 +22,7 @@ public InvalidAttributeException(PMMLObject object, Enum value){ } public InvalidAttributeException(PMMLObject object, Field field, Object value){ - super(formatMessage(XPathUtil.formatAttribute(field, value)), object); + super(formatMessage(XPathUtil.formatAttribute(object.getClass(), field, value)), object); } static diff --git a/pmml-model/src/main/java/org/jpmml/model/InvalidElementListException.java b/pmml-model/src/main/java/org/jpmml/model/InvalidElementListException.java index 2514a085..c7e0b96b 100644 --- a/pmml-model/src/main/java/org/jpmml/model/InvalidElementListException.java +++ b/pmml-model/src/main/java/org/jpmml/model/InvalidElementListException.java @@ -9,6 +9,14 @@ public class InvalidElementListException extends InvalidMarkupException { + public InvalidElementListException(String message){ + super(message); + } + + public InvalidElementListException(String message, PMMLObject object){ + super(message, object); + } + public InvalidElementListException(List objects){ super("List of elements " + XPathUtil.formatElement((objects.get(0)).getClass()) + " is not valid", objects.get(0)); } diff --git a/pmml-model/src/main/java/org/jpmml/model/MisplacedAttributeException.java b/pmml-model/src/main/java/org/jpmml/model/MisplacedAttributeException.java index e1999599..dc5c485c 100644 --- a/pmml-model/src/main/java/org/jpmml/model/MisplacedAttributeException.java +++ b/pmml-model/src/main/java/org/jpmml/model/MisplacedAttributeException.java @@ -14,7 +14,7 @@ public MisplacedAttributeException(PMMLObject object, Enum value){ } public MisplacedAttributeException(PMMLObject object, Field field, Object value){ - super(formatMessage(XPathUtil.formatAttribute(field, value)), object); + super(formatMessage(XPathUtil.formatAttribute(object.getClass(), field, value)), object); } static diff --git a/pmml-model/src/main/java/org/jpmml/model/MisplacedElementListException.java b/pmml-model/src/main/java/org/jpmml/model/MisplacedElementListException.java new file mode 100644 index 00000000..230db75c --- /dev/null +++ b/pmml-model/src/main/java/org/jpmml/model/MisplacedElementListException.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Villu Ruusmann + */ +package org.jpmml.model; + +import java.util.List; + +import org.dmg.pmml.PMMLObject; + +public class MisplacedElementListException extends InvalidElementListException { + + public MisplacedElementListException(List objects){ + super(formatMessage(XPathUtil.formatElement((objects.get(0)).getClass())), objects.get(0)); + } + + static + public String formatMessage(String xPath){ + return "List of elements " + xPath + " is not permitted in this location"; + } +} \ No newline at end of file diff --git a/pmml-model/src/main/java/org/jpmml/model/MissingAttributeException.java b/pmml-model/src/main/java/org/jpmml/model/MissingAttributeException.java index c6599ade..544838db 100644 --- a/pmml-model/src/main/java/org/jpmml/model/MissingAttributeException.java +++ b/pmml-model/src/main/java/org/jpmml/model/MissingAttributeException.java @@ -18,7 +18,7 @@ public MissingAttributeException(String message, PMMLObject context){ } public MissingAttributeException(PMMLObject object, Field field){ - super(formatMessage(XPathUtil.formatElementOrAttribute(field)), object); + super(formatMessage(XPathUtil.formatElementOrAttribute(object.getClass(), field)), object); } static diff --git a/pmml-model/src/main/java/org/jpmml/model/MissingElementException.java b/pmml-model/src/main/java/org/jpmml/model/MissingElementException.java index 3eac41c3..ab5f38e1 100644 --- a/pmml-model/src/main/java/org/jpmml/model/MissingElementException.java +++ b/pmml-model/src/main/java/org/jpmml/model/MissingElementException.java @@ -18,7 +18,7 @@ public MissingElementException(String message, PMMLObject context){ } public MissingElementException(PMMLObject object, Field field){ - super(formatMessage(XPathUtil.formatElementOrAttribute(field)), object); + super(formatMessage(XPathUtil.formatElementOrAttribute(object.getClass(), field)), object); } static diff --git a/pmml-model/src/main/java/org/jpmml/model/PMMLOutputStream.java b/pmml-model/src/main/java/org/jpmml/model/PMMLOutputStream.java new file mode 100644 index 00000000..79ec3250 --- /dev/null +++ b/pmml-model/src/main/java/org/jpmml/model/PMMLOutputStream.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 Villu Ruusmann + */ +package org.jpmml.model; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Objects; + +import org.dmg.pmml.Version; + +public class PMMLOutputStream extends FilterOutputStream { + + private Version version = null; + + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024); + + + public PMMLOutputStream(OutputStream os, Version version){ + super(os); + + this.version = Objects.requireNonNull(version); + + if(!version.isStandard()){ + throw new IllegalArgumentException(); + } + } + + @Override + public void write(byte[] bytes) throws IOException { + this.write(bytes, 0, bytes.length); + } + + @Override + public void write(byte[] bytes, int offset, int length) throws IOException { + + if(this.buffer != null){ + + for(int i = offset, max = offset + length; i < max; i++){ + this.write(bytes[i]); + } + } else + + { + super.out.write(bytes, offset, length); + } + } + + @Override + public void write(int b) throws IOException { + + if(this.buffer != null){ + this.buffer.write(b); + + if(b == '>'){ + String string = this.buffer.toString("UTF-8"); + + if(string.endsWith("?>")){ + super.out.write(string.getBytes("UTF-8")); + + this.buffer.reset(); + } else + + if(string.endsWith(">")){ + String updatedString = string.replace(Version.PMML_4_4.getNamespaceURI(), this.version.getNamespaceURI()); + + if(Objects.equals(string, updatedString)){ + throw new IllegalStateException(); + } + + super.out.write(updatedString.getBytes("UTF-8")); + + this.buffer = null; + } else + + { + throw new IllegalStateException(); + } + } + } else + + { + super.out.write(b); + } + } + + @Override + public void flush() throws IOException { + + if(this.buffer != null){ + throw new IllegalStateException(); + } + + super.flush(); + } + + @Override + public void close() throws IOException { + super.close(); + } +} \ No newline at end of file diff --git a/pmml-model/src/main/java/org/jpmml/model/UnsupportedAttributeException.java b/pmml-model/src/main/java/org/jpmml/model/UnsupportedAttributeException.java index ed98f546..141d88f9 100644 --- a/pmml-model/src/main/java/org/jpmml/model/UnsupportedAttributeException.java +++ b/pmml-model/src/main/java/org/jpmml/model/UnsupportedAttributeException.java @@ -22,6 +22,6 @@ public UnsupportedAttributeException(PMMLObject object, Enum value){ } public UnsupportedAttributeException(PMMLObject object, Field field, Object value){ - super("Attribute with value " + XPathUtil.formatAttribute(field, value) + " is not supported", object); + super("Attribute with value " + XPathUtil.formatAttribute(object.getClass(), field, value) + " is not supported", object); } } \ No newline at end of file diff --git a/pmml-model/src/main/java/org/jpmml/model/annotations/Added.java b/pmml-model/src/main/java/org/jpmml/model/annotations/Added.java index ad2b1281..d7f2ad69 100644 --- a/pmml-model/src/main/java/org/jpmml/model/annotations/Added.java +++ b/pmml-model/src/main/java/org/jpmml/model/annotations/Added.java @@ -24,4 +24,6 @@ public @interface Added { Version value(); + + boolean removable() default false; } \ No newline at end of file diff --git a/pmml-model/src/main/java/org/jpmml/model/visitors/VersionChecker.java b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionChecker.java index 5164ca5a..a164e8cd 100644 --- a/pmml-model/src/main/java/org/jpmml/model/visitors/VersionChecker.java +++ b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionChecker.java @@ -14,11 +14,13 @@ import org.jpmml.model.MarkupException; import org.jpmml.model.MisplacedAttributeException; import org.jpmml.model.MisplacedElementException; +import org.jpmml.model.MisplacedElementListException; import org.jpmml.model.MissingAttributeException; import org.jpmml.model.MissingElementException; import org.jpmml.model.ReflectionUtil; import org.jpmml.model.UnsupportedAttributeException; import org.jpmml.model.UnsupportedElementException; +import org.jpmml.model.UnsupportedElementListException; import org.jpmml.model.annotations.Added; import org.jpmml.model.annotations.Optional; import org.jpmml.model.annotations.Removed; @@ -60,7 +62,14 @@ public void handleAdded(PMMLObject object, AnnotatedElement element, Added added } else if(isElement(field)){ - report(new UnsupportedElementException((PMMLObject)value)); + + if(value instanceof List){ + report(new UnsupportedElementListException((List)value)); + } else + + { + report(new UnsupportedElementException((PMMLObject)value)); + } } else { @@ -90,7 +99,14 @@ public void handleRemoved(PMMLObject object, AnnotatedElement element, Removed r } else if(isElement(field)){ - report(new MisplacedElementException((PMMLObject)value)); + + if(value instanceof List){ + report(new MisplacedElementListException((List)value)); + } else + + { + report(new MisplacedElementException((PMMLObject)value)); + } } else { diff --git a/pmml-model/src/main/java/org/jpmml/model/visitors/VersionDowngrader.java b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionDowngrader.java new file mode 100644 index 00000000..10aa74d1 --- /dev/null +++ b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionDowngrader.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2024 Villu Ruusmann + */ +package org.jpmml.model.visitors; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.util.Objects; + +import org.dmg.pmml.Apply; +import org.dmg.pmml.MiningField; +import org.dmg.pmml.PMML; +import org.dmg.pmml.PMMLAttributes; +import org.dmg.pmml.PMMLObject; +import org.dmg.pmml.TargetValue; +import org.dmg.pmml.Version; +import org.dmg.pmml.VisitorAction; +import org.dmg.pmml.time_series.TrendExpoSmooth; +import org.jpmml.model.ReflectionUtil; +import org.jpmml.model.UnsupportedAttributeException; +import org.jpmml.model.UnsupportedElementException; +import org.jpmml.model.annotations.Added; +import org.jpmml.model.annotations.Optional; +import org.jpmml.model.annotations.Removed; +import org.jpmml.model.annotations.Required; +import org.jpmml.model.filters.ExportFilter; + +/** + *

+ * A Visitor that downgrades a class model object from the latest PMML schema version to some earlier one. + *

+ * + * @see ExportFilter + */ +public class VersionDowngrader extends VersionInspector { + + private Version version = null; + + + public VersionDowngrader(Version version){ + this.version = Objects.requireNonNull(version); + + if(!version.isStandard()){ + throw new IllegalArgumentException(); + } + } + + @Override + public void handleAdded(PMMLObject object, AnnotatedElement element, Added added){ + Version version = added.value(); + + if(version.isStandard() && version.compareTo(this.version) > 0){ + + if(element instanceof Class){ + // Ignored + } else + + if(element instanceof Field){ + Field field = (Field)element; + + if(added.removable()){ + ReflectionUtil.setFieldValue(field, object, null); + } + } else + + { + throw new IllegalArgumentException(); + } + } + } + + @Override + public void handleRemoved(PMMLObject object, AnnotatedElement element, Removed removed){ + } + + @Override + public void handleOptional(PMMLObject object, AnnotatedElement element, Optional optional){ + } + + @Override + public void handleRequired(PMMLObject object, AnnotatedElement element, Required required){ + } + + @Override + public VisitorAction visit(Apply apply){ + Object defaultValue = apply.getDefaultValue(); + + if(defaultValue != null){ + + if(this.version.compareTo(Version.PMML_4_1) == 0){ + Object mapMissingTo = apply.getMapMissingTo(); + + if(mapMissingTo != null){ + throw new UnsupportedAttributeException(apply, PMMLAttributes.APPLY_DEFAULTVALUE, defaultValue); + } + + apply + .setDefaultValue(null) + .setMapMissingTo(defaultValue); + } + } + + return super.visit(apply); + } + + @Override + public VisitorAction visit(MiningField miningField){ + MiningField.UsageType usageType = miningField.getUsageType(); + + switch(usageType){ + case TARGET: + if(this.version.compareTo(Version.PMML_4_2) < 0){ + miningField.setUsageType(MiningField.UsageType.PREDICTED); + } + break; + default: + break; + } + + return super.visit(miningField); + } + + @Override + public VisitorAction visit(PMML pmml){ + pmml.setVersion(this.version.getVersion()); + + return super.visit(pmml); + } + + @Override + public VisitorAction visit(TargetValue targetValue){ + String displayValue = targetValue.getDisplayValue(); + + if(displayValue != null){ + + if(this.version.compareTo(Version.PMML_3_2) <= 0){ + throw new UnsupportedAttributeException(targetValue, PMMLAttributes.TARGETVALUE_DISPLAYVALUE, displayValue); + } + } + + return super.visit(targetValue); + } + + @Override + public VisitorAction visit(TrendExpoSmooth trendExpoSmooth){ + + if(this.version.compareTo(Version.PMML_4_0) == 0){ + throw new UnsupportedElementException(trendExpoSmooth); + } + + return super.visit(trendExpoSmooth); + } +} \ No newline at end of file diff --git a/pmml-model/src/main/java/org/jpmml/model/visitors/VersionInspector.java b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionInspector.java index 6962805b..03127340 100644 --- a/pmml-model/src/main/java/org/jpmml/model/visitors/VersionInspector.java +++ b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionInspector.java @@ -11,6 +11,7 @@ import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElements; import jakarta.xml.bind.annotation.XmlEnumValue; import org.dmg.pmml.Apply; import org.dmg.pmml.PMMLAttributes; @@ -96,6 +97,11 @@ public Class annotationType(){ public Version value(){ return version; } + + @Override + public boolean removable(){ + return false; + } }; handleAdded(apply, PMMLAttributes.APPLY_FUNCTION, added); @@ -174,7 +180,8 @@ protected boolean isEnumValue(Field field){ static protected boolean isElement(Field field){ XmlElement xmlElement = field.getAnnotation(XmlElement.class); + XmlElements xmlElements = field.getAnnotation(XmlElements.class); - return (xmlElement != null); + return (xmlElement != null) || (xmlElements != null); } } \ No newline at end of file diff --git a/pmml-model/src/main/java/org/jpmml/model/visitors/VersionStandardizer.java b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionStandardizer.java new file mode 100644 index 00000000..ee917373 --- /dev/null +++ b/pmml-model/src/main/java/org/jpmml/model/visitors/VersionStandardizer.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 Villu Ruusmann + */ +package org.jpmml.model.visitors; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; + +import org.dmg.pmml.PMMLObject; +import org.dmg.pmml.Version; +import org.jpmml.model.ReflectionUtil; +import org.jpmml.model.annotations.Added; +import org.jpmml.model.annotations.Optional; +import org.jpmml.model.annotations.Removed; +import org.jpmml.model.annotations.Required; +import org.jpmml.model.filters.ExportFilter; + +/** + *

+ * A Visitor that standardizes a class model object by reducing vendor extension markup. + *

+ * + * @see ExportFilter + */ +public class VersionStandardizer extends VersionInspector { + + @Override + public void handleAdded(PMMLObject object, AnnotatedElement element, Added added){ + Version version = added.value(); + + if(!version.isStandard()){ + + if(element instanceof Class){ + // Ignored + } else + + if(element instanceof Field){ + Field field = (Field)element; + + if(added.removable()){ + ReflectionUtil.setFieldValue(field, object, null); + } + } else + + { + throw new IllegalArgumentException(); + } + } + } + + @Override + public void handleRemoved(PMMLObject object, AnnotatedElement element, Removed removed){ + } + + @Override + public void handleOptional(PMMLObject object, AnnotatedElement element, Optional optional){ + } + + @Override + public void handleRequired(PMMLObject object, AnnotatedElement element, Required required){ + } +} \ No newline at end of file diff --git a/pmml-model/src/main/schema/pmml.xjb b/pmml-model/src/main/schema/pmml.xjb index 151886dc..ad33273e 100644 --- a/pmml-model/src/main/schema/pmml.xjb +++ b/pmml-model/src/main/schema/pmml.xjb @@ -91,7 +91,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.5.0") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) @@ -234,14 +234,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -341,14 +341,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) @@ -589,7 +589,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -606,7 +606,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -631,7 +631,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) @@ -872,7 +872,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -1144,7 +1144,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -1294,7 +1294,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -1323,7 +1323,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_3) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_3, removable = true) @@ -1589,14 +1589,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -1719,14 +1719,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -1760,14 +1760,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -1856,7 +1856,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -2060,7 +2060,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_3) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_3, removable = true) @@ -2159,7 +2159,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) @@ -2193,7 +2193,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) @@ -2302,7 +2302,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.3") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -2393,59 +2393,59 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_4) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_4, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -2536,7 +2536,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -2557,7 +2557,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -2568,7 +2568,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -2705,14 +2705,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -2739,7 +2739,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -2752,7 +2752,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -2972,7 +2972,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -3178,7 +3178,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -3406,14 +3406,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) @@ -3593,14 +3593,14 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -3671,7 +3671,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_1) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_1, removable = true) @@ -3714,7 +3714,7 @@ Copyright (c) 2014 Villu Ruusmann - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.XPMML) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.XPMML, removable = true) org.jpmml.model.annotations.Since("1.3.7") - org.jpmml.model.annotations.Added(org.dmg.pmml.Version.PMML_4_0) + org.jpmml.model.annotations.Added(value = org.dmg.pmml.Version.PMML_4_0, removable = true) diff --git a/pmml-model/src/test/java/org/jpmml/model/XPathUtilTest.java b/pmml-model/src/test/java/org/jpmml/model/XPathUtilTest.java index 22d07ed3..2edde8dd 100644 --- a/pmml-model/src/test/java/org/jpmml/model/XPathUtilTest.java +++ b/pmml-model/src/test/java/org/jpmml/model/XPathUtilTest.java @@ -3,13 +3,22 @@ */ package org.jpmml.model; +import org.dmg.pmml.ComplexScoreDistribution; import org.dmg.pmml.DataField; import org.dmg.pmml.IntSparseArray; import org.dmg.pmml.PMMLAttributes; import org.dmg.pmml.PMMLElements; +import org.dmg.pmml.ScoreDistribution; +import org.dmg.pmml.ScoreProbability; +import org.dmg.pmml.SimpleScoreDistribution; +import org.dmg.pmml.tree.ComplexNode; +import org.dmg.pmml.tree.LeafNode; +import org.dmg.pmml.tree.Node; +import org.dmg.pmml.tree.SimpleNode; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class XPathUtilTest { @@ -41,11 +50,65 @@ public void formatDerivedField(){ assertEquals("DerivedField/", XPathUtil.formatElementOrAttribute(PMMLElements.DERIVEDFIELD_EXPRESSION)); } + @Test + public void formatComplexNode(){ + assertEquals("Node", XPathUtil.formatElement(ComplexNode.class)); + + assertEquals("Node@score", XPathUtil.formatElementOrAttribute(org.dmg.pmml.tree.PMMLAttributes.COMPLEXNODE_SCORE)); + assertEquals("Node/", XPathUtil.formatElementOrAttribute(org.dmg.pmml.tree.PMMLElements.COMPLEXNODE_PREDICATE)); + } + + @Test + public void formatSimpleNode(){ + SimpleNode node = new LeafNode(); + + Class nodeClazz = node.getClass(); + + assertEquals("Node", XPathUtil.formatElement(nodeClazz)); + + try { + XPathUtil.formatElementOrAttribute(ReflectionUtil.getField(nodeClazz, "score")); + + fail(); + } catch(IllegalArgumentException iae){ + // Ignored + } + + assertEquals("Node@score", XPathUtil.formatElementOrAttribute(nodeClazz, ReflectionUtil.getField(nodeClazz, "score"))); + assertEquals("Node/", XPathUtil.formatElementOrAttribute(nodeClazz, ReflectionUtil.getField(nodeClazz, "predicate"))); + } + @Test public void formatPMML(){ assertEquals("PMML/", XPathUtil.formatElementOrAttribute(PMMLElements.PMML_MODELS)); } + @Test + public void formatComplexScoreDistribution(){ + assertEquals("ScoreDistribution", XPathUtil.formatElement(ComplexScoreDistribution.class)); + + assertEquals("ScoreDistribution@value", XPathUtil.formatElementOrAttribute(PMMLAttributes.COMPLEXSCOREDISTRIBUTION_VALUE)); + } + + @Test + public void formatSimpleScoreDistribution(){ + SimpleScoreDistribution scoreDistribution = new ScoreProbability(); + + Class scoreDistributionClazz = scoreDistribution.getClass(); + + assertEquals("ScoreDistribution", XPathUtil.formatElement(scoreDistributionClazz)); + + try { + XPathUtil.formatElementOrAttribute(ReflectionUtil.getField(scoreDistributionClazz, "value")); + + fail(); + } catch(IllegalArgumentException iae){ + // Ignored + } + + assertEquals("ScoreDistribution@value", XPathUtil.formatElementOrAttribute(scoreDistributionClazz, ReflectionUtil.getField(scoreDistributionClazz, "value"))); + } + @Test public void formatSegment(){ assertEquals("Segment/", XPathUtil.formatElementOrAttribute(org.dmg.pmml.mining.PMMLElements.SEGMENT_MODEL)); diff --git a/pmml-model/src/test/java/org/jpmml/model/visitors/VersionCheckerTest.java b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionCheckerTest.java index 9e3be4b0..61374ff8 100644 --- a/pmml-model/src/test/java/org/jpmml/model/visitors/VersionCheckerTest.java +++ b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionCheckerTest.java @@ -11,10 +11,15 @@ import java.util.stream.Collectors; import org.dmg.pmml.Apply; +import org.dmg.pmml.DataType; +import org.dmg.pmml.DerivedField; +import org.dmg.pmml.FieldRef; +import org.dmg.pmml.Interval; import org.dmg.pmml.LocalTransformations; import org.dmg.pmml.MathContext; import org.dmg.pmml.MiningFunction; import org.dmg.pmml.MiningSchema; +import org.dmg.pmml.OpType; import org.dmg.pmml.Output; import org.dmg.pmml.OutputField; import org.dmg.pmml.PMMLFunctions; @@ -26,6 +31,7 @@ import org.dmg.pmml.mining.Segmentation; import org.jpmml.model.MarkupException; import org.jpmml.model.MisplacedElementException; +import org.jpmml.model.MisplacedElementListException; import org.jpmml.model.MissingAttributeException; import org.jpmml.model.MissingElementException; import org.jpmml.model.UnsupportedAttributeException; @@ -56,6 +62,23 @@ public void inspectApply(){ assertExceptions(apply, Version.PMML_4_4, Collections.singleton(UnsupportedAttributeException.class)); } + @Test + public void inspectDerivedField(){ + Interval negativeInterval = new Interval(Interval.Closure.CLOSED_OPEN) + .setLeftMargin(-Double.MAX_VALUE) + .setRightMargin(0d); + + Interval positiveInterval = new Interval(Interval.Closure.OPEN_CLOSED) + .setLeftMargin(0d) + .setRightMargin(Double.MAX_VALUE); + + DerivedField derivedField = new DerivedField("double(x)", OpType.CONTINUOUS, DataType.DOUBLE, new FieldRef("x")) + .addIntervals(negativeInterval, positiveInterval); + + assertExceptions(derivedField, Version.PMML_4_4, Collections.singleton(MisplacedElementListException.class)); + assertExceptions(derivedField, Version.PMML_3_0, Collections.emptySet()); + } + @Test public void inspectMiningModel(){ Segmentation segmentation = new Segmentation() diff --git a/pmml-model/src/test/java/org/jpmml/model/visitors/VersionDowngraderTest.java b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionDowngraderTest.java new file mode 100644 index 00000000..229a0032 --- /dev/null +++ b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionDowngraderTest.java @@ -0,0 +1,175 @@ +/* + * Copyrght (c) 2024 Villu Ruusmann + */ +package org.jpmml.model.visitors; + +import org.dmg.pmml.Apply; +import org.dmg.pmml.ClusteringModelQuality; +import org.dmg.pmml.Extension; +import org.dmg.pmml.Header; +import org.dmg.pmml.MiningField; +import org.dmg.pmml.ModelExplanation; +import org.dmg.pmml.PMML; +import org.dmg.pmml.PMMLObject; +import org.dmg.pmml.TargetValue; +import org.dmg.pmml.Version; +import org.dmg.pmml.clustering.ClusteringModel; +import org.dmg.pmml.clustering.PMMLAttributes; +import org.dmg.pmml.time_series.TrendExpoSmooth; +import org.jpmml.model.ReflectionUtil; +import org.jpmml.model.UnsupportedAttributeException; +import org.jpmml.model.UnsupportedElementException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class VersionDowngraderTest { + + @Test + public void downgradeApply(){ + Apply apply = new Apply() + .setDefaultValue(0) + .setMapMissingTo(null); + + apply = downgrade(apply, Version.PMML_4_2); + + assertEquals(0, apply.getDefaultValue()); + assertNull(apply.getMapMissingTo()); + + apply = downgrade(apply, Version.PMML_4_1); + + assertNull(apply.getDefaultValue()); + assertEquals(0, apply.getMapMissingTo()); + + apply = new Apply() + .setDefaultValue(0) + .setMapMissingTo(-999); + + apply = downgrade(apply, Version.PMML_4_2); + + assertEquals(0, apply.getDefaultValue()); + assertEquals(-999, apply.getMapMissingTo()); + + try { + downgrade(apply, Version.PMML_4_1); + + fail(); + } catch(UnsupportedAttributeException uae){ + // Ignored + } + } + + @Test + public void downgradeMiningField(){ + MiningField miningField = new MiningField() + .setUsageType(MiningField.UsageType.TARGET); + + miningField = downgrade(miningField, Version.PMML_4_2); + + assertEquals(MiningField.UsageType.TARGET, miningField.getUsageType()); + + miningField = downgrade(miningField, Version.PMML_4_1); + + assertEquals(MiningField.UsageType.PREDICTED, miningField.getUsageType()); + } + + @Test + public void downgradePMML(){ + Extension extension = new Extension(); + + ClusteringModelQuality clusteringModelQuality = new ClusteringModelQuality() + .addExtensions(extension); + + ModelExplanation modelExplanation = new ModelExplanation() + .addClusteringModelQualities(clusteringModelQuality); + + ClusteringModel clusteringModel = new ClusteringModel() + .setScorable(false) + .setModelExplanation(modelExplanation); + + Header header = new Header() + .setModelVersion("1.0"); + + PMML pmml = new PMML() + .setHeader(header) + .addModels(clusteringModel); + + pmml = downgrade(pmml, Version.PMML_4_4); + + assertTrue(clusteringModelQuality.hasExtensions()); + + assertEquals("4.4", pmml.getVersion()); + + pmml = downgrade(pmml, Version.PMML_4_3); + + assertFalse(clusteringModelQuality.hasExtensions()); + + assertNotNull(header.getModelVersion()); + + pmml = downgrade(pmml, Version.PMML_4_1); + + assertNull(header.getModelVersion()); + + assertFalse(clusteringModel.isScorable()); + + pmml = downgrade(pmml, Version.PMML_4_0); + + assertTrue(clusteringModel.isScorable()); + + assertNull(ReflectionUtil.getFieldValue(PMMLAttributes.CLUSTERINGMODEL_SCORABLE, clusteringModel)); + + assertNotNull(clusteringModel.getModelExplanation()); + + pmml = downgrade(pmml, Version.PMML_3_2); + + assertNull(clusteringModel.getModelExplanation()); + + assertEquals("3.2", pmml.getVersion()); + } + + @Test + public void downgradeTargetValue(){ + TargetValue targetValue = new TargetValue() + .setValue(1) + .setDisplayValue("one"); + + targetValue = downgrade(targetValue, Version.PMML_4_0); + + assertEquals(1, targetValue.getValue()); + assertEquals("one", targetValue.getDisplayValue()); + + try { + downgrade(targetValue, Version.PMML_3_2); + + fail(); + } catch(UnsupportedAttributeException uae){ + // Ignored + } + } + + @Test + public void downgradeTrendExpoSmooth(){ + TrendExpoSmooth trendExpoSmooth = new TrendExpoSmooth(); + + trendExpoSmooth = downgrade(trendExpoSmooth, Version.PMML_4_1); + + try { + downgrade(trendExpoSmooth, Version.PMML_4_0); + } catch(UnsupportedElementException uee){ + // Ignored + } + } + + static + private E downgrade(E object, Version version){ + VersionDowngrader inspector = new VersionDowngrader(version); + inspector.applyTo(object); + + return object; + } +} \ No newline at end of file diff --git a/pmml-model/src/test/java/org/jpmml/model/visitors/VersionInspectorTest.java b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionInspectorTest.java new file mode 100644 index 00000000..9c52c7e8 --- /dev/null +++ b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionInspectorTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Villu Ruusmann + */ +package org.jpmml.model.visitors; + +import java.lang.reflect.Field; + +import org.dmg.pmml.PMMLAttributes; +import org.dmg.pmml.PMMLElements; +import org.dmg.pmml.ResultFeature; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class VersionInspectorTest { + + @Test + public void isAttribute(){ + assertTrue(VersionInspector.isAttribute(PMMLAttributes.OUTPUTFIELD_NAME)); + + assertFalse(VersionInspector.isAttribute(PMMLElements.OUTPUTFIELD_EXTENSIONS)); + } + + @Test + public void isEnumValue() throws NoSuchFieldException { + Field field = ResultFeature.class.getField("PREDICTED_VALUE"); + + assertTrue(VersionInspector.isEnumValue(field)); + + assertFalse(VersionInspector.isEnumValue(PMMLAttributes.OUTPUTFIELD_RESULTFEATURE)); + } + + @Test + public void isElement(){ + assertFalse(VersionInspector.isElement(PMMLAttributes.OUTPUTFIELD_NAME)); + + assertTrue(VersionInspector.isElement(PMMLElements.OUTPUTFIELD_EXTENSIONS)); + assertTrue(VersionInspector.isElement(PMMLElements.OUTPUTFIELD_EXPRESSION)); + } +} \ No newline at end of file diff --git a/pmml-model/src/test/java/org/jpmml/model/visitors/VersionStandardizerTest.java b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionStandardizerTest.java new file mode 100644 index 00000000..103070f3 --- /dev/null +++ b/pmml-model/src/test/java/org/jpmml/model/visitors/VersionStandardizerTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Villu Ruusmann + */ +package org.jpmml.model.visitors; + +import org.dmg.pmml.MathContext; +import org.dmg.pmml.PMML; +import org.dmg.pmml.tree.PMMLAttributes; +import org.dmg.pmml.tree.TreeModel; +import org.jpmml.model.ReflectionUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class VersionStandardizerTest { + + @Test + public void standardizePMML(){ + PMML pmml = new PMML() + .setVersion("4.4") + .setBaseVersion("4.3"); + + TreeModel treeModel = new TreeModel() + .setMathContext(MathContext.FLOAT); + + pmml.addModels(treeModel); + + VersionStandardizer inspector = new VersionStandardizer(); + inspector.applyTo(pmml); + + assertNotNull(pmml.getVersion()); + assertNull(pmml.getBaseVersion()); + + // The getter method is returning the default field value + assertEquals(MathContext.DOUBLE, treeModel.getMathContext()); + + assertNull(ReflectionUtil.getFieldValue(PMMLAttributes.TREEMODEL_MATHCONTEXT, treeModel)); + } +} \ No newline at end of file diff --git a/pmml-xjc/src/main/java/org/jpmml/xjc/CodeModelUtil.java b/pmml-xjc/src/main/java/org/jpmml/xjc/CodeModelUtil.java index acda944e..2c53cf8e 100644 --- a/pmml-xjc/src/main/java/org/jpmml/xjc/CodeModelUtil.java +++ b/pmml-xjc/src/main/java/org/jpmml/xjc/CodeModelUtil.java @@ -3,9 +3,18 @@ */ package org.jpmml.xjc; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.util.Collection; import java.util.List; +import com.sun.codemodel.JAnnotatable; +import com.sun.codemodel.JAnnotationUse; import com.sun.codemodel.JClass; +import com.sun.codemodel.JFormatter; +import com.sun.codemodel.JGenerable; import com.sun.codemodel.JType; public class CodeModelUtil { @@ -24,4 +33,89 @@ public JType getElementType(JType collectionType){ return elementTypes.get(0); } + + static + public boolean hasAnnotation(Collection annotations, Class clazz){ + JAnnotationUse annotation = findAnnotation(annotations, clazz); + + return (annotation != null); + } + + static + public JAnnotationUse findAnnotation(Collection annotations, Class clazz){ + String fullName = clazz.getName(); + + for(JAnnotationUse annotation : annotations){ + JClass type = annotation.getAnnotationClass(); + + if((type.fullName()).equals(fullName)){ + return annotation; + } + } + + return null; + } + + static + public List getAnnotations(JAnnotatable annotatable){ + + try { + Class clazz = annotatable.getClass(); + + Field annotationsField; + + while(true){ + + try { + annotationsField = clazz.getDeclaredField("annotations"); + + break; + } catch(NoSuchFieldException nsfe){ + clazz = clazz.getSuperclass(); + + if(clazz == null){ + throw nsfe; + } + } + } + + ensureAccessible(annotationsField); + + return (List)annotationsField.get(annotatable); + } catch(ReflectiveOperationException roe){ + throw new RuntimeException(roe); + } + } + + static + public String stringValue(JGenerable generable){ + String result; + + try(StringWriter writer = new StringWriter()){ + generable.generate(new JFormatter(writer)); + + result = writer.toString(); + } catch(IOException ioe){ + throw new RuntimeException(ioe); + } + + if(result.length() >= 2 && (result.startsWith("\"") && result.endsWith("\""))){ + result = result.substring(1, result.length() - 1); + } else + + { + throw new RuntimeException(); + } + + return result; + } + + @SuppressWarnings("deprecation") + static + void ensureAccessible(AccessibleObject accessibleObject){ + + if(!accessibleObject.isAccessible()){ + accessibleObject.setAccessible(true); + } + } } \ No newline at end of file diff --git a/pmml-xjc/src/main/java/org/jpmml/xjc/JacksonPlugin.java b/pmml-xjc/src/main/java/org/jpmml/xjc/JacksonPlugin.java index c2d3c74f..3ef285d8 100644 --- a/pmml-xjc/src/main/java/org/jpmml/xjc/JacksonPlugin.java +++ b/pmml-xjc/src/main/java/org/jpmml/xjc/JacksonPlugin.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.sun.codemodel.JAnnotationArrayMember; import com.sun.codemodel.JAnnotationUse; +import com.sun.codemodel.JAnnotationValue; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JDefinedClass; @@ -38,6 +39,7 @@ import com.sun.tools.xjc.outline.EnumOutline; import com.sun.tools.xjc.outline.FieldOutline; import com.sun.tools.xjc.outline.Outline; +import jakarta.xml.bind.annotation.XmlRootElement; import org.xml.sax.ErrorHandler; public class JacksonPlugin extends Plugin { @@ -58,14 +60,18 @@ public boolean run(Outline outline, Options options, ErrorHandler errorHandler){ Collection classOutlines = outline.getClasses(); for(ClassOutline classOutline : classOutlines){ - CClassInfo classInfo = classOutline.target; JDefinedClass beanClazz = classOutline.implClass; - if(classInfo.isElement()){ - QName elementName = classInfo.getElementName(); + List beanClazzAnnotations = CodeModelUtil.getAnnotations(beanClazz); + + JAnnotationUse xmlRootElement = CodeModelUtil.findAnnotation(beanClazzAnnotations, XmlRootElement.class); + if(xmlRootElement != null){ + Map annotationMembers = xmlRootElement.getAnnotationMembers(); + + JAnnotationValue nameValue = annotationMembers.get("name"); JAnnotationUse jsonRootName = beanClazz.annotate(JsonRootName.class) - .param("value", elementName.getLocalPart()); + .param("value", CodeModelUtil.stringValue(nameValue)); } FieldOutline[] fieldOutlines = classOutline.getDeclaredFields(); diff --git a/pmml-xjc/src/main/java/org/jpmml/xjc/PMMLPlugin.java b/pmml-xjc/src/main/java/org/jpmml/xjc/PMMLPlugin.java index 21df1e5f..ed4b4fea 100644 --- a/pmml-xjc/src/main/java/org/jpmml/xjc/PMMLPlugin.java +++ b/pmml-xjc/src/main/java/org/jpmml/xjc/PMMLPlugin.java @@ -4,7 +4,6 @@ */ package org.jpmml.xjc; -import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; @@ -20,7 +19,6 @@ import javax.xml.namespace.QName; -import com.sun.codemodel.JAnnotatable; import com.sun.codemodel.JAnnotationArrayMember; import com.sun.codemodel.JAnnotationUse; import com.sun.codemodel.JAnnotationValue; @@ -59,6 +57,7 @@ import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlElements; import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlType; import jakarta.xml.bind.annotation.XmlValue; import org.eclipse.persistence.oxm.annotations.XmlValueExtension; import org.glassfish.jaxb.core.api.impl.NameConverter; @@ -151,7 +150,7 @@ public int compare(CPropertyInfo left, CPropertyInfo right){ try { Field pkgField = CClassInfoParent.Package.class.getDeclaredField("pkg"); - ensureAccessible(pkgField); + CodeModelUtil.ensureAccessible(pkgField); JPackage subPackage = packageParent.pkg.subPackage(name); @@ -166,7 +165,7 @@ public int compare(CPropertyInfo left, CPropertyInfo right){ try { Field elementNameField = CClassInfo.class.getDeclaredField("elementName"); - ensureAccessible(elementNameField); + CodeModelUtil.ensureAccessible(elementNameField); elementNameField.set(classInfo, new QName("http://www.dmg.org/PMML-4_4", name)); } catch(ReflectiveOperationException roe){ @@ -312,17 +311,40 @@ public boolean run(Outline outline, Options options, ErrorHandler errorHandler){ for(ClassOutline classOutline : classOutlines){ JDefinedClass beanClazz = classOutline.implClass; - List beanClazzAnnotations = getAnnotations(beanClazz); + List beanClazzAnnotations = CodeModelUtil.getAnnotations(beanClazz); - JAnnotationUse xmlAccessorType = findAnnotation(beanClazzAnnotations, XmlAccessorType.class); + JAnnotationUse xmlAccessorType = CodeModelUtil.findAnnotation(beanClazzAnnotations, XmlAccessorType.class); if(xmlAccessorType != null){ beanClazzAnnotations.remove(xmlAccessorType); } - JAnnotationUse xmlRootElement = findAnnotation(beanClazzAnnotations, XmlRootElement.class); + JAnnotationUse xmlType = CodeModelUtil.findAnnotation(beanClazzAnnotations, XmlType.class); + if(xmlType != null){ + beanClazzAnnotations.remove(xmlType); + beanClazzAnnotations.add(0, xmlType); + } else + + { + throw new RuntimeException(); + } + + JAnnotationUse xmlRootElement = CodeModelUtil.findAnnotation(beanClazzAnnotations, XmlRootElement.class); + if(xmlRootElement == null){ + String elementName = getElementName(beanClazz.name()); + + beanClazz.annotate(XmlRootElement.class) + .param("name", elementName) + .param("namespace", "http://www.dmg.org/PMML-4_4"); + } + + xmlRootElement = CodeModelUtil.findAnnotation(beanClazzAnnotations, XmlRootElement.class); if(xmlRootElement != null){ beanClazzAnnotations.remove(xmlRootElement); beanClazzAnnotations.add(0, xmlRootElement); + } else + + { + throw new RuntimeException(); } Map fieldVars = beanClazz.fields(); @@ -457,11 +479,11 @@ public boolean run(Outline outline, Options options, ErrorHandler errorHandler){ } } - List fieldVarAnnotations = getAnnotations(fieldVar); + List fieldVarAnnotations = CodeModelUtil.getAnnotations(fieldVar); // XXX if(("node").equals(name) || ("nodes").equals(name) || ("scoreDistributions").equals(name)){ - JAnnotationUse xmlElement = findAnnotation(fieldVarAnnotations, XmlElement.class); + JAnnotationUse xmlElement = CodeModelUtil.findAnnotation(fieldVarAnnotations, XmlElement.class); fieldVarAnnotations.remove(xmlElement); @@ -477,7 +499,7 @@ public boolean run(Outline outline, Options options, ErrorHandler errorHandler){ fieldVarAnnotations.add(0, xmlElements); } // End if - if(hasAnnotation(fieldVarAnnotations, XmlValue.class)){ + if(CodeModelUtil.hasAnnotation(fieldVarAnnotations, XmlValue.class)){ fieldVar.annotate(XmlValueExtension.class); } } @@ -639,6 +661,36 @@ public boolean run(Outline outline, Options options, ErrorHandler errorHandler){ return true; } + static + private String getElementName(String name){ + + switch(name){ + // baseline + case "CountTable": + return "COUNT-TABLE-TYPE"; + // bayesian_network + case "ContinuousDistribution": + return name; + // bayesian_network + case "LognormalDistribution": + case "NormalDistribution": + case "TriangularDistribution": + case "UniformDistribution": + return name + "ForBN"; + // support_vector_machne + case "LinearKernel": + case "PolynomialKernel": + case "RadialBasisKernel": + case "SigmoidKernel": + return name + "Type"; + // time_series + case "TrendExpoSmooth": + return "Trend_ExpoSmooth"; + default: + throw new IllegalArgumentException(name); + } + } + static private FieldOutline getExtensionsField(ClassOutline classOutline){ Predicate predicate = new Predicate(){ @@ -690,65 +742,12 @@ public boolean test(FieldOutline fieldOutline){ .findFirst().orElse(null); } - static - private boolean hasAnnotation(Collection annotations, Class clazz){ - JAnnotationUse annotation = findAnnotation(annotations, clazz); - - return (annotation != null); - } - - static - private JAnnotationUse findAnnotation(Collection annotations, Class clazz){ - String fullName = clazz.getName(); - - for(JAnnotationUse annotation : annotations){ - JClass type = annotation.getAnnotationClass(); - - if(checkType(type, fullName)){ - return annotation; - } - } - - return null; - } - - static - private List getAnnotations(JAnnotatable annotatable){ - - try { - Class clazz = annotatable.getClass(); - - Field annotationsField; - - while(true){ - - try { - annotationsField = clazz.getDeclaredField("annotations"); - - break; - } catch(NoSuchFieldException nsfe){ - clazz = clazz.getSuperclass(); - - if(clazz == null){ - throw nsfe; - } - } - } - - ensureAccessible(annotationsField); - - return (List)annotationsField.get(annotatable); - } catch(ReflectiveOperationException roe){ - throw new RuntimeException(roe); - } - } - static private void addValues(JAnnotationUse annotationUse, Map memberValues){ try { Method addValueMethod = JAnnotationUse.class.getDeclaredMethod("addValue", String.class, JAnnotationValue.class); - ensureAccessible(addValueMethod); + CodeModelUtil.ensureAccessible(addValueMethod); Collection> entries = memberValues.entrySet(); for(Map.Entry entry : entries){ @@ -814,7 +813,7 @@ private JExpression constantExpr(JFieldVar fieldVar){ try { Field ownerField = JFieldVar.class.getDeclaredField("owner"); - ensureAccessible(ownerField); + CodeModelUtil.ensureAccessible(ownerField); owner = (JDefinedClass)ownerField.get(fieldVar); } catch(ReflectiveOperationException roe){ @@ -864,7 +863,7 @@ private void addOverrideAnnotations(JDefinedClass beanClazz, String[][] typeMemb if((name.startsWith("has") || name.startsWith("is") || name.startsWith("get") || name.startsWith("require")) && params.size() == 0){ - if(!hasAnnotation(method.annotations(), Override.class)){ + if(!CodeModelUtil.hasAnnotation(method.annotations(), Override.class)){ method.annotate(Override.class); } } else @@ -875,7 +874,7 @@ private void addOverrideAnnotations(JDefinedClass beanClazz, String[][] typeMemb if(name.startsWith("set") && params.size() == 1){ - if(!hasAnnotation(method.annotations(), Override.class)){ + if(!CodeModelUtil.hasAnnotation(method.annotations(), Override.class)){ method.annotate(Override.class); } } else @@ -927,15 +926,6 @@ private boolean checkType(JType type, String fullName){ return (type.fullName()).equals(fullName); } - @SuppressWarnings("deprecation") - static - private void ensureAccessible(AccessibleObject accessibleObject){ - - if(!accessibleObject.isAccessible()){ - accessibleObject.setAccessible(true); - } - } - static private int parseVersion(String version){