XmlComplexTypeAssemblyDefinition.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.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.IChoiceInstance;
import gov.nist.secauto.metaschema.core.model.IFieldInstance;
import gov.nist.secauto.metaschema.core.model.IFlagContainer;
import gov.nist.secauto.metaschema.core.model.IFlagInstance;
import gov.nist.secauto.metaschema.core.model.IModelInstance;
import gov.nist.secauto.metaschema.core.model.INamedModelInstance;
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 IModelInstance> modelInstances = definition.getModelInstances();
    if (!modelInstances.isEmpty()) {
      state.writeStartElement(XmlSchemaGenerator.PREFIX_XML_SCHEMA, "sequence", XmlSchemaGenerator.NS_XML_SCHEMA);
      for (IModelInstance 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 IModelInstance 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.getXmlGroupAsQName());

      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((INamedModelInstance) modelInstance, grouped, state);
      break;
    case FIELD: {
      IFieldInstance fieldInstance = (IFieldInstance) modelInstance;
      if (!fieldInstance.isInXmlWrapped()
          && fieldInstance.getDefinition().getJavaTypeAdapter().isUnrappedValueAllowedInXml()) {
        generateUnwrappedFieldInstance(fieldInstance, grouped, state);
      } else {
        generateNamedModelInstance(fieldInstance, grouped, state);
      }
      break;
    }
    case CHOICE:
      generateChoiceModelInstance((IChoiceInstance) modelInstance, state);
      break;
    default:
      throw new UnsupportedOperationException();
    }

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

  protected void generateNamedModelInstance(
      @NonNull INamedModelInstance 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())));
    }

    IFlagContainer definition = modelInstance.getDefinition();
    IXmlType type = state.getTypeForDefinition(definition);

    if (state.isInline(definition)) {
      DocumentationGenerator.generateDocumentation(modelInstance, state);
      type.generateType(state, true);
    } else {
      state.writeAttribute("type", type.getTypeReference());
      DocumentationGenerator.generateDocumentation(modelInstance, state);
    }
    state.writeEndElement(); // xs:element
  }

  protected static void generateUnwrappedFieldInstance(
      @NonNull IFieldInstance 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 (IModelInstance instance : choice.getModelInstances()) {
      assert instance != null;

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

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