uoa-validator-engine2/src/main/java/eu/dnetlib/validator2/validation/guideline/Builders.java

430 lines
18 KiB
Java

package eu.dnetlib.validator2.validation.guideline;
import eu.dnetlib.validator2.engine.Helper;
import eu.dnetlib.validator2.engine.Predicates;
import eu.dnetlib.validator2.engine.Rule;
import org.w3c.dom.Document;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class Builders {
@Deprecated
public static ElementSpecBuilder forElement(String elementName) {
return new ElementSpecBuilder(elementName);
}
public static ElementSpecBuilder forOptionalElement(String elementName) {
return new ElementSpecBuilder(elementName).optional();
}
public static ElementSpecBuilder forOptionalRepeatableElement(String elementName) {
return new ElementSpecBuilder(elementName).optionalRepeatable();
}
public static ElementSpecBuilder forRecommendedElement(String elementName) {
return new ElementSpecBuilder(elementName).recommended();
}
public static ElementSpecBuilder forRecommendedRepeatableElement(String elementName) {
return new ElementSpecBuilder(elementName).recommendedRepeatable();
}
public static ElementSpecBuilder forMandatoryElement(String elementName, Cardinality cardinality) {
return new ElementSpecBuilder(elementName).mandatory(cardinality);
}
public static <T> ElementSpecBuilder forMandatoryIfApplicableElement(String elementName,
Cardinality cardinality,
Rule<Document> applicabilityRule) {
return new ElementSpecBuilder(elementName).mandatoryIfApplicable(cardinality, applicabilityRule);
}
@Deprecated
public static ElementSpecBuilder forElement(String elementName,
RequirementLevel requirementLevel,
Cardinality cardinality) {
return Helper.buildElement(elementName, requirementLevel, cardinality);
}
@Deprecated
public static ElementSpecBuilder forElement(String elementName,
RequirementLevel requirementLevel,
Cardinality cardinality,
String... allowedValues) {
return Helper.buildElement(elementName, requirementLevel, cardinality).allowedValues(allowedValues);
}
@Deprecated
public static ElementSpecBuilder forElement(String elementName,
RequirementLevel requirementLevel,
Cardinality cardinality,
Predicate<String> allowedValuesPredicate) {
return Helper.buildElement(elementName, requirementLevel, cardinality).allowedValues(allowedValuesPredicate);
}
static final Predicate<String> ALLOW_ALL_VALUES = (String value) -> true;
private static class DefaultNodeSpec implements NodeSpec {
private final String name;
private final RequirementLevel requirementLevel;
private final Cardinality cardinality;
private final Predicate<String> allowedValuesPredicate;
private final Rule<Document> applicabilityRule;
DefaultNodeSpec(String name,
RequirementLevel requirementLevel,
Cardinality cardinality,
Predicate<String> allowedValuesPredicate,
Rule<Document> applicabilityRule) {
this.name = name;
this.requirementLevel = requirementLevel;
this.cardinality = cardinality;
this.allowedValuesPredicate = allowedValuesPredicate;
this.applicabilityRule = applicabilityRule;
}
@Override
public String nodeName() {
return name;
}
@Override
public RequirementLevel requirementLevel() {
return requirementLevel;
}
@Override
public Cardinality cardinality() {
return cardinality;
}
@Override
public Predicate<String> allowedValuesPredicate() {
return allowedValuesPredicate;
}
@Override
public Rule<Document> applicabilityRule() {
return applicabilityRule;
}
}
private static class DefaultAttributeSpec extends DefaultNodeSpec implements AttributeSpec {
DefaultAttributeSpec(String name,
RequirementLevel requirementLevel,
Predicate<String> allowedValuesPredicate,
Rule<Document> applicabilityRule) {
super(name, requirementLevel, Cardinality.ONE, allowedValuesPredicate, applicabilityRule);
}
}
private static class DefaultElementSpec extends DefaultNodeSpec implements ElementSpec {
private final Set<String> parents;
private final Set<AttributeSpec> attributeSpecs;
private final Set<ElementSpec> subElementSpecs;
private final String valuePrefix;
private final ElementPosition elementPosition;
DefaultElementSpec(Set<String> parents,
String name,
RequirementLevel requirementLevel,
Cardinality cardinality,
Predicate<String> allowedValuesPredicate,
Rule<Document> applicabilityRule,
ElementPosition elementPosition,
String valuePrefix,
Set<AttributeSpec> attributeSpecs,
Set<ElementSpec> subElementSpecs) {
super(name, requirementLevel, cardinality, allowedValuesPredicate, applicabilityRule);
this.parents = parents;
this.elementPosition = elementPosition;
this.valuePrefix = valuePrefix;
this.attributeSpecs = attributeSpecs;
this.subElementSpecs = subElementSpecs;
}
@Override
public Set<String> parents() {
return parents;
}
@Override
public Set<ElementSpec> subElementSpecs() {
return subElementSpecs;
}
@Override
public Set<AttributeSpec> attributeSpecs() {
return attributeSpecs;
}
@Override
public String valuePrefix() {
return valuePrefix;
}
@Override
public ElementPosition position() {
return elementPosition;
}
}
public static class ElementSpecBuilder {
private final String elementName;
private String[] parents;
private RequirementLevel elementRequirementLevel;
private Cardinality elementCardinality;
private ElementPosition elementPosition;
private String valuePrefix;
private Predicate<String> allowedValuesPredicate = ALLOW_ALL_VALUES;
private Set<AttributeSpec> attributeSpecs = new LinkedHashSet<>();
private Set<ElementSpecBuilder> subElementSpecs = new LinkedHashSet<>();
private Rule<Document> applicabilityRule;
private ElementSpecBuilder(String elementName) {
this.elementName = elementName;
}
private ElementSpecBuilder with(RequirementLevel requirementLevel, Cardinality cardinality) {
elementRequirementLevel = requirementLevel;
elementCardinality = cardinality;
return this;
}
public ElementSpecBuilder inContext(String... parentElementNames) {
this.parents = parentElementNames;
return this;
}
private ElementSpecBuilder optional() {
return with(RequirementLevel.OPTIONAL, Cardinality.ONE);
}
private ElementSpecBuilder optionalRepeatable() {
return with(RequirementLevel.OPTIONAL, Cardinality.ONE_TO_N);
}
private ElementSpecBuilder recommended() {
return with(RequirementLevel.RECOMMENDED, Cardinality.ONE);
}
private ElementSpecBuilder recommendedRepeatable() {
return with(RequirementLevel.RECOMMENDED, Cardinality.ONE_TO_N);
}
private ElementSpecBuilder mandatory(Cardinality cardinality) {
return with(RequirementLevel.MANDATORY, cardinality);
}
private ElementSpecBuilder mandatoryIfApplicable(Cardinality cardinality, Rule<Document> applicabilityRule) {
this.applicabilityRule = applicabilityRule;
return with(RequirementLevel.MANDATORY_IF_APPLICABLE, cardinality);
}
public ElementSpecBuilder allowedValues(String... allowedValues) {
allowedValuesPredicate = allowedValuesPredicateFor("Element", allowedValues);
return this;
}
public ElementSpecBuilder allowedValues(Predicate<String> allowedValuesPredicate) {
this.allowedValuesPredicate = allowedValuesPredicate;
return this;
}
public ElementSpecBuilder atPosition(ElementPosition elementOccurrence) {
this.elementPosition = elementOccurrence;
return this;
}
public ElementSpecBuilder valueMustStartWith(String prefix) {
String canonical = Helper.canonicalize(prefix);
if (canonical.isEmpty()) {
throw new IllegalArgumentException("Prefix cannot be empty");
}
this.valuePrefix = canonical;
return this;
}
public ElementSpecBuilder withOptionalAttribute(String attributeName) {
return withAttribute(attributeName, RequirementLevel.OPTIONAL, ALLOW_ALL_VALUES);
}
public ElementSpecBuilder withOptionalAttribute(String attributeName, String... allowedValues) {
return withAttribute(attributeName, RequirementLevel.OPTIONAL, allowedValues);
}
public ElementSpecBuilder withOptionalAttribute(String attributeName, Predicate<String> allowedValuesPredicate) {
return withAttribute(attributeName, RequirementLevel.OPTIONAL, allowedValuesPredicate);
}
public ElementSpecBuilder withRecommendedAttribute(String attributeName) {
return withAttribute(attributeName, RequirementLevel.RECOMMENDED, ALLOW_ALL_VALUES);
}
public ElementSpecBuilder withRecommendedAttribute(String attributeName, String... allowedValues) {
return withAttribute(attributeName, RequirementLevel.RECOMMENDED, allowedValues);
}
public ElementSpecBuilder withRecommendedAttribute(String attributeName, Predicate<String> allowedValuesPredicate) {
return withAttribute(attributeName, RequirementLevel.RECOMMENDED, allowedValuesPredicate);
}
public ElementSpecBuilder withMandatoryAttribute(String attributeName) {
return withAttribute(attributeName, RequirementLevel.MANDATORY, ALLOW_ALL_VALUES);
}
public ElementSpecBuilder withMandatoryAttribute(String attributeName, String... allowedValues) {
return withAttribute(attributeName, RequirementLevel.MANDATORY, allowedValues);
}
public ElementSpecBuilder withMandatoryAttribute(String attributeName, Predicate<String> allowedValuesPredicate) {
return withAttribute(attributeName, RequirementLevel.MANDATORY, allowedValuesPredicate);
}
public ElementSpecBuilder withMandatoryIfApplicableAttribute(String attributeName,
Rule<Document> applicabilityRule) {
return withAttribute(attributeName, RequirementLevel.MANDATORY_IF_APPLICABLE, ALLOW_ALL_VALUES, applicabilityRule);
}
public ElementSpecBuilder withMandatoryIfApplicableAttribute(String attributeName,
Rule<Document> applicabilityRule,
String... allowedValues) {
return withAttribute(attributeName, RequirementLevel.MANDATORY_IF_APPLICABLE, allowedValuesPredicateFor("Attribute", allowedValues), applicabilityRule);
}
public ElementSpecBuilder withMandatoryIfApplicableAttribute(String attributeName,
Rule<Document> applicabilityRule,
Predicate<String> allowedValuesPredicate) {
return withAttribute(attributeName, RequirementLevel.MANDATORY_IF_APPLICABLE, allowedValuesPredicate, applicabilityRule);
}
public ElementSpecBuilder withSubElement(ElementSpecBuilder subElementSpecBuilder) {
subElementSpecs.add(subElementSpecBuilder);
return this;
}
public ElementSpec build() {
String canonical = Helper.ensureNonEmpty(elementName,
() -> new IllegalStateException("Element name cannot be empty"));
validate("Element", elementName, elementRequirementLevel, elementCardinality, allowedValuesPredicate, applicabilityRule);
Set<String> setOfParents = parents == null ?
Collections.emptySet() :
Stream.of(parents).filter( p -> !Helper.isEmpty(p)).collect(Collectors.toSet());
if (elementPosition == null) {
elementPosition = ElementPosition.ALL;
}
return new DefaultElementSpec(
setOfParents,
canonical,
elementRequirementLevel,
elementCardinality,
allowedValuesPredicate,
applicabilityRule,
elementPosition,
valuePrefix,
attributeSpecs,
subElementSpecs.stream().map(ElementSpecBuilder::build).collect(Collectors.toCollection(LinkedHashSet::new))
);
}
private ElementSpecBuilder withAttribute(String attrName,
RequirementLevel requirementLevel) {
return withAttribute(attrName, requirementLevel, ALLOW_ALL_VALUES, null);
}
private ElementSpecBuilder withAttribute(String attrName,
RequirementLevel requirementLevel,
String... allowedValues) {
return withAttribute(attrName, requirementLevel, allowedValuesPredicateFor("Attribute", allowedValues), null);
}
private ElementSpecBuilder withAttribute(String attrName,
RequirementLevel requirementLevel,
Predicate<String> allowedValuesPredicate) {
return withAttribute(attrName, requirementLevel, allowedValuesPredicate, null);
}
private ElementSpecBuilder withAttribute(String attrName,
RequirementLevel requirementLevel,
Predicate<String> allowedValuesPredicate,
Rule<Document> applicabilityRule) {
String canonical = Helper.ensureNonEmpty(attrName,
() -> new IllegalStateException("Attribute name cannot be empty"));
validate("Attribute", attrName, requirementLevel, allowedValuesPredicate, applicabilityRule);
attributeSpecs.add(new DefaultAttributeSpec(canonical, requirementLevel, allowedValuesPredicate, applicabilityRule));
return this;
}
}
private static Predicate<String> allowedValuesPredicateFor(String type, String... allowedValues) {
if (allowedValues == null || allowedValues.length == 0) {
throw new IllegalArgumentException(type + " allowed values cannot be empty");
}
//TODO: Confirm that ignoring case is the right thing to do.
Predicates.SetOfCaseInsensitiveAllowedValues setOfAllowedValues =
new Predicates.SetOfCaseInsensitiveAllowedValues(allowedValues);
if (setOfAllowedValues.isEmpty()) {
throw new IllegalArgumentException(type + " allowed values cannot be empty");
}
return setOfAllowedValues;
}
private static void validate(String type,
String name,
RequirementLevel requirementLevel,
Predicate<String> allowedValuesPredicate,
Rule<Document> applicabilityRule) {
if (requirementLevel == null) {
throw new IllegalStateException(type + ":" + name + " requirement level cannot be empty");
}
if (allowedValuesPredicate == null) {
throw new IllegalStateException(type + ":" + name + " allowed values predicate cannot be empty");
}
validate(type, name, requirementLevel, applicabilityRule);
}
private static void validate(String type,
String name,
RequirementLevel requirementLevel,
Cardinality cardinality,
Predicate<String> allowedValuesPredicate,
Rule<Document> applicabilityRule) {
validate(type, name, requirementLevel, allowedValuesPredicate, applicabilityRule);
if (cardinality == null) {
throw new IllegalStateException(type + ":" + name + " cardinality cannot be empty");
}
}
private static void validate(String type, String name, RequirementLevel requirementLevel, Rule<Document> applicabilityRule) {
if (requirementLevel == RequirementLevel.MANDATORY_IF_APPLICABLE &&
applicabilityRule == null) {
throw new IllegalStateException(type + ": " + name + " is invalid: required applicability rule is missing");
}
if (requirementLevel != RequirementLevel.MANDATORY_IF_APPLICABLE &&
applicabilityRule != null) {
throw new IllegalStateException(type + ": " + name + " is invalid: an applicability rule is present");
}
}
}