ModelFactory.java

/*
 * Portions of this software was developed by employees of the National Institute
 * of Standards and Technology (NIST), an agency of the Federal Government and is
 * being made available as a public service. Pursuant to title 17 United States
 * Code Section 105, works of NIST employees are not subject to copyright
 * protection in the United States. This software may be subject to foreign
 * copyright. Permission in the United States and in foreign countries, to the
 * extent that NIST may hold copyright, to use, copy, modify, create derivative
 * works, and distribute this software and its documentation without fee is hereby
 * granted on a non-exclusive basis, provided that this notice and disclaimer
 * of warranty appears in all copies.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
 */

package gov.nist.secauto.metaschema.core.model.xml;

import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
import gov.nist.secauto.metaschema.core.model.IModule;
import gov.nist.secauto.metaschema.core.model.constraint.AbstractConstraint.AbstractConstraintBuilder;
import gov.nist.secauto.metaschema.core.model.constraint.AbstractKeyConstraint.AbstractKeyConstraintBuilder;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultAllowedValue;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultAllowedValuesConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultCardinalityConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultExpectConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultIndexConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultIndexHasKeyConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultKeyField;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultMatchesConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.DefaultUniqueConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.ISource;
import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.AllowedValuesType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.EnumType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ExpectConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.HasCardinalityConstraintType;
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.ScopedAllowedValuesType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ScopedExpectConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ScopedIndexConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ScopedIndexHasKeyConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ScopedKeyConstraintType;
import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ScopedMatchesConstraintType;
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;

@SuppressWarnings("PMD.CouplingBetweenObjects")
final class ModelFactory {
  private ModelFactory() {
    // disable
  }

