1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qscxmlexecutablecontent_p.h"
5#include "qscxmlevent_p.h"
6#include "qscxmlstatemachine_p.h"
7
8#include <qjsondocument.h>
9#include <qjsonobject.h>
10
11QT_BEGIN_NAMESPACE
12
13using namespace QScxmlExecutableContent;
14
15QAtomicInt QScxmlEventBuilder::idCounter = QAtomicInt(0);
16
17QScxmlEvent *QScxmlEventBuilder::buildEvent()
18{
19 auto dataModel = stateMachine ? stateMachine->dataModel() : nullptr;
20 auto tableData = stateMachine ? stateMachine->tableData() : nullptr;
21
22 QString eventName = event;
23 bool ok = true;
24 if (eventexpr != NoEvaluator) {
25 eventName = dataModel->evaluateToString(id: eventexpr, ok: &ok);
26 ok = true; // ignore failure.
27 }
28
29 QVariant data;
30 if ((!params || params->count == 0) && (!namelist || namelist->count == 0)) {
31 if (contentExpr == NoEvaluator) {
32 data = contents;
33 } else {
34 data = dataModel->evaluateToVariant(id: contentExpr, ok: &ok);
35 }
36 if (!ok) {
37 // expr evaluation failure results in the data property of the event being set to null. See e.g. test528.
38 data = QVariant(QMetaType(QMetaType::VoidStar), nullptr);
39 }
40 } else {
41 QVariantMap keyValues;
42 if (evaluate(params, stateMachine, keyValues)) {
43 if (namelist) {
44 for (qint32 i = 0; i < namelist->count; ++i) {
45 QString name = tableData->string(id: namelist->const_data()[i]);
46 keyValues.insert(key: name, value: dataModel->scxmlProperty(name));
47 }
48 }
49 data = keyValues;
50 } else {
51 // If the evaluation of the <param> tags fails, set _event.data to an empty string.
52 // See test343.
53 data = QVariant(QMetaType(QMetaType::VoidStar), nullptr);
54 }
55 }
56
57 QString sendid = id;
58 if (!idLocation.isEmpty()) {
59 sendid = generateId();
60 ok = stateMachine->dataModel()->setScxmlProperty(name: idLocation, value: sendid, context: tableData->string(id: instructionLocation));
61 if (!ok)
62 return nullptr;
63 }
64
65 QString origin = target;
66 if (targetexpr != NoEvaluator) {
67 origin = dataModel->evaluateToString(id: targetexpr, ok: &ok);
68 if (!ok)
69 return nullptr;
70 }
71 if (origin.isEmpty()) {
72 if (eventType == QScxmlEvent::ExternalEvent) {
73 origin = QStringLiteral("#_internal");
74 }
75 } else if (origin == QStringLiteral("#_parent")) {
76 // allow sending messages to the parent, independently of whether we're invoked or not.
77 } else if (!origin.startsWith(c: QLatin1Char('#'))) {
78 // [6.2.4] and test194.
79 submitError(QStringLiteral("error.execution"),
80 QStringLiteral("Error in %1: %2 is not a legal target")
81 .arg(args: tableData->string(id: instructionLocation), args&: origin),
82 sendid);
83 return nullptr;
84 } else if (!stateMachine->isDispatchableTarget(target: origin)) {
85 // [6.2.4] and test521.
86 submitError(QStringLiteral("error.communication"),
87 QStringLiteral("Error in %1: cannot dispatch to target '%2'")
88 .arg(args: tableData->string(id: instructionLocation), args&: origin),
89 sendid);
90 return nullptr;
91 }
92
93 QString origintype = type;
94 if (origintype.isEmpty()) {
95 // [6.2.5] and test198
96 origintype = QStringLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor");
97 }
98 if (typeexpr != NoEvaluator) {
99 origintype = dataModel->evaluateToString(id: typeexpr, ok: &ok);
100 if (!ok)
101 return nullptr;
102 }
103 if (!origintype.isEmpty()
104 && origintype != QStringLiteral("http://www.w3.org/TR/scxml/#SCXMLEventProcessor")) {
105 // [6.2.5] and test199
106 submitError(QStringLiteral("error.execution"),
107 QStringLiteral("Error in %1: %2 is not a valid type")
108 .arg(args: tableData->string(id: instructionLocation), args&: origintype),
109 sendid);
110 return nullptr;
111 }
112
113 QString invokeid;
114 if (stateMachine && stateMachine->isInvoked()) {
115 invokeid = stateMachine->sessionId();
116 }
117
118 QScxmlEvent *event = new QScxmlEvent;
119 event->setName(eventName);
120 event->setEventType(eventType);
121 event->setData(data);
122 event->setSendId(sendid);
123 event->setOrigin(origin);
124 event->setOriginType(origintype);
125 event->setInvokeId(invokeid);
126 return event;
127}
128
129QScxmlEvent *QScxmlEventBuilder::errorEvent(QScxmlStateMachine *stateMachine, const QString &name,
130 const QString &message, const QString &sendid)
131{
132 QScxmlEventBuilder event;
133 event.stateMachine = stateMachine;
134 event.event = name;
135 event.eventType = QScxmlEvent::PlatformEvent; // Errors are platform events. See e.g. test331.
136 // _event.data == null, see test528
137 event.id = sendid;
138 auto error = event();
139 error->setErrorMessage(message);
140 return error;
141}
142
143bool QScxmlEventBuilder::evaluate(const ParameterInfo &param, QScxmlStateMachine *stateMachine,
144 QVariantMap &keyValues)
145{
146 auto dataModel = stateMachine->dataModel();
147 auto tableData = stateMachine->tableData();
148 if (param.expr != NoEvaluator) {
149 bool success = false;
150 auto v = dataModel->evaluateToVariant(id: param.expr, ok: &success);
151 keyValues.insert(key: tableData->string(id: param.name), value: v);
152 return success;
153 }
154
155 QString loc;
156 if (param.location != QScxmlExecutableContent::NoString) {
157 loc = tableData->string(id: param.location);
158 }
159
160 if (loc.isEmpty()) {
161 return false;
162 }
163
164 if (dataModel->hasScxmlProperty(name: loc)) {
165 keyValues.insert(key: tableData->string(id: param.name), value: dataModel->scxmlProperty(name: loc));
166 return true;
167 } else {
168 submitError(QStringLiteral("error.execution"),
169 QStringLiteral("Error in <param>: %1 is not a valid location")
170 .arg(a: loc));
171 return false;
172 }
173}
174
175bool QScxmlEventBuilder::evaluate(const QScxmlExecutableContent::Array<ParameterInfo> *params,
176 QScxmlStateMachine *stateMachine, QVariantMap &keyValues)
177{
178 if (!params)
179 return true;
180
181 auto paramPtr = params->const_data();
182 for (qint32 i = 0; i != params->count; ++i, ++paramPtr) {
183 if (!evaluate(param: *paramPtr, stateMachine, keyValues))
184 return false;
185 }
186
187 return true;
188}
189
190void QScxmlEventBuilder::submitError(const QString &type, const QString &msg, const QString &sendid)
191{
192 QScxmlStateMachinePrivate::get(t: stateMachine)->submitError(type, msg, sendid);
193}
194
195/*!
196 * \class QScxmlEvent
197 * \brief The QScxmlEvent class is an event for a Qt SCXML state machine.
198 * \since 5.7
199 * \inmodule QtScxml
200 *
201 * SCXML \e events drive transitions. Most events are generated by using the
202 * \c <raise> and \c <send> elements in the application. The state machine
203 * automatically generates some mandatory events, such as errors.
204 *
205 * For more information, see
206 * \l {SCXML Specification - 5.10.1 The Internal Structure of Events}.
207 * For more information about how the Qt SCXML API differs from the
208 * specification, see \l {SCXML Compliance}.
209 *
210 * \sa QScxmlStateMachine
211 */
212
213/*!
214 \enum QScxmlEvent::EventType
215
216 This enum type specifies the type of an SCXML event:
217
218 \value PlatformEvent
219 An event generated internally by the state machine. For example,
220 errors.
221 \value InternalEvent
222 An event generated by a \c <raise> element.
223 \value ExternalEvent
224 An event generated by a \c <send> element.
225 */
226
227/*!
228 * Creates a new external SCXML event.
229 */
230QScxmlEvent::QScxmlEvent()
231 : d(new QScxmlEventPrivate)
232{ }
233
234/*!
235 * Destroys the SCXML event.
236 */
237QScxmlEvent::~QScxmlEvent()
238{
239 delete d;
240}
241
242/*!
243 \property QScxmlEvent::scxmlType
244 \brief The event type.
245
246*/
247
248/*!
249 * Returns the event type.
250 */
251QString QScxmlEvent::scxmlType() const
252{
253 switch (d->eventType) {
254 case PlatformEvent:
255 return QLatin1String("platform");
256 case InternalEvent:
257 return QLatin1String("internal");
258 case ExternalEvent:
259 break;
260 }
261 return QLatin1String("external");
262}
263
264/*!
265 * Clears the contents of the event.
266 */
267void QScxmlEvent::clear()
268{
269 *d = QScxmlEventPrivate();
270}
271
272/*!
273 * Assigns \a other to this SCXML event and returns a reference to this SCXML
274 * event.
275 */
276QScxmlEvent &QScxmlEvent::operator=(const QScxmlEvent &other)
277{
278 *d = *other.d;
279 return *this;
280}
281
282/*!
283 * Constructs a copy of \a other.
284 */
285QScxmlEvent::QScxmlEvent(const QScxmlEvent &other)
286 : d(new QScxmlEventPrivate(*other.d))
287{
288}
289
290/*!
291 \property QScxmlEvent::name
292
293 \brief the name of the event.
294
295 If the event is generated inside the SCXML document, this property holds the
296 value of the \e event attribute specified inside the \c <raise> or \c <send>
297 element.
298
299 If the event is created in the C++ code and submitted to the
300 QScxmlStateMachine, the value of this property is matched against the value
301 of the \e event attribute specified inside the \c <transition> element in
302 the SCXML document.
303*/
304
305/*!
306 * Returns the name of the event.
307 */
308QString QScxmlEvent::name() const
309{
310 return d->name;
311}
312
313/*!
314 * Sets the name of the event to \a name.
315 */
316void QScxmlEvent::setName(const QString &name)
317{
318 d->name = name;
319}
320
321/*!
322 \property QScxmlEvent::sendId
323
324 \brief the ID of the event.
325
326 The ID is used by the \c <cancel> element to identify the event to be
327 canceled.
328
329 \note The state machine generates a unique ID if the \e id attribute is not
330 specified in the \c <send> element. The generated ID can be accessed through
331 this property.
332*/
333
334/*!
335 * Returns the ID of the event.
336 */
337QString QScxmlEvent::sendId() const
338{
339 return d->sendid;
340}
341
342/*!
343 * Sets the ID \a sendid for this event.
344 */
345void QScxmlEvent::setSendId(const QString &sendid)
346{
347 d->sendid = sendid;
348}
349
350/*!
351 \property QScxmlEvent::origin
352
353 \brief the URI that points to the origin of an SCXML event.
354
355 The origin is equivalent to the \e target attribute of the \c <send>
356 element.
357*/
358
359/*!
360 * Returns a URI that points to the origin of an SCXML event.
361 */
362QString QScxmlEvent::origin() const
363{
364 return d->origin;
365}
366
367/*!
368 * Sets the origin of an SCXML event to \a origin.
369 *
370 * \sa QScxmlEvent::origin
371 */
372void QScxmlEvent::setOrigin(const QString &origin)
373{
374 d->origin = origin;
375}
376
377/*!
378 \property QScxmlEvent::originType
379
380 \brief the origin type of an SCXML event.
381
382 The origin type is equivalent to the \e type attribute of the \c <send>
383 element.
384*/
385
386/*!
387 * Returns the origin type of an SCXML event.
388 */
389QString QScxmlEvent::originType() const
390{
391 return d->originType;
392}
393
394/*!
395 * Sets the origin type of an SCXML event to \a origintype.
396 *
397 * \sa QScxmlEvent::originType
398 */
399void QScxmlEvent::setOriginType(const QString &origintype)
400{
401 d->originType = origintype;
402}
403
404/*!
405 \property QScxmlEvent::invokeId
406
407 \brief the ID of the invoked state machine if the event is generated by one.
408*/
409
410/*!
411 * If this event is generated by an invoked state machine, returns the ID of
412 * the \c <invoke> element. Otherwise, returns an empty value.
413 */
414QString QScxmlEvent::invokeId() const
415{
416 return d->invokeId;
417}
418
419/*!
420 * Sets the ID of an invoked state machine to \a invokeid.
421 * \sa QScxmlEvent::invokeId
422 */
423void QScxmlEvent::setInvokeId(const QString &invokeid)
424{
425 d->invokeId = invokeid;
426}
427
428/*!
429 \property QScxmlEvent::delay
430
431 \brief The delay in milliseconds after which the event is to be delivered
432 after processing the \c <send> element.
433*/
434
435/*!
436 * Returns the delay in milliseconds after which this event is to be delivered
437 * after processing the \c <send> element.
438 */
439int QScxmlEvent::delay() const
440{
441 return d->delayInMiliSecs;
442}
443
444/*!
445 * Sets the delay in milliseconds as the value of \a delayInMiliSecs.
446 * \sa QScxmlEvent::delay
447 */
448void QScxmlEvent::setDelay(int delayInMiliSecs)
449{
450 d->delayInMiliSecs = delayInMiliSecs;
451}
452/*!
453 \property QScxmlEvent::eventType
454
455 \brief the type of the event.
456*/
457
458/*!
459 * Returns the type of this event.
460 * \sa QScxmlEvent::EventType
461 */
462QScxmlEvent::EventType QScxmlEvent::eventType() const
463{
464 return d->eventType;
465}
466
467/*!
468 * Sets the event type to \a type.
469 * \sa QScxmlEvent::eventType QScxmlEvent::EventType
470 */
471void QScxmlEvent::setEventType(const EventType &type)
472{
473 d->eventType = type;
474}
475
476/*!
477 \property QScxmlEvent::data
478
479 \brief the data included by the sender.
480
481 When \c <param> elements are used in the \c <send> element, the data will
482 contain a QVariantMap where the key is the \e name attribute, and the value
483 is taken from the \e expr attribute or the \e location attribute.
484
485 When a \c <content> element is used, the data will contain a single item
486 with either the value of the \e expr attribute of the \c <content> element
487 or the child data of the \c <content> element.
488*/
489
490/*!
491 * Returns the data included by the sender.
492 */
493QVariant QScxmlEvent::data() const
494{
495 if (isErrorEvent())
496 return QVariant();
497 return d->data;
498}
499
500/*!
501 * Sets the payload data to \a data.
502 * \sa QScxmlEvent::data
503 */
504void QScxmlEvent::setData(const QVariant &data)
505{
506 if (!isErrorEvent())
507 d->data = data;
508}
509
510/*!
511 \property QScxmlEvent::errorEvent
512 \brief Whether the event represents an error.
513*/
514
515/*!
516 * Returns \c true when this is an error event, \c false otherwise.
517 */
518bool QScxmlEvent::isErrorEvent() const
519{
520 return eventType() == PlatformEvent && name().startsWith(QStringLiteral("error."));
521}
522
523/*!
524 \property QScxmlEvent::errorMessage
525 \brief An error message for an error event, or an empty QString.
526*/
527
528/*!
529 * If this is an error event, returns the error message. Otherwise, returns an
530 * empty QString.
531 */
532QString QScxmlEvent::errorMessage() const
533{
534 if (!isErrorEvent())
535 return QString();
536 return d->data.toString();
537}
538
539/*!
540 * If this is an error event, the \a message is set as the error message.
541 */
542void QScxmlEvent::setErrorMessage(const QString &message)
543{
544 if (isErrorEvent())
545 d->data = message;
546}
547
548QByteArray QScxmlEventPrivate::debugString(QScxmlEvent *event)
549{
550 if (event == nullptr) {
551 return "<null>";
552 }
553
554 QJsonObject o;
555 if (!event->name().isNull())
556 o[QStringLiteral("name")] = event->name();
557 if (!event->scxmlType().isNull())
558 o[QStringLiteral("type")] = event->scxmlType();
559 if (!event->sendId().isNull())
560 o[QStringLiteral("sendid")] = event->sendId();
561 if (!event->origin().isNull())
562 o[QStringLiteral("origin")] = event->origin();
563 if (!event->originType().isNull())
564 o[QStringLiteral("origintype")] = event->originType();
565 if (!event->invokeId().isNull())
566 o[QStringLiteral("invokeid")] = event->invokeId();
567 if (!event->data().isNull())
568 o[QStringLiteral("data")] = QJsonValue::fromVariant(variant: event->data());
569
570 return QJsonDocument(o).toJson(format: QJsonDocument::Compact);
571}
572
573QT_END_NAMESPACE
574

source code of qtscxml/src/scxml/qscxmlevent.cpp