@SuppressWarnings("PMD.AvoidReassigningParameters") // makes code simpler
  private NeutralSchema parseComplexType(
      XmlSchemaComplexType schemaComplexType, NeutralSchema complexSchema, XmlSchema schema) {

    if ((schemaComplexType.getContentModel() != null)
        && (schemaComplexType.getContentModel().getContent() != null)) {
      XmlSchemaContent content = schemaComplexType.getContentModel().getContent();
      if (content instanceof XmlSchemaComplexContentExtension) {
        XmlSchemaComplexContentExtension schemaComplexContent =
            (XmlSchemaComplexContentExtension) content;
        XmlSchemaComplexType complexBaseType = getComplexBaseType(schemaComplexContent, schema);
        if (complexBaseType != null) {
          complexSchema = parseComplexType(complexBaseType, complexSchema, schema);
        }
        this.parseFields(schemaComplexContent, complexSchema, schema);

      } else if (content instanceof XmlSchemaSimpleContentExtension) {
        QName baseTypeName = ((XmlSchemaSimpleContentExtension) content).getBaseTypeName();
        NeutralSchema simpleContentSchema = schemaFactory.createSchema(baseTypeName);
        complexSchema.addField(complexSchema.getType(), simpleContentSchema);

        parseAttributes(
            ((XmlSchemaSimpleContentExtension) content).getAttributes(), complexSchema, schema);
      }
    }

    // Annotations are inherited by ComplexType fields so we need to parse these first.
    parseAnnotations(complexSchema, schemaComplexType);

    this.parseFields(schemaComplexType, complexSchema, schema);

    // Check for ChoiceSchemas. We only support complex types that contain choice if choice is
    // the ONLY element. If we find one, swap out the current ComplexSchema object that contains
    // this single choice schema for the actual choice schema itself.
    for (NeutralSchema ns : complexSchema.getFields().values()) {
      if (ns instanceof ChoiceSchema) {
        if (complexSchema.getFields().size() > 1 && !mergedChoiceSchemas.contains(ns)) {
          throw new RuntimeException(
              "Choice elements are only supported on complex objects with no other fields: "
                  + schemaComplexType.getName());
        } else if (!mergedChoiceSchemas.contains(ns)) {
          ns.setType(complexSchema.getType());
          complexSchema.getAppInfo();
          mergedChoiceSchemas.add(ns);
          if (ns.getType().equals("serviceDescriptorType")) {
            ns.addAnnotation(complexSchema.getAppInfo());
          }
          return ns;
        }
      }
    }

    return complexSchema;
  }
  private void parseAttributes(
      XmlSchemaObjectCollection attributes, NeutralSchema complexSchema, XmlSchema schema) {

    if (attributes != null) {
      for (int i = 0; i < attributes.getCount(); i++) {
        XmlSchemaAttribute attribute = (XmlSchemaAttribute) attributes.getItem(i);
        QName attributeTypeName = attribute.getSchemaTypeName();

        XmlSchemaType attributeSchemaType = attribute.getSchemaType();

        if (attribute.getName() != null) {

          String attributeName = attribute.getName();

          // Derive Attribute Schema
          NeutralSchema attributeSchema = null;
          if (attributeSchemaType != null) {
            attributeSchema = parse(attributeSchemaType, schema);
          } else if (attributeTypeName != null) {
            attributeSchema = getSchemaFactory().createSchema(attributeTypeName);
          }

          // Update Neutral Schema Field
          if (attributeSchema != null) {

            // Optional Attributes
            if (attribute.getUse().equals(REQUIRED_USE)) {

              AppInfo info = attributeSchema.getAppInfo();

              if (info == null) {
                info = new AppInfo(null);
              }

              info.put(REQUIRED_USE.getValue(), "true");
              attributeSchema.addAnnotation(info);
            }

            complexSchema.addField(attributeName, attributeSchema);
          }
        }
      }
    }
  }
  private void parseParticle(
      XmlSchemaParticle particle, NeutralSchema complexSchema, XmlSchema schema) {

    if (particle != null) {
      if (particle instanceof XmlSchemaElement) {
        XmlSchemaElement element = (XmlSchemaElement) particle;

        NeutralSchema elementSchema = parseElement(element, schema);

        String elementName = element.getName();

        // Required Elements
        if (!element.isNillable() && (element.getMinOccurs() > 0)) {
          AppInfo info = elementSchema.getAppInfo();
          if (info == null) {
            info = new AppInfo(null);
          }
          info.put(REQUIRED_USE.getValue(), "true");
          elementSchema.addAnnotation(info);
        }

        // Update Neutral Schema Field
        complexSchema.addField(elementName, elementSchema);

        // DE840 address.addressLines were not being encrypted
        for (Map.Entry<String, NeutralSchema> entry : elementSchema.getFields().entrySet()) {
          String fieldName = entry.getKey();
          NeutralSchema fieldSchema = entry.getValue();
          fieldSchema.inheritAnnotations(complexSchema.getAppInfo());
        }

      } else if (particle instanceof XmlSchemaSequence) {
        XmlSchemaSequence schemaSequence = (XmlSchemaSequence) particle;
        for (int i = 0; i < schemaSequence.getItems().getCount(); i++) {
          XmlSchemaObject item = schemaSequence.getItems().getItem(i);
          if (item instanceof XmlSchemaParticle) {
            parseParticle((XmlSchemaParticle) item, complexSchema, schema);
          } else {
            throw new RuntimeException(
                "Unsupported XmlSchemaSequence item: " + item.getClass().getCanonicalName());
          }
        }
      } else if (particle instanceof XmlSchemaChoice) {

        XmlSchemaChoice xmlSchemaChoice = (XmlSchemaChoice) particle;

        ChoiceSchema choiceSchema =
            (ChoiceSchema) getSchemaFactory().createSchema(NeutralSchemaType.CHOICE.name());

        choiceSchema.setMinOccurs(xmlSchemaChoice.getMinOccurs());
        choiceSchema.setMaxOccurs(xmlSchemaChoice.getMaxOccurs());

        XmlSchemaObjectCollection choices = xmlSchemaChoice.getItems();
        for (int i = 0; i < choices.getCount(); ++i) {
          XmlSchemaObject item = xmlSchemaChoice.getItems().getItem(i);
          if (item instanceof XmlSchemaParticle) {
            parseParticle((XmlSchemaParticle) item, choiceSchema, schema);
          }
        }

        complexSchema.addField("choice_" + choiceCount++, choiceSchema);

      } else {
        throw new RuntimeException(
            "Unsupported XmlSchemaParticle item: " + particle.getClass().getCanonicalName());
      }
    }
  }