@Test
  public void testParsingCityGML()
      throws ClassCastException, ClassNotFoundException, InstantiationException,
          IllegalAccessException {

    String schemaURL = "http://schemas.opengis.net/citygml/profiles/base/1.0/CityGML.xsd";
    ApplicationSchemaXSDDecoder adapter =
        new ApplicationSchemaXSDDecoder(GMLVersion.GML_31, null, schemaURL);
    ApplicationSchema schema = adapter.extractFeatureTypeSchema();
    FeatureType[] fts = schema.getFeatureTypes();
    Assert.assertEquals(54, fts.length);

    FeatureType buildingFt =
        schema.getFeatureType(
            QName.valueOf("{http://www.opengis.net/citygml/building/1.0}Building"));
    PropertyType<?> pt =
        buildingFt.getPropertyDeclaration(
            QName.valueOf(
                "{http://www.opengis.net/citygml/1.0}_GenericApplicationPropertyOfCityObject"));
    Assert.assertEquals(8, pt.getSubstitutions().length);
  }
  /**
   * Returns the feature type with the given name.
   *
   * <p>If no feature type with the given name is defined, an XMLParsingException is thrown.
   *
   * @param xmlStreamReader
   * @param ftName feature type name to look up
   * @return the feature type with the given name
   * @throws XMLParsingException if no feature type with the given name is defined
   */
  protected FeatureType lookupFeatureType(XMLStreamReaderWrapper xmlStreamReader, QName ftName)
      throws XMLParsingException {

    // TODO implement this less hacky
    if (ftName.equals(DefaultGMLTypes.GML311_FEATURECOLLECTION.getName())) {
      return DefaultGMLTypes.GML311_FEATURECOLLECTION;
    }

    FeatureType ft = null;
    ft = schema.getFeatureType(ftName);
    if (ft == null) {
      String msg = Messages.getMessage("ERROR_SCHEMA_FEATURE_TYPE_UNKNOWN", ftName);
      throw new XMLParsingException(xmlStreamReader, msg);
    }
    return ft;
  }
  /**
   * Prints out an SQL create script for the relational schema.
   *
   * @param dbSchema optional db schema, can be null
   * @param writer
   */
  public void writeCreateScript(String dbSchema, PrintWriter writer) {

    if (dbSchema == null) {
      dbSchema = "";
    }

    if (dbSchema.length() != 0) {
      writer.println("/* --- BEGIN schema setup --- */");
      writer.println();
      writer.println("CREATE SCHEMA " + dbSchema + ";");
      writer.println("SET search_path TO " + dbSchema + ",public;");
      writer.println();
      writer.println("/* --- END schema setup --- */");
      writer.println();
    }

    FeatureType[] fts = appSchema.getFeatureTypes();
    Arrays.sort(
        fts,
        new Comparator<FeatureType>() {
          @Override
          public int compare(FeatureType o1, FeatureType o2) {
            return o1.getName().toString().compareTo(o2.getName().toString());
          }
        });

    if (getGlobalHints().isUseObjectLookupTable()) {
      writeCreateGeneral(fts, writer, dbSchema);
    }

    for (FeatureType ft : fts) {
      if (!ft.isAbstract()) {
        writer.println();
        writeCreateFeatureType(ft, writer, dbSchema);
      }
    }

    writer.flush();
  }
  /**
   * Returns the object representation for the given property element.
   *
   * @param xmlStream cursor must point at the <code>START_ELEMENT</code> event of the property,
   *     afterwards points at the next event after the <code>END_ELEMENT</code> of the property
   * @param propDecl property declaration
   * @param crs default SRS for all a descendant geometry properties
   * @param occurence
   * @return object representation for the given property element.
   * @throws XMLParsingException
   * @throws XMLStreamException
   * @throws UnknownCRSException
   */
  public Property<?> parseProperty(
      XMLStreamReaderWrapper xmlStream, PropertyType propDecl, CRS crs, int occurence)
      throws XMLParsingException, XMLStreamException, UnknownCRSException {

    Property<?> property = null;
    QName propName = xmlStream.getName();
    LOG.debug("- parsing property (begin): " + xmlStream.getCurrentEventInfo());
    LOG.debug("- property declaration: " + propDecl);

    CustomPropertyReader<?> parser = ptToParser.get(propDecl);

    if (parser == null) {
      if (propDecl instanceof SimplePropertyType) {
        property =
            createSimpleProperty(
                xmlStream, (SimplePropertyType) propDecl, xmlStream.getElementText().trim());
      } else if (propDecl instanceof GeometryPropertyType) {
        String href = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        if (href != null) {
          // TODO respect geometry type information (Point, Surface, etc.)
          GeometryReference<Geometry> refGeometry = null;
          if (specialResolver != null) {
            refGeometry =
                new GeometryReference<Geometry>(specialResolver, href, xmlStream.getSystemId());
          } else {
            refGeometry = new GeometryReference<Geometry>(idContext, href, xmlStream.getSystemId());
          }
          idContext.addReference(refGeometry);
          property = new GenericProperty<Geometry>(propDecl, propName, refGeometry);
          xmlStream.nextTag();
        } else {
          xmlStream.nextTag();
          Geometry geometry = null;
          geometry = geomReader.parse(xmlStream, crs);
          property = new GenericProperty<Geometry>(propDecl, propName, geometry);
          xmlStream.nextTag();
        }
      } else if (propDecl instanceof FeaturePropertyType) {
        String uri = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        if (uri != null) {
          FeatureReference refFeature = null;
          if (specialResolver != null) {
            refFeature = new FeatureReference(specialResolver, uri, xmlStream.getSystemId());
          } else {
            refFeature = new FeatureReference(idContext, uri, xmlStream.getSystemId());
          }
          idContext.addReference(refFeature);
          property = new GenericProperty<Feature>(propDecl, propName, refFeature);
          xmlStream.nextTag();
        } else {
          // inline feature
          if (xmlStream.nextTag() != START_ELEMENT) {
            String msg = Messages.getMessage("ERROR_INVALID_FEATURE_PROPERTY", propName);
            throw new XMLParsingException(xmlStream, msg);
          }
          // TODO make this check (no constraints on contained feature type) better
          if (((FeaturePropertyType) propDecl).getFTName() != null) {
            FeatureType expectedFt = ((FeaturePropertyType) propDecl).getValueFt();
            FeatureType presentFt = lookupFeatureType(xmlStream, xmlStream.getName());
            if (!schema.isSubType(expectedFt, presentFt)) {
              String msg =
                  Messages.getMessage(
                      "ERROR_PROPERTY_WRONG_FEATURE_TYPE",
                      expectedFt.getName(),
                      propName,
                      presentFt.getName());
              throw new XMLParsingException(xmlStream, msg);
            }
          }
          Feature subFeature = parseFeature(xmlStream, crs);
          property = new GenericProperty<Feature>(propDecl, propName, subFeature);
          xmlStream.skipElement();
        }
      } else if (propDecl instanceof CustomPropertyType) {
        Object value = new GenericCustomPropertyReader().parse(xmlStream);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof EnvelopePropertyType) {
        Envelope env = null;
        xmlStream.nextTag();
        if (xmlStream.getName().equals(new QName(gmlNs, "Null"))) {
          // TODO
          StAXParsingHelper.skipElement(xmlStream);
        } else {
          env = geomReader.parseEnvelope(xmlStream, crs);
          property = new GenericProperty<Object>(propDecl, propName, env);
        }
        xmlStream.nextTag();
      } else if (propDecl instanceof CodePropertyType) {
        String codeSpace = xmlStream.getAttributeValue(null, "codeSpace");
        String code = xmlStream.getElementText().trim();
        Object value = new CodeType(code, codeSpace);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof MeasurePropertyType) {
        String uom = xmlStream.getAttributeValue(null, "uom");
        Object value = new Measure(xmlStream.getElementText(), uom);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof StringOrRefPropertyType) {
        String ref = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        String string = xmlStream.getElementText().trim();
        property = new GenericProperty<Object>(propDecl, propName, new StringOrRef(string, ref));
      }
    } else {
      LOG.trace("************ Parsing property using custom parser.");
      Object value = parser.parse(xmlStream);
      property = new GenericProperty<Object>(propDecl, propName, value);
    }

    LOG.debug(" - parsing property (end): " + xmlStream.getCurrentEventInfo());
    return property;
  }