private AttributeDescriptor createAttributeDescriptor(
      final XSDComplexTypeDefinition container,
      final XSDElementDeclaration elemDecl,
      CoordinateReferenceSystem crs) {
    int minOccurs = container == null ? 0 : Schemas.getMinOccurs(container, elemDecl);
    int maxOccurs =
        container == null ? Integer.MAX_VALUE : Schemas.getMaxOccurs(container, elemDecl);

    return createAttributeDescriptor(elemDecl, minOccurs, maxOccurs, crs);
  }
  public void _testParseDescribeFeatureType() throws Exception {
    String loc = getClass().getResource("geoserver-DescribeFeatureType.xml").getFile();
    XSDSchema schema = Schemas.parse(loc);

    assertNotNull(schema);
    final String targetNs = "http://cite.opengeospatial.org/gmlsf";
    final String featureName = "PrimitiveGeoFeature";
    QName name = new QName(targetNs, featureName);
    XSDElementDeclaration elementDeclaration = Schemas.getElementDeclaration(schema, name);
    assertNotNull(elementDeclaration);

    XSDComplexTypeDefinition type = (XSDComplexTypeDefinition) elementDeclaration.getType();

    assertEquals("PrimitiveGeoFeatureType", type.getName());
    assertEquals(targetNs, type.getTargetNamespace());
  }
  /**
   * Creates an {@link AttributeType} that matches the xsd type definition as much as possible.
   *
   * <p>The original type definition given by the {@link XSDTypeDefinition} is kept as
   * AttributeType's metadata stored as a "user data" property using <code>XSDTypeDefinition.class
   * </code> as key.
   *
   * <p>If it is a complex attribute, it will contain all the properties declared in the <code>
   * typeDefinition</code>, as well as all the properties declared in its super types. TODO: handle
   * the case where the extension mechanism is restriction.
   *
   * @param assignedName
   * @param typeDefinition
   * @return
   */
  private AttributeType createType(
      final Name assignedName,
      final XSDTypeDefinition typeDefinition,
      CoordinateReferenceSystem crs,
      boolean anonymous) {

    AttributeType attType;
    // /////////
    if (processingTypes.contains(assignedName)) {
      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.fine("Recursion found for type " + assignedName + ". Proxying it.");
      }
      attType =
          createProxiedType(
              assignedName, typeDefinition, anonymous ? anonTypeRegistry : typeRegistry);
      return attType;
    }
    processingTypes.push(assignedName);
    // //////////

    final XSDTypeDefinition baseType = typeDefinition.getBaseType();

    AttributeType superType = null;
    if (baseType != null) {
      String targetNamespace = baseType.getTargetNamespace();
      String name = baseType.getName();
      if (name != null) {
        Name baseTypeName = new NameImpl(targetNamespace, name);
        superType = getAttributeType(baseTypeName, baseType, crs);
      }
    } else {
      LOGGER.warning(assignedName + " has no super type");
    }

    if (typeDefinition instanceof XSDComplexTypeDefinition) {
      XSDComplexTypeDefinition complexTypeDef;
      complexTypeDef = (XSDComplexTypeDefinition) typeDefinition;
      boolean includeParents = true;
      List<XSDElementDeclaration> children =
          Schemas.getChildElementDeclarations(typeDefinition, includeParents);

      final Collection<PropertyDescriptor> schema =
          new ArrayList<PropertyDescriptor>(children.size());

      XSDElementDeclaration childDecl;
      AttributeDescriptor descriptor;
      for (Iterator it = children.iterator(); it.hasNext(); ) {
        childDecl = (XSDElementDeclaration) it.next();
        try {
          descriptor = createAttributeDescriptor(complexTypeDef, childDecl, crs);
        } catch (NoSuchElementException e) {
          String msg =
              "Failed to create descriptor for '"
                  + childDecl.getTargetNamespace()
                  + "#"
                  + childDecl.getName()
                  + " from container '"
                  + typeDefinition.getTargetNamespace()
                  + "#"
                  + typeDefinition.getName()
                  + "'";
          NoSuchElementException nse = new NoSuchElementException(msg);
          nse.initCause(e);
          throw nse;
        }
        schema.add(descriptor);
      }

      if (includeAttributes) {
        for (XSDAttributeUse attgcontent : complexTypeDef.getAttributeUses()) {
          XSDAttributeDeclaration att = attgcontent.getContent();
          descriptor =
              createAttributeDescriptor(
                  getXmlAttributeType(),
                  null,
                  new NameImpl(null, "@" + att.getName()),
                  0,
                  1,
                  false,
                  null);
          schema.add(descriptor);
        }
      }

      // set substitution group for descriptors here
      for (XSDElementDeclaration elemDecl : children) {
        if (elemDecl.isElementDeclarationReference()) {
          elemDecl = elemDecl.getResolvedElementDeclaration();
        }
        PropertyDescriptor att = null;
        for (PropertyDescriptor desc : schema) {
          if (desc.getName().getLocalPart().equals(elemDecl.getName())
              && desc.getName().getNamespaceURI().equals(elemDecl.getTargetNamespace())) {
            att = desc;
            break;
          }
        }
        setSubstitutionGroup(complexTypeDef, elemDecl, att, crs);
      }
      attType = createComplexAttributeType(assignedName, schema, complexTypeDef, superType);
    } else {
      Class<?> binding = String.class;
      boolean isIdentifiable = false;
      boolean isAbstract = false;
      List<Filter> restrictions = Collections.emptyList();
      InternationalString description = null;
      attType =
          typeFactory.createAttributeType(
              assignedName,
              binding,
              isIdentifiable,
              isAbstract,
              restrictions,
              superType,
              description);
    }

    attType.getUserData().put(XSDTypeDefinition.class, typeDefinition);

    processingTypes.pop();

    // even if the type is anonymous, it still has to be registered somewhere because
    // it's needed for the proxied types to find them. That's why we have 2 registries,
    // typeRegistry
    // and anonTypeRegistry. TypeRegistry is the global one, since anonymous types are meant to
    // be
    // local and shouldn't be searchable.
    register(attType, anonymous);

    return attType;
  }
  private void setSubstitutionGroup(
      XSDComplexTypeDefinition container,
      XSDElementDeclaration elemDecl,
      PropertyDescriptor descriptor,
      CoordinateReferenceSystem crs) {

    if (descriptor.getUserData().get("substitutionGroup") != null) {
      // this has been done before
      return;
    }

    List<AttributeDescriptor> substitutionGroup = new ArrayList<AttributeDescriptor>();
    descriptor.getUserData().put("substitutionGroup", substitutionGroup);

    int minOccurs = Schemas.getMinOccurs(container, elemDecl);
    int maxOccurs = Schemas.getMaxOccurs(container, elemDecl);
    boolean nillable = elemDecl.isNillable();

    Iterator substitutions = elemDecl.getSubstitutionGroup().iterator();
    XSDElementDeclaration sub;
    while (substitutions.hasNext()) {
      sub = (XSDElementDeclaration) substitutions.next();
      if (!(sub.getName().equals(elemDecl.getName()))
          || !(sub.getTargetNamespace().equals(elemDecl.getTargetNamespace()))) {
        Name elemName = Types.typeName(sub.getTargetNamespace(), sub.getName());
        AttributeType type = getTypeOf(sub, crs);
        if (type != null) {
          substitutionGroup.add(
              createAttributeDescriptor(type, crs, elemName, minOccurs, maxOccurs, nillable, null));
        }
      }
    }

    XSDTypeDefinition typeDef = elemDecl.getType();

    if (typeDef instanceof XSDComplexTypeDefinition) {
      Name typeName = Types.typeName(typeDef.getTargetNamespace(), typeDef.getName());
      AttributeType attType = typeRegistry.get(typeName);

      if (!processingTypes.contains(typeName)) {
        // ignore processingTypes to avoid endless recursion
        if (attType == null || attType instanceof AbstractLazyComplexTypeImpl) {
          // type is not yet registered or it's a lazy type from foundation types
          // recreate lazy type to ensure everything is loaded
          // it will eventually call this method so substitution groups will be set then
          LOGGER.finest("Creating attribute type " + typeName);
          createType(typeName, typeDef, crs, false);
          LOGGER.finest("Registering attribute type " + typeName);
        } else if (attType instanceof ComplexType) {
          // ensure substitution groups are set for children including non lazy foundation
          // types
          ComplexType complexType = (ComplexType) attType;
          Collection<PropertyDescriptor> children = complexType.getDescriptors();

          List<XSDParticle> childParticles = Schemas.getChildElementParticles(typeDef, true);

          for (XSDParticle particle : childParticles) {
            XSDElementDeclaration element = (XSDElementDeclaration) particle.getContent();

            if (element.isElementDeclarationReference()) {
              element = element.getResolvedElementDeclaration();
            }
            PropertyDescriptor childDesc = null;
            for (PropertyDescriptor desc : children) {
              if (desc.getName().getLocalPart().equals(element.getName())
                  && desc.getName().getNamespaceURI().equals(element.getTargetNamespace())) {
                childDesc = desc;
                break;
              }
            }
            if (childDesc != null) {
              setSubstitutionGroup((XSDComplexTypeDefinition) typeDef, element, childDesc, crs);
            }
          }
        }
      }
    }
  }