DefaultDiagramNode.java

/*
 * SPDX-FileCopyrightText: none
 * SPDX-License-Identifier: CC0-1.0
 */

package gov.nist.secauto.metaschema.core.util;

import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
import gov.nist.secauto.metaschema.core.model.IChoiceGroupInstance;
import gov.nist.secauto.metaschema.core.model.IChoiceInstance;
import gov.nist.secauto.metaschema.core.model.IModelDefinition;
import gov.nist.secauto.metaschema.core.model.IModelInstance;
import gov.nist.secauto.metaschema.core.model.INamedInstance;
import gov.nist.secauto.metaschema.core.model.INamedModelInstance;
import gov.nist.secauto.metaschema.core.model.INamedModelInstanceAbsolute;
import gov.nist.secauto.metaschema.core.model.INamedModelInstanceGrouped;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import edu.umd.cs.findbugs.annotations.NonNull;
import nl.talsmasoftware.lazy4j.Lazy;

/**
 * A basic implementation of a {@link IDiagramNodeVisitor}.
 */
@SuppressWarnings("PMD.DataClass")
public class DefaultDiagramNode implements IDiagramNode {
  @NonNull
  private final IModelDefinition definition;
  @NonNull
  private final String name;
  @NonNull
  private final List<IDiagramNode.IAttribute> attributes;
  @NonNull
  private final Lazy<List<IDiagramNode.IEdge>> edges;

  @NonNull
  private static String getParentContext(@NonNull IModelDefinition definition) {
    String retval;
    if (definition.isInline()) {
      INamedInstance instance = definition.getInlineInstance();
      String name = instance.getEffectiveName();
      retval = getParentContext(instance.getContainingDefinition()) + "_" + name;
    } else {
      retval = definition.getName();
    }
    return retval;
  }

  /**
   * Construct a new diagram node.
   *
   * @param definition
   *          the definition to base the node on
   */
  public DefaultDiagramNode(
      @NonNull IModelDefinition definition) {
    this.definition = definition;
    this.name = getParentContext(definition);
    this.attributes = ObjectUtils.notNull(Stream.concat(
        definition.getFlagInstances().stream()
            // all flags
            .map((flag) -> new Attribute(flag.getEffectiveName(), flag.getDefinition().getJavaTypeAdapter())),
        definition instanceof IAssemblyDefinition
            ? ((IAssemblyDefinition) definition).getFieldInstances().stream()
                // singleton fields with no flags
                .filter(field -> !INamedModelInstance.complexObjectFilter(field))
                .map(field -> new Attribute(field.getEffectiveName(), field.getDefinition().getJavaTypeAdapter()))
            : Stream.empty())
        .collect(Collectors.toUnmodifiableList()));
    this.edges = ObjectUtils.notNull(Lazy.lazy(() -> definition instanceof IAssemblyDefinition
        ? generateEdges((IAssemblyDefinition) definition)
        : CollectionUtil.emptyList()));
  }

  @NonNull
  private List<IDiagramNode.IEdge> generateEdges(
      @NonNull IAssemblyDefinition definition) {
    return ObjectUtils.notNull(definition.getModelInstances().stream()
        .flatMap(instance -> {
          Stream<AbstractEdge<?>> retval;
          if (instance instanceof IChoiceInstance) {
            IChoiceInstance choice = (IChoiceInstance) instance;
            retval = choice.getNamedModelInstances().stream()
                .filter(INamedModelInstance::complexObjectFilter)
                .map(ci -> new ChoiceEdge(choice, ObjectUtils.requireNonNull(ci)));
          } else if (instance instanceof IChoiceGroupInstance) {
            IChoiceGroupInstance choiceGroup = (IChoiceGroupInstance) instance;
            retval = choiceGroup.getNamedModelInstances().stream()
                .filter(INamedModelInstance::complexObjectFilter)
                .map(cgi -> new ChoiceGroupEdge(choiceGroup, ObjectUtils.requireNonNull(cgi)));
          } else {
            INamedModelInstanceAbsolute modelInstance = (INamedModelInstanceAbsolute) instance;
            retval = INamedModelInstance.complexObjectFilter(modelInstance)
                ? Stream.of(new ModelEdge(ObjectUtils.requireNonNull(modelInstance)))
                : Stream.empty();
          }
          return retval;
        })
        .collect(Collectors.toUnmodifiableList()));
  }

