private NeutralSchema parseElement(XmlSchemaElement element, XmlSchema schema) {

    QName elementTypeName = element.getSchemaTypeName();

    // Derive Element Schema
    XmlSchemaType elementSchemaType = element.getSchemaType();

    // Element annotations override type annotations.
    if (element.getAnnotation() != null) {
      elementSchemaType.setAnnotation(element.getAnnotation());
    }

    NeutralSchema elementSchema = null;
    if (elementSchemaType != null) {
      if (elementSchemaType.getName() != null) {
        elementSchema = this.parse(elementSchemaType, schema);
      } else {
        elementSchema = this.parse(elementSchemaType, element.getName(), schema);
      }
    } else if (elementTypeName != null) {
      elementSchema = getSchemaFactory().createSchema(elementTypeName);
    }

    if (elementSchema != null) {

      // List Schema
      if (element.getMaxOccurs() > 1) {
        ListSchema listSchema = (ListSchema) getSchemaFactory().createSchema("list");
        listSchema.getList().add(elementSchema);
        listSchema.updateAnnotations();
        elementSchema = listSchema;
      }
    }

    Long min = element.getMinOccurs();
    Long max = element.getMaxOccurs();
    QName type = element.getSchemaTypeName();

    if (min != null) {
      elementSchema.getProperties().put("minCardinality", min);
    }
    if (max != null) {
      elementSchema.getProperties().put("maxCardinality", max);
    }
    if (type != null) {
      elementSchema.getProperties().put("elementType", type.toString());
    }

    return elementSchema;
  }
  private NeutralSchema parseSimpleType(
      XmlSchemaSimpleType schemaSimpleType, XmlSchema schema, String name) {

    NeutralSchema ns = recursiveParseSimpleType(schemaSimpleType, schema);
    if (schemaSimpleType.getName() == null) {
      // Type defined in-line. Need to capture it in the element schema set.
      int i = 1;
      NeutralSchema existing = elementSchemas.get(name + i);
      while (existing != null && !schemasEqual(ns, existing)) {
        i++;
        existing = schemas.get(name + i);
      }
      ns.setType(name + i);
      elementSchemas.put(name + i, ns);
    }
    return ns;
  }
  /**
   * Gets the schema for an underlying field on the given type
   *
   * @param type the type for the schema to look up
   * @param field the potentially nested field whose schema is to be returned
   */
  @Override
  public NeutralSchema getSchema(String type, String field) {

    // get schema for entity
    NeutralSchema schema = this.getSchema(type);

    // loop through dotted notation for nested schemas
    for (String fieldName : field.split("\\.")) {
      schema = getNestedSchema(schema, fieldName);
      if (schema != null) {
        LOG.debug("nested schema type is {}", schema.getSchemaType());
      } else {
        LOG.debug("nested schema type is {}", "NULL");
      }
    }

    return schema;
  }
  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 parseDocumentation(NeutralSchema neutralSchema, XmlSchemaType schemaType) {
    XmlSchemaObjectCollection annotations = schemaType.getAnnotation().getItems();
    for (int annotationIdx = 0; annotationIdx < annotations.getCount(); ++annotationIdx) {

      XmlSchemaObject annotation = annotations.getItem(annotationIdx);
      if (annotation instanceof XmlSchemaDocumentation) {
        XmlSchemaDocumentation docs = (XmlSchemaDocumentation) annotation;
        neutralSchema.addAnnotation(new Documentation(docs.getMarkup()));
      }
    }
  }
  private void parseAppInfo(NeutralSchema neutralSchema, XmlSchemaType schemaType) {

    XmlSchemaObjectCollection annotations = schemaType.getAnnotation().getItems();
    for (int annotationIdx = 0; annotationIdx < annotations.getCount(); ++annotationIdx) {

      XmlSchemaObject annotation = annotations.getItem(annotationIdx);
      if (annotation instanceof XmlSchemaAppInfo) {
        XmlSchemaAppInfo info = (XmlSchemaAppInfo) annotation;
        neutralSchema.addAnnotation(new AppInfo(info.getMarkup()));
      }
    }
  }
  void loadSchema(XmlSchema schema) {
    XmlSchemaObjectCollection schemaItems = schema.getItems();

    // Iterate XML Schema items
    for (int i = 0; i < schemaItems.getCount(); i++) {
      XmlSchemaObject schemaObject = schemaItems.getItem(i);

      NeutralSchema neutralSchema;
      if (schemaObject instanceof XmlSchemaType) {
        neutralSchema = parse((XmlSchemaType) schemaObject, schema);
      } else if (schemaObject instanceof XmlSchemaElement) {
        neutralSchema = parseElement((XmlSchemaElement) schemaObject, schema);
      } else if (schemaObject instanceof XmlSchemaInclude) {
        continue; // nothing to do for includes
      } else {
        throw new RuntimeException(
            "Unhandled XmlSchemaObject: " + schemaObject.getClass().getCanonicalName());
      }
      schemas.put(neutralSchema.getType(), neutralSchema);
      partialSchemas.clear();
    }
  }
 private NeutralSchema getNestedSchema(NeutralSchema schema, String field) {
   if (schema == null) {
     return null;
   }
   switch (schema.getSchemaType()) {
     case STRING:
     case INTEGER:
     case DATE:
     case TIME:
     case DATETIME:
     case ID:
     case IDREF:
     case INT:
     case LONG:
     case DOUBLE:
     case BOOLEAN:
     case TOKEN:
       return null;
     case LIST:
       for (NeutralSchema possibleSchema : ((ListSchema) schema).getList()) {
         LOG.debug("possible schema type is {}", possibleSchema.getSchemaType());
         if (getNestedSchema(possibleSchema, field) != null) {
           return getNestedSchema(possibleSchema, field);
         }
       }
       return null;
     case COMPLEX:
       for (String key : schema.getFields().keySet()) {
         NeutralSchema possibleSchema = schema.getFields().get(key);
         if (key.startsWith("*")) {
           key = key.substring(1);
         }
         if (key.equals(field)) {
           return possibleSchema;
         }
       }
       return null;
     default:
       {
         throw new RuntimeException("Unknown Schema Type: " + schema.getSchemaType());
       }
   }
 }
 private static boolean schemasEqual(NeutralSchema ns1, NeutralSchema ns2) {
   if (ns1.getValidatorClass().equals(ns2.getValidatorClass())
       && ns1.getVersion().equals(ns2.getVersion())
       && ns1.getFields().size() == ns2.getFields().size()) {
     for (Entry<String, NeutralSchema> entry : ns1.getFields().entrySet()) {
       if (!ns2.getFields().containsKey(entry.getKey())) {
         return false;
       }
       if (!schemasEqual(entry.getValue(), ns2.getFields().get(entry.getKey()))) {
         return false;
       }
     }
     for (Entry<String, Object> entry : ns1.getProperties().entrySet()) {
       if (!ns2.getProperties().containsKey(entry.getKey())) {
         return false;
       }
       if (!entry
           .getValue()
           .getClass()
           .equals(ns2.getProperties().get(entry.getKey()).getClass())) {
         return false;
       }
       if (entry.getValue() instanceof List) {
         List<?> list1 = (List<?>) entry.getValue();
         List<?> list2 = (List<?>) ns2.getProperties().get(entry.getKey());
         if (!list1.containsAll(list2)) {
           return false;
         }
       } else {
         if (!entry.getValue().equals(ns2.getProperties().get(entry.getKey()))) {
           return false;
         }
       }
     }
     return true;
   } else {
     return false;
   }
 }
  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());
      }
    }
  }
  @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 NeutralSchema recursiveParseSimpleType(
      XmlSchemaSimpleType schemaSimpleType, XmlSchema schema) {
    NeutralSchema simpleSchema = null;

    String simpleTypeName = schemaSimpleType.getName();

    if (NeutralSchemaType.isPrimitive(schemaSimpleType.getQName())) {
      simpleSchema = getSchemaFactory().createSchema(schemaSimpleType.getQName());

    } else if (NeutralSchemaType.exists(schemaSimpleType.getBaseSchemaTypeName())) {

      if (NeutralSchemaType.isPrimitive(schemaSimpleType.getBaseSchemaTypeName())) {
        simpleSchema = getSchemaFactory().createSchema(schemaSimpleType.getBaseSchemaTypeName());

      } else {
        XmlSchemaSimpleType simpleBaseType =
            getSimpleBaseType(schemaSimpleType.getBaseSchemaTypeName(), schema);
        if (simpleBaseType != null) {

          if (simpleTypeName == null) {
            simpleTypeName = simpleBaseType.getName();
          }
          simpleSchema = getSchemaFactory().createSchema(simpleTypeName);
        }
      }

    } else if (schemaSimpleType.getContent() != null
        && schemaSimpleType.getContent() instanceof XmlSchemaSimpleTypeList) {

      ListSchema listSchema = (ListSchema) getSchemaFactory().createSchema("list");

      XmlSchemaSimpleTypeList content = (XmlSchemaSimpleTypeList) schemaSimpleType.getContent();
      NeutralSchema listContentSchema = null;

      if (content.getItemType() != null) {
        listContentSchema = parseSimpleType(content.getItemType(), schema, null);

      } else {
        QName itemTypeName = content.getItemTypeName();
        listContentSchema = getSchemaFactory().createSchema(itemTypeName.getLocalPart());
      }
      listSchema.getList().add(listContentSchema);
      listSchema.updateAnnotations();
      return listSchema;

    } else if (getSimpleContentTypeName(schemaSimpleType) != null) {

      if (NeutralSchemaType.isPrimitive(getSimpleContentTypeName(schemaSimpleType))) {
        simpleSchema = getSchemaFactory().createSchema(getSimpleContentTypeName(schemaSimpleType));

      } else {

        XmlSchemaSimpleType simpleBaseType =
            getSimpleBaseType(getSimpleContentTypeName(schemaSimpleType), schema);
        simpleSchema = recursiveParseSimpleType(simpleBaseType, schema);
      }
    }

    if (simpleSchema != null && schemaSimpleType.getContent() != null) {

      if (schemaSimpleType.getContent() instanceof XmlSchemaSimpleTypeRestriction) {

        XmlSchemaSimpleTypeRestriction simpleRestrictedContent =
            (XmlSchemaSimpleTypeRestriction) schemaSimpleType.getContent();
        XmlSchemaObjectCollection facets = simpleRestrictedContent.getFacets();
        List<String> tokens = new ArrayList<String>();
        for (int i = 0; i < facets.getCount(); i++) {
          XmlSchemaObject facetObject = facets.getItem(i);

          if (facetObject instanceof XmlSchemaEnumerationFacet) {
            XmlSchemaEnumerationFacet enumerationFacet = (XmlSchemaEnumerationFacet) facetObject;
            tokens.add(enumerationFacet.getValue().toString());
          } else if (facetObject instanceof XmlSchemaFacet) {
            XmlSchemaFacet facet = (XmlSchemaFacet) facetObject;
            String facetPropertyName = NeutralSchemaType.lookupPropertyName(facet);
            simpleSchema.getProperties().put(facetPropertyName, facet.getValue().toString());
          }
        }

        if (tokens.size() > 0) {
          // Token Schema
          Collections.sort(tokens); // sort so we can binary search
          simpleSchema.getProperties().put(TokenSchema.TOKENS, tokens);
        }
      }
    }

    parseAnnotations(simpleSchema, schemaSimpleType);

    if ((simpleSchema != null) && (simpleTypeName != null)) {
      simpleSchema.setType(simpleTypeName);
    }

    return simpleSchema;
  }