6#include "application.h"
9#include "controller_p.h"
10#include "dispatcher.h"
12#include <QMetaClassInfo>
13#include <QRegularExpression>
19 , d_ptr(new ControllerPrivate(this))
23Controller::~Controller()
26 qDeleteAll(d->actionList);
39 auto it = d->actions.constFind(name);
40 if (it != d->actions.constEnd()) {
43 return d->dispatcher->getAction(name, d->pathPrefix);
49 auto it = d->actions.constFind(name);
50 if (it != d->actions.constEnd()) {
53 return d->dispatcher->getAction(name.toString(), d->pathPrefix);
64 return !qstrcmp(metaObject()->className(), className);
79ControllerPrivate::ControllerPrivate(
Controller *parent)
88 dispatcher = _dispatcher;
94 const QMetaObject *meta = q->metaObject();
95 const QString className = QString::fromLatin1(meta->className());
96 q->setObjectName(className);
98 bool namespaceFound =
false;
99 for (
int i = meta->classInfoCount() - 1; i >= 0; --i) {
100 if (qstrcmp(meta->classInfo(i).name(),
"Namespace") == 0) {
101 pathPrefix = QString::fromLatin1(meta->classInfo(i).value());
102 while (pathPrefix.startsWith(u
'/')) {
103 pathPrefix.remove(0, 1);
105 namespaceFound =
true;
110 if (!namespaceFound) {
112 bool lastWasUpper =
true;
114 for (
int i = 0; i < className.length(); ++i) {
115 const QChar c = className.at(i);
116 if (c.isLower() || c.isDigit()) {
117 controlerNS.append(c);
118 lastWasUpper =
false;
119 }
else if (c == u
'_') {
120 controlerNS.append(c);
124 controlerNS.append(u
'/');
127 controlerNS.append(c.toLower());
132 pathPrefix = controlerNS;
135 registerActionMethods(meta, q, app);
138void ControllerPrivate::setupFinished()
142 const ActionList beginList = dispatcher->getActions(QStringLiteral(
"Begin"), pathPrefix);
143 if (!beginList.isEmpty()) {
144 beginAutoList.append(beginList.last());
147 beginAutoList.append(dispatcher->getActions(QStringLiteral(
"Auto"), pathPrefix));
149 const ActionList endList = dispatcher->getActions(QStringLiteral(
"End"), pathPrefix);
150 if (!endList.isEmpty()) {
151 end = endList.last();
154 const auto actions = actionList;
155 for (
Action *action : actions) {
156 action->dispatcherReady(dispatcher, q);
159 q->preFork(qobject_cast<Application *>(q->parent()));
168 int &actionRefCount = c->d_ptr->actionRefCount;
171 const auto beginAutoList = d->beginAutoList;
172 for (
Action *action : beginAutoList) {
173 if (actionRefCount) {
174 c->d_ptr->pendingAsync.enqueue(action);
175 }
else if (!action->dispatch(c)) {
183 if (actionRefCount) {
184 c->d_ptr->pendingAsync.enqueue(c->action());
192 if (actionRefCount) {
193 c->d_ptr->pendingAsync.enqueue(d->end);
194 }
else if (!d->end->dispatch(c)) {
199 if (actionRefCount) {
200 c->d_ptr->engineRequest->status |= EngineRequest::Async;
206Action *ControllerPrivate::actionClass(
const QVariantHash &args)
208 const auto attributes = args.value(QStringLiteral(
"attributes")).value<
ParamsMultiMap>();
209 const QString actionClass = attributes.value(QStringLiteral(
"ActionClass"));
211 QObject *
object = instantiateClass(actionClass,
"Cutelyst::Action");
213 Action *action = qobject_cast<Action *>(
object);
217 qCWarning(CUTELYST_CONTROLLER) <<
"ActionClass" << actionClass <<
"is not an ActionClass";
224Action *ControllerPrivate::createAction(
const QVariantHash &args,
225 const QMetaMethod &method,
229 Action *action = actionClass(args);
234 QStack<Component *> roles = gatherActionRoles(args);
235 for (
int i = 0; i < roles.size(); ++i) {
237 code->
init(app, args);
238 code->setParent(action);
243 action->
setName(args.value(QStringLiteral(
"name")).toString());
244 action->
setReverse(args.value(QStringLiteral(
"reverse")).toString());
250void ControllerPrivate::registerActionMethods(
const QMetaObject *meta,
255 for (
int i = 0; i < meta->methodCount(); ++i) {
256 const QMetaMethod method = meta->method(i);
257 const QByteArray name = method.
name();
262 if (method.isValid() &&
263 (method.methodType() == QMetaMethod::Method ||
264 method.methodType() == QMetaMethod::Slot) &&
265 (method.parameterCount() &&
266 method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
269 QByteArray attributeArray;
270 for (
int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
271 QMetaClassInfo classInfo = meta->classInfo(i2);
272 if (name == classInfo.name()) {
273 attributeArray.append(classInfo.value());
276 ParamsMultiMap attrs = parseAttributes(method, attributeArray, name);
279 if (controller->
ns().isEmpty()) {
280 reverse = QString::fromLatin1(name);
282 reverse = controller->
ns() + QLatin1Char(
'/') + QString::fromLatin1(name);
286 createAction({{QStringLiteral(
"name"), QVariant::fromValue(name)},
287 {QStringLiteral(
"reverse"), QVariant::fromValue(reverse)},
288 {QStringLiteral(
"namespace"), QVariant::fromValue(controller->
ns())},
289 {QStringLiteral(
"attributes"), QVariant::fromValue(attrs)}},
295 actionList.append(action);
300ParamsMultiMap ControllerPrivate::parseAttributes(
const QMetaMethod &method,
301 const QByteArray &str,
302 const QByteArray &name)
305 std::vector<std::pair<QString, QString>> attributes;
312 int size = str.size();
319 if (str.at(pos) ==
':') {
320 int keyStart = ++pos;
323 if (str.at(pos) ==
'(') {
325 int valueStart = ++pos;
327 if (str.at(pos) ==
')') {
330 if (++pos < size && str.at(pos) ==
':') {
334 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
336 }
else if (pos >= size) {
340 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
350 }
else if (str.at(pos) ==
':') {
359 key = QString::fromLatin1(str.mid(keyStart, keyLength));
362 if (!value.isEmpty()) {
363 if ((value.startsWith(u
'\'') && value.endsWith(u
'\'')) ||
364 (value.startsWith(u
'"') && value.endsWith(u
'"'))) {
366 value.remove(value.size() - 1, 1);
371 attributes.emplace_back(std::make_pair(key, value));
379 for (
const auto &pair : attributes) {
380 QString key = pair.first;
381 QString value = pair.second;
382 if (key.compare(u
"Global") == 0) {
383 key = QStringLiteral(
"Path");
384 value = parsePathAttr(QLatin1Char(
'/') + QString::fromLatin1(name));
385 }
else if (key.compare(u
"Local") == 0) {
386 key = QStringLiteral(
"Path");
387 value = parsePathAttr(QString::fromLatin1(name));
388 }
else if (key.compare(u
"Path") == 0) {
389 value = parsePathAttr(value);
390 }
else if (key.compare(u
"Args") == 0) {
391 QString args = value;
392 if (!args.isEmpty()) {
393 value = args.remove(QRegularExpression(QStringLiteral(
"\\D")));
395 }
else if (key.compare(u
"CaptureArgs") == 0) {
396 QString captureArgs = value;
397 value = captureArgs.remove(QRegularExpression(QStringLiteral(
"\\D")));
398 }
else if (key.compare(u
"Chained") == 0) {
399 value = parseChainedAttr(value);
402 ret.insert(key, value);
406 if (!ret.contains(QStringLiteral(
"Args")) && !ret.contains(QStringLiteral(
"CaptureArgs")) &&
407 (ret.contains(QStringLiteral(
"AutoArgs")) ||
408 ret.contains(QStringLiteral(
"AutoCaptureArgs")))) {
409 if (ret.contains(QStringLiteral(
"AutoArgs")) &&
410 ret.contains(QStringLiteral(
"AutoCaptureArgs"))) {
411 qFatal(
"Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed",
414 QString parameterName;
415 if (ret.contains(QStringLiteral(
"AutoArgs"))) {
416 ret.remove(QStringLiteral(
"AutoArgs"));
417 parameterName = QStringLiteral(
"Args");
419 ret.remove(QStringLiteral(
"AutoCaptureArgs"));
420 parameterName = QStringLiteral(
"CaptureArgs");
424 if (!(method.parameterCount() == 2 &&
425 method.parameterType(1) == QMetaType::QStringList)) {
426 int parameterCount = 0;
427 for (
int i2 = 1; i2 < method.parameterCount(); ++i2) {
428 int typeId = method.parameterType(i2);
429 if (typeId == QMetaType::QString) {
433 ret.replace(parameterName, QString::number(parameterCount));
439 if (!ret.contains(QStringLiteral(
"Private")) && method.access() == QMetaMethod::Private) {
440 ret.replace(QStringLiteral(
"Private"), QString());
446QStack<Component *> ControllerPrivate::gatherActionRoles(
const QVariantHash &args)
448 QStack<Component *> roles;
449 const auto attributes = args.value(QStringLiteral(
"attributes")).value<
ParamsMultiMap>();
450 auto doesIt = attributes.constFind(QStringLiteral(
"Does"));
451 while (doesIt != attributes.constEnd() && doesIt.key().compare(u
"Does") == 0) {
453 instantiateClass(doesIt.value(), QByteArrayLiteral(
"Cutelyst::Component"));
455 roles.push(qobject_cast<Component *>(
object));
462QString ControllerPrivate::parsePathAttr(
const QString &value)
464 QString ret = pathPrefix;
465 if (value.startsWith(u
'/')) {
467 }
else if (!value.isEmpty()) {
468 ret = pathPrefix + u
'/' + value;
473QString ControllerPrivate::parseChainedAttr(
const QString &attr)
475 QString ret = QStringLiteral(
"/");
476 if (attr.isEmpty()) {
480 if (attr.compare(u
".") == 0) {
481 ret.append(pathPrefix);
482 }
else if (!attr.startsWith(u
'/')) {
483 if (!pathPrefix.isEmpty()) {
484 ret.append(pathPrefix + u
'/' + attr);
496QObject *ControllerPrivate::instantiateClass(
const QString &name,
const QByteArray &super)
498 QString instanceName = name;
499 if (!instanceName.isEmpty()) {
500 instanceName.remove(QRegularExpression(QStringLiteral(
"\\W")));
502#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
503 QMetaType
id = QMetaType::fromName(instanceName.toLatin1().data());
505 if (!instanceName.endsWith(QLatin1Char(
'*'))) {
506 instanceName.append(QLatin1Char(
'*'));
509 id = QMetaType::fromName(instanceName.toLatin1().data());
510 if (!
id.isValid() && !instanceName.startsWith(u
"Cutelyst::")) {
511 instanceName = QLatin1String(
"Cutelyst::") + instanceName;
512 id = QMetaType::fromName(instanceName.toLatin1().data());
517 const QMetaObject *metaObj =
id.metaObject();
519 if (!superIsClassName(metaObj->superClass(), super)) {
520 qCWarning(CUTELYST_CONTROLLER)
521 <<
"Class name" << instanceName <<
"is not a derived class of" << super;
524 QObject *
object = metaObj->newInstance();
526 qCWarning(CUTELYST_CONTROLLER)
527 <<
"Could create a new instance of" << instanceName
528 <<
"make sure it's default constructor is "
529 "marked with the Q_INVOKABLE macro";
535 Component *component = application->createComponentPlugin(name);
540 component = application->createComponentPlugin(instanceName);
547 qCCritical(CUTELYST_CONTROLLER,
548 "Could not create component '%s', you can register it with "
549 "qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
550 qPrintable(instanceName),
551 qPrintable(instanceName));
555 int id = QMetaType::type(instanceName.toLatin1().data());
557 if (!instanceName.endsWith(QLatin1Char(
'*'))) {
558 instanceName.append(QLatin1Char(
'*'));
561 id = QMetaType::type(instanceName.toLatin1().data());
562 if (!
id && !instanceName.startsWith(u
"Cutelyst::")) {
563 instanceName = QLatin1String(
"Cutelyst::") + instanceName;
564 id = QMetaType::type(instanceName.toLatin1().data());
569 const QMetaObject *metaObj = QMetaType::metaObjectForType(
id);
571 if (!superIsClassName(metaObj->superClass(), super)) {
572 qCWarning(CUTELYST_CONTROLLER)
573 <<
"Class name" << instanceName <<
"is not a derived class of" << super;
576 QObject *
object = metaObj->newInstance();
578 qCWarning(CUTELYST_CONTROLLER)
579 <<
"Could create a new instance of" << instanceName
580 <<
"make sure it's default constructor is "
581 "marked with the Q_INVOKABLE macro";
587 Component *component = application->createComponentPlugin(name);
592 component = application->createComponentPlugin(instanceName);
599 qCCritical(CUTELYST_CONTROLLER,
600 "Could not create component '%s', you can register it with "
601 "qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
602 qPrintable(instanceName),
603 qPrintable(instanceName));
610bool ControllerPrivate::superIsClassName(
const QMetaObject *super,
const QByteArray &className)
613 if (super->className() == className) {
616 return superIsClassName(super->superClass(), className);
621#include "moc_controller.cpp"
This class represents a Cutelyst Action.
void setupAction(const QVariantHash &args, Application *app)
bool dispatch(Context *c)
void setMethod(const QMetaMethod &method)
void setController(Controller *controller)
The Cutelyst Application.
The Cutelyst Component base class.
void setReverse(const QString &reverse)
virtual bool init(Application *application, const QVariantHash &args)
void applyRoles(const QStack< Component * > &roles)
void setName(const QString &name)
Cutelyst Controller base class
virtual bool preFork(Application *app)
Action * actionFor(const QString &name) const
ActionList actions() const
virtual bool postFork(Application *app)
bool operator==(const char *className)
bool _DISPATCH(Context *c)
Controller(QObject *parent=nullptr)
The Cutelyst namespace holds all public Cutelyst API.
QVector< Action * > ActionList
QMultiMap< QString, QString > ParamsMultiMap