  @Override
  @NonNull
  public IModelDefinition getDefinition() {
    return definition;
  }

  @Override
  @NonNull
  public String getIdentifier() {
    return name;
  }

  @Override
  public List<IAttribute> getAttributes() {
    return attributes;
  }

  @Override
  public List<IEdge> getEdges() {
    return ObjectUtils.notNull(edges.get());
  }

  private final class Attribute implements IDiagramNode.IAttribute {
    @NonNull
    private final String name;
    @NonNull
    private final IDataTypeAdapter<?> dataType;

    private Attribute(@NonNull String name, @NonNull IDataTypeAdapter<?> dataType) {
      this.name = name;
      this.dataType = dataType;
    }

    @Override
    public IDiagramNode getNode() {
      return DefaultDiagramNode.this;
    }

    @Override
    @NonNull
    public String getLabel() {
      return name;
    }

    @Override
    @NonNull
    public IDataTypeAdapter<?> getDataType() {
      return dataType;
    }
  }

  private abstract class AbstractEdge<T extends IModelInstance> implements IDiagramNode.IEdge {
    @NonNull
    private final Relationship relationship;
    @NonNull
    private final T instance;

    protected AbstractEdge(
        @NonNull Relationship relationship,
        @NonNull T instance) {
      this.relationship = relationship;
      this.instance = instance;
    }

    @Override
    public IDiagramNode getNode() {
      return DefaultDiagramNode.this;
    }

    @Override
    @NonNull
    public Relationship getRelationship() {
      return relationship;
    }

    @Override
    @NonNull
    public T getInstance() {
      return instance;
    }
  }

  public final class ModelEdge
      extends AbstractEdge<INamedModelInstanceAbsolute> {
    private ModelEdge(
        @NonNull INamedModelInstanceAbsolute instance) {
      super(Relationship.toRelationship(instance), instance);
    }

    @Override
    public void accept(IDiagramNodeVisitor visitor) {
      visitor.visit(this);
    }
  }

  public final class ChoiceEdge
      extends AbstractEdge<INamedModelInstanceAbsolute> {
    @NonNull
    private final IChoiceInstance choice;

    private ChoiceEdge(
        @NonNull IChoiceInstance choice,
        @NonNull INamedModelInstanceAbsolute instance) {
      super(Relationship.toRelationship(instance), instance);
      this.choice = choice;
    }

    /**
     * Get the associated choice.
     *
     * @return the choice instance
     */
    @NonNull
    public IChoiceInstance getChoice() {
      return choice;
    }

    @Override
    public void accept(IDiagramNodeVisitor visitor) {
      visitor.visit(this);
    }
  }

  public final class ChoiceGroupEdge
      extends AbstractEdge<INamedModelInstanceGrouped> {
    @NonNull
    private final IChoiceGroupInstance choiceGroup;

    private ChoiceGroupEdge(
        @NonNull IChoiceGroupInstance choiceGroup,
        @NonNull INamedModelInstanceGrouped instance) {
      super(Relationship.toRelationship(choiceGroup), instance);
      this.choiceGroup = choiceGroup;
    }

    /**
     * Get the associated choice group.
     *
     * @return the choice group instance
     */
    @NonNull
    public IChoiceGroupInstance getChoiceGroup() {
      return choiceGroup;
    }

    @Override
    public void accept(IDiagramNodeVisitor visitor) {
      visitor.visit(this);
    }
  }
}