XmlComplexTypeAssemblyDefinition.java

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

package gov.nist.secauto.metaschema.schemagen.xml.schematype;

import gov.nist.secauto.metaschema.core.datatype.markup.MarkupDataTypeProvider;
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.IFieldInstanceAbsolute;
import gov.nist.secauto.metaschema.core.model.IFlagInstance;
import gov.nist.secauto.metaschema.core.model.IModelInstanceAbsolute;
import gov.nist.secauto.metaschema.core.model.INamedModelInstanceAbsolute;
import gov.nist.secauto.metaschema.core.model.INamedModelInstanceGrouped;
import gov.nist.secauto.metaschema.core.model.XmlGroupAsBehavior;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException;
import gov.nist.secauto.metaschema.schemagen.xml.XmlSchemaGenerator;
import gov.nist.secauto.metaschema.schemagen.xml.impl.DocumentationGenerator;
import gov.nist.secauto.metaschema.schemagen.xml.impl.XmlGenerationState;

import java.util.Collection;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

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

public class XmlComplexTypeAssemblyDefinition
    extends AbstractXmlComplexType<IAssemblyDefinition> {

  public XmlComplexTypeAssemblyDefinition(
      @NonNull QName qname,
      @NonNull IAssemblyDefinition definition) {
    super(qname, definition);
  }

  @Override
  protected void generateTypeBody(XmlGenerationState state) throws XMLStreamException {
    IAssemblyDefinition definition = getDefinition();

    Collection<? extends IModelInstanceAbsolute> modelInstances = definition.getModelInstances();
    if (!modelInstances.isEmpty()) {
      state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "sequence", XmlSchemaGenerator.NS_XML_SCHEMA);
      for (IModelInstanceAbsolute modelInstance : modelInstances) {
        assert modelInstance != null;
        generateModelInstance(modelInstance, state);
      }
      state.writeEndElement();
    }

    Collection<? extends IFlagInstance> flagInstances = definition.getFlagInstances();
    if (!flagInstances.isEmpty()) {
      for (IFlagInstance flagInstance : flagInstances) {
        assert flagInstance != null;
        generateFlagInstance(flagInstance, state);
      }
    }
  }

  protected void generateModelInstance( // NOPMD acceptable complexity
      @NonNull IModelInstanceAbsolute modelInstance,
      @NonNull XmlGenerationState state)
      throws XMLStreamException {

    boolean grouped = false;
    if (XmlGroupAsBehavior.GROUPED.equals(modelInstance.getXmlGroupAsBehavior())) {
      // handle grouping
      state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "element", XmlSchemaGenerator.NS_XML_SCHEMA);

      QName groupAsQName = ObjectUtils.requireNonNull(modelInstance.getEffectiveXmlGroupAsQName());

      if (state.getDefaultNS().equals(groupAsQName.getNamespaceURI())) {
        state.writeAttribute("name", ObjectUtils.requireNonNull(groupAsQName.getLocalPart()));
      } else {
        throw new SchemaGenerationException(
            String.format("Attempt to create element '%s' on definition '%s' with different namespace", groupAsQName,
                getDefinition().toCoordinates()));
      }

      if (modelInstance.getMinOccurs() == 0) {
        // this is an optional instance group
        state.writeAttribute("minOccurs", "0");
      }

      // now generate the child elements of the group
      state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "complexType", XmlSchemaGenerator.NS_XML_SCHEMA);
      state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "sequence", XmlSchemaGenerator.NS_XML_SCHEMA);

      // mark that we need to close these elements
      grouped = true;
    }

    switch (modelInstance.getModelType()) {
    case ASSEMBLY:
      generateNamedModelInstance((INamedModelInstanceAbsolute) modelInstance, grouped, state);
      break;
    case FIELD: {
      IFieldInstanceAbsolute fieldInstance = (IFieldInstanceAbsolute) modelInstance;
      if (fieldInstance.isEffectiveValueWrappedInXml()) {
        generateNamedModelInstance(fieldInstance, grouped, state);
      } else {
        generateUnwrappedFieldInstance(fieldInstance, grouped, state);
      }
      break;
    }
    case CHOICE:
      generateChoiceModelInstance((IChoiceInstance) modelInstance, state);
      break;
    case CHOICE_GROUP:
      generateChoiceGroupInstance((IChoiceGroupInstance) modelInstance, state);
      break;
    default:
      throw new UnsupportedOperationException(modelInstance.getModelType().toString());
    }

    if (grouped) {
      state.writeEndElement(); // xs:sequence
      state.writeEndElement(); // xs:complexType
      state.writeEndElement(); // xs:element
    }
  }

  protected void generateNamedModelInstance(
      @NonNull INamedModelInstanceAbsolute modelInstance,
      boolean grouped,
      @NonNull XmlGenerationState state) throws XMLStreamException {
    state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "element", XmlSchemaGenerator.NS_XML_SCHEMA);

    state.writeAttribute("name", modelInstance.getEffectiveName());

    // state.generateElementNameOrRef(modelInstance);

    if (!grouped && modelInstance.getMinOccurs() != 1) {
      state.writeAttribute("minOccurs", ObjectUtils.notNull(Integer.toString(modelInstance.getMinOccurs())));
    }

    if (modelInstance.getMaxOccurs() != 1) {
      state.writeAttribute("maxOccurs",
          modelInstance.getMaxOccurs() == -1 ? "unbounded"
              : ObjectUtils.notNull(Integer.toString(modelInstance.getMaxOccurs())));
    }

    IXmlType type = state.getXmlForDefinition(modelInstance.getDefinition());
    if (type.isGeneratedType(state) && type.isInline(state)) {
      DocumentationGenerator.generateDocumentation(modelInstance, state);
      type.generate(state);
    } else {
      state.writeAttribute("type", type.getTypeReference());
      DocumentationGenerator.generateDocumentation(modelInstance, state);
    }
    state.writeEndElement(); // xs:element
  }

  protected static void generateUnwrappedFieldInstance(
      @NonNull IFieldInstanceAbsolute fieldInstance,
      boolean grouped,
      @NonNull XmlGenerationState state) throws XMLStreamException {

    if (!MarkupDataTypeProvider.MARKUP_MULTILINE.equals(fieldInstance.getDefinition().getJavaTypeAdapter())) {
      throw new IllegalStateException();
    }

    state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "group", XmlSchemaGenerator.NS_XML_SCHEMA);

    state.writeAttribute("ref", "blockElementGroup");

    // minOccurs=1 is the schema default
    if (!grouped && fieldInstance.getMinOccurs() != 1) {
      state.writeAttribute("minOccurs", ObjectUtils.notNull(Integer.toString(fieldInstance.getMinOccurs())));
    }

    // if (fieldInstance.getMaxOccurs() != 1) {
    // state.writeAttribute("maxOccurs",
    // fieldInstance.getMaxOccurs() == -1 ? "unbounded"
    // : ObjectUtils.notNull(Integer.toString(fieldInstance.getMaxOccurs())));
    // }

    // unwrapped fields always have a max-occurance of 1. Since the markup multiline
    // is unbounded, this
    // value is unbounded.
    state.writeAttribute("maxOccurs", "unbounded");

    DocumentationGenerator.generateDocumentation(fieldInstance, state);

    state.writeEndElement(); // xs:group
  }

  protected void generateChoiceModelInstance(
      @NonNull IChoiceInstance choice,
      @NonNull XmlGenerationState state) throws XMLStreamException {
    state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "choice", XmlSchemaGenerator.NS_XML_SCHEMA);

    for (IModelInstanceAbsolute instance : choice.getModelInstances()) {
      assert instance != null;

      if (instance instanceof IChoiceInstance) {
        generateChoiceModelInstance((IChoiceInstance) instance, state);
      } else {
        generateModelInstance(instance, state);
      }
    }

    state.writeEndElement(); // xs:choice
  }

  private void generateChoiceGroupInstance(IChoiceGroupInstance choiceGroup, XmlGenerationState state)
      throws XMLStreamException {
    state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "choice", XmlSchemaGenerator.NS_XML_SCHEMA);

    int min = choiceGroup.getMinOccurs();
    if (min != 1) {
      state.writeAttribute("minOccurs", ObjectUtils.notNull(Integer.toString(min)));
    }

    int max = choiceGroup.getMaxOccurs();
    if (max < 0) {
      state.writeAttribute("maxOccurs", "unbounded");
    } else if (max > 1) {
      state.writeAttribute("maxOccurs", ObjectUtils.notNull(Integer.toString(max)));
    }

    for (INamedModelInstanceGrouped instance : choiceGroup.getNamedModelInstances()) {
      assert instance != null;

      generateGroupedNamedModelInstance(instance, state);
    }

    state.writeEndElement(); // xs:choice
  }

  protected void generateGroupedNamedModelInstance(
      @NonNull INamedModelInstanceGrouped instance,
      @NonNull XmlGenerationState state) throws XMLStreamException {
    state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "element", XmlSchemaGenerator.NS_XML_SCHEMA);

    state.writeAttribute("name", instance.getEffectiveName());

    // state.generateElementNameOrRef(modelInstance);

    IXmlType type = state.getXmlForDefinition(instance.getDefinition());
    if (type.isGeneratedType(state) && type.isInline(state)) {
      DocumentationGenerator.generateDocumentation(instance, state);
      type.generate(state);
    } else {
      state.writeAttribute("type", type.getTypeReference());
      DocumentationGenerator.generateDocumentation(instance, state);
    }
    state.writeEndElement(); // xs:element
  }
}