File indexing completed on 2024-06-30 05:51:31

0001 /*
0002     This file is part of the Okteta Kasten Framework, made within the KDE community.
0003 
0004     SPDX-FileCopyrightText: 2011, 2012, 2013 Alex Richardson <alex.richardson@gmx.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "defaultscriptclass.hpp"
0010 
0011 #include "../../datatypes/datainformation.hpp"
0012 #include "../../datatypes/topleveldatainformation.hpp"
0013 #include "../../datatypes/uniondatainformation.hpp"
0014 #include "../../datatypes/structuredatainformation.hpp"
0015 #include "../../datatypes/primitive/pointerdatainformation.hpp"
0016 #include "../../parsers/parserutils.hpp"
0017 #include "../../parsers/scriptvalueconverter.hpp"
0018 #include "../scriptlogger.hpp"
0019 #include "../scripthandlerinfo.hpp"
0020 #include "../safereference.hpp"
0021 #include <structureslogging.hpp>
0022 // Std
0023 #include <utility>
0024 
0025 DefaultScriptClass::DefaultScriptClass(QScriptEngine* engine, ScriptHandlerInfo* handlerInfo)
0026     : QScriptClass(engine)
0027     , mHandlerInfo(handlerInfo)
0028 {
0029     s_valid = engine->toStringHandle(ParserStrings::PROPERTY_VALID());
0030     s_wasAbleToRead = engine->toStringHandle(ParserStrings::PROPERTY_ABLE_TO_READ());
0031     s_validationError = engine->toStringHandle(ParserStrings::PROPERTY_VALIDATION_ERROR());
0032     s_parent = engine->toStringHandle(ParserStrings::PROPERTY_PARENT());
0033     s_byteOrder = engine->toStringHandle(ParserStrings::PROPERTY_BYTEORDER());
0034     s_name = engine->toStringHandle(ParserStrings::PROPERTY_NAME());
0035     s_datatype = engine->toStringHandle(ParserStrings::PROPERTY_DATATYPE());
0036     s_updateFunc = engine->toStringHandle(ParserStrings::PROPERTY_UPDATE_FUNC());
0037     s_validationFunc = engine->toStringHandle(ParserStrings::PROPERTY_VALIDATION_FUNC());
0038     s_customTypeName = engine->toStringHandle(ParserStrings::PROPERTY_CUSTOM_TYPE_NAME());
0039     s_asStringFunc = engine->toStringHandle(ParserStrings::PROPERTY_TO_STRING_FUNC());
0040 
0041     // TODO remove, every subclass should have proto
0042     mDefaultPrototype = engine->newObject();
0043     mDefaultPrototype.setProperty(QStringLiteral("toString"), engine->newFunction(Default_proto_toString));
0044     // add all our properties
0045     mIterableProperties.reserve(11);
0046     mIterableProperties.append(qMakePair(s_parent, QScriptValue::ReadOnly | QScriptValue::Undeletable));
0047     mIterableProperties.append(qMakePair(s_name, QScriptValue::PropertyFlags(QScriptValue::Undeletable)));
0048     mIterableProperties.append(qMakePair(s_wasAbleToRead, QScriptValue::ReadOnly | QScriptValue::Undeletable));
0049     mIterableProperties.append(qMakePair(s_byteOrder, QScriptValue::PropertyFlags(QScriptValue::Undeletable)));
0050     mIterableProperties.append(qMakePair(s_valid, QScriptValue::ReadOnly | QScriptValue::Undeletable));
0051     mIterableProperties.append(qMakePair(s_validationError, QScriptValue::ReadOnly | QScriptValue::Undeletable));
0052     mIterableProperties.append(qMakePair(s_validationFunc, QScriptValue::PropertyFlags(QScriptValue::Undeletable)));
0053     mIterableProperties.append(qMakePair(s_updateFunc, QScriptValue::PropertyFlags(QScriptValue::Undeletable)));
0054     mIterableProperties.append(qMakePair(s_datatype, QScriptValue::PropertyFlags(QScriptValue::Undeletable)));
0055     mIterableProperties.append(qMakePair(s_customTypeName, QScriptValue::PropertyFlags(QScriptValue::Undeletable)));
0056     mIterableProperties.append(qMakePair(s_asStringFunc, QScriptValue::PropertyFlags(QScriptValue::Undeletable)));
0057 }
0058 
0059 DefaultScriptClass::~DefaultScriptClass() = default;
0060 
0061 DataInformation* DefaultScriptClass::toDataInformation(const QScriptValue& obj)
0062 {
0063     if (!obj.scriptClass()) {
0064         return nullptr;
0065     }
0066     Q_ASSERT(obj.data().isVariant());
0067     const QVariant variant = obj.data().toVariant();
0068     if (variant.isValid() && variant.canConvert<SafeReference>() && variant.userType() == qMetaTypeId<SafeReference>()) {
0069         const SafeReference& ref = *reinterpret_cast<const SafeReference*>(variant.constData());
0070         return ref.data();
0071     }
0072     return nullptr;
0073 }
0074 
0075 QScriptClass::QueryFlags DefaultScriptClass::queryProperty(const QScriptValue& object,
0076                                                            const QScriptString& name, QScriptClass::QueryFlags flags, uint* id)
0077 {
0078     const ScriptHandlerInfo::Mode mode = mHandlerInfo->mode();
0079     Q_ASSERT(mode != ScriptHandlerInfo::Mode::None);
0080     DataInformation* data = toDataInformation(object);
0081     if (!data) {
0082         mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString();
0083         engine()->currentContext()->throwError(QScriptContext::ReferenceError,
0084                                                QStringLiteral("Attempting to access an invalid object"));
0085         return {};
0086     }
0087     if (name == s_valid || name == s_validationError) {
0088         return mode == ScriptHandlerInfo::Mode::Validating ? flags : flags& ~HandlesWriteAccess;
0089     }
0090     if (mode != ScriptHandlerInfo::Mode::Updating) {
0091         // the only properties that are possibly writable when not updating are valid and validationError
0092         // but we checked them before so we remove handlesWriteAccess from the flags
0093         flags &= ~HandlesWriteAccess;
0094     }
0095 
0096     if (name == s_byteOrder || name == s_name || name == s_updateFunc || name == s_validationFunc
0097         || name == s_datatype || name == s_customTypeName || name == s_asStringFunc) {
0098         return flags;
0099     }
0100     if (name == s_wasAbleToRead || name == s_parent) {
0101         return flags & ~HandlesWriteAccess;
0102     }
0103     if (queryAdditionalProperty(data, name, &flags, id)) {
0104         return flags;
0105     }
0106 
0107     data->logError() << "could not find property with name" << name.toString();
0108     engine()->currentContext()->throwError(QScriptContext::ReferenceError,
0109                                                QLatin1String("Could not find property with name ") + name.toString());
0110     return {};
0111 }
0112 
0113 QScriptValue DefaultScriptClass::property(const QScriptValue& object, const QScriptString& name, uint id)
0114 {
0115     Q_ASSERT(mHandlerInfo->mode() != ScriptHandlerInfo::Mode::None);
0116     DataInformation* data = toDataInformation(object);
0117     if (!data) {
0118         mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString();
0119         return engine()->currentContext()->throwError(QScriptContext::ReferenceError,
0120                                                       QStringLiteral("Attempting to access an invalid object"));
0121     }
0122     if (name == s_valid) {
0123         return data->validationSuccessful();
0124     }
0125     if (name == s_wasAbleToRead) {
0126         return data->wasAbleToRead();
0127     }
0128     if (name == s_parent) {
0129         Q_CHECK_PTR(data->parent());
0130         // parent() cannot be null
0131         if (data->parent()->isTopLevel()) {
0132             return engine()->nullValue();
0133         }
0134         return data->parent()->asDataInformation()->toScriptValue(engine(), mHandlerInfo);
0135     }
0136     if (name == s_datatype) {
0137         return data->typeName();
0138     }
0139     if (name == s_updateFunc) {
0140         return data->updateFunc();
0141     }
0142     if (name == s_validationFunc) {
0143         return data->validationFunc();
0144     }
0145     if (name == s_validationError) {
0146         return data->validationError();
0147     }
0148     if (name == s_byteOrder) {
0149         return ParserUtils::byteOrderToString(data->byteOrder());
0150     }
0151     if (name == s_name) {
0152         return data->name();
0153     }
0154     if (name == s_customTypeName) {
0155         return data->typeName();
0156     }
0157     if (name == s_asStringFunc) {
0158         return data->toStringFunction();
0159     }
0160     QScriptValue other = additionalProperty(data, name, id);
0161     if (other.isValid()) {
0162         return other;
0163     }
0164     data->logError() << "could not find property with name" << name.toString();
0165     return engine()->currentContext()->throwError(QScriptContext::ReferenceError,
0166                                                   QLatin1String("Cannot read property ") + name.toString());
0167 }
0168 
0169 void DefaultScriptClass::setDataType(const QScriptValue& value, DataInformation* data)
0170 {
0171     DataInformation* thisObj = toDataInformation(engine()->currentContext()->thisObject());
0172     Q_CHECK_PTR(thisObj);
0173     const bool isThisObj = thisObj == data;
0174     // this object always has mHasBeenUpdated set just before calling updateFunc, so in that case it is okay
0175     if (data->hasBeenUpdated() && !isThisObj) {
0176         // this element has already been updated (and probably read, replacing it could cause crazy errors
0177         data->logError() << "Attempting to replace an already updated object. This could cause errors."
0178             "Current this object: " << (thisObj ? thisObj->fullObjectPath() : QString());
0179         return;
0180     }
0181     // change the type of the underlying object
0182     DataInformation* newType = ScriptValueConverter::convert(value, data->name(), data->logger(), data);
0183     if (!newType) {
0184         data->logError() << "Failed to set new type, could not convert value!";
0185         return;
0186     }
0187 
0188     DataInformationBase* parent = data->parent();
0189     Q_CHECK_PTR(parent);
0190     TopLevelDataInformation* top = data->topLevelDataInformation();
0191     Q_CHECK_PTR(top);
0192     // only if parent is toplevel, struct or union, can we replace
0193     bool replaced = false;
0194     if (parent->isTopLevel()) {
0195         Q_ASSERT(isThisObj); // we can only do this if we are currently at the top level element
0196         parent->asTopLevel()->setActualDataInformation(newType);
0197         replaced = true;
0198     } else if (parent->isStruct()) {
0199         StructureDataInformation* stru = parent->asStruct();
0200         int index = stru->indexOf(data);
0201         Q_ASSERT(index != -1);
0202         Q_ASSERT(uint(index) < stru->childCount());
0203         replaced = stru->replaceChildAt(index, newType);
0204         if (!replaced) {
0205             stru->logError() << "failed to replace child at index" << index;
0206         }
0207     } else if (parent->isUnion()) {
0208         UnionDataInformation* un = parent->asUnion();
0209         int index = un->indexOf(data);
0210         Q_ASSERT(index != -1);
0211         Q_ASSERT(uint(index) < un->childCount());
0212         replaced = un->replaceChildAt(index, newType);
0213         if (!replaced) {
0214             un->logError() << "failed to replace child at index" << index;
0215         }
0216     } else if (parent->isPointer()) {
0217         parent->asPointer()->setPointerTarget(newType);
0218         replaced = true;
0219     } else {
0220         data->logError() << "Failed to set data type since element is not toplevel and parent"
0221             " is neither struct nor union nor pointer.";
0222     }
0223     if (replaced) {
0224         top->setChildDataChanged();
0225         // if the current object was "this" in javascript we have to replace it
0226         if (isThisObj) {
0227             engine()->currentContext()->setThisObject(newType->toScriptValue(engine(), mHandlerInfo));
0228         }
0229         newType->mHasBeenUpdated = true;
0230     } else {
0231         delete newType; // could not set new type
0232     }
0233 }
0234 
0235 void DefaultScriptClass::setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value)
0236 {
0237     const ScriptHandlerInfo::Mode mode = mHandlerInfo->mode();
0238     Q_ASSERT(mode != ScriptHandlerInfo::Mode::None);
0239     DataInformation* data = toDataInformation(object);
0240     if (!data) {
0241         mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString();
0242         engine()->currentContext()->throwError(QScriptContext::ReferenceError,
0243                                                QStringLiteral("Attempting to access an invalid object"));
0244         return;
0245     }
0246     if (mode == ScriptHandlerInfo::Mode::Validating) {
0247         // only way write access is allowed is when validating: valid and validationError
0248         if (data->hasBeenValidated()) {
0249             data->logError() << "Cannot modify this object, it has already been validated!";
0250         } else if (name == s_valid) {
0251             data->mValidationSuccessful = value.toBool();
0252         } else if (name == s_validationError) {
0253             data->setValidationError(value.toString());
0254         } else {
0255             data->logError() << "Cannot write to property" << name.toString() << "while validating!";
0256         }
0257         return;
0258     }
0259 
0260     if (mode != ScriptHandlerInfo::Mode::Updating) {
0261         data->logError() << "Writing to property" << name.toString() << "is only allowed when updating.";
0262         return;
0263     }
0264     Q_ASSERT(mode == ScriptHandlerInfo::Mode::Updating);
0265 
0266     if (name == s_byteOrder) {
0267         data->setByteOrder(ParserUtils::byteOrderFromString(value.toString(),
0268                                                             LoggerWithContext(data->logger(), data->fullObjectPath())));
0269     } else if (name == s_datatype) {
0270         // change the type of the underlying object
0271         setDataType(value, data);
0272     } else if (name == s_updateFunc) {
0273         data->setUpdateFunc(value);
0274     } else if (name == s_validationFunc) {
0275         data->setValidationFunc(value);
0276     } else if (name == s_name) {
0277         data->setName(value.toString());
0278     } else if (name == s_customTypeName) {
0279         if (!value.isValid() || value.isNull() || value.isUndefined()) {
0280             data->setCustomTypeName(QString()); // unset
0281         } else {
0282             data->setCustomTypeName(value.toString());
0283         }
0284     } else if (name == s_asStringFunc) {
0285         data->setToStringFunction(value);
0286     } else {
0287         bool setAdditional = setAdditionalProperty(data, name, id, value);
0288         if (setAdditional) {
0289             return;
0290         }
0291         data->logError() << "could not set property with name" << name.toString();
0292         engine()->currentContext()->throwError(QScriptContext::ReferenceError,
0293                                                QLatin1String("Cannot write property ") + name.toString());
0294     }
0295 }
0296 
0297 QScriptValue::PropertyFlags DefaultScriptClass::propertyFlags(const QScriptValue& object, const QScriptString& name, uint id)
0298 {
0299     QScriptValue::PropertyFlags result;
0300     const ScriptHandlerInfo::Mode mode = mHandlerInfo->mode();
0301     Q_ASSERT(mode != ScriptHandlerInfo::Mode::None);
0302     DataInformation* data = toDataInformation(object);
0303     if (!data) {
0304         mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString();
0305         engine()->currentContext()->throwError(QScriptContext::ReferenceError,
0306                                                QStringLiteral("Attempting to access an invalid object"));
0307         return {};
0308     }
0309     if (name == s_valid || name == s_validationError) {
0310         if (mode != ScriptHandlerInfo::Mode::Validating) {
0311             result |= QScriptValue::ReadOnly;
0312         }
0313     } else if (mode != ScriptHandlerInfo::Mode::Updating) {
0314         result |= QScriptValue::ReadOnly;
0315     }
0316 
0317     for (const auto& property : std::as_const(mIterableProperties)) {
0318         if (property.first == name) {
0319             return result | property.second;
0320         }
0321     }
0322 
0323     if (additionalPropertyFlags(data, name, id, &result)) {
0324         return result; // is a child element
0325     }
0326     data->logError() << "could not find flags for property with name" << name.toString();
0327     return {};
0328 }
0329 
0330 QScriptValue DefaultScriptClass::prototype() const
0331 {
0332     return mDefaultPrototype;
0333 }
0334 
0335 QScriptValue DefaultScriptClass::Default_proto_toString(QScriptContext* ctx, QScriptEngine* eng)
0336 {
0337     DataInformation* data = toDataInformation(ctx->thisObject());
0338     if (!data) {
0339         qCWarning(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "could not cast data";
0340         return eng->undefinedValue();
0341     }
0342     return QString(data->typeName() + QLatin1Char(' ') + data->name());
0343 }
0344 
0345 QScriptClassPropertyIterator* DefaultScriptClass::newIterator(const QScriptValue& object)
0346 {
0347     return new DefaultscriptClassIterator(object, this);
0348 }
0349 
0350 DefaultscriptClassIterator::DefaultscriptClassIterator(const QScriptValue& object, DefaultScriptClass* cls)
0351     : QScriptClassPropertyIterator(object)
0352     , mClass(cls)
0353 {
0354     DataInformation* data = DefaultScriptClass::toDataInformation(object);
0355     Q_CHECK_PTR(data);
0356     mData = data;
0357 }
0358 
0359 DefaultscriptClassIterator::~DefaultscriptClassIterator() = default;
0360 
0361 bool DefaultscriptClassIterator::hasNext() const
0362 {
0363     return mCurrent < mClass->mIterableProperties.size() - 1;
0364 }
0365 
0366 bool DefaultscriptClassIterator::hasPrevious() const
0367 {
0368     return mCurrent > 0;
0369 }
0370 
0371 QScriptString DefaultscriptClassIterator::name() const
0372 {
0373     Q_ASSERT(mCurrent >= 0 && (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount());
0374     if (mCurrent < 0 || (uint)mCurrent >= mClass->mIterableProperties.size() + mData->childCount()) {
0375         return {};
0376     }
0377     if (mCurrent < mClass->mIterableProperties.size()) {
0378         return mClass->mIterableProperties.at(mCurrent).first;
0379     }
0380     int index = mCurrent - mClass->mIterableProperties.size();
0381     Q_ASSERT(index >= 0);
0382     DataInformation* child = mData->childAt(index);
0383     return mClass->engine()->toStringHandle(child->name());
0384 }
0385 
0386 QScriptValue::PropertyFlags DefaultscriptClassIterator::flags() const
0387 {
0388     Q_ASSERT(mCurrent >= 0 && (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount());
0389     if (mCurrent < 0 || (uint)mCurrent >= mClass->mIterableProperties.size() + mData->childCount()) {
0390         return {};
0391     }
0392     if (mCurrent < mClass->mIterableProperties.size()) {
0393         return mClass->propertyFlags(object(), mClass->mIterableProperties.at(mCurrent).first, id());
0394     }
0395     return QScriptValue::ReadOnly;
0396 }
0397 
0398 uint DefaultscriptClassIterator::id() const
0399 {
0400     Q_ASSERT(mCurrent >= 0 && (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount());
0401     if (mCurrent < 0 || (uint)mCurrent >= mClass->mIterableProperties.size() + mData->childCount()) {
0402         return 0;
0403     }
0404     // only children have an id assigned
0405     if (mCurrent < mClass->mIterableProperties.size()) {
0406         return 0;
0407     }
0408     return mCurrent - mClass->mIterableProperties.size() + 1;
0409 }
0410 
0411 void DefaultscriptClassIterator::next()
0412 {
0413     Q_ASSERT(mCurrent == -1 || (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount());
0414     mCurrent++;
0415 }
0416 
0417 void DefaultscriptClassIterator::previous()
0418 {
0419     Q_ASSERT(mCurrent >= 0);
0420     mCurrent--;
0421 }
0422 
0423 void DefaultscriptClassIterator::toBack()
0424 {
0425     mCurrent = mClass->mIterableProperties.size() + mData->childCount();
0426 }
0427 
0428 void DefaultscriptClassIterator::toFront()
0429 {
0430     mCurrent = -1;
0431 }