18 #include "controller_p.h"
20 #include "application.h"
21 #include "dispatcher.h"
25 #include "context_p.h"
27 #include <QMetaClassInfo>
28 #include <QRegularExpression>
33 , d_ptr(new ControllerPrivate(this))
37 Controller::~Controller()
40 qDeleteAll(d->actionList);
53 Action *ret = d->actions.value(name);
57 return d->dispatcher->getAction(name, d->pathPrefix);
68 return !qstrcmp(metaObject()->className(), className);
83 ControllerPrivate::ControllerPrivate(
Controller *parent) :
92 dispatcher = _dispatcher;
98 const QMetaObject *meta = q->metaObject();
99 const QString className = QString::fromLatin1(meta->className());
100 q->setObjectName(className);
102 bool namespaceFound =
false;
103 for (
int i = meta->classInfoCount() - 1; i >= 0; --i) {
104 if (qstrcmp(meta->classInfo(i).name(),
"Namespace") == 0) {
105 pathPrefix = QString::fromLatin1(meta->classInfo(i).value());
106 while (pathPrefix.startsWith(QLatin1Char(
'/'))) {
107 pathPrefix.remove(0, 1);
109 namespaceFound =
true;
114 if (!namespaceFound) {
116 bool lastWasUpper =
true;
118 for (
int i = 0; i < className.length(); ++i) {
119 const QChar c = className.at(i);
120 if (c.isLower() || c.isDigit()) {
121 controlerNS.append(c);
122 lastWasUpper =
false;
123 }
else if (c == QLatin1Char(
'_')) {
124 controlerNS.append(c);
128 controlerNS.append(QLatin1Char(
'/'));
130 controlerNS.append(c.toLower());
134 pathPrefix = controlerNS;
137 registerActionMethods(meta, q, app);
140 void ControllerPrivate::setupFinished()
144 const ActionList beginList = dispatcher->getActions(QStringLiteral(
"Begin"), pathPrefix);
145 if (!beginList.isEmpty()) {
146 beginAutoList.append(beginList.last());
149 beginAutoList.append(dispatcher->getActions(QStringLiteral(
"Auto"), pathPrefix));
151 const ActionList endList = dispatcher->getActions(QStringLiteral(
"End"), pathPrefix);
152 if (!endList.isEmpty()) {
153 end = endList.last();
156 const auto actions = actionList;
157 for (
Action *action : actions) {
158 action->dispatcherReady(dispatcher, q);
161 q->preFork(qobject_cast<Application *>(q->parent()));
170 QVector<Component *> stack;
172 stack.append(d->end);
174 stack.append(c->action());
176 auto rit = d->beginAutoList.crbegin();
177 while (rit != d->beginAutoList.crend()) {
181 c->d_ptr->pendingAsync = stack;
223 Action *ControllerPrivate::actionClass(
const QVariantHash &args)
225 const auto attributes = args.value(QStringLiteral(
"attributes")).value<QMap<QString, QString> >();
226 const QString actionClass = attributes.value(QStringLiteral(
"ActionClass"));
228 QObject *
object = instantiateClass(actionClass,
"Cutelyst::Action");
230 Action *action = qobject_cast<Action*>(
object);
234 qCWarning(CUTELYST_CONTROLLER) <<
"ActionClass"
236 <<
"is not an ActionClass";
243 Action *ControllerPrivate::createAction(
const QVariantHash &args,
const QMetaMethod &method,
Controller *controller,
Application *app)
245 Action *action = actionClass(args);
250 QStack<Component *> roles = gatherActionRoles(args);
251 for (
int i = 0; i < roles.size(); ++i) {
253 code->
init(app, args);
254 code->setParent(action);
259 action->
setName(args.value(QStringLiteral(
"name")).toString());
260 action->
setReverse(args.value(QStringLiteral(
"reverse")).toString());
266 void ControllerPrivate::registerActionMethods(
const QMetaObject *meta,
Controller *controller,
Application *app)
269 for (
int i = 0; i < meta->methodCount(); ++i) {
270 const QMetaMethod method = meta->method(i);
271 const QByteArray name = method.
name();
276 if (method.isValid() &&
277 (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot) &&
278 (method.parameterCount() && method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
281 QByteArray attributeArray;
282 for (
int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
283 QMetaClassInfo classInfo = meta->classInfo(i2);
284 if (name == classInfo.name()) {
285 attributeArray.append(classInfo.value());
288 QMap<QString, QString> attrs = parseAttributes(method, attributeArray, name);
291 if (controller->
ns().isEmpty()) {
292 reverse = QString::fromLatin1(name);
294 reverse = controller->
ns() + QLatin1Char(
'/') + QString::fromLatin1(name);
297 Action *action = createAction({
298 {QStringLiteral(
"name"), QVariant::fromValue(name)},
299 {QStringLiteral(
"reverse"), QVariant::fromValue(reverse)},
300 {QStringLiteral(
"namespace"), QVariant::fromValue(controller->
ns())},
301 {QStringLiteral(
"attributes"), QVariant::fromValue(attrs)}
307 actions.insertMulti(action->
reverse(), action);
308 actionList.append(action);
313 QMap<QString, QString> ControllerPrivate::parseAttributes(
const QMetaMethod &method,
const QByteArray &str,
const QByteArray &name)
315 QMap<QString, QString> ret;
316 std::vector<std::pair<QString, QString> > attributes;
323 int size = str.size();
330 if (str.at(pos) ==
':') {
331 int keyStart = ++pos;
334 if (str.at(pos) ==
'(') {
336 int valueStart = ++pos;
338 if (str.at(pos) ==
')') {
341 if (++pos < size && str.at(pos) ==
':') {
344 value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
346 }
else if (pos >= size) {
349 value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
359 }
else if (str.at(pos) ==
':') {
368 key = QString::fromLatin1(str.mid(keyStart, keyLength));
371 if (!value.isEmpty()) {
372 if ((value.startsWith(QLatin1Char(
'\'')) && value.endsWith(QLatin1Char(
'\''))) ||
373 (value.startsWith(QLatin1Char(
'"')) && value.endsWith(QLatin1Char(
'"')))) {
375 value.remove(value.size() - 1, 1);
380 attributes.push_back({ key, value });
388 auto i = attributes.crbegin();
389 const auto end = attributes.crend();
391 QString key = i->first;
392 QString value = i->second;
393 if (key == QLatin1String(
"Global")) {
394 key = QStringLiteral(
"Path");
395 value = parsePathAttr(QLatin1Char(
'/') + QString::fromLatin1(name));
396 }
else if (key == QLatin1String(
"Local")) {
397 key = QStringLiteral(
"Path");
398 value = parsePathAttr(QString::fromLatin1(name));
399 }
else if (key == QLatin1String(
"Path")) {
400 value = parsePathAttr(value);
401 }
else if (key == QLatin1String(
"Args")) {
402 QString args = value;
403 if (!args.isEmpty()) {
404 value = args.remove(QRegularExpression(QStringLiteral(
"\\D")));
406 }
else if (key == QLatin1String(
"CaptureArgs")) {
407 QString captureArgs = value;
408 value = captureArgs.remove(QRegularExpression(QStringLiteral(
"\\D")));
409 }
else if (key == QLatin1String(
"Chained")) {
410 value = parseChainedAttr(value);
413 ret.insertMulti(key, value);
418 if (!ret.contains(QStringLiteral(
"Args")) && !ret.contains(QStringLiteral(
"CaptureArgs")) &&
419 (ret.contains(QStringLiteral(
"AutoArgs")) || ret.contains(QStringLiteral(
"AutoCaptureArgs")))) {
420 if (ret.contains(QStringLiteral(
"AutoArgs")) && ret.contains(QStringLiteral(
"AutoCaptureArgs"))) {
421 qFatal(
"Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed", name.constData());
423 QString parameterName;
424 if (ret.contains(QStringLiteral(
"AutoArgs"))) {
425 ret.remove(QStringLiteral(
"AutoArgs"));
426 parameterName = QStringLiteral(
"Args");
428 ret.remove(QStringLiteral(
"AutoCaptureArgs"));
429 parameterName = QStringLiteral(
"CaptureArgs");
433 if (!(method.parameterCount() == 2 && method.parameterType(1) == QMetaType::QStringList)) {
434 int parameterCount = 0;
435 for (
int i2 = 1; i2 < method.parameterCount(); ++i2) {
436 int typeId = method.parameterType(i2);
437 if (typeId == QMetaType::QString) {
441 ret.insert(parameterName, QString::number(parameterCount));
448 if (!ret.contains(QStringLiteral(
"Private")) && method.access() == QMetaMethod::Private) {
449 ret.insert(QStringLiteral(
"Private"), QString());
455 QStack<Component *> ControllerPrivate::gatherActionRoles(
const QVariantHash &args)
457 QStack<Component *> roles;
458 const auto attributes = args.value(QStringLiteral(
"attributes")).value<
ParamsMultiMap>();
459 auto doesIt = attributes.constFind(QStringLiteral(
"Does"));
460 while (doesIt != attributes.constEnd() && doesIt.key() == QLatin1String(
"Does")) {
461 QObject *
object = instantiateClass(doesIt.value(), QByteArrayLiteral(
"Cutelyst::Component"));
463 roles.push(qobject_cast<Component *>(
object));
470 QString ControllerPrivate::parsePathAttr(
const QString &value)
472 QString ret = pathPrefix;
473 if (value.startsWith(QLatin1Char(
'/'))) {
475 }
else if (!value.isEmpty()) {
476 ret = pathPrefix + QLatin1Char(
'/') + value;
481 QString ControllerPrivate::parseChainedAttr(
const QString &attr)
483 QString ret = QStringLiteral(
"/");
484 if (attr.isEmpty()) {
488 if (attr == QStringLiteral(
".")) {
489 ret.append(pathPrefix);
490 }
else if (!attr.startsWith(QLatin1Char(
'/'))) {
491 if (!pathPrefix.isEmpty()) {
492 ret.append(pathPrefix + QLatin1Char(
'/') + attr);
504 QObject *ControllerPrivate::instantiateClass(
const QString &name,
const QByteArray &super)
506 QString instanceName = name;
507 if (!instanceName.isEmpty()) {
508 instanceName.remove(QRegularExpression(QStringLiteral(
"\\W")));
510 int id = QMetaType::type(instanceName.toLatin1().data());
512 if (!instanceName.endsWith(QLatin1Char(
'*'))) {
513 instanceName.append(QLatin1Char(
'*'));
516 id = QMetaType::type(instanceName.toLatin1().data());
517 if (!
id && !instanceName.startsWith(QStringLiteral(
"Cutelyst::"))) {
518 instanceName = QLatin1String(
"Cutelyst::") + instanceName;
519 id = QMetaType::type(instanceName.toLatin1().data());
524 const QMetaObject *metaObj = QMetaType::metaObjectForType(
id);
526 if (!superIsClassName(metaObj->superClass(), super)) {
527 qCWarning(CUTELYST_CONTROLLER)
530 <<
"is not a derived class of"
534 QObject *
object = metaObj->newInstance();
536 qCWarning(CUTELYST_CONTROLLER)
537 <<
"Could create a new instance of"
539 <<
"make sure it's default constructor is "
540 "marked with the Q_INVOKABLE macro";
546 Component *component = application->createComponentPlugin(name);
551 component = application->createComponentPlugin(instanceName);
558 qFatal(
"Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
559 qPrintable(instanceName), qPrintable(instanceName));
565 bool ControllerPrivate::superIsClassName(
const QMetaObject *super,
const QByteArray &className)
568 if (super->className() == className) {
571 return superIsClassName(super->superClass(), className);
576 #include "moc_controller.cpp"