  @NonNull
  private static MetapathExpression target(@Nullable MetapathExpression target) {
    return target == null ? IConstraint.DEFAULT_TARGET : 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);
  }

  @SuppressWarnings("null")
  @NonNull
  static Map<QName, Set<String>> toProperties(@NonNull List<PropertyType> properties) {
    return properties.stream()
        .map(prop -> {
          String name = prop.getName();
          String namespace = prop.isSetNamespace() ? prop.getNamespace() : IModule.METASCHEMA_XML_NS;
          QName qname = new QName(namespace, name);
          String value = prop.getValue();

          return Map.entry(qname, value);
        })
        .collect(Collectors.groupingBy(Map.Entry<QName, String>::getKey,
            Collectors.mapping(Map.Entry<QName, String>::getValue, Collectors.toSet())));
  }

  @NonNull
  static Map<String, DefaultAllowedValue> toAllowedValues(@NonNull AllowedValuesType xmlConstraint) {
    Map<String, DefaultAllowedValue> allowedValues // NOPMD - intentional
        = new LinkedHashMap<>(xmlConstraint.sizeOfEnumArray());
    for (EnumType xmlEnum : xmlConstraint.getEnumList()) {
      @SuppressWarnings("null") DefaultAllowedValue allowedValue = new DefaultAllowedValue( // NOPMD - intentional
          xmlEnum.getValue(),
          MarkupStringConverter.toMarkupString(xmlEnum));
      allowedValues.put(allowedValue.getValue(), allowedValue);
    }
    return CollectionUtil.unmodifiableMap(allowedValues);
  }

  @NonNull
  static DefaultAllowedValuesConstraint newAllowedValuesConstraint(
      @NonNull ScopedAllowedValuesType xmlConstraint,
      @NonNull ISource source) {
    return newAllowedValuesConstraint(xmlConstraint, target(xmlConstraint.getTarget()), source);
  }

  @NonNull
  static DefaultAllowedValuesConstraint newAllowedValuesConstraint(
      @NonNull AllowedValuesType xmlConstraint,
      @NonNull ISource source) {
    return newAllowedValuesConstraint(xmlConstraint, MetapathExpression.CONTEXT_NODE, source);
  }

  @NonNull
  static DefaultAllowedValuesConstraint newAllowedValuesConstraint(
      @NonNull AllowedValuesType xmlConstraint,
      @NonNull MetapathExpression target,
      @NonNull ISource source) {

    DefaultAllowedValuesConstraint.Builder builder = DefaultAllowedValuesConstraint.builder();

    applyToBuilder(xmlConstraint, target, source, builder);

    if (xmlConstraint.isSetRemarks()) {
      builder.remarks(remarks(ObjectUtils.notNull(xmlConstraint.getRemarks())));
    }

    builder.allowedValues(toAllowedValues(xmlConstraint));
    if (xmlConstraint.isSetAllowOther()) {
      builder.allowedOther(xmlConstraint.getAllowOther());
    }
    if (xmlConstraint.isSetExtensible()) {
      builder.extensible(ObjectUtils.notNull(xmlConstraint.getExtensible()));
    }

    return builder.build();
  }

  @NonNull
  private static <T extends AbstractConstraintBuilder<T, ?>> T applyToBuilder(
      @NonNull ConstraintType xmlConstraint,
      @NonNull MetapathExpression target,
      @NonNull ISource source,
      @NonNull T builder) {

    if (xmlConstraint.isSetId()) {
      builder.identifier(ObjectUtils.notNull(xmlConstraint.getId()));
    }
    builder.target(target);
    builder.source(source);
    builder.level(level(xmlConstraint.getLevel()));
    return builder;
  }

  @NonNull
  static DefaultMatchesConstraint newMatchesConstraint(
      @NonNull ScopedMatchesConstraintType xmlConstraint,
      @NonNull ISource source) {
    return newMatchesConstraint(xmlConstraint, target(xmlConstraint.getTarget()), source);
  }

  @NonNull
  static DefaultMatchesConstraint newMatchesConstraint(
      @NonNull MatchesConstraintType xmlConstraint,
      @NonNull ISource source) {
    return newMatchesConstraint(xmlConstraint, MetapathExpression.CONTEXT_NODE, source);
  }

  @NonNull
  static DefaultMatchesConstraint newMatchesConstraint(
      @NonNull MatchesConstraintType xmlConstraint,
      @NonNull MetapathExpression target,
      @NonNull ISource source) {
    DefaultMatchesConstraint.Builder builder = DefaultMatchesConstraint.builder();

    applyToBuilder(xmlConstraint, target, source, builder);

    if (xmlConstraint.isSetRemarks()) {
      builder.remarks(remarks(ObjectUtils.notNull(xmlConstraint.getRemarks())));
    }

    if (xmlConstraint.isSetRegex()) {
      builder.regex(ObjectUtils.notNull(xmlConstraint.getRegex()));
    }
    if (xmlConstraint.isSetDatatype()) {
      builder.datatype(ObjectUtils.notNull(xmlConstraint.getDatatype()));
    }

    return builder.build();
  }

  static void buildKeyFields(
      @NonNull KeyConstraintType xmlConstraint,
      @NonNull AbstractKeyConstraintBuilder<?, ?> builder) {
    for (KeyConstraintType.KeyField xmlKeyField : xmlConstraint.getKeyFieldList()) {
      @SuppressWarnings("null") DefaultKeyField keyField
          = new DefaultKeyField( // NOPMD - intentional
              xmlKeyField.getTarget(),
              xmlKeyField.isSetPattern() ? xmlKeyField.getPattern() : null, // NOPMD - intentional
              xmlKeyField.isSetRemarks() ? remarks(xmlKeyField.getRemarks()) : null);
      builder.keyField(keyField);
    }
  }

  @NonNull
  static DefaultUniqueConstraint newUniqueConstraint(
      @NonNull ScopedKeyConstraintType xmlConstraint,
      @NonNull ISource source) {
    DefaultUniqueConstraint.Builder builder = DefaultUniqueConstraint.builder();

    applyToBuilder(xmlConstraint, target(xmlConstraint.getTarget()), source, builder);

    if (xmlConstraint.isSetRemarks()) {
      builder.remarks(remarks(ObjectUtils.notNull(xmlConstraint.getRemarks())));
    }

    buildKeyFields(xmlConstraint, builder);

    return builder.build();
  }

  @NonNull
  static DefaultIndexConstraint newIndexConstraint(
      @NonNull ScopedIndexConstraintType xmlConstraint,
      @NonNull ISource source) {
    DefaultIndexConstraint.Builder builder = DefaultIndexConstraint.builder();

    applyToBuilder(xmlConstraint, target(xmlConstraint.getTarget()), source, builder);

    if (xmlConstraint.isSetRemarks()) {
      builder.remarks(remarks(ObjectUtils.notNull(xmlConstraint.getRemarks())));
    }

    builder.name(ObjectUtils.requireNonNull(xmlConstraint.getName()));
    buildKeyFields(xmlConstraint, builder);

    return builder.build();
  }

  @NonNull
  static DefaultIndexHasKeyConstraint newIndexHasKeyConstraint(
      @NonNull ScopedIndexHasKeyConstraintType xmlConstraint,
      @NonNull ISource source) {
    return newIndexHasKeyConstraint(xmlConstraint, target(xmlConstraint.getTarget()), source);
  }

  @NonNull
  static DefaultIndexHasKeyConstraint newIndexHasKeyConstraint(
      @NonNull IndexHasKeyConstraintType xmlConstraint,
      @NonNull ISource source) {
    return newIndexHasKeyConstraint(xmlConstraint, MetapathExpression.CONTEXT_NODE, source);

  }

  @NonNull
  static DefaultIndexHasKeyConstraint newIndexHasKeyConstraint(
      @NonNull IndexHasKeyConstraintType xmlConstraint,
      @NonNull MetapathExpression target,
      @NonNull ISource source) {
    DefaultIndexHasKeyConstraint.Builder builder = DefaultIndexHasKeyConstraint.builder();

    applyToBuilder(xmlConstraint, target, source, builder);

    if (xmlConstraint.isSetRemarks()) {
      builder.remarks(remarks(ObjectUtils.notNull(xmlConstraint.getRemarks())));
    }

    builder.name(ObjectUtils.requireNonNull(xmlConstraint.getName()));
    buildKeyFields(xmlConstraint, builder);

    return builder.build();
  }

  @NonNull
  static DefaultExpectConstraint newExpectConstraint(
      @NonNull ScopedExpectConstraintType xmlConstraint,
      @NonNull ISource source) {
    return newExpectConstraint(xmlConstraint, target(xmlConstraint.getTarget()), source);
  }

  @NonNull
  static DefaultExpectConstraint newExpectConstraint(
      @NonNull ExpectConstraintType xmlConstraint,
      @NonNull ISource source) {
    return newExpectConstraint(xmlConstraint, MetapathExpression.CONTEXT_NODE, source);
  }

  @NonNull
  static DefaultExpectConstraint newExpectConstraint(
      @NonNull ExpectConstraintType xmlConstraint,
      @NonNull MetapathExpression target,
      @NonNull ISource source) {

    DefaultExpectConstraint.Builder builder = DefaultExpectConstraint.builder();

    applyToBuilder(xmlConstraint, target, source, builder);

    if (xmlConstraint.isSetRemarks()) {
      builder.remarks(remarks(ObjectUtils.notNull(xmlConstraint.getRemarks())));
    }

    if (xmlConstraint.isSetMessage()) {
      builder.message(ObjectUtils.notNull(xmlConstraint.getMessage()));
    }

    builder.test(ObjectUtils.requireNonNull(xmlConstraint.getTest()));

    return builder.build();
  }

  @NonNull
  static DefaultCardinalityConstraint newCardinalityConstraint(
      @NonNull HasCardinalityConstraintType xmlConstraint,
      @NonNull ISource source) {

    DefaultCardinalityConstraint.Builder builder = DefaultCardinalityConstraint.builder();

    applyToBuilder(xmlConstraint, target(xmlConstraint.getTarget()), source, builder);

    if (xmlConstraint.isSetRemarks()) {
      builder.remarks(remarks(ObjectUtils.notNull(xmlConstraint.getRemarks())));
    }

    if (xmlConstraint.isSetMinOccurs()) {
      builder.minOccurs(xmlConstraint.getMinOccurs().intValueExact());
    }

    if (xmlConstraint.isSetMaxOccurs()) {
      builder.maxOccurs(xmlConstraint.getMaxOccurs().intValueExact());
    }

    return builder.build();
  }
}