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

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