private AttributeDescriptor createAttributeDescriptor(
      final XSDElementDeclaration elemDecl,
      int minOccurs,
      int maxOccurs,
      CoordinateReferenceSystem crs) {
    String targetNamespace = elemDecl.getTargetNamespace();
    String name = elemDecl.getName();
    Name elemName = Types.typeName(targetNamespace, name);
    AttributeType type = getTypeOf(elemDecl, crs);
    boolean nillable = elemDecl.isNillable();
    Object defaultValue = null;
    AttributeDescriptor descriptor =
        createAttributeDescriptor(
            type, crs, elemName, minOccurs, maxOccurs, nillable, defaultValue);
    descriptor.getUserData().put(XSDElementDeclaration.class, elemDecl);

    return descriptor;
  }
  /**
   * If the type of elemDecl is annonymous creates a new type with the same name than the atrribute
   * and returns it. If it is not anonymous, looks it up on the registry and in case the type does
   * not exists in the registry uses a proxy.
   *
   * @param elemDecl
   * @return
   */
  private AttributeType getTypeOf(XSDElementDeclaration elemDecl, CoordinateReferenceSystem crs) {
    XSDTypeDefinition typeDefinition;

    // TODO REVISIT, I'm not sure this is the way to find out if the
    // element's type is defined in line (an thus no need to register it
    // as a global type)
    if (elemDecl.isElementDeclarationReference()) {
      elemDecl = elemDecl.getResolvedElementDeclaration();
    }
    boolean hasToBeRegistered = false;
    typeDefinition = elemDecl.getAnonymousTypeDefinition();
    if (typeDefinition == null) {
      // anonymous types already has type definition inline in the element
      // so the handling is different
      hasToBeRegistered = true;
      typeDefinition = elemDecl.getTypeDefinition();
    }

    if (typeDefinition == null) {
      // last resort.. look in the lazy schemas
      QName qname =
          Types.toQName(Types.typeName(elemDecl.getTargetNamespace(), elemDecl.getName()));
      for (SchemaIndex schemaIndex : schemas) {
        elemDecl = schemaIndex.getElementDeclaration(qname);
        if (elemDecl != null) {
          break;
        }
      }
      if (elemDecl != null) {
        if (elemDecl.isElementDeclarationReference()) {
          elemDecl = elemDecl.getResolvedElementDeclaration();
        }
        typeDefinition = elemDecl.getAnonymousTypeDefinition();
        if (typeDefinition == null) {
          typeDefinition = elemDecl.getTypeDefinition();
        }
      }
    }

    if (typeDefinition == null) {
      String msg =
          "The element declaration "
              + elemDecl.getTargetNamespace()
              + "#"
              + elemDecl.getName()
              + " has a null type definition, can't continue, fix it on the schema";
      LOGGER.warning(msg);
      throw new NoSuchElementException(msg);
    }

    AttributeType type;
    if (hasToBeRegistered) {
      String targetNamespace = typeDefinition.getTargetNamespace();
      String name = typeDefinition.getName();
      Name typeName = Types.typeName(targetNamespace, name);
      type = getAttributeType(typeName, typeDefinition, crs);
      if (type == null) {
        type = createType(typeName, typeDefinition, crs, false);
      }
    } else {
      String name = elemDecl.getName();
      String targetNamespace = elemDecl.getTargetNamespace();
      Name overrideName = Types.typeName(targetNamespace, name);
      type = createType(overrideName, typeDefinition, crs, true);
    }

    return type;
  }
  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);
            }
          }
        }
      }
    }
  }