DefaultContainerModelSupport.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.model.IAssemblyInstance;
import gov.nist.secauto.metaschema.core.model.IContainerModelSupport;
import gov.nist.secauto.metaschema.core.model.IFieldInstance;
import gov.nist.secauto.metaschema.core.model.IModelInstance;
import gov.nist.secauto.metaschema.core.model.INamedModelInstance;
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Provides model container support.
 * <p>
 * This class supports generic model instance operations on model instances.
 * <p>
 * This implementation uses underlying {@link LinkedHashMap} instances to
 * preserve ordering.
 *
 * @param <MI>
 *          the model instance Java type
 * @param <NMI>
 *          the named model instance Java type
 * @param <FI>
 *          the field instance Java type
 * @param <AI>
 *          the assembly instance Java type
 */
public class DefaultContainerModelSupport<
    MI extends IModelInstance,
    NMI extends INamedModelInstance,
    FI extends IFieldInstance,
    AI extends IAssemblyInstance>
    implements IContainerModelSupport<MI, NMI, FI, AI> {

  @SuppressWarnings("rawtypes")
  public static final DefaultContainerModelSupport EMPTY = new DefaultContainerModelSupport<>(
      CollectionUtil.emptyList(),
      CollectionUtil.emptyMap(),
      CollectionUtil.emptyMap(),
      CollectionUtil.emptyMap());

  @NonNull
  private final List<MI> modelInstances;
  @NonNull
  private final Map<QName, NMI> namedModelInstances;
  @NonNull
  private final Map<QName, FI> fieldInstances;
  @NonNull
  private final Map<QName, AI> assemblyInstances;

  /**
   * Construct an empty, mutable container.
   */
  @SuppressWarnings("PMD.UseConcurrentHashMap")
  public DefaultContainerModelSupport() {
    this(
        new LinkedList<>(),
        new LinkedHashMap<>(),
        new LinkedHashMap<>(),
        new LinkedHashMap<>());
  }

  /**
   * Construct an immutable container from a collection of model instances.
   *
   * @param instances
   *          the collection of model instances to add to the new container.
   * @param namedModelClass
   *          the Java type for named model instances
   * @param fieldClass
   *          the Java type for field instances
   * @param assemblyClass
   *          the Java type for assembly instances
   */
  @SuppressWarnings({ "PMD.UseConcurrentHashMap" })
  @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
  protected DefaultContainerModelSupport(
      @NonNull Collection<MI> instances,
      @NonNull Class<NMI> namedModelClass,
      @NonNull Class<FI> fieldClass,
      @NonNull Class<AI> assemblyClass) {
    assert namedModelClass.isAssignableFrom(fieldClass) : String.format(
        "The field class '%s' is not assignment compatible to class '%s'.",
        fieldClass.getName(),
        namedModelClass.getName());
    assert namedModelClass.isAssignableFrom(assemblyClass) : String.format(
        "The assembly class '%s' is not assignment compatible to class '%s'.",
        assemblyClass.getName(),
        namedModelClass.getName());
    assert !fieldClass.isAssignableFrom(assemblyClass) : String.format(
        "The field class '%s' must not be assignment compatible to the assembly class '%s'.",
        fieldClass.getName(),
        assemblyClass.getName());

    this.modelInstances = ObjectUtils.notNull(List.copyOf(instances));

    Map<QName, NMI> namedModelInstances = new LinkedHashMap<>();
    Map<QName, FI> fieldInstances = new LinkedHashMap<>();
    Map<QName, AI> assemblyInstances = new LinkedHashMap<>();
    for (MI instance : instances) {
      if (namedModelClass.isInstance(instance)) {
        NMI named = namedModelClass.cast(instance);
        QName key = named.getXmlQName();
        namedModelInstances.put(key, named);

        if (fieldClass.isInstance(instance)) {
          fieldInstances.put(key, fieldClass.cast(named));
        } else if (assemblyClass.isInstance(instance)) {
          assemblyInstances.put(key, assemblyClass.cast(named));
        }
      }
    }

    this.namedModelInstances = namedModelInstances.isEmpty()
        ? CollectionUtil.emptyMap()
        : CollectionUtil.unmodifiableMap(namedModelInstances);
    this.fieldInstances = fieldInstances.isEmpty()
        ? CollectionUtil.emptyMap()
        : CollectionUtil.unmodifiableMap(fieldInstances);
    this.assemblyInstances = assemblyInstances.isEmpty()
        ? CollectionUtil.emptyMap()
        : CollectionUtil.unmodifiableMap(assemblyInstances);
  }

  /**
   * Construct an new container using the provided collections.
   *
   * @param modelInstances
   *          a collection of model instances
   * @param namedModelInstances
   *          a collection of named model instances
   * @param fieldInstances
   *          a collection of field instances
   * @param assemblyInstances
   *          a collection of assembly instances
   */
  protected DefaultContainerModelSupport(
      @NonNull List<MI> modelInstances,
      @NonNull Map<QName, NMI> namedModelInstances,
      @NonNull Map<QName, FI> fieldInstances,
      @NonNull Map<QName, AI> assemblyInstances) {
    this.modelInstances = modelInstances;
    this.namedModelInstances = namedModelInstances;
    this.fieldInstances = fieldInstances;
    this.assemblyInstances = assemblyInstances;
  }

  @Override
  public List<MI> getModelInstances() {
    return modelInstances;
  }

  @Override
  public Map<QName, NMI> getNamedModelInstanceMap() {
    return namedModelInstances;
  }

  @Override
  public Map<QName, FI> getFieldInstanceMap() {
    return fieldInstances;
  }

  @Override
  public Map<QName, AI> getAssemblyInstanceMap() {
    return assemblyInstances;
  }
}