/****************************************************************************
**
** Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Script Generator project on Qt Labs.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "typesystem.h"
#include "generator.h"

#include "customtypes.h"

#include <reporthandler.h>

#include <memory>

#include <QFile>
#include <QTextStream>
#include <QXmlStreamReader>
#include <qcompilerdetection.h> // Q_FALLTHROUGH

QString strings_Object = QLatin1String("Object");
QString strings_String = QLatin1String("String");
QString strings_Thread = QLatin1String("Thread");
QString strings_char = QLatin1String("char");
QString strings_java_lang = QLatin1String("java.lang");
QString strings_jchar = QLatin1String("jchar");
QString strings_jobject = QLatin1String("jobject");

static void addRemoveFunctionToTemplates(TypeDatabase* db);

class StackElement
{
public:
  enum ElementType {
    None = 0x0,

    // Type tags (0x1, ... , 0xff)
    ObjectTypeEntry = 0x1,
    ValueTypeEntry = 0x2,
    InterfaceTypeEntry = 0x3,
    NamespaceTypeEntry = 0x4,
    ComplexTypeEntryMask = 0xf,

    // Non-complex type tags (0x10, 0x20, ... , 0xf0)
    PrimitiveTypeEntry = 0x10,
    EnumTypeEntry = 0x20,
    TypeEntryMask = 0xff,

    // Simple tags (0x100, 0x200, ... , 0xf00)
    ExtraIncludes = 0x100,
    Include = 0x200,
    ModifyFunction = 0x300,
    ModifyField = 0x400,
    Root = 0x500,
    CustomMetaConstructor = 0x600,
    CustomMetaDestructor = 0x700,
    ArgumentMap = 0x800,
    SuppressedWarning = 0x900,
    Rejection = 0xa00,
    LoadTypesystem = 0xb00,
    RejectEnumValue = 0xc00,
    Template = 0xd00,
    TemplateInstanceEnum = 0xe00,
    Replace = 0xf00,
    SimpleMask = 0xf00,

    // Code snip tags (0x1000, 0x2000, ... , 0xf000)
    InjectCode = 0x1000,
    InjectCodeInFunction = 0x2000,
    CodeSnipMask = 0xf000,

    // Function modifier tags (0x010000, 0x020000, ... , 0xf00000)
    Access = 0x010000,
    Removal = 0x020000,
    Rename = 0x040000,
    ModifyArgument = 0x080000,
    FunctionModifiers = 0xff0000,

    // Argument modifier tags (0x01000000 ... 0xf0000000)
    ConversionRule = 0x01000000,
    ReplaceType = 0x02000000,
    ReplaceDefaultExpression = 0x04000000,
    RemoveArgument = 0x08000000,
    DefineOwnership = 0x10000000,
    RemoveDefaultExpression = 0x20000000,
    NoNullPointers = 0x40000000,
    ReferenceCount = 0x80000000,
    ArgumentModifiers = 0xff000000
  };

  StackElement(StackElement* p)
    : entry(0)
    , type(None)
    , parent(p)
  {
  }

  TypeEntry* entry;
  ElementType type;
  StackElement* parent;

  union {
    TemplateInstance* templateInstance;
    TemplateEntry* templateEntry;
    CustomFunction* customFunction;
  } value;
};

class Handler
{
public:
  class Attributes
  {
  public:
    Attributes() = default;
    explicit Attributes(const QXmlStreamAttributes& attributes)
      : m_attributes(attributes)
    {
    }

    int length() const { return m_attributes.size(); }

    QString localName(int index) const { return m_attributes.at(index).name().toString(); }

    QString value(int index) const { return m_attributes.at(index).value().toString(); }

    QString value(const QString& qualifiedName) const
    {
      return m_attributes.hasAttribute(qualifiedName) ? m_attributes.value(qualifiedName).toString() : QString();
    }

    int index(const QString& qualifiedName) const
    {
      for (int i = 0; i < m_attributes.size(); ++i) {
        if (m_attributes.at(i).name().toString() == qualifiedName)
          return i;
      }
      return -1;
    }

  private:
    QXmlStreamAttributes m_attributes;
  };

  Handler(TypeDatabase* database, unsigned int qtVersion, bool generate)
    : m_database(database)
    , m_generate(generate ? TypeEntry::GenerateAll : TypeEntry::GenerateForSubclass)
    , m_qtVersion(qtVersion)
  {
    m_current_enum = 0;
    current = 0;

    tagNames["rejection"] = StackElement::Rejection;
    tagNames["primitive-type"] = StackElement::PrimitiveTypeEntry;
    tagNames["object-type"] = StackElement::ObjectTypeEntry;
    tagNames["value-type"] = StackElement::ValueTypeEntry;
    tagNames["interface-type"] = StackElement::InterfaceTypeEntry;
    tagNames["namespace-type"] = StackElement::NamespaceTypeEntry;
    tagNames["enum-type"] = StackElement::EnumTypeEntry;
    tagNames["extra-includes"] = StackElement::ExtraIncludes;
    tagNames["include"] = StackElement::Include;
    tagNames["inject-code"] = StackElement::InjectCode;
    tagNames["modify-function"] = StackElement::ModifyFunction;
    tagNames["modify-field"] = StackElement::ModifyField;
    tagNames["access"] = StackElement::Access;
    tagNames["remove"] = StackElement::Removal;
    tagNames["rename"] = StackElement::Rename;
    tagNames["typesystem"] = StackElement::Root;
    tagNames["custom-constructor"] = StackElement::CustomMetaConstructor;
    tagNames["custom-destructor"] = StackElement::CustomMetaDestructor;
    tagNames["argument-map"] = StackElement::ArgumentMap;
    tagNames["suppress-warning"] = StackElement::SuppressedWarning;
    tagNames["load-typesystem"] = StackElement::LoadTypesystem;
    tagNames["define-ownership"] = StackElement::DefineOwnership;
    tagNames["replace-default-expression"] = StackElement::ReplaceDefaultExpression;
    tagNames["reject-enum-value"] = StackElement::RejectEnumValue;
    tagNames["replace-type"] = StackElement::ReplaceType;
    tagNames["conversion-rule"] = StackElement::ConversionRule;
    tagNames["modify-argument"] = StackElement::ModifyArgument;
    tagNames["remove-argument"] = StackElement::RemoveArgument;
    tagNames["remove-default-expression"] = StackElement::RemoveDefaultExpression;
    tagNames["template"] = StackElement::Template;
    tagNames["insert-template"] = StackElement::TemplateInstanceEnum;
    tagNames["replace"] = StackElement::Replace;
    tagNames["no-null-pointer"] = StackElement::NoNullPointers;
    tagNames["reference-count"] = StackElement::ReferenceCount;
  }

  bool parse(QXmlStreamReader& reader);
  bool startDocument()
  {
    m_nestingLevel = 0;
    m_disabledLevel = -1;
    return true;
  }
  bool startElement(const QString& namespaceURI, const QString& localName, const QString& qName,
    const Attributes& atts);
  bool endElement(const QString& namespaceURI, const QString& localName, const QString& qName);

  QString errorString() const { return m_error; }

  bool characters(const QString& ch);

private:
  void fetchAttributeValues(const QString& name, const Attributes& atts, QHash<QString, QString>* acceptedAttributes);

  bool importFileElement(const Attributes& atts);
  bool convertBoolean(const QString&, const QString&, bool);
  bool qtVersionMatches(const Attributes& atts, bool& ok);

  TypeDatabase* m_database;
  StackElement* current;
  QString m_defaultPackage;
  QString m_defaultSuperclass;
  QString m_error;
  TypeEntry::CodeGeneration m_generate;

  EnumTypeEntry* m_current_enum;

  CodeSnipList m_code_snips;
  FunctionModificationList m_function_mods;
  FieldModificationList m_field_mods;

  QHash<QString, StackElement::ElementType> tagNames;

  unsigned int m_qtVersion {};
  int m_nestingLevel {};  // current nesting level, needed to reset m_disabledLevel if leaving element
  int m_disabledLevel {}; // if this is != 0, elements should be ignored
};

bool Handler::parse(QXmlStreamReader& reader)
{
  if (!startDocument())
    return false;

  while (!reader.atEnd()) {
    const auto token = reader.readNext();
    switch (token) {
    case QXmlStreamReader::StartElement:
    {
      Attributes attributes(reader.attributes());
      if (!startElement(reader.namespaceUri().toString(), reader.name().toString(), reader.qualifiedName().toString(),
            attributes))
      {
        return false;
      }
      break;
    }
    case QXmlStreamReader::EndElement:
      if (!endElement(reader.namespaceUri().toString(), reader.name().toString(), reader.qualifiedName().toString())) {
        return false;
      }
      break;
    case QXmlStreamReader::Characters:
    case QXmlStreamReader::EntityReference:
      if (!characters(reader.text().toString())) {
        return false;
      }
      break;
    default:
      break;
    }
  }

  if (reader.hasError()) {
    m_error = QStringLiteral("Parse error at line %1, column %2: %3")
                .arg(reader.lineNumber())
                .arg(reader.columnNumber())
                .arg(reader.errorString());
    qWarning() << m_error;
    return false;
  }

  return true;
}

void Handler::fetchAttributeValues(const QString& name, const Attributes& atts,
  QHash<QString, QString>* acceptedAttributes)
{
  Q_ASSERT(acceptedAttributes != 0);

  for (int i = 0; i < atts.length(); ++i) {
    QString key = atts.localName(i).toLower();
    QString val = atts.value(i);

    if (!acceptedAttributes->contains(key) && key != "since-version" && key != "before-version") {
      ReportHandler::warning(QString("Unknown attribute for '%1': '%2'").arg(name).arg(key));
    } else {
      (*acceptedAttributes)[key] = val;
    }
  }
}

bool Handler::endElement(const QString&, const QString& localName, const QString&)
{
  QString tagName = localName.toLower();
  int oldNestingLevel = m_nestingLevel--;
  if (m_disabledLevel >= 0) {
    if (m_disabledLevel == oldNestingLevel) {
      m_disabledLevel = -1;
    }
    return true;
  }
  if (tagName == "import-file" || tagName == "group")
    return true;

  if (!current)
    return true;

  switch (current->type) {
  case StackElement::ObjectTypeEntry:
  case StackElement::ValueTypeEntry:
  case StackElement::InterfaceTypeEntry:
  case StackElement::NamespaceTypeEntry:
  {
    ComplexTypeEntry* centry = static_cast<ComplexTypeEntry*>(current->entry);
    centry->setFunctionModifications(m_function_mods);
    centry->setFieldModifications(m_field_mods);
    centry->setCodeSnips(m_code_snips);

    if (centry->designatedInterface()) {
      centry->designatedInterface()->setCodeSnips(m_code_snips);
      centry->designatedInterface()->setFunctionModifications(m_function_mods);
    }
    m_code_snips = CodeSnipList();
    m_function_mods = FunctionModificationList();
    m_field_mods = FieldModificationList();
  } break;
  case StackElement::CustomMetaConstructor:
  {
    current->entry->setCustomConstructor(*current->value.customFunction);
    delete current->value.customFunction;
  } break;
  case StackElement::CustomMetaDestructor:
  {
    current->entry->setCustomDestructor(*current->value.customFunction);
    delete current->value.customFunction;
  } break;
  case StackElement::EnumTypeEntry:
    m_current_enum = 0;
    break;
  case StackElement::Template:
    m_database->addTemplate(current->value.templateEntry);
    break;
  case StackElement::TemplateInstanceEnum:
    if (current->parent->type == StackElement::InjectCode) {
      m_code_snips.last().addTemplateInstance(current->value.templateInstance);
    } else if (current->parent->type == StackElement::Template) {
      current->parent->value.templateEntry->addTemplateInstance(current->value.templateInstance);
    } else if (current->parent->type == StackElement::CustomMetaConstructor) {
      current->parent->value.customFunction->addTemplateInstance(current->value.templateInstance);
    } else if (current->parent->type == StackElement::ConversionRule) {
      m_function_mods.last().argument_mods.last().conversion_rules.last().addTemplateInstance(
        current->value.templateInstance);
    } else if (current->parent->type == StackElement::InjectCodeInFunction) {
      m_function_mods.last().snips.last().addTemplateInstance(current->value.templateInstance);
    }
    break;
  default:
    break;
  }

  StackElement* child = current;
  current = current->parent;
  delete (child);

  return true;
}

bool Handler::characters(const QString& ch)
{
  if (current->type == StackElement::Template) {
    current->value.templateEntry->addCode(ch);
    return true;
  }

  if (current->type == StackElement::CustomMetaConstructor || current->type == StackElement::CustomMetaDestructor) {
    current->value.customFunction->addCode(ch);
    return true;
  }

  if (current->type == StackElement::ConversionRule) {
    m_function_mods.last().argument_mods.last().conversion_rules.last().addCode(ch);
    return true;
  }

  if (current->parent) {
    if ((current->type & StackElement::CodeSnipMask) != 0) {
      switch (current->parent->type) {
      case StackElement::Root:
        ((TypeSystemTypeEntry*)current->parent->entry)->snips.last().addCode(ch);
        break;
      case StackElement::ModifyFunction:
        m_function_mods.last().snips.last().addCode(ch);
        break;
      case StackElement::NamespaceTypeEntry:
      case StackElement::ObjectTypeEntry:
      case StackElement::ValueTypeEntry:
      case StackElement::InterfaceTypeEntry:
        m_code_snips.last().addCode(ch);
        break;
      default:
        Q_ASSERT(false);
      };
      return true;
    }
  }

  return true;
}

bool Handler::importFileElement(const Attributes& atts)
{
  QString fileName = atts.value("name");
  if (fileName.isEmpty()) {
    m_error = "Required attribute 'name' missing for include-file tag.";
    return false;
  }

  QFile file(fileName);
  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    file.setFileName(":/trolltech/generator/" + fileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
      m_error = QString("Could not open file: '%1'").arg(fileName);
      return false;
    }
  }

  QString quoteFrom = atts.value("quote-after-line");
  bool foundFromOk = quoteFrom.isEmpty();
  bool from = quoteFrom.isEmpty();

  QString quoteTo = atts.value("quote-before-line");
  bool foundToOk = quoteTo.isEmpty();
  bool to = true;

  QTextStream in(&file);
  while (!in.atEnd()) {
    QString line = in.readLine();
    if (from && to && line.contains(quoteTo)) {
      to = false;
      foundToOk = true;
      break;
    }
    if (from && to)
      characters(line + "\n");
    if (!from && line.contains(quoteFrom)) {
      from = true;
      foundFromOk = true;
    }
  }
  if (!foundFromOk || !foundToOk) {
    QString fromError = QString("Could not find quote-after-line='%1' in file '%2'.").arg(quoteFrom).arg(fileName);
    QString toError = QString("Could not find quote-before-line='%1' in file '%2'.").arg(quoteTo).arg(fileName);

    if (!foundToOk)
      m_error = toError;
    if (!foundFromOk)
      m_error = fromError;
    if (!foundFromOk && !foundToOk)
      m_error = fromError + " " + toError;
    return false;
  }

  return true;
}

bool Handler::convertBoolean(const QString& _value, const QString& attributeName, bool defaultValue)
{
  QString value = _value.toLower();
  if (value == "true" || value == "yes") {
    return true;
  } else if (value == "false" || value == "no") {
    return false;
  } else {
    QString warn = QString("Boolean value '%1' not supported in attribute '%2'. Use 'yes' or 'no'. Defaulting to '%3'.")
                     .arg(value)
                     .arg(attributeName)
                     .arg(defaultValue ? "yes" : "no");

    ReportHandler::warning(warn);
    return defaultValue;
  }
}

bool Handler::qtVersionMatches(const Attributes& atts, bool& ok)
{
  ok = true;
  int beforeIndex = atts.index("before-version");
  if (beforeIndex >= 0) {
    uint beforeVersion = TypeSystem::qtVersionFromString(atts.value(beforeIndex), ok);
    if (ok) {
      if (m_qtVersion >= beforeVersion) {
        return false;
      }
    } else {
      m_error = "Invalid 'before-version' version string: " + atts.value(beforeIndex);
    }
  }
  int sinceIndex = atts.index("since-version");
  if (sinceIndex >= 0) {
    uint sinceVersion = TypeSystem::qtVersionFromString(atts.value(sinceIndex), ok);
    if (ok) {
      if (m_qtVersion < sinceVersion) {
        return false;
      }
    } else {
      m_error = "Invalid 'since-version' version string: " + atts.value(sinceIndex);
    }
  }
  return true;
}

bool Handler::startElement(const QString&, const QString& n, const QString&, const Attributes& atts)
{
  QString tagName = n.toLower();
  m_nestingLevel++;
  if (m_disabledLevel >= 0 && m_disabledLevel < m_nestingLevel) {
    return true;
  }
  bool ok;
  bool versionMatches = qtVersionMatches(atts, ok);
  if (!ok) {
    return false; // abort
  } else if (!versionMatches) {
    m_disabledLevel = m_nestingLevel;
    return true;
  }

  if (tagName == "import-file") {
    return importFileElement(atts);
  } else if (tagName == "group") { // non-functional element to handle version ranges
    return true;
  }

  std::unique_ptr<StackElement> element(new StackElement(current));

  if (!tagNames.contains(tagName)) {
    m_error = QString("Unknown tag name: '%1'").arg(tagName);
    return false;
  }

  element->type = tagNames[tagName];
  if (element->type & StackElement::TypeEntryMask) {
    if (current->type != StackElement::Root) {
      m_error = "Nested types not supported";
      return false;
    }

    QHash<QString, QString> attributes;
    attributes["name"] = QString();
    attributes["aliases"] = QString();

    switch (element->type) {
    case StackElement::PrimitiveTypeEntry:
      attributes["java-name"] = QString();
      attributes["jni-name"] = QString();
      attributes["preferred-conversion"] = "yes";
      attributes["preferred-java-type"] = "yes";
      break;
    case StackElement::EnumTypeEntry:
      attributes["flags"] = "no";
      attributes["upper-bound"] = QString();
      attributes["lower-bound"] = QString();
      attributes["force-integer"] = "no";
      attributes["extensible"] = "no";

      break;

    case StackElement::ObjectTypeEntry:
    case StackElement::ValueTypeEntry:
      attributes["force-abstract"] = QString("no");
      attributes["deprecated"] = QString("no");
      Q_FALLTHROUGH();
    case StackElement::InterfaceTypeEntry:
      attributes["default-superclass"] = m_defaultSuperclass;
      attributes["polymorphic-id-expression"] = QString();
      attributes["delete-in-main-thread"] = QString("no");
      Q_FALLTHROUGH();
    case StackElement::NamespaceTypeEntry:
      attributes["java-name"] = QString();
      attributes["package"] = m_defaultPackage;
      attributes["expense-cost"] = "1";
      attributes["expense-limit"] = "none";
      attributes["polymorphic-base"] = QString("no");
      attributes["create-shell"] = QString("yes");
      attributes["create-promoter"] = QString("yes");
      attributes["generate"] = QString("yes");
      attributes["target-type"] = QString();
      attributes["generic-class"] = QString("no");
      break;
    default:; // nada
    };

    fetchAttributeValues(tagName, atts, &attributes);

    QString name = attributes["name"];
    QStringList aliases = attributes["aliases"].split(',', Qt::SkipEmptyParts);

    // We need to be able to have duplicate primitive type entries, or it's not possible to
    // cover all primitive java types (which we need to do in order to support fake
    // meta objects)
    if (element->type != StackElement::PrimitiveTypeEntry) {
      if (m_database->findType(name)) {
        ReportHandler::warning(QString("Duplicate type entry: '%1'").arg(name));
      }
      for (const QString& alias : aliases) {
        if (m_database->findType(alias)) {
          ReportHandler::warning(QString("Duplicate alias type entry: '%1'").arg(alias));
        }
      }
    }

    if (name.isEmpty()) {
      m_error = "no 'name' attribute specified";
      return false;
    }
    switch (element->type) {
    case StackElement::PrimitiveTypeEntry:
    {
      QString java_name = attributes["java-name"];
      QString jni_name = attributes["jni-name"];
      QString preferred_conversion = attributes["preferred-conversion"].toLower();
      QString preferred_java_type = attributes["preferred-java-type"].toLower();

      if (java_name.isEmpty())
        java_name = name;
      if (jni_name.isEmpty())
        jni_name = name;

      PrimitiveTypeEntry* type = new PrimitiveTypeEntry(name);
      type->setCodeGeneration(m_generate);
      type->setTargetLangName(java_name);
      type->setJniName(jni_name);

      type->setPreferredConversion(convertBoolean(preferred_conversion, "preferred-conversion", true));
      type->setPreferredTargetLangType(convertBoolean(preferred_java_type, "preferred-java-type", true));

      element->entry = type;
    } break;
    case StackElement::EnumTypeEntry:
    {
      QStringList names = name.split(QLatin1String("::"));

      if (names.size() == 1) {
        m_current_enum = new EnumTypeEntry(QString(), name);
      } else
        m_current_enum = new EnumTypeEntry(QStringList(names.mid(0, names.size() - 1)).join("::"), names.last());
      element->entry = m_current_enum;
      m_current_enum->setCodeGeneration(m_generate);
      m_current_enum->setTargetLangPackage(m_defaultPackage);
      m_current_enum->setUpperBound(attributes["upper-bound"]);
      m_current_enum->setLowerBound(attributes["lower-bound"]);
      m_current_enum->setForceInteger(convertBoolean(attributes["force-integer"], "force-integer", false));
      m_current_enum->setExtensible(convertBoolean(attributes["extensible"], "extensible", false));

      // put in the flags parallel...
      if (!attributes["flags"].isEmpty() && attributes["flags"].toLower() != "no") {
        FlagsTypeEntry* ftype = new FlagsTypeEntry("QFlags<" + name + ">");
        ftype->setOriginator(m_current_enum);
        ftype->setOriginalName(attributes["flags"]);
        ftype->setCodeGeneration(m_generate);
        QString origname = ftype->originalName();

        QStringList lst = origname.split("::");
        if (QStringList(lst.mid(0, lst.size() - 1)).join("::") != m_current_enum->javaQualifier()) {
          ReportHandler::warning(
            QString("enum %1 and flags %2 differ in qualifiers").arg(m_current_enum->javaQualifier()).arg(lst.at(0)));
        }

        ftype->setFlagsName(lst.last());
        m_current_enum->setFlags(ftype);

        m_database->addFlagsType(ftype);
        m_database->addType(ftype);
      }
    } break;

    case StackElement::InterfaceTypeEntry:
    {
      ObjectTypeEntry* otype = new ObjectTypeEntry(name);
      QString javaName = attributes["java-name"];
      if (javaName.isEmpty())
        javaName = name;
      InterfaceTypeEntry* itype = new InterfaceTypeEntry(InterfaceTypeEntry::interfaceName(javaName));

      if (!convertBoolean(attributes["generate"], "generate", true))
        itype->setCodeGeneration(TypeEntry::GenerateForSubclass);
      else
        itype->setCodeGeneration(m_generate);
      otype->setDesignatedInterface(itype);
      itype->setOrigin(otype);
      element->entry = otype;
    }
      // fall through
    case StackElement::NamespaceTypeEntry:
      if (element->entry == 0) {
        element->entry = new NamespaceTypeEntry(name);
      }
      // fall through
    case StackElement::ObjectTypeEntry:
      if (element->entry == 0) {
        element->entry = new ObjectTypeEntry(name);
      }
      // fall through
    case StackElement::ValueTypeEntry:
    {
      if (element->entry == 0) {
        element->entry = new ValueTypeEntry(name);
      }

      ComplexTypeEntry* ctype = static_cast<ComplexTypeEntry*>(element->entry);
      ctype->setTargetLangPackage(attributes["package"]);
      ctype->setDefaultSuperclass(attributes["default-superclass"]);
      ctype->setGenericClass(convertBoolean(attributes["generic-class"], "generic-class", false));

      if (!convertBoolean(attributes["generate"], "generate", true))
        element->entry->setCodeGeneration(TypeEntry::GenerateForSubclass);
      else
        element->entry->setCodeGeneration(m_generate);

      QString javaName = attributes["java-name"];
      if (!javaName.isEmpty())
        ctype->setTargetLangName(javaName);

      // The expense policy
      QString limit = attributes["expense-limit"];
      if (!limit.isEmpty() && limit != "none") {
        ExpensePolicy ep;
        ep.limit = limit.toInt();
        ep.cost = attributes["expense-cost"];
        ctype->setExpensePolicy(ep);
      }
      ctype->setCreateShell(convertBoolean(attributes["create-shell"], "create-shell", false));
      ctype->setCreatePromoter(convertBoolean(attributes["create-promoter"], "create-promoter", false));

      ctype->setIsPolymorphicBase(convertBoolean(attributes["polymorphic-base"], "polymorphic-base", false));
      ctype->setPolymorphicIdValue(attributes["polymorphic-id-expression"]);

      if (element->type == StackElement::ObjectTypeEntry || element->type == StackElement::ValueTypeEntry) {
        if (convertBoolean(attributes["force-abstract"], "force-abstract", false))
          ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::ForceAbstract);
        if (convertBoolean(attributes["deprecated"], "deprecated", false))
          ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::Deprecated);
      }

      if (element->type == StackElement::InterfaceTypeEntry || element->type == StackElement::ValueTypeEntry
          || element->type == StackElement::ObjectTypeEntry)
      {
        if (convertBoolean(attributes["delete-in-main-thread"], "delete-in-main-thread", false))
          ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::DeleteInMainThread);
      }

      QString targetType = attributes["target-type"];
      if (!targetType.isEmpty() && element->entry->isComplex())
        static_cast<ComplexTypeEntry*>(element->entry)->setTargetType(targetType);

      // ctype->setInclude(Include(Include::IncludePath, ctype->name()));
      ctype = ctype->designatedInterface();
      if (ctype != 0)
        ctype->setTargetLangPackage(attributes["package"]);

    } break;
    default:
      Q_ASSERT(false);
    };

    element->entry->setAliases(aliases); // also add type under given aliases into type database
    if (element->entry)
      m_database->addType(element->entry);
    else
      ReportHandler::warning(QString("Type: %1 was rejected by typesystem").arg(name));

  } else if (element->type != StackElement::None) {
    bool topLevel = element->type == StackElement::Root || element->type == StackElement::SuppressedWarning
                    || element->type == StackElement::Rejection || element->type == StackElement::LoadTypesystem
                    || element->type == StackElement::InjectCode || element->type == StackElement::Template;

    if (!topLevel && current->type == StackElement::Root) {
      m_error = QString("Tag requires parent: '%1'").arg(tagName);
      return false;
    }

    StackElement topElement = current == 0 ? StackElement(0) : *current;
    element->entry = topElement.entry;

    QHash<QString, QString> attributes;
    switch (element->type) {
    case StackElement::Root:
      attributes["package"] = QString();
      attributes["default-superclass"] = QString();
      break;
    case StackElement::LoadTypesystem:
      attributes["name"] = QString();
      attributes["generate"] = "yes";
      break;
    case StackElement::NoNullPointers:
      attributes["default-value"] = QString();
      break;
    case StackElement::SuppressedWarning:
      attributes["text"] = QString();
      break;
    case StackElement::ReplaceDefaultExpression:
      attributes["with"] = QString();
      break;
    case StackElement::DefineOwnership:
      attributes["class"] = "java";
      attributes["owner"] = "";
      break;
    case StackElement::ModifyFunction:
      attributes["signature"] = QString();
      attributes["access"] = QString();
      attributes["remove"] = QString();
      attributes["rename"] = QString();
      attributes["deprecated"] = QString("no");
      attributes["associated-to"] = QString();
      attributes["virtual-slot"] = QString("no");
      break;
    case StackElement::ModifyArgument:
      attributes["index"] = QString();
      attributes["replace-value"] = QString();
      attributes["invalidate-after-use"] = QString("no");
      break;
    case StackElement::ModifyField:
      attributes["name"] = QString();
      attributes["write"] = "true";
      attributes["read"] = "true";
      break;
    case StackElement::Access:
      attributes["modifier"] = QString();
      break;
    case StackElement::Include:
      attributes["file-name"] = QString();
      attributes["location"] = QString();
      break;
    case StackElement::CustomMetaConstructor:
      attributes["name"] = topElement.entry->name().toLower() + "_create";
      attributes["param-name"] = "copy";
      break;
    case StackElement::CustomMetaDestructor:
      attributes["name"] = topElement.entry->name().toLower() + "_delete";
      attributes["param-name"] = "copy";
      break;
    case StackElement::ReplaceType:
      attributes["modified-type"] = QString();
      break;
    case StackElement::InjectCode:
      attributes["class"] = "java";
      attributes["position"] = "beginning";
      break;
    case StackElement::ConversionRule:
      attributes["class"] = "";
      break;
    case StackElement::RejectEnumValue:
      attributes["name"] = "";
      break;
    case StackElement::ArgumentMap:
      attributes["index"] = "1";
      attributes["meta-name"] = QString();
      break;
    case StackElement::Rename:
      attributes["to"] = QString();
      break;
    case StackElement::Rejection:
      attributes["class"] = "*";
      attributes["function-name"] = "*";
      attributes["field-name"] = "*";
      attributes["enum-name"] = "*";
      break;
    case StackElement::Removal:
      attributes["class"] = "all";
      break;
    case StackElement::Template:
      attributes["name"] = QString();
      break;
    case StackElement::TemplateInstanceEnum:
      attributes["name"] = QString();
      break;
    case StackElement::Replace:
      attributes["from"] = QString();
      attributes["to"] = QString();
      break;
    case StackElement::ReferenceCount:
      attributes["action"] = QString();
      attributes["variable-name"] = QString();
      attributes["thread-safe"] = QString("no");
      attributes["declare-variable"] = QString();
      attributes["access"] = QString("private");
      attributes["conditional"] = QString("");
      break;
    default:; // nada
    };

    if (attributes.count() > 0)
      fetchAttributeValues(tagName, atts, &attributes);

    switch (element->type) {
    case StackElement::Root:
      m_defaultPackage = attributes["package"];
      m_defaultSuperclass = attributes["default-superclass"];
      element->type = StackElement::Root;
      element->entry = new TypeSystemTypeEntry(m_defaultPackage);
      TypeDatabase::instance()->addType(element->entry);
      break;
    case StackElement::LoadTypesystem:
    {
      QString name = attributes["name"];
      if (name.isEmpty()) {
        m_error = "No typesystem name specified";
        return false;
      }

      if (!m_database->parseFile(name, m_qtVersion, convertBoolean(attributes["generate"], "generate", true))) {
        m_error = QString("Failed to parse: '%1'").arg(name);
        return false;
      }
    } break;
    case StackElement::RejectEnumValue:
    {
      if (!m_current_enum) {
        m_error = "<reject-enum-value> node must be used inside a <enum-type> node";
        return false;
      }
      QString name = attributes["name"];

      if (!name.isEmpty()) {
        m_current_enum->addEnumValueRejection(name);
      }

    } break;
    case StackElement::ReplaceType:
    {
      if (topElement.type != StackElement::ModifyArgument) {
        m_error = "Type replacement can only be specified for argument modifications";
        return false;
      }

      if (attributes["modified-type"].isEmpty()) {
        m_error = "Type replacement requires 'modified-type' attribute";
        return false;
      }

      m_function_mods.last().argument_mods.last().modified_type = attributes["modified-type"];
    } break;
    case StackElement::ConversionRule:
    {
      if (topElement.type != StackElement::ModifyArgument) {
        m_error = "Conversion rules can only be specified for argument modification";
        return false;
      }

      static QHash<QString, TypeSystem::Language> languageNames;
      if (languageNames.isEmpty()) {
        languageNames["native"] = TypeSystem::NativeCode;
        languageNames["shell"] = TypeSystem::ShellCode;
      }

      CodeSnip snip;
      QString languageAttribute = attributes["class"].toLower();
      TypeSystem::Language lang = languageNames.value(languageAttribute, TypeSystem::NoLanguage);
      if (lang == TypeSystem::NoLanguage) {
        m_error = QString("unsupported class attribute: '%1'").arg(languageAttribute);
        return false;
      }

      snip.language = lang;
      m_function_mods.last().argument_mods.last().conversion_rules.append(snip);
    }

    break;
    case StackElement::ModifyArgument:
    {
      if (topElement.type != StackElement::ModifyFunction) {
        m_error = QString::fromLatin1("argument modification requires function"
                                      " modification as parent, was %1")
                    .arg(topElement.type, 0, 16);
        return false;
      }

      QString index = attributes["index"];
      if (index == "return")
        index = "0";
      else if (index == "this")
        index = "-1";

      bool ok = false;
      int idx = index.toInt(&ok);
      if (!ok) {
        m_error = QString("Cannot convert '%1' to integer").arg(index);
        return false;
      }

      QString replace_value = attributes["replace-value"];

      if (!replace_value.isEmpty() && idx != 0) {
        m_error = QString("replace-value is only supported for return values (index=0).");
        return false;
      }

      ArgumentModification argumentModification = ArgumentModification(idx);
      argumentModification.replace_value = replace_value;
      argumentModification.reset_after_use =
        convertBoolean(attributes["invalidate-after-use"], "invalidate-after-use", false);
      m_function_mods.last().argument_mods.append(argumentModification);
    } break;
    case StackElement::NoNullPointers:
    {
      if (topElement.type != StackElement::ModifyArgument) {
        m_error = "no-null-pointer requires argument modification as parent";
        return false;
      }

      m_function_mods.last().argument_mods.last().no_null_pointers = true;
      if (m_function_mods.last().argument_mods.last().index == 0) {
        m_function_mods.last().argument_mods.last().null_pointer_default_value = attributes["default-value"];
      } else if (!attributes["default-value"].isEmpty()) {
        ReportHandler::warning("default values for null pointer guards are only effective for return values");
      }
    } break;
    case StackElement::DefineOwnership:
    {
      if (topElement.type != StackElement::ModifyArgument) {
        m_error = "define-ownership requires argument modification as parent";
        return false;
      }

      static QHash<QString, TypeSystem::Language> languageNames;
      if (languageNames.isEmpty()) {
        languageNames["java"] = TypeSystem::TargetLangCode;
        languageNames["shell"] = TypeSystem::ShellCode;
      }

      QString classAttribute = attributes["class"].toLower();
      TypeSystem::Language lang = languageNames.value(classAttribute, TypeSystem::NoLanguage);
      if (lang == TypeSystem::NoLanguage) {
        m_error = QString("unsupported class attribute: '%1'").arg(classAttribute);
        return false;
      }

      static QHash<QString, TypeSystem::Ownership> ownershipNames;
      if (ownershipNames.isEmpty()) {
        ownershipNames["python"] = TypeSystem::TargetLangOwnership;
        ownershipNames["c++"] = TypeSystem::CppOwnership;
        ownershipNames["default"] = TypeSystem::DefaultOwnership;
        ownershipNames["new-owner-of-this"] = TypeSystem::TargetLangThisOwnership;
      }

      QString ownershipAttribute = attributes["owner"].toLower();
      TypeSystem::Ownership owner = ownershipNames.value(ownershipAttribute, TypeSystem::InvalidOwnership);
      if (owner == TypeSystem::InvalidOwnership) {
        m_error = QString("unsupported owner attribute: '%1'").arg(ownershipAttribute);
        return false;
      }

      m_function_mods.last().argument_mods.last().ownerships[lang] = owner;
    } break;
    case StackElement::SuppressedWarning:
      if (attributes["text"].isEmpty())
        ReportHandler::warning("Suppressed warning with no text specified");
      else
        m_database->addSuppressedWarning(attributes["text"]);
      break;
    case StackElement::ArgumentMap:
    {
      if (!(topElement.type & StackElement::CodeSnipMask)) {
        m_error = "Argument maps requires code injection as parent";
        return false;
      }

      bool ok;
      int pos = attributes["index"].toInt(&ok);
      if (!ok) {
        m_error = QString("Can't convert position '%1' to integer").arg(attributes["position"]);
        return false;
      }

      if (pos <= 0) {
        m_error = QString("Argument position %1 must be a positive number").arg(pos);
        return false;
      }

      QString meta_name = attributes["meta-name"];
      if (meta_name.isEmpty()) {
        ReportHandler::warning("Empty meta name in argument map");
      }

      if (topElement.type == StackElement::InjectCodeInFunction) {
        m_function_mods.last().snips.last().argumentMap[pos] = meta_name;
      } else {
        ReportHandler::warning("Argument maps are only useful for injection of code "
                               "into functions.");
      }
    } break;
    case StackElement::Removal:
    {
      if (topElement.type != StackElement::ModifyFunction) {
        m_error = "Function modification parent required";
        return false;
      }

      static QHash<QString, TypeSystem::Language> languageNames;
      if (languageNames.isEmpty()) {
        languageNames["java"] = TypeSystem::TargetLangAndNativeCode;
        languageNames["all"] = TypeSystem::All;
      }

      QString languageAttribute = attributes["class"].toLower();
      TypeSystem::Language lang = languageNames.value(languageAttribute, TypeSystem::NoLanguage);
      if (lang == TypeSystem::NoLanguage) {
        m_error = QString("unsupported class attribute: '%1'").arg(languageAttribute);
        return false;
      }

      m_function_mods.last().removal = lang;
    } break;
    case StackElement::Rename:
    case StackElement::Access:
    {
      if (topElement.type != StackElement::ModifyField && topElement.type != StackElement::ModifyFunction) {
        m_error = "Function or field modification parent required";
        return false;
      }

      Modification* mod = 0;
      if (topElement.type == StackElement::ModifyFunction)
        mod = &m_function_mods.last();
      else
        mod = &m_field_mods.last();

      QString modifier;
      if (element->type == StackElement::Rename) {
        modifier = "rename";
        QString renamed_to = attributes["to"];
        if (renamed_to.isEmpty()) {
          m_error = "Rename modifier requires 'to' attribute";
          return false;
        }

        if (topElement.type == StackElement::ModifyFunction)
          mod->setRenamedTo(renamed_to);
        else
          mod->setRenamedTo(renamed_to);
      } else {
        modifier = attributes["modifier"].toLower();
      }

      if (modifier.isEmpty()) {
        m_error = "No access modification specified";
        return false;
      }

      static QHash<QString, FunctionModification::Modifiers> modifierNames;
      if (modifierNames.isEmpty()) {
        modifierNames["private"] = Modification::Private;
        modifierNames["public"] = Modification::Public;
        modifierNames["protected"] = Modification::Protected;
        modifierNames["friendly"] = Modification::Friendly;
        modifierNames["rename"] = Modification::Rename;
        modifierNames["final"] = Modification::Final;
        modifierNames["non-final"] = Modification::NonFinal;
      }

      if (!modifierNames.contains(modifier)) {
        m_error = QString("Unknown access modifier: '%1'").arg(modifier);
        return false;
      }

      mod->modifiers |= modifierNames[modifier];
    } break;
    case StackElement::RemoveArgument:
      if (topElement.type != StackElement::ModifyArgument) {
        m_error = "Removing argument requires argument modification as parent";
        return false;
      }

      m_function_mods.last().argument_mods.last().removed = true;

      break;

    case StackElement::ModifyField:
    {
      QString name = attributes["name"];
      if (name.isEmpty())
        break;
      FieldModification fm;
      fm.name = name;
      fm.modifiers = 0;

      QString read = attributes["read"];
      QString write = attributes["write"];

      if (read == "true")
        fm.modifiers |= FieldModification::Readable;
      if (write == "true")
        fm.modifiers |= FieldModification::Writable;

      m_field_mods << fm;
    } break;
    case StackElement::ModifyFunction:
    {
      if (!(topElement.type & StackElement::ComplexTypeEntryMask)) {
        m_error = QString::fromLatin1("Modify function requires complex type as parent"
                                      ", was=%1")
                    .arg(topElement.type, 0, 16);
        return false;
      }
      QString signature = attributes["signature"];

      signature = TypeSystem::normalizedSignature(signature.toLocal8Bit().constData());
      if (signature.isEmpty()) {
        m_error = "No signature for modified function";
        return false;
      }

      FunctionModification mod;
      mod.signature = signature;

      QString access = attributes["access"].toLower();
      if (!access.isEmpty()) {
        if (access == QLatin1String("private"))
          mod.modifiers |= Modification::Private;
        else if (access == QLatin1String("protected"))
          mod.modifiers |= Modification::Protected;
        else if (access == QLatin1String("public"))
          mod.modifiers |= Modification::Public;
        else if (access == QLatin1String("final"))
          mod.modifiers |= Modification::Final;
        else if (access == QLatin1String("non-final"))
          mod.modifiers |= Modification::NonFinal;
        else {
          m_error = QString::fromLatin1("Bad access type '%1'").arg(access);
          return false;
        }
      }

      if (convertBoolean(attributes["deprecated"], "deprecated", false)) {
        mod.modifiers |= Modification::Deprecated;
      }

      QString remove = attributes["remove"].toLower();
      if (!remove.isEmpty()) {
        if (remove == QLatin1String("all"))
          mod.removal = TypeSystem::All;
        else if (remove == QLatin1String("java"))
          mod.removal = TypeSystem::TargetLangAndNativeCode;
        else {
          m_error = QString::fromLatin1("Bad removal type '%1'").arg(remove);
          return false;
        }
      }

      QString rename = attributes["rename"];
      if (!rename.isEmpty()) {
        mod.renamedToName = rename;
        mod.modifiers |= Modification::Rename;
      }

      QString association = attributes["associated-to"];
      if (!association.isEmpty())
        mod.association = association;

      mod.modifiers |=
        (convertBoolean(attributes["virtual-slot"], "virtual-slot", false) ? Modification::VirtualSlot : 0);

      m_function_mods << mod;
    } break;
    case StackElement::ReplaceDefaultExpression:
      if (!(topElement.type & StackElement::ModifyArgument)) {
        m_error = "Replace default expression only allowed as child of argument modification";
        return false;
      }

      if (attributes["with"].isEmpty()) {
        m_error = "Default expression replaced with empty string. Use remove-default-expression instead.";
        return false;
      }

      m_function_mods.last().argument_mods.last().replaced_default_expression = attributes["with"];
      break;
    case StackElement::RemoveDefaultExpression:
      m_function_mods.last().argument_mods.last().removed_default_expression = true;
      break;
    case StackElement::CustomMetaConstructor:
    case StackElement::CustomMetaDestructor:
    {
      CustomFunction* func = new CustomFunction(attributes["name"]);
      func->param_name = attributes["param-name"];
      element->value.customFunction = func;
    } break;
    case StackElement::ReferenceCount:
    {
      if (topElement.type != StackElement::ModifyArgument) {
        m_error = "reference-count must be child of modify-argument";
        return false;
      }

      ReferenceCount rc;
      rc.threadSafe = convertBoolean(attributes["thread-safe"], "thread-safe", false);

      static QHash<QString, ReferenceCount::Action> actions;
      if (actions.isEmpty()) {
        actions["add"] = ReferenceCount::Add;
        actions["add-all"] = ReferenceCount::AddAll;
        actions["remove"] = ReferenceCount::Remove;
        actions["set"] = ReferenceCount::Set;
        actions["ignore"] = ReferenceCount::Ignore;
      }
      rc.action = actions.value(attributes["action"].toLower(), ReferenceCount::Invalid);

      rc.variableName = attributes["variable-name"];
      if (rc.action != ReferenceCount::Ignore && rc.variableName.isEmpty()) {
        m_error = "variable-name attribute must be specified";
        return false;
      }

      rc.declareVariable = attributes["declare-variable"];
      rc.conditional = attributes["conditional"];

      static QHash<QString, int> accessRights;
      if (accessRights.isEmpty()) {
        accessRights["private"] = ReferenceCount::Private;
        accessRights["public"] = ReferenceCount::Public;
        accessRights["protected"] = ReferenceCount::Protected;
        accessRights["friendly"] = ReferenceCount::Friendly;
      }
      rc.access = accessRights.value(attributes["access"].toLower(), 0);
      if (rc.access == 0) {
        m_error = "unrecognized access value: " + attributes["access"];
        return false;
      }

      if (rc.action == ReferenceCount::Invalid) {
        m_error = "unrecognized value for action attribute. supported actions:";
        QStringList actionKeys = actions.keys();
        for (QString action : actionKeys)
          m_error += " " + action;
      }

      m_function_mods.last().argument_mods.last().referenceCounts.append(rc);
    } break;
    case StackElement::InjectCode:
    {
      if (((topElement.type & StackElement::ComplexTypeEntryMask) == 0)
          && (topElement.type != StackElement::ModifyFunction) && (topElement.type != StackElement::Root))
      {
        m_error = "wrong parent type for code injection";
        return false;
      }

      static QHash<QString, TypeSystem::Language> languageNames;
      if (languageNames.isEmpty()) {
        languageNames["java"] = TypeSystem::TargetLangCode;
        languageNames["native"] = TypeSystem::NativeCode;
        languageNames["shell"] = TypeSystem::ShellCode;
        languageNames["shell-declaration"] = TypeSystem::ShellDeclaration;
        languageNames["library-initializer"] = TypeSystem::PackageInitializer;
        languageNames["destructor-function"] = TypeSystem::DestructorFunction;
        languageNames["constructors"] = TypeSystem::Constructors;
        languageNames["interface"] = TypeSystem::Interface;
        languageNames["pywrap-cpp"] = TypeSystem::PyWrapperCode;
        languageNames["pywrap-h"] = TypeSystem::PyWrapperDeclaration;
        languageNames["pywrap-operators"] = TypeSystem::PyWrapperOperators;
        languageNames["pyshell-h"] = TypeSystem::PyShellDeclaration;
        languageNames["pyinheritshell-h"] = TypeSystem::PyInheritShellDeclaration;
        languageNames["pyinheritshell-constructor-code"] = TypeSystem::PyInheritShellConstructorCode;
        languageNames["pyinit-cpp"] = TypeSystem::PyInitSource;
        languageNames["pysetwrapperfunc"] = TypeSystem::PySetWrapperFunc;
      }

      QString className = attributes["class"].toLower();
      if (!languageNames.contains(className)) {
        m_error = QString("Invalid class specifier: '%1'").arg(className);
        return false;
      }

      static QHash<QString, CodeSnip::Position> positionNames;
      if (positionNames.isEmpty()) {
        positionNames["beginning"] = CodeSnip::Beginning;
        positionNames["end"] = CodeSnip::End;
        // QtScript
        positionNames["prototype-initialization"] = CodeSnip::PrototypeInitialization;
        positionNames["constructor-initialization"] = CodeSnip::ConstructorInitialization;
        positionNames["constructor"] = CodeSnip::Constructor;
      }

      QString position = attributes["position"].toLower();
      if (!positionNames.contains(position)) {
        m_error = QString("Invalid position: '%1'").arg(position);
        return false;
      }

      CodeSnip snip;
      snip.language = languageNames[className];
      snip.position = positionNames[position];

      if (snip.language == TypeSystem::Interface && topElement.type != StackElement::InterfaceTypeEntry) {
        m_error = "Interface code injections must be direct child of an interface type entry";
        return false;
      }

      if (topElement.type == StackElement::ModifyFunction) {
        FunctionModification mod = m_function_mods.last();
        if (snip.language == TypeSystem::ShellDeclaration) {
          m_error = "no function implementation in shell declaration in which to inject code";
          return false;
        }

        m_function_mods.last().snips << snip;
        element->type = StackElement::InjectCodeInFunction;
      } else if (topElement.type == StackElement::Root) {
        ((TypeSystemTypeEntry*)element->entry)->snips << snip;

      } else if (topElement.type != StackElement::Root) {
        m_code_snips << snip;
      }
    } break;
    case StackElement::Include:
    {
      QString location = attributes["location"].toLower();

      static QHash<QString, Include::IncludeType> locationNames;
      if (locationNames.isEmpty()) {
        locationNames["global"] = Include::IncludePath;
        locationNames["local"] = Include::LocalPath;
        locationNames["java"] = Include::TargetLangImport;
      }

      if (!locationNames.contains(location)) {
        m_error = QString("Location not recognized: '%1'").arg(location);
        return false;
      }

      Include::IncludeType loc = locationNames[location];
      Include inc(loc, attributes["file-name"]);

      ComplexTypeEntry* ctype = static_cast<ComplexTypeEntry*>(element->entry);
      if (topElement.type & StackElement::ComplexTypeEntryMask) {
        ctype->setInclude(inc);
      } else if (topElement.type == StackElement::ExtraIncludes) {
        ctype->addExtraInclude(inc);
      } else {
        m_error = "Only supported parents are complex types and extra-includes";
        return false;
      }

      inc = ctype->include();
      IncludeList lst = ctype->extraIncludes();
      ctype = ctype->designatedInterface();
      if (ctype != 0) {
        ctype->setExtraIncludes(lst);
        ctype->setInclude(inc);
      }
    } break;
    case StackElement::Rejection:
    {
      QString cls = attributes["class"];
      QString function = attributes["function-name"];
      QString field = attributes["field-name"];
      QString enum_ = attributes["enum-name"];
      if (cls == "*" && function == "*" && field == "*" && enum_ == "*") {
        m_error = "bad reject entry, neither 'class', 'function-name' nor "
                  "'field' specified";
        return false;
      }
      m_database->addRejection(cls, function, field, enum_);
    } break;
    case StackElement::Template:
      element->value.templateEntry = new TemplateEntry(attributes["name"]);
      break;
    case StackElement::TemplateInstanceEnum:
      if (!(topElement.type & StackElement::CodeSnipMask) && (topElement.type != StackElement::Template)
          && (topElement.type != StackElement::CustomMetaConstructor)
          && (topElement.type != StackElement::CustomMetaDestructor)
          && (topElement.type != StackElement::ConversionRule))
      {
        m_error = "Can only insert templates into code snippets, templates, custom-constructors, custom-destructors or "
                  "conversion-rule.";
        return false;
      }
      element->value.templateInstance = new TemplateInstance(attributes["name"]);
      break;
    case StackElement::Replace:
      if (topElement.type != StackElement::TemplateInstanceEnum) {
        m_error = "Can only insert replace rules into insert-template.";
        return false;
      }
      element->parent->value.templateInstance->addReplaceRule(attributes["from"], attributes["to"]);
      break;
    default:
      break; // nada
    };
  }

  current = element.release();
  return true;
}

TypeDatabase* TypeDatabase::instance()
{
  static TypeDatabase* db = new TypeDatabase();
  return db;
}

TypeDatabase::TypeDatabase()
  : m_suppressWarnings(true)
{
  StringTypeEntry* mainStringType = new StringTypeEntry("QString");
  addType(mainStringType);

  StringTypeEntry* e = new StringTypeEntry("QLatin1String");
  e->setPreferredConversion(false);
  e->setEquivalentType(mainStringType);
  addType(e);

  e = new StringTypeEntry("QStringRef");
  e->setPreferredConversion(false);
  e->setEquivalentType(mainStringType);
  addType(e);

  e = new StringTypeEntry("QStringView");
  e->setPreferredConversion(false);
  e->setEquivalentType(mainStringType);
  addType(e);

  e = new StringTypeEntry("QAnyStringView");
  e->setPreferredConversion(false);
  e->setEquivalentType(mainStringType);
  addType(e);

  e = new StringTypeEntry("QXmlStreamStringRef");
  e->setPreferredConversion(false);
  addType(e);

  addType(new CharTypeEntry("QChar"));

  CharTypeEntry* c = new CharTypeEntry("QLatin1Char");
  c->setPreferredConversion(false);
  addType(c);

  {
    VariantTypeEntry* qvariant = new VariantTypeEntry("QVariant");
    qvariant->setCodeGeneration(TypeEntry::GenerateNothing);
    addType(qvariant);
  }

  {
    JObjectWrapperTypeEntry* wrapper = new JObjectWrapperTypeEntry("JObjectWrapper");
    wrapper->setCodeGeneration(TypeEntry::GenerateNothing);
    addType(wrapper);
  }

  //addType(new ThreadTypeEntry());
  addType(new VoidTypeEntry());

  // Predefined containers...
  addType(new ContainerTypeEntry("QList", ContainerTypeEntry::ListContainer));
  addType(new ContainerTypeEntry("QStringList", ContainerTypeEntry::StringListContainer));
  addType(new ContainerTypeEntry("QLinkedList", ContainerTypeEntry::LinkedListContainer));
  addType(new ContainerTypeEntry("QVector", ContainerTypeEntry::VectorContainer));
  addType(new ContainerTypeEntry("QStack", ContainerTypeEntry::StackContainer));
  addType(new ContainerTypeEntry("QSet", ContainerTypeEntry::SetContainer));
  addType(new ContainerTypeEntry("QMap", ContainerTypeEntry::MapContainer));
  addType(new ContainerTypeEntry("QHash", ContainerTypeEntry::HashContainer));
  addType(new ContainerTypeEntry("QPair", ContainerTypeEntry::PairContainer));
  addType(new ContainerTypeEntry("std::pair", ContainerTypeEntry::PairContainer));
  addType(new ContainerTypeEntry("QQueue", ContainerTypeEntry::QueueContainer));
  addType(new ContainerTypeEntry("QMultiMap", ContainerTypeEntry::MultiMapContainer));

  addRemoveFunctionToTemplates(this);
}

void TypeDatabase::finalSetup()
{
  TypeEntry* byteArrayType = findType("QByteArray");
  if (byteArrayType) {
    // Support QByteArrayView as alternative parameter type.
    // Using StringTypeEntry for it, because no wrappers are generated for those types
    StringTypeEntry* e = new StringTypeEntry("QByteArrayView");
    e->setPreferredConversion(false);
    e->setEquivalentType(byteArrayType);
    addType(e);
  }
}

bool TypeDatabase::parseFile(const QString& filename, unsigned int qtVersion, bool generate)
{
  QFile file(filename);

  // Attempt to open the file from the specified path
  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    // If opening fails, attempt to load from Qt resources
    file.setFileName(":/trolltech/generator/" + filename);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
      ReportHandler::warning(QString::fromLatin1("Could not open typesystem file: '%1'").arg(filename));
      return false;
    }
  }

  int count = m_entries.size();

  Handler handler(this, qtVersion, generate);
  QXmlStreamReader reader(&file);

  bool ok = handler.parse(reader);
  if (!ok && !handler.errorString().isEmpty())
    ReportHandler::warning(handler.errorString());

  int newCount = m_entries.size();

  ReportHandler::debugSparse(QString::fromLatin1("Parsed: '%1', %2 new entries").arg(filename).arg(newCount - count));

  return ok;
}

QString PrimitiveTypeEntry::javaObjectName() const
{
  static QHash<QString, QString> table;
  if (table.isEmpty()) {
    table["boolean"] = "Boolean";
    table["byte"] = "Byte";
    table["char"] = "Character";
    table["short"] = "Short";
    table["int"] = "Integer";
    table["long"] = "Long";
    table["float"] = "Float";
    table["double"] = "Double";
  }
  Q_ASSERT(table.contains(targetLangName()));
  return table[targetLangName()];
}

ContainerTypeEntry* TypeDatabase::findContainerType(const QString& name)
{
  QString template_name = name;

  int pos = name.indexOf('<');
  if (pos > 0)
    template_name = name.left(pos);

  TypeEntry* type_entry = findType(template_name);
  if (type_entry && type_entry->isContainer())
    return static_cast<ContainerTypeEntry*>(type_entry);
  return 0;
}

PrimitiveTypeEntry* TypeDatabase::findTargetLangPrimitiveType(const QString& java_name)
{
  for (QList<TypeEntry*> entries : m_entries.values()) {
    for (TypeEntry* e : entries) {
      if (e && e->isPrimitive()) {
        PrimitiveTypeEntry* pe = static_cast<PrimitiveTypeEntry*>(e);
        if (pe->targetLangName() == java_name && pe->preferredConversion())
          return pe;
      }
    }
  }

  return 0;
}

IncludeList TypeDatabase::extraIncludes(const QString& className)
{
  ComplexTypeEntry* typeEntry = findComplexType(className);
  if (typeEntry != 0)
    return typeEntry->extraIncludes();
  else
    return IncludeList();
}

QString Include::toString() const
{
  if (type == IncludePath)
    return "#include <" + name + '>';
  else if (type == LocalPath)
    return "#include \"" + name + "\"";
  else
    return "import " + name + ";";
}

QString Modification::accessModifierString() const
{
  if (isPrivate())
    return "private";
  if (isProtected())
    return "protected";
  if (isPublic())
    return "public";
  if (isFriendly())
    return "friendly";
  return QString();
}

FunctionModificationList ComplexTypeEntry::functionModifications(const QString& signature) const
{
  FunctionModificationList lst;
  for (int i = 0; i < m_function_mods.count(); ++i) {
    FunctionModification mod = m_function_mods.at(i);
    if (mod.signature == signature) {
      lst << mod;
    }
  }

  return lst;
}

FieldModification ComplexTypeEntry::fieldModification(const QString& name) const
{
  for (int i = 0; i < m_field_mods.size(); ++i)
    if (m_field_mods.at(i).name == name)
      return m_field_mods.at(i);
  FieldModification mod;
  mod.name = name;
  mod.modifiers = FieldModification::Readable | FieldModification::Writable;
  return mod;
}

QString ContainerTypeEntry::javaPackage() const
{
  if (m_type == PairContainer)
    return "com.trolltech.qt";
  return "java.util";
}

QString ContainerTypeEntry::targetLangName() const
{

  switch (m_type) {
  case StringListContainer:
    return "List";
  case ListContainer:
    return "List";
  case LinkedListContainer:
    return "LinkedList";
  case VectorContainer:
    return "List";
  case StackContainer:
    return "Stack";
  case QueueContainer:
    return "Queue";
  case SetContainer:
    return "Set";
  case MapContainer:
    return "SortedMap";
  case MultiMapContainer:
    return "SortedMap";
  case HashContainer:
    return "HashMap";
    //     case MultiHashCollectio: return "MultiHash";
  case PairContainer:
    return "QPair";
  default:
    qWarning() << "bad type... " << m_type;
    break;
  }
  return QString();
}

QString ContainerTypeEntry::qualifiedCppName() const
{
  if (m_type == StringListContainer)
    return "QStringList";
  return ComplexTypeEntry::qualifiedCppName();
}

QString EnumTypeEntry::javaQualifier() const
{
  TypeEntry* te = TypeDatabase::instance()->findType(m_qualifier);
  if (te != 0)
    return te->targetLangName();
  else
    return m_qualifier;
}

QString EnumTypeEntry::jniName() const
{
  return "jint";
}

QString FlagsTypeEntry::jniName() const
{
  return "jint";
}

QString FlagsTypeEntry::qualifiedTargetLangName() const
{
  return javaPackage() + "." + m_enum->javaQualifier() + "." + targetLangName();
}

void TypeDatabase::addRejection(const QString& class_name, const QString& function_name, const QString& field_name,
  const QString& enum_name)
{
  TypeRejection r;
  r.class_name = class_name;
  r.function_name = function_name;
  r.field_name = field_name;
  r.enum_name = enum_name;

  m_rejections << r;
}

bool TypeDatabase::isClassRejected(const QString& class_name)
{
  if (!m_rebuild_classes.isEmpty())
    return !m_rebuild_classes.contains(class_name);

  for (const TypeRejection& r : m_rejections)
    if (r.class_name == class_name && r.function_name == "*" && r.field_name == "*" && r.enum_name == "*") {
      return true;
    }
  return false;
}

bool TypeDatabase::isEnumRejected(const QString& class_name, const QString& enum_name)
{
  for (const TypeRejection& r : m_rejections) {
    if (r.enum_name == enum_name && (r.class_name == class_name || r.class_name == "*")) {
      return true;
    }
  }

  return false;
}

bool TypeDatabase::isFunctionRejected(const QString& class_name, const QString& function_name)
{
  for (const TypeRejection& r : m_rejections)
    if (r.function_name == function_name && (r.class_name == class_name || r.class_name == "*"))
      return true;
  return false;
}

bool TypeDatabase::isFieldRejected(const QString& class_name, const QString& field_name)
{
  for (const TypeRejection& r : m_rejections)
    if (r.field_name == field_name && (r.class_name == class_name || r.class_name == "*"))
      return true;
  return false;
}

void TypeDatabase::addType(TypeEntry* e)
{
  m_entries[e->qualifiedCppName()].append(e);
  for (const QString& alias : e->aliases()) {
    m_entries[alias].append(e);
  }
}

FlagsTypeEntry* TypeDatabase::findFlagsType(const QString& name) const
{
  FlagsTypeEntry* fte = (FlagsTypeEntry*)findType(name);
  return fte ? fte : (FlagsTypeEntry*)m_flags_entries.value(name);
}

QString TypeDatabase::globalNamespaceClassName(const TypeEntry* entry)
{
  return "Qt" + entry->javaPackage().split('.').back();
}

/*!
 * The Visual Studio 2002 compiler doesn't support these symbols,
 * which our typedefs unforntuatly expand to.
 */
QString fixCppTypeName(const QString& name)
{
  if (name == "long long")
    return "qint64";
  else if (name == "unsigned long long")
    return "quint64";
  return name;
}

QString formattedCodeHelper(QTextStream& s, Indentor& indentor, QStringList& lines)
{
  bool multilineComment = false;
  bool lastEmpty = true;
  QString lastLine;
  while (!lines.isEmpty()) {
    const QString line = lines.takeFirst().trimmed();
    if (line.isEmpty()) {
      if (!lastEmpty)
        s << Qt::endl;
      lastEmpty = true;
      continue;
    } else {
      lastEmpty = false;
    }
    if (line.startsWith("/*"))
      multilineComment = true;

    if (multilineComment) {
      s << indentor;
      if (line.startsWith("*"))
        s << " ";
      s << line << Qt::endl;
      if (line.endsWith("*/"))
        multilineComment = false;
    } else if (line.startsWith("}")) {
      return line;
    } else if (line.endsWith("}")) {
      s << indentor << line << Qt::endl;
      return 0;
    } else if (line.endsWith("{")) {
      s << indentor << line << Qt::endl;
      QString tmp;
      {
        Indentation indent(indentor);
        tmp = formattedCodeHelper(s, indentor, lines);
      }
      if (!tmp.isNull()) {
        s << indentor << tmp << Qt::endl;
      }
      lastLine = tmp;
      continue;
    } else {
      s << indentor;
      if (!lastLine.isEmpty() && !lastLine.endsWith(";") && !line.startsWith("@") && !line.startsWith("//")
          && !lastLine.startsWith("//") && !lastLine.endsWith("}") && !line.startsWith("{"))
        s << "    ";
      s << line << Qt::endl;
    }
    lastLine = line;
  }
  return 0;
}

QTextStream& CodeSnip::formattedCode(QTextStream& s, Indentor& indentor) const
{
  QStringList lst(code().split("\n"));
  while (!lst.isEmpty()) {
    QString tmp = formattedCodeHelper(s, indentor, lst);
    if (!tmp.isNull()) {
      s << indentor << tmp << Qt::endl;
    }
  }
  s.flush();
  return s;
}

QString TemplateInstance::expandCode() const
{
  TemplateEntry* templateEntry = TypeDatabase::instance()->findTemplate(m_name);
  if (templateEntry) {
    QString res = templateEntry->code();
    for (QString key : replaceRules.keys()) {
      res.replace(key, replaceRules[key]);
    }
    return "// TEMPLATE - " + m_name + " - START" + res + "// TEMPLATE - " + m_name + " - END";
  } else {
    ReportHandler::warning("insert-template referring to non-existing template '" + m_name + "'");
  }
  return QString();
}

QString CodeSnipAbstract::code() const
{
  QString res;
  for (CodeSnipFragment* codeFrag : codeList) {
    res.append(codeFrag->code());
  }
  return res;
}

QString CodeSnipFragment::code() const
{
  if (m_instance)
    return m_instance->expandCode();
  else
    return m_code;
}

QString FunctionModification::toString() const
{
  QString str = signature + QLatin1String("->");
  if (modifiers & AccessModifierMask) {
    switch (modifiers & AccessModifierMask) {
    case Private:
      str += QLatin1String("private");
      break;
    case Protected:
      str += QLatin1String("protected");
      break;
    case Public:
      str += QLatin1String("public");
      break;
    case Friendly:
      str += QLatin1String("friendly");
      break;
    }
  }

  if (modifiers & Final)
    str += QLatin1String("final");
  if (modifiers & NonFinal)
    str += QLatin1String("non-final");

  if (modifiers & Readable)
    str += QLatin1String("readable");
  if (modifiers & Writable)
    str += QLatin1String("writable");

  if (modifiers & CodeInjection) {
    for (CodeSnip s : snips) {
      str += QLatin1String("\n//code injection:\n");
      str += s.code();
    }
  }

  if (modifiers & Rename)
    str += QLatin1String("renamed:") + renamedToName;

  if (modifiers & Deprecated)
    str += QLatin1String("deprecate");

  if (modifiers & ReplaceExpression)
    str += QLatin1String("replace-expression");

  return str;
}

static void removeFunction(ComplexTypeEntry* e, const char* signature)
{
  FunctionModification mod;
  mod.signature = TypeSystem::normalizedSignature(signature);
  mod.removal = TypeSystem::All;

  e->addFunctionModification(mod);
}

static void injectCode(ComplexTypeEntry* e, const char* signature, const QByteArray& code, const ArgumentMap& args)
{
  CodeSnip snip;
  snip.language = TypeSystem::NativeCode;
  snip.position = CodeSnip::Beginning;
  snip.addCode(QString::fromLatin1(code));
  snip.argumentMap = args;

  FunctionModification mod;
  mod.signature = TypeSystem::normalizedSignature(signature);
  mod.snips << snip;
  mod.modifiers = Modification::CodeInjection;
  e->addFunctionModification(mod);
}

static void addRemoveFunctionToTemplates(TypeDatabase* db)
{
  ContainerTypeEntry* qvector = db->findContainerType(QLatin1String("QVector"));
  removeFunction(qvector, "constData() const");
  removeFunction(qvector, "data() const");
  removeFunction(qvector, "data()");
  removeFunction(qvector, "first()");
  removeFunction(qvector, "last()");
  removeFunction(qvector, "operator[](int)");
  removeFunction(qvector, "operator[](int) const");
  removeFunction(qvector, "operator=(QVector<T>)");

  ContainerTypeEntry* qlist = db->findContainerType(QLatin1String("QList"));
  removeFunction(qlist, "constData() const");
  removeFunction(qlist, "data() const");
  removeFunction(qlist, "data()");
  removeFunction(qlist, "back()");
  removeFunction(qlist, "front()");
  removeFunction(qlist, "first()");
  removeFunction(qlist, "last()");
  removeFunction(qlist, "operator[](int)");
  removeFunction(qlist, "operator[](int) const");
  removeFunction(qlist, "operator=(QList<T>)");

  ContainerTypeEntry* qqueue = db->findContainerType(QLatin1String("QQueue"));
  removeFunction(qqueue, "head() const");

  // QtScript: The next part is Java-specific, skip it for now...
  return;

  ArgumentMap args1;
  args1[1] = QLatin1String("$1");
  ArgumentMap args2 = args1;
  args2[2] = QLatin1String("$2");

  QByteArray code = "\nif ($1 >= __qt_this->size() || $1 < 0) {"
                    "\n   __jni_env->ThrowNew(__jni_env->FindClass(\"java/lang/IndexOutOfBoundsException\"),"
                    "\n                       QString::fromLatin1(\"Accessing container of size %3 at %4\")"
                    "\n                       .arg(__qt_this->size()).arg($1).toLatin1());"
                    "\n   return;"
                    "\n}";

  QByteArray code_with_return = QByteArray(code).replace("return;", "return 0;");

  QByteArray code_index_length =
    "\nif ($1 < 0 || $2 < 0 || ($1 + $2) >= __qt_this->size()) {"
    "\n   __jni_env->ThrowNew(__jni_env->FindClass(\"java/lang/IndexOutOfBoundsException\"),"
    "\n                       QString::fromLatin1(\"Accessing container of size %3 from %4 to %5\")"
    "\n                       .arg(__qt_this->size()).arg($1).arg($1+$2).toLatin1());"
    "\n   return;"
    "\n}";

  QByteArray code_non_empty =
    "\nif (__qt_this->isEmpty()) {"
    "\n   __jni_env->ThrowNew(__jni_env->FindClass(\"java/lang/IndexOutOfBoundsException\"),"
    "\n                       QString::fromLatin1(\"Accessing empty container...\").toLatin1());"
    "\n   return;"
    "\n}";

  QByteArray code_two_indices =
    "\nif ($1 < 0 || $2 < 0 || $1 >= __qt_this->size() || $2 >= __qt_this->size()) {"
    "\n   __jni_env->ThrowNew(__jni_env->FindClass(\"java/lang/IndexOutOfBoundsException\"),"
    "\n                       QString::fromLatin1(\"Accessing container of size %3 from %4 to %5\")"
    "\n                       .arg(__qt_this->size()).arg($1).arg($1+$2).toLatin1());"
    "\n   return;"
    "\n}";

  { // QVector safty...
    injectCode(qvector, "at(int) const", code_with_return, args1);
    injectCode(qvector, "replace(int,T)", code, args1);
    injectCode(qvector, "remove(int)", code, args1);
    injectCode(qvector, "remove(int, int)", code_index_length, args2);
    injectCode(qvector, "pop_back()", code_non_empty, ArgumentMap());
    injectCode(qvector, "pop_front()", code_non_empty, ArgumentMap());
  }

  { // QList safty...
    injectCode(qlist, "at(int) const", code_with_return, args1);
    injectCode(qlist, "replace(int, T)", code, args1);
    injectCode(qlist, "pop_back()", code_non_empty, ArgumentMap());
    injectCode(qlist, "pop_front()", code_non_empty, ArgumentMap());
    injectCode(qlist, "swap(int, int)", code_two_indices, args2);
    injectCode(qlist, "move(int, int)", code_two_indices, args2);
    injectCode(qlist, "removeAt(int)", code, args1);
    injectCode(qlist, "takeAt(int)", code_with_return, args1);
  }
}

QByteArray TypeSystem::normalizedSignature(const char* signature)
{
  QByteArray result = QMetaObject::normalizedSignature(signature);
  result.replace("unsigned ", "u");
  result.replace("qreal", "double");
  result.replace("long long", "longlong");
  result.replace("qlonglong", "longlong");
  result.replace("qulonglong", "ulonglong");
  result.replace("qint64", "longlong");
  result.replace("quint64", "ulonglong");
  result.replace("QStringList<QString>", "QStringList");
  return result;
}

unsigned int TypeSystem::qtVersionFromString(const QString& value, bool& ok)
{
  ok = true;
  QList<uint> values;
  for (QString dotValue : value.split('.')) {
    dotValue = dotValue.trimmed();
    bool ok2;
    values.append(dotValue.toUInt(&ok2));
    if (!ok2) {
      ok = false;
    }
  }
  uint result = values[0] << 16;
  if (values.size() >= 2) {
    result += values[1] << 8;
  }
  if (values.size() >= 3) {
    result += values[2];
  }
  return result;
}
