| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtScxml module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qscxmlexecutablecontent_p.h" |
| #include "qscxmlevent_p.h" |
| #include "qscxmlstatemachine_p.h" |
| |
| #include <qjsondocument.h> |
| #include <qjsonobject.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| using namespace QScxmlExecutableContent; |
| |
| QAtomicInt QScxmlEventBuilder::idCounter = QAtomicInt(0); |
| |
| QScxmlEvent *QScxmlEventBuilder::buildEvent() |
| { |
| auto dataModel = stateMachine ? stateMachine->dataModel() : nullptr; |
| auto tableData = stateMachine ? stateMachine->tableData() : nullptr; |
| |
| QString eventName = event; |
| bool ok = true; |
| if (eventexpr != NoEvaluator) { |
| eventName = dataModel->evaluateToString(eventexpr, &ok); |
| ok = true; // ignore failure. |
| } |
| |
| QVariant data; |
| if ((!params || params->count == 0) && (!namelist || namelist->count == 0)) { |
| if (contentExpr == NoEvaluator) { |
| data = contents; |
| } else { |
| data = dataModel->evaluateToVariant(contentExpr, &ok); |
| } |
| if (!ok) { |
| // expr evaluation failure results in the data property of the event being set to null. See e.g. test528. |
| data = QVariant(QMetaType::VoidStar, 0); |
| } |
| } else { |
| QVariantMap keyValues; |
| if (evaluate(params, stateMachine, keyValues)) { |
| if (namelist) { |
| for (qint32 i = 0; i < namelist->count; ++i) { |
| QString name = tableData->string(namelist->const_data()[i]); |
| keyValues.insert(name, dataModel->scxmlProperty(name)); |
| } |
| } |
| data = keyValues; |
| } else { |
| // If the evaluation of the <param> tags fails, set _event.data to an empty string. |
| // See test343. |
| data = QVariant(QMetaType::VoidStar, 0); |
| } |
| } |
| |
| QString sendid = id; |
| if (!idLocation.isEmpty()) { |
| sendid = generateId(); |
| ok = stateMachine->dataModel()->setScxmlProperty(idLocation, sendid, tableData->string(instructionLocation)); |
| if (!ok) |
| return nullptr; |
| } |
| |
| QString origin = target; |
| if (targetexpr != NoEvaluator) { |
| origin = dataModel->evaluateToString(targetexpr, &ok); |
| if (!ok) |
| return nullptr; |
| } |
| if (origin.isEmpty()) { |
| if (eventType == QScxmlEvent::ExternalEvent) { |
| origin = QStringLiteral("#_internal"); |
| } |
| } else if (origin == QStringLiteral("#_parent")) { |
| // allow sending messages to the parent, independently of whether we're invoked or not. |
| } else if (!origin.startsWith(QLatin1Char('#'))) { |
| // [6.2.4] and test194. |
| submitError(QStringLiteral("error.execution"), |
| QStringLiteral("Error in %1: %2 is not a legal target") |
| .arg(tableData->string(instructionLocation), origin), |
| sendid); |
| return nullptr; |
| } else if (!stateMachine->isDispatchableTarget(origin)) { |
| // [6.2.4] and test521. |
| submitError(QStringLiteral("error.communication"), |
| QStringLiteral("Error in %1: cannot dispatch to target '%2'") |
| .arg(tableData->string(instructionLocation), origin), |
| sendid); |
| return nullptr; |
| } |
| |
| QString origintype = type; |
| if (origintype.isEmpty()) { |
| // [6.2.5] and test198 |
| origintype = QStringLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor"); |
| } |
| if (typeexpr != NoEvaluator) { |
| origintype = dataModel->evaluateToString(typeexpr, &ok); |
| if (!ok) |
| return nullptr; |
| } |
| if (!origintype.isEmpty() |
| && origintype != QStringLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor")) { |
| // [6.2.5] and test199 |
| submitError(QStringLiteral("error.execution"), |
| QStringLiteral("Error in %1: %2 is not a valid type") |
| .arg(tableData->string(instructionLocation), origintype), |
| sendid); |
| return nullptr; |
| } |
| |
| QString invokeid; |
| if (stateMachine && stateMachine->isInvoked()) { |
| invokeid = stateMachine->sessionId(); |
| } |
| |
| QScxmlEvent *event = new QScxmlEvent; |
| event->setName(eventName); |
| event->setEventType(eventType); |
| event->setData(data); |
| event->setSendId(sendid); |
| event->setOrigin(origin); |
| event->setOriginType(origintype); |
| event->setInvokeId(invokeid); |
| return event; |
| } |
| |
| QScxmlEvent *QScxmlEventBuilder::errorEvent(QScxmlStateMachine *stateMachine, const QString &name, |
| const QString &message, const QString &sendid) |
| { |
| QScxmlEventBuilder event; |
| event.stateMachine = stateMachine; |
| event.event = name; |
| event.eventType = QScxmlEvent::PlatformEvent; // Errors are platform events. See e.g. test331. |
| // _event.data == null, see test528 |
| event.id = sendid; |
| auto error = event(); |
| error->setErrorMessage(message); |
| return error; |
| } |
| |
| bool QScxmlEventBuilder::evaluate(const ParameterInfo ¶m, QScxmlStateMachine *stateMachine, |
| QVariantMap &keyValues) |
| { |
| auto dataModel = stateMachine->dataModel(); |
| auto tableData = stateMachine->tableData(); |
| if (param.expr != NoEvaluator) { |
| bool success = false; |
| auto v = dataModel->evaluateToVariant(param.expr, &success); |
| keyValues.insert(tableData->string(param.name), v); |
| return success; |
| } |
| |
| QString loc; |
| if (param.location != QScxmlExecutableContent::NoString) { |
| loc = tableData->string(param.location); |
| } |
| |
| if (loc.isEmpty()) { |
| return false; |
| } |
| |
| if (dataModel->hasScxmlProperty(loc)) { |
| keyValues.insert(tableData->string(param.name), dataModel->scxmlProperty(loc)); |
| return true; |
| } else { |
| submitError(QStringLiteral("error.execution"), |
| QStringLiteral("Error in <param>: %1 is not a valid location") |
| .arg(loc)); |
| return false; |
| } |
| } |
| |
| bool QScxmlEventBuilder::evaluate(const QScxmlExecutableContent::Array<ParameterInfo> *params, |
| QScxmlStateMachine *stateMachine, QVariantMap &keyValues) |
| { |
| if (!params) |
| return true; |
| |
| auto paramPtr = params->const_data(); |
| for (qint32 i = 0; i != params->count; ++i, ++paramPtr) { |
| if (!evaluate(*paramPtr, stateMachine, keyValues)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void QScxmlEventBuilder::submitError(const QString &type, const QString &msg, const QString &sendid) |
| { |
| QScxmlStateMachinePrivate::get(stateMachine)->submitError(type, msg, sendid); |
| } |
| |
| /*! |
| * \class QScxmlEvent |
| * \brief The QScxmlEvent class is an event for a Qt SCXML state machine. |
| * \since 5.7 |
| * \inmodule QtScxml |
| * |
| * SCXML \e events drive transitions. Most events are generated by using the |
| * \c <raise> and \c <send> elements in the application. The state machine |
| * automatically generates some mandatory events, such as errors. |
| * |
| * For more information, see |
| * \l {SCXML Specification - 5.10.1 The Internal Structure of Events}. |
| * For more information about how the Qt SCXML API differs from the |
| * specification, see \l {SCXML Compliance}. |
| * |
| * \sa QScxmlStateMachine |
| */ |
| |
| /*! |
| \enum QScxmlEvent::EventType |
| |
| This enum type specifies the type of an SCXML event: |
| |
| \value PlatformEvent |
| An event generated internally by the state machine. For example, |
| errors. |
| \value InternalEvent |
| An event generated by a \c <raise> element. |
| \value ExternalEvent |
| An event generated by a \c <send> element. |
| */ |
| |
| /*! |
| * Creates a new external SCXML event. |
| */ |
| QScxmlEvent::QScxmlEvent() |
| : d(new QScxmlEventPrivate) |
| { } |
| |
| /*! |
| * Destroys the SCXML event. |
| */ |
| QScxmlEvent::~QScxmlEvent() |
| { |
| delete d; |
| } |
| |
| /*! |
| \property QScxmlEvent::scxmlType |
| \brief The event type. |
| |
| */ |
| |
| /*! |
| * Returns the event type. |
| */ |
| QString QScxmlEvent::scxmlType() const |
| { |
| switch (d->eventType) { |
| case PlatformEvent: |
| return QLatin1String("platform"); |
| case InternalEvent: |
| return QLatin1String("internal"); |
| case ExternalEvent: |
| break; |
| } |
| return QLatin1String("external"); |
| } |
| |
| /*! |
| * Clears the contents of the event. |
| */ |
| void QScxmlEvent::clear() |
| { |
| *d = QScxmlEventPrivate(); |
| } |
| |
| /*! |
| * Assigns \a other to this SCXML event and returns a reference to this SCXML |
| * event. |
| */ |
| QScxmlEvent &QScxmlEvent::operator=(const QScxmlEvent &other) |
| { |
| *d = *other.d; |
| return *this; |
| } |
| |
| /*! |
| * Constructs a copy of \a other. |
| */ |
| QScxmlEvent::QScxmlEvent(const QScxmlEvent &other) |
| : d(new QScxmlEventPrivate(*other.d)) |
| { |
| } |
| |
| /*! |
| \property QScxmlEvent::name |
| |
| \brief the name of the event. |
| |
| If the event is generated inside the SCXML document, this property holds the |
| value of the \e event attribute specified inside the \c <raise> or \c <send> |
| element. |
| |
| If the event is created in the C++ code and submitted to the |
| QScxmlStateMachine, the value of this property is matched against the value |
| of the \e event attribute specified inside the \c <transition> element in |
| the SCXML document. |
| */ |
| |
| /*! |
| * Returns the name of the event. |
| */ |
| QString QScxmlEvent::name() const |
| { |
| return d->name; |
| } |
| |
| /*! |
| * Sets the name of the event to \a name. |
| */ |
| void QScxmlEvent::setName(const QString &name) |
| { |
| d->name = name; |
| } |
| |
| /*! |
| \property QScxmlEvent::sendId |
| |
| \brief the ID of the event. |
| |
| The ID is used by the \c <cancel> element to identify the event to be |
| canceled. |
| |
| \note The state machine generates a unique ID if the \e id attribute is not |
| specified in the \c <send> element. The generated ID can be accessed through |
| this property. |
| */ |
| |
| /*! |
| * Returns the ID of the event. |
| */ |
| QString QScxmlEvent::sendId() const |
| { |
| return d->sendid; |
| } |
| |
| /*! |
| * Sets the ID \a sendid for this event. |
| */ |
| void QScxmlEvent::setSendId(const QString &sendid) |
| { |
| d->sendid = sendid; |
| } |
| |
| /*! |
| \property QScxmlEvent::origin |
| |
| \brief the URI that points to the origin of an SCXML event. |
| |
| The origin is equivalent to the \e target attribute of the \c <send> |
| element. |
| */ |
| |
| /*! |
| * Returns a URI that points to the origin of an SCXML event. |
| */ |
| QString QScxmlEvent::origin() const |
| { |
| return d->origin; |
| } |
| |
| /*! |
| * Sets the origin of an SCXML event to \a origin. |
| * |
| * \sa QScxmlEvent::origin |
| */ |
| void QScxmlEvent::setOrigin(const QString &origin) |
| { |
| d->origin = origin; |
| } |
| |
| /*! |
| \property QScxmlEvent::originType |
| |
| \brief the origin type of an SCXML event. |
| |
| The origin type is equivalent to the \e type attribute of the \c <send> |
| element. |
| */ |
| |
| /*! |
| * Returns the origin type of an SCXML event. |
| */ |
| QString QScxmlEvent::originType() const |
| { |
| return d->originType; |
| } |
| |
| /*! |
| * Sets the origin type of an SCXML event to \a origintype. |
| * |
| * \sa QScxmlEvent::originType |
| */ |
| void QScxmlEvent::setOriginType(const QString &origintype) |
| { |
| d->originType = origintype; |
| } |
| |
| /*! |
| \property QScxmlEvent::invokeId |
| |
| \brief the ID of the invoked state machine if the event is generated by one. |
| */ |
| |
| /*! |
| * If this event is generated by an invoked state machine, returns the ID of |
| * the \c <invoke> element. Otherwise, returns an empty value. |
| */ |
| QString QScxmlEvent::invokeId() const |
| { |
| return d->invokeId; |
| } |
| |
| /*! |
| * Sets the ID of an invoked state machine to \a invokeid. |
| * \sa QScxmlEvent::invokeId |
| */ |
| void QScxmlEvent::setInvokeId(const QString &invokeid) |
| { |
| d->invokeId = invokeid; |
| } |
| |
| /*! |
| \property QScxmlEvent::delay |
| |
| \brief The delay in milliseconds after which the event is to be delivered |
| after processing the \c <send> element. |
| */ |
| |
| /*! |
| * Returns the delay in milliseconds after which this event is to be delivered |
| * after processing the \c <send> element. |
| */ |
| int QScxmlEvent::delay() const |
| { |
| return d->delayInMiliSecs; |
| } |
| |
| /*! |
| * Sets the delay in milliseconds as the value of \a delayInMiliSecs. |
| * \sa QScxmlEvent::delay |
| */ |
| void QScxmlEvent::setDelay(int delayInMiliSecs) |
| { |
| d->delayInMiliSecs = delayInMiliSecs; |
| } |
| /*! |
| \property QScxmlEvent::eventType |
| |
| \brief the type of the event. |
| */ |
| |
| /*! |
| * Returns the type of this event. |
| * \sa QScxmlEvent::EventType |
| */ |
| QScxmlEvent::EventType QScxmlEvent::eventType() const |
| { |
| return d->eventType; |
| } |
| |
| /*! |
| * Sets the event type to \a type. |
| * \sa QScxmlEvent::eventType QScxmlEvent::EventType |
| */ |
| void QScxmlEvent::setEventType(const EventType &type) |
| { |
| d->eventType = type; |
| } |
| |
| /*! |
| \property QScxmlEvent::data |
| |
| \brief the data included by the sender. |
| |
| When \c <param> elements are used in the \c <send> element, the data will |
| contain a QVariantMap where the key is the \e name attribute, and the value |
| is taken from the \e expr attribute or the \e location attribute. |
| |
| When a \c <content> element is used, the data will contain a single item |
| with either the value of the \e expr attribute of the \c <content> element |
| or the child data of the \c <content> element. |
| */ |
| |
| /*! |
| * Returns the data included by the sender. |
| */ |
| QVariant QScxmlEvent::data() const |
| { |
| if (isErrorEvent()) |
| return QVariant(); |
| return d->data; |
| } |
| |
| /*! |
| * Sets the payload data to \a data. |
| * \sa QScxmlEvent::data |
| */ |
| void QScxmlEvent::setData(const QVariant &data) |
| { |
| if (!isErrorEvent()) |
| d->data = data; |
| } |
| |
| /*! |
| \property QScxmlEvent::errorEvent |
| \brief Whether the event represents an error. |
| */ |
| |
| /*! |
| * Returns \c true when this is an error event, \c false otherwise. |
| */ |
| bool QScxmlEvent::isErrorEvent() const |
| { |
| return eventType() == PlatformEvent && name().startsWith(QStringLiteral("error.")); |
| } |
| |
| /*! |
| \property QScxmlEvent::errorMessage |
| \brief An error message for an error event, or an empty QString. |
| */ |
| |
| /*! |
| * If this is an error event, returns the error message. Otherwise, returns an |
| * empty QString. |
| */ |
| QString QScxmlEvent::errorMessage() const |
| { |
| if (!isErrorEvent()) |
| return QString(); |
| return d->data.toString(); |
| } |
| |
| /*! |
| * If this is an error event, the \a message is set as the error message. |
| */ |
| void QScxmlEvent::setErrorMessage(const QString &message) |
| { |
| if (isErrorEvent()) |
| d->data = message; |
| } |
| |
| QByteArray QScxmlEventPrivate::debugString(QScxmlEvent *event) |
| { |
| if (event == nullptr) { |
| return "<null>"; |
| } |
| |
| QJsonObject o; |
| if (!event->name().isNull()) |
| o[QStringLiteral("name")] = event->name(); |
| if (!event->scxmlType().isNull()) |
| o[QStringLiteral("type")] = event->scxmlType(); |
| if (!event->sendId().isNull()) |
| o[QStringLiteral("sendid")] = event->sendId(); |
| if (!event->origin().isNull()) |
| o[QStringLiteral("origin")] = event->origin(); |
| if (!event->originType().isNull()) |
| o[QStringLiteral("origintype")] = event->originType(); |
| if (!event->invokeId().isNull()) |
| o[QStringLiteral("invokeid")] = event->invokeId(); |
| if (!event->data().isNull()) |
| o[QStringLiteral("data")] = QJsonValue::fromVariant(event->data()); |
| |
| return QJsonDocument(o).toJson(QJsonDocument::Compact); |
| } |
| |
| QT_END_NAMESPACE |