ModelFactory.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.core.model.xml.impl;
import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
import gov.nist.secauto.metaschema.core.model.IAttributable;
import gov.nist.secauto.metaschema.core.model.ISource;
import gov.nist.secauto.metaschema.core.model.constraint.AbstractConstraintBuilder;
import gov.nist.secauto.metaschema.core.model.constraint.AbstractKeyConstraintBuilder;
import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValue;
import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValuesConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.ICardinalityConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level;
import gov.nist.secauto.metaschema.core.model.constraint.IExpectConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IIndexConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IIndexHasKeyConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IKeyField;
import gov.nist.secauto.metaschema.core.model.constraint.ILet;
import gov.nist.secauto.metaschema.core.model.constraint.IMatchesConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IUniqueConstraint;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.AllowedValueType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.AllowedValuesType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ConstraintLetType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ExpectConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.IndexHasKeyConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.KeyConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.MatchesConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.PropertyType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.RemarksType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.TargetedAllowedValuesConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.TargetedExpectConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.TargetedHasCardinalityConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.TargetedIndexConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.TargetedIndexHasKeyConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.TargetedKeyConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.TargetedMatchesConstraintType;
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.namespace.QName;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
/**
* Produces Metaschema module data objects from XML-based XMLBeans data
* bindings.
*/
@SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.GodClass" })
public final class ModelFactory {
private ModelFactory() {
// disable
}
@NonNull
private static String target(@Nullable String target) {
return target == null ? IConstraint.DEFAULT_TARGET_METAPATH : target;
}
@NonNull
private static Level level(@Nullable Level level) {
return level == null ? IConstraint.DEFAULT_LEVEL : level;
}
@NonNull
private static MarkupMultiline remarks(@NonNull RemarksType remarks) {
return MarkupStringConverter.toMarkupString(remarks);
}
/**
* Parse the properties.
*
* @param properties
* the XmlBeans property representation to parse
* @return the properties as a mapping of name to values
*/
@SuppressWarnings("null")
@NonNull
public static Map<IAttributable.Key, Set<String>> toProperties(
@NonNull List<PropertyType> properties) {
return properties.stream()
.map(prop -> {
String name = prop.getName();
String namespace = prop.isSetNamespace() ? prop.getNamespace() : IAttributable.DEFAULT_PROPERY_NAMESPACE;
IAttributable.Key key = IAttributable.key(name, namespace);
String value = prop.getValue();
return Map.entry(key, value);
})
.collect(Collectors.groupingBy(Map.Entry<IAttributable.Key, String>::getKey,
Collectors.mapping(Map.Entry<IAttributable.Key, String>::getValue, Collectors.toSet())));
}
/**
* Parse the allowed values.
*
* @param properties
* the XmlBeans allowed values representation to parse
* @return the allowed values as a mapping of name to value object
*/
@NonNull
private static Map<String, IAllowedValue> toAllowedValues(
@NonNull AllowedValuesType xmlObject) {
Map<String, IAllowedValue> allowedValues // NOPMD - intentional
= new LinkedHashMap<>(xmlObject.sizeOfEnumArray());
for (AllowedValueType xmlEnum : xmlObject.getEnumList()) {
String value = xmlEnum.getValue();
if (value == null) {
throw new IllegalStateException(String.format("Null value found in allowed value enumeration: %s",
xmlObject.xmlText()));
}
IAllowedValue allowedValue = IAllowedValue.of(
value,
MarkupStringConverter.toMarkupString(xmlEnum),
xmlEnum.getDeprecated());
allowedValues.put(allowedValue.getValue(), allowedValue);
}
return CollectionUtil.unmodifiableMap(allowedValues);
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IAllowedValuesConstraint newAllowedValuesConstraint(
@NonNull TargetedAllowedValuesConstraintType xmlObject,
@NonNull ISource source) {
return newAllowedValuesConstraint(xmlObject, target(xmlObject.getTarget()), source);
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IAllowedValuesConstraint newAllowedValuesConstraint(
@NonNull AllowedValuesType xmlObject,
@NonNull ISource source) {
return newAllowedValuesConstraint(xmlObject, IConstraint.DEFAULT_TARGET_METAPATH, source);
}
@NonNull
private static IAllowedValuesConstraint newAllowedValuesConstraint(
@NonNull AllowedValuesType xmlObject,
@NonNull String target,
@NonNull ISource source) {
IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder();
applyToBuilder(xmlObject, target, source, builder);
if (xmlObject.isSetRemarks()) {
builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks())));
}
builder.allowedValues(toAllowedValues(xmlObject));
if (xmlObject.isSetAllowOther()) {
builder.allowsOther(xmlObject.getAllowOther());
}
if (xmlObject.isSetExtensible()) {
builder.extensible(ObjectUtils.notNull(xmlObject.getExtensible()));
}
return builder.build();
}
@NonNull
private static <T extends AbstractConstraintBuilder<T, ?>> T applyToBuilder(
@NonNull ConstraintType xmlObject,
@NonNull String target,
@NonNull ISource source,
@NonNull T builder) {
if (xmlObject.isSetId()) {
builder.identifier(ObjectUtils.notNull(xmlObject.getId()));
}
builder.target(target);
builder.source(source);
builder.level(level(xmlObject.getLevel()));
return builder;
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IMatchesConstraint newMatchesConstraint(
@NonNull TargetedMatchesConstraintType xmlObject,
@NonNull ISource source) {
return newMatchesConstraint(xmlObject, target(xmlObject.getTarget()), source);
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlConstraint
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IMatchesConstraint newMatchesConstraint(
@NonNull MatchesConstraintType xmlConstraint,
@NonNull ISource source) {
return newMatchesConstraint(xmlConstraint, IConstraint.DEFAULT_TARGET_METAPATH, source);
}
@NonNull
private static IMatchesConstraint newMatchesConstraint(
@NonNull MatchesConstraintType xmlObject,
@NonNull String target,
@NonNull ISource source) {
IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
applyToBuilder(xmlObject, target, source, builder);
if (xmlObject.isSetMessage()) {
builder.message(ObjectUtils.notNull(xmlObject.getMessage()));
}
if (xmlObject.isSetRemarks()) {
builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks())));
}
if (xmlObject.isSetRegex()) {
builder.regex(ObjectUtils.notNull(xmlObject.getRegex()));
}
if (xmlObject.isSetDatatype()) {
builder.datatype(ObjectUtils.notNull(xmlObject.getDatatype()));
}
return builder.build();
}
private static void buildKeyFields(
@NonNull KeyConstraintType xmlObject,
@NonNull AbstractKeyConstraintBuilder<?, ?> builder,
@NonNull ISource source) {
for (KeyConstraintType.KeyField xmlKeyField : xmlObject.getKeyFieldList()) {
IKeyField keyField = IKeyField.of(
ObjectUtils.requireNonNull(xmlKeyField.getTarget()),
xmlKeyField.isSetPattern() ? xmlKeyField.getPattern() : null, // NOPMD - intentional
xmlKeyField.isSetRemarks() ? remarks(ObjectUtils.notNull(xmlKeyField.getRemarks())) : null,
source);
builder.keyField(keyField);
}
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IUniqueConstraint newUniqueConstraint(
@NonNull TargetedKeyConstraintType xmlObject,
@NonNull ISource source) {
IUniqueConstraint.Builder builder = IUniqueConstraint.builder();
applyToBuilder(xmlObject, target(xmlObject.getTarget()), source, builder);
if (xmlObject.isSetMessage()) {
builder.message(ObjectUtils.notNull(xmlObject.getMessage()));
}
if (xmlObject.isSetRemarks()) {
builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks())));
}
buildKeyFields(xmlObject, builder, source);
return builder.build();
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IIndexConstraint newIndexConstraint(
@NonNull TargetedIndexConstraintType xmlObject,
@NonNull ISource source) {
IIndexConstraint.Builder builder = IIndexConstraint.builder(ObjectUtils.requireNonNull(xmlObject.getName()));
applyToBuilder(xmlObject, target(xmlObject.getTarget()), source, builder);
if (xmlObject.isSetMessage()) {
builder.message(ObjectUtils.notNull(xmlObject.getMessage()));
}
if (xmlObject.isSetRemarks()) {
builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks())));
}
buildKeyFields(xmlObject, builder, source);
return builder.build();
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IIndexHasKeyConstraint newIndexHasKeyConstraint(
@NonNull TargetedIndexHasKeyConstraintType xmlObject,
@NonNull ISource source) {
return newIndexHasKeyConstraint(xmlObject, target(xmlObject.getTarget()), source);
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IIndexHasKeyConstraint newIndexHasKeyConstraint(
@NonNull IndexHasKeyConstraintType xmlObject,
@NonNull ISource source) {
return newIndexHasKeyConstraint(xmlObject, IConstraint.DEFAULT_TARGET_METAPATH, source);
}
@NonNull
private static IIndexHasKeyConstraint newIndexHasKeyConstraint(
@NonNull IndexHasKeyConstraintType xmlObject,
@NonNull String target,
@NonNull ISource source) {
IIndexHasKeyConstraint.Builder builder
= IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(xmlObject.getName()));
applyToBuilder(xmlObject, target, source, builder);
if (xmlObject.isSetMessage()) {
builder.message(ObjectUtils.notNull(xmlObject.getMessage()));
}
if (xmlObject.isSetRemarks()) {
builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks())));
}
buildKeyFields(xmlObject, builder, source);
return builder.build();
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IExpectConstraint newExpectConstraint(
@NonNull TargetedExpectConstraintType xmlObject,
@NonNull ISource source) {
return newExpectConstraint(xmlObject, target(xmlObject.getTarget()), source);
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static IExpectConstraint newExpectConstraint(
@NonNull ExpectConstraintType xmlObject,
@NonNull ISource source) {
return newExpectConstraint(xmlObject, IConstraint.DEFAULT_TARGET_METAPATH, source);
}
@NonNull
private static IExpectConstraint newExpectConstraint(
@NonNull ExpectConstraintType xmlObject,
@NonNull String target,
@NonNull ISource source) {
IExpectConstraint.Builder builder = IExpectConstraint.builder();
applyToBuilder(xmlObject, target, source, builder);
if (xmlObject.isSetMessage()) {
builder.message(ObjectUtils.notNull(xmlObject.getMessage()));
}
if (xmlObject.isSetRemarks()) {
builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks())));
}
builder.test(ObjectUtils.requireNonNull(xmlObject.getTest()));
return builder.build();
}
/**
* Parse the constraint XMLBeans representation.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the parsed constraint object
*/
@NonNull
public static ICardinalityConstraint newCardinalityConstraint(
@NonNull TargetedHasCardinalityConstraintType xmlObject,
@NonNull ISource source) {
ICardinalityConstraint.Builder builder = ICardinalityConstraint.builder();
applyToBuilder(xmlObject, target(xmlObject.getTarget()), source, builder);
if (xmlObject.isSetMessage()) {
builder.message(ObjectUtils.notNull(xmlObject.getMessage()));
}
if (xmlObject.isSetRemarks()) {
builder.remarks(remarks(ObjectUtils.notNull(xmlObject.getRemarks())));
}
if (xmlObject.isSetMinOccurs()) {
builder.minOccurs(xmlObject.getMinOccurs().intValueExact());
}
if (xmlObject.isSetMaxOccurs()) {
builder.maxOccurs(xmlObject.getMaxOccurs().intValueExact());
}
return builder.build();
}
/**
* Generate a new Let expression by parsing the provided XMLBeans object.
*
* @param xmlObject
* the XmlObject representing the constraint
* @param source
* the descriptor for the resource containing the constraint
* @return the original let statement with the same name or {@code null}
*/
@NonNull
public static ILet newLet(
@NonNull ConstraintLetType xmlObject,
@NonNull ISource source) {
// TODO: figure out how to resolve the namespace prefix on var
return ILet.of(
new QName(xmlObject.getVar()),
ObjectUtils.notNull(xmlObject.getExpression()),
source,
xmlObject.isSetRemarks()
? remarks(ObjectUtils.notNull(xmlObject.getRemarks()))
: null);
}
}