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 ElementSpecBuilder forMandatoryIfApplicableElement(String elementName, Cardinality cardinality, Rule 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 allowedValuesPredicate) { return Helper.buildElement(elementName, requirementLevel, cardinality).allowedValues(allowedValuesPredicate); } static final Predicate 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 allowedValuesPredicate; private final Rule applicabilityRule; DefaultNodeSpec(String name, RequirementLevel requirementLevel, Cardinality cardinality, Predicate allowedValuesPredicate, Rule 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 allowedValuesPredicate() { return allowedValuesPredicate; } @Override public Rule applicabilityRule() { return applicabilityRule; } } private static class DefaultAttributeSpec extends DefaultNodeSpec implements AttributeSpec { DefaultAttributeSpec(String name, RequirementLevel requirementLevel, Predicate allowedValuesPredicate, Rule applicabilityRule) { super(name, requirementLevel, Cardinality.ONE, allowedValuesPredicate, applicabilityRule); } } private static class DefaultElementSpec extends DefaultNodeSpec implements ElementSpec { private final Set parents; private final Set attributeSpecs; private final Set subElementSpecs; private final String valuePrefix; private final ElementPosition elementPosition; DefaultElementSpec(Set parents, String name, RequirementLevel requirementLevel, Cardinality cardinality, Predicate allowedValuesPredicate, Rule applicabilityRule, ElementPosition elementPosition, String valuePrefix, Set attributeSpecs, Set 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 parents() { return parents; } @Override public Set subElementSpecs() { return subElementSpecs; } @Override public Set 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 allowedValuesPredicate = ALLOW_ALL_VALUES; private Set attributeSpecs = new LinkedHashSet<>(); private Set subElementSpecs = new LinkedHashSet<>(); private Rule 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 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 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 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 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 allowedValuesPredicate) { return withAttribute(attributeName, RequirementLevel.MANDATORY, allowedValuesPredicate); } public ElementSpecBuilder withMandatoryIfApplicableAttribute(String attributeName, Rule applicabilityRule) { return withAttribute(attributeName, RequirementLevel.MANDATORY_IF_APPLICABLE, ALLOW_ALL_VALUES, applicabilityRule); } public ElementSpecBuilder withMandatoryIfApplicableAttribute(String attributeName, Rule applicabilityRule, String... allowedValues) { return withAttribute(attributeName, RequirementLevel.MANDATORY_IF_APPLICABLE, allowedValuesPredicateFor("Attribute", allowedValues), applicabilityRule); } public ElementSpecBuilder withMandatoryIfApplicableAttribute(String attributeName, Rule applicabilityRule, Predicate 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 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 allowedValuesPredicate) { return withAttribute(attrName, requirementLevel, allowedValuesPredicate, null); } private ElementSpecBuilder withAttribute(String attrName, RequirementLevel requirementLevel, Predicate allowedValuesPredicate, Rule 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 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 allowedValuesPredicate, Rule 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 allowedValuesPredicate, Rule 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 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"); } } }