From b254313c686d07057acba243198100e49159e6e5 Mon Sep 17 00:00:00 2001
From: Frantisek Vacek <fvacek@blackberry.com>
Date: Wed, 5 Feb 2014 08:49:29 +0100
Subject: [PATCH] QNX: Use BarDescriptorDocument API to prepare bar-descriptor
 for deployment
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Patch is refactoring create package step to use new BarDescriptorDocument
class when bar-descriptor.xml is prepared for deployment.

BarDescriptorDocument API is extended to allow this.

Change-Id: If00fba3310c5acf1cc8feefe0cf919aa2a05637e
Reviewed-by: Tobias Nätterlund <tobias.naetterlund@kdab.com>
Reviewed-by: Mehdi Fekari <mfekari@blackberry.com>
Reviewed-by: Nicolas Arnaud-Cormos <nicolas@kdab.com>
---
 src/plugins/qnx/bardescriptordocument.cpp     | 120 ++++++++++++++++++
 src/plugins/qnx/bardescriptordocument.h       |  11 +-
 .../qnx/blackberrycreatepackagestep.cpp       |  92 +++++++-------
 src/plugins/qnx/qnxplugin.cpp                 |  61 +++++++++
 src/plugins/qnx/qnxplugin.h                   |   3 +
 5 files changed, 242 insertions(+), 45 deletions(-)

diff --git a/src/plugins/qnx/bardescriptordocument.cpp b/src/plugins/qnx/bardescriptordocument.cpp
index b07c92daee8..92b80f06f8b 100644
--- a/src/plugins/qnx/bardescriptordocument.cpp
+++ b/src/plugins/qnx/bardescriptordocument.cpp
@@ -40,6 +40,7 @@
 #include <QFileInfo>
 #include <QMetaEnum>
 #include <QTextCodec>
+#include <QSet>
 
 using namespace Qnx;
 using namespace Qnx::Internal;
@@ -573,3 +574,122 @@ void BarDescriptorDocument::emitAllChanged()
         emit changed(tag, value(tag));
     }
 }
+
+QString BarDescriptorDocument::bannerComment() const
+{
+    QDomNode nd = m_barDocument.firstChild();
+    QDomProcessingInstruction pi = nd.toProcessingInstruction();
+    if (!pi.isNull())
+        nd = pi.nextSibling();
+
+    return nd.toComment().data();
+}
+
+void BarDescriptorDocument::setBannerComment(const QString &commentText)
+{
+    QDomNode nd = m_barDocument.firstChild();
+    QDomProcessingInstruction pi = nd.toProcessingInstruction();
+    if (!pi.isNull())
+        nd = pi.nextSibling();
+
+    bool oldDirty = m_dirty;
+    QDomComment cnd = nd.toComment();
+    if (cnd.isNull()) {
+        if (!commentText.isEmpty()) {
+            cnd = m_barDocument.createComment(commentText);
+            m_barDocument.insertBefore(cnd, nd);
+            m_dirty = true;
+        }
+    } else {
+        if (commentText.isEmpty()) {
+            m_barDocument.removeChild(cnd);
+            m_dirty = true;
+        } else {
+            if (cnd.data() != commentText) {
+                cnd.setData(commentText);
+                m_dirty = true;
+            }
+        }
+    }
+    if (m_dirty != oldDirty)
+        emit Core::IDocument::changed();
+}
+
+int BarDescriptorDocument::tagForElement(const QDomElement &element)
+{
+    QMetaEnum tags = metaObject()->enumerator(metaObject()->enumeratorOffset());
+    QDomElement el = element;
+    while (!el.isNull()) {
+        bool ok;
+        int n = tags.keyToValue(el.tagName().toLatin1().constData(), &ok);
+        if (ok)
+            return n;
+        el = el.parentNode().toElement();
+    }
+    return -1;
+}
+
+bool BarDescriptorDocument::expandPlaceHolder_helper(const QDomElement &el,
+                                                     const QString &placeholderKey,
+                                                     const QString &placeholderText,
+                                                     QSet<BarDescriptorDocument::Tag> &changedTags)
+{
+    // replace attributes
+    bool elementChanged = false;
+    QDomNamedNodeMap attrs = el.attributes();
+    for (int i = 0; i < attrs.count(); ++i) {
+        QDomAttr attr = attrs.item(i).toAttr();
+        if (!attr.isNull()) {
+            QString s = attr.value();
+            s.replace(placeholderKey, placeholderText);
+            if (s != attr.value()) {
+                attr.setValue(s);
+                elementChanged = true;
+            }
+        }
+    }
+
+    bool documentChanged = false;
+    // replace text
+    for (QDomNode nd = el.firstChild(); !nd.isNull(); nd = nd.nextSibling()) {
+        QDomText txtnd = nd.toText();
+        if (!txtnd.isNull()) {
+            QString s = txtnd.data();
+            s.replace(placeholderKey, placeholderText);
+            if (s != txtnd.data()) {
+                txtnd.setData(s);
+                elementChanged = true;
+            }
+        }
+        QDomElement child = nd.toElement();
+        if (!child.isNull()) {
+            bool hit = expandPlaceHolder_helper(child, placeholderKey, placeholderText, changedTags);
+            documentChanged = documentChanged || hit;
+        }
+    }
+    if (elementChanged) {
+        int n = tagForElement(el);
+        if (n >= 0)
+            changedTags << static_cast<Tag>(n);
+    }
+    documentChanged = documentChanged || elementChanged;
+    return documentChanged;
+}
+
+void BarDescriptorDocument::expandPlaceHolders(const QHash<QString, QString> &placeholdersKeyVals)
+{
+    QSet<Tag> changedTags;
+    QHashIterator<QString, QString> it(placeholdersKeyVals);
+    bool docChanged = false;
+    while (it.hasNext()) {
+        it.next();
+        bool expanded = expandPlaceHolder_helper(m_barDocument.documentElement(),
+                                                 it.key(), it.value(), changedTags);
+        docChanged = docChanged || expanded;
+    }
+    m_dirty = m_dirty || docChanged;
+    foreach (Tag tag, changedTags)
+        emit changed(tag, value(tag));
+    if (docChanged)
+        emit Core::IDocument::changed();
+}
diff --git a/src/plugins/qnx/bardescriptordocument.h b/src/plugins/qnx/bardescriptordocument.h
index 05c135f7779..87a745bdda6 100644
--- a/src/plugins/qnx/bardescriptordocument.h
+++ b/src/plugins/qnx/bardescriptordocument.h
@@ -105,12 +105,16 @@ public:
 
     QVariant value(Tag tag) const;
 
+    void expandPlaceHolders(const QHash<QString, QString> &placeholdersKeyVals);
+
+    QString bannerComment() const;
+    void setBannerComment(const QString &commentText);
+
 signals:
     void changed(BarDescriptorDocument::Tag tag, const QVariant &value);
 
 public slots:
     void setValue(BarDescriptorDocument::Tag tag, const QVariant &value);
-
 private:
     QString stringValue(const QString &tagName) const;
     void setStringValue(const QString &tagName, const QString &value);
@@ -127,6 +131,11 @@ private:
     QList<Utils::EnvironmentItem> environment() const;
     void setEnvironment(const QList<Utils::EnvironmentItem> &environment);
 
+    int tagForElement(const QDomElement &element);
+    bool expandPlaceHolder_helper(const QDomElement &el, const QString &placeholderKey,
+                                  const QString &placeholderText,
+                                  QSet<BarDescriptorDocument::Tag> &changedTags);
+
     void emitAllChanged();
 
     bool m_dirty;
diff --git a/src/plugins/qnx/blackberrycreatepackagestep.cpp b/src/plugins/qnx/blackberrycreatepackagestep.cpp
index 16794354427..440388556d1 100644
--- a/src/plugins/qnx/blackberrycreatepackagestep.cpp
+++ b/src/plugins/qnx/blackberrycreatepackagestep.cpp
@@ -39,6 +39,7 @@
 #include "blackberrydeviceconfiguration.h"
 #include "blackberrydeployinformation.h"
 #include "blackberrysigningpasswordsdialog.h"
+#include "bardescriptordocument.h"
 
 #include <debugger/debuggerrunconfigurationaspect.h>
 #include <projectexplorer/projectexplorerconstants.h>
@@ -50,24 +51,12 @@
 #include <qtsupport/qtkitinformation.h>
 #include <utils/qtcassert.h>
 
-#include <QFile>
-
 using namespace Qnx;
 using namespace Qnx::Internal;
 
 namespace {
 const char PACKAGER_CMD[] = "blackberry-nativepackager";
 
-const char QT_INSTALL_LIBS[]        = "QT_INSTALL_LIBS";
-const char QT_INSTALL_LIBS_VAR[]    = "%QT_INSTALL_LIBS%";
-const char QT_INSTALL_PLUGINS[]     = "QT_INSTALL_PLUGINS";
-const char QT_INSTALL_PLUGINS_VAR[] = "%QT_INSTALL_PLUGINS%";
-const char QT_INSTALL_IMPORTS[]     = "QT_INSTALL_IMPORTS";
-const char QT_INSTALL_IMPORTS_VAR[] = "%QT_INSTALL_IMPORTS%";
-const char QT_INSTALL_QML[]         = "QT_INSTALL_QML";
-const char QT_INSTALL_QML_VAR[]     = "%QT_INSTALL_QML%";
-const char SRC_DIR_VAR[]            = "%SRC_DIR%";
-
 const char PACKAGE_MODE_KEY[]      = "Qt4ProjectManager.BlackBerryCreatePackageStep.PackageMode";
 const char CSK_PASSWORD_KEY[]      = "Qt4ProjectManager.BlackBerryCreatePackageStep.CskPassword";
 const char KEYSTORE_PASSWORD_KEY[] = "Qt4ProjectManager.BlackBerryCreatePackageStep.KeystorePassword";
@@ -253,6 +242,13 @@ void BlackBerryCreatePackageStep::setSavePasswords(bool savePasswords)
     m_savePasswords = savePasswords;
 }
 
+static void addQtInfoPlaceHolderToHash(QHash<QString, QString> &hash,
+                                       const BlackBerryQtVersion *qtVersion, const char *key)
+{
+    hash[QLatin1Char('%') + QString::fromLatin1(key) + QLatin1Char('%')] =
+        qtVersion->versionInfo().value(QLatin1String(key));
+}
+
 bool BlackBerryCreatePackageStep::prepareAppDescriptorFile(const QString &appDescriptorPath, const QString &preparedFilePath)
 {
     BlackBerryQtVersion *qtVersion = dynamic_cast<BlackBerryQtVersion *>(QtSupport::QtKitInformation::qtVersion(target()->kit()));
@@ -261,54 +257,62 @@ bool BlackBerryCreatePackageStep::prepareAppDescriptorFile(const QString &appDes
         return false;
     }
 
-    QFile file(appDescriptorPath);
-    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
-        raiseError(tr("Could not open '%1' for reading").arg(appDescriptorPath));
+    BarDescriptorDocument doc;
+    QString errorString;
+    if (!doc.open(&errorString, appDescriptorPath)) {
+        raiseError(tr("Error opening application descriptor file '%1' - %2")
+            .arg(QDir::toNativeSeparators(appDescriptorPath))
+            .arg(errorString));
         return false;
     }
-
-    QFile preparedFile(preparedFilePath);
-
-    QByteArray fileContent = file.readAll();
-
     // Add Warning text
-    const QString warningText = QString::fromLatin1("<!-- This file is autogenerated;"
-                                                       " any changes will get overwritten if deploying with Qt Creator -->\n<qnx");
-    fileContent.replace("<qnx", warningText.toLatin1());
+    const QString warningText = QString::fromLatin1("This file is autogenerated,"
+                " any changes will get overwritten if deploying with Qt Creator");
+    doc.setBannerComment(warningText);
 
     // Replace Qt path placeholders
-    if (fileContent.contains(QT_INSTALL_LIBS_VAR))
-        fileContent.replace(QT_INSTALL_LIBS_VAR, qtVersion->versionInfo().value(QLatin1String(QT_INSTALL_LIBS)).toLatin1());
-    if (fileContent.contains(QT_INSTALL_PLUGINS_VAR))
-        fileContent.replace(QT_INSTALL_PLUGINS_VAR, qtVersion->versionInfo().value(QLatin1String(QT_INSTALL_PLUGINS)).toLatin1());
-    if (fileContent.contains(QT_INSTALL_IMPORTS_VAR))
-        fileContent.replace(QT_INSTALL_IMPORTS_VAR, qtVersion->versionInfo().value(QLatin1String(QT_INSTALL_IMPORTS)).toLatin1());
-    if (fileContent.contains(QT_INSTALL_QML_VAR))
-        fileContent.replace(QT_INSTALL_QML_VAR, qtVersion->versionInfo().value(QLatin1String(QT_INSTALL_QML)).toLatin1());
-
+    QHash<QString, QString> placeHoldersHash;
+    addQtInfoPlaceHolderToHash(placeHoldersHash, qtVersion, "QT_INSTALL_LIBS");
+    addQtInfoPlaceHolderToHash(placeHoldersHash, qtVersion, "QT_INSTALL_PLUGINS");
+    addQtInfoPlaceHolderToHash(placeHoldersHash, qtVersion, "QT_INSTALL_IMPORTS");
+    addQtInfoPlaceHolderToHash(placeHoldersHash, qtVersion, "QT_INSTALL_QML");
     //Replace Source path placeholder
-    if (fileContent.contains(SRC_DIR_VAR))
-        fileContent.replace(SRC_DIR_VAR, QDir::toNativeSeparators(target()->project()->projectDirectory()).toLatin1());
+    placeHoldersHash[QLatin1String("%SRC_DIR%")] =
+        QDir::toNativeSeparators(target()->project()->projectDirectory());
+    doc.expandPlaceHolders(placeHoldersHash);
+
+    QStringList commandLineArguments = doc.value(BarDescriptorDocument::arg).toStringList();
+    QStringList extraCommandLineArguments;
 
     // Add parameter for QML debugging (if enabled)
     Debugger::DebuggerRunConfigurationAspect *aspect
             = target()->activeRunConfiguration()->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
     if (aspect->useQmlDebugger()) {
-        if (!fileContent.contains("-qmljsdebugger")) {
-            const QString argString = QString::fromLatin1("<arg>-qmljsdebugger=port:%1</arg>\n</qnx>")
-                    .arg(aspect->qmlDebugServerPort());
-            fileContent.replace("</qnx>", argString.toLatin1());
+        bool qmljsdebuggerExists = false;
+        foreach (const QString &s, commandLineArguments) {
+            if (s.startsWith(QLatin1String("-qmljsdebugger="))) {
+                qmljsdebuggerExists = true;
+                break;
+            }
+        }
+        if (!qmljsdebuggerExists) {
+            extraCommandLineArguments << QString::fromLatin1("-qmljsdebugger=port:%1")
+                .arg(aspect->qmlDebugServerPort());
         }
     }
 
-    if (!preparedFile.open(QIODevice::WriteOnly)) {
-        const QString buildDir = target()->activeBuildConfiguration()->buildDirectory().toUserOutput();
-        raiseError(tr("Could not create prepared application descriptor file in '%1'").arg(buildDir));
-        return false;
+    if (extraCommandLineArguments.count()) {
+        commandLineArguments << extraCommandLineArguments;
+        doc.setValue(BarDescriptorDocument::arg, commandLineArguments);
     }
 
-    preparedFile.write(fileContent);
-    preparedFile.close();
+    doc.setFilePath(preparedFilePath);
+    if (!doc.save(&errorString)) {
+        raiseError(tr("Error saving prepared application descriptor file '%1' - %2")
+            .arg(QDir::toNativeSeparators(preparedFilePath))
+            .arg(errorString));
+        return false;
+    }
 
     return true;
 }
diff --git a/src/plugins/qnx/qnxplugin.cpp b/src/plugins/qnx/qnxplugin.cpp
index 465086da8fe..6fcf1b0bc6b 100644
--- a/src/plugins/qnx/qnxplugin.cpp
+++ b/src/plugins/qnx/qnxplugin.cpp
@@ -331,6 +331,67 @@ void QNXPlugin::testBarDescriptorDocumentSetValue()
         QCOMPARE(doc.value(tag), value);
 }
 
+void QNXPlugin::testBarDescriptorDocumentSetBannerComment_data()
+{
+    QTest::addColumn<QString>("comment");
+    QTest::addColumn<QString>("baseXml");
+    QTest::addColumn<QString>("xml");
+
+    QString procInstr = QString::fromLatin1("<?xml version='1.0' encoding='UTF-8' standalone='no'?>");
+    QString comment = QString::fromLatin1("This file is autogenerated, any change will be ...");
+    QString xmlComment = QString::fromLatin1("<!--%1-->").arg(comment);
+    QString oldXmlComment = QString::fromLatin1("<!-- Some old banner comment -->");
+    QString docRoot = QString::fromLatin1("<qnx xmlns=\"http://www.qnx.com/schemas/application/1.0\"/>");
+    QChar lf = QChar::fromLatin1('\n');
+
+    QTest::newRow("new-comment")
+        << comment
+        << QString(procInstr + lf + docRoot + lf)
+        << QString(procInstr + lf + xmlComment + lf + docRoot + lf);
+
+    QTest::newRow("new-comment-noproc")
+        << comment
+        << QString(docRoot + lf)
+        << QString(xmlComment + lf + docRoot + lf);
+
+    QTest::newRow("replace-comment")
+        << comment
+        << QString(procInstr + lf + oldXmlComment + lf + docRoot + lf)
+        << QString(procInstr + lf + xmlComment + lf + docRoot + lf);
+
+    QTest::newRow("replace-comment-noproc")
+        << comment
+        << QString(oldXmlComment + lf + docRoot + lf)
+        << QString(xmlComment + lf + docRoot + lf);
+
+    QTest::newRow("remove-comment")
+        << QString()
+        << QString(procInstr + lf + oldXmlComment + lf + docRoot + lf)
+        << QString(procInstr + lf + docRoot + lf);
+
+    QTest::newRow("remove-comment-noproc")
+        << QString()
+        << QString(oldXmlComment + lf + docRoot + lf)
+        << QString(docRoot + lf);
+
+}
+
+void QNXPlugin::testBarDescriptorDocumentSetBannerComment()
+{
+    QFETCH(QString, comment);
+    QFETCH(QString, baseXml);
+    QFETCH(QString, xml);
+
+    BarDescriptorDocument doc;
+    doc.loadContent(baseXml, false);
+    QCOMPARE(doc.xmlSource(), baseXml);
+
+    doc.setBannerComment(comment);
+    QCOMPARE(doc.xmlSource(), xml);
+    QCOMPARE(doc.isModified(), true);
+    QCOMPARE(doc.bannerComment(), comment);
+}
+
 #endif
 
 Q_EXPORT_PLUGIN2(QNX, QNXPlugin)
diff --git a/src/plugins/qnx/qnxplugin.h b/src/plugins/qnx/qnxplugin.h
index 9286464c8c3..3a9820b87b7 100644
--- a/src/plugins/qnx/qnxplugin.h
+++ b/src/plugins/qnx/qnxplugin.h
@@ -54,6 +54,9 @@ public:
 private slots:
     void testBarDescriptorDocumentSetValue_data();
     void testBarDescriptorDocumentSetValue();
+
+    void testBarDescriptorDocumentSetBannerComment_data();
+    void testBarDescriptorDocumentSetBannerComment();
 #endif
 };
 
-- 
GitLab