/** * @file rlvhelper.cpp * @author Kitty Barnett * @brief RLVa helper classes for internal use only * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2024, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "lltrans.h" #include "llviewercontrol.h" #include "rlvhelper.h" #include using namespace Rlv; // ============================================================================ // BehaviourDictionary // BehaviourDictionary::BehaviourDictionary() { // // Restrictions // // // Reply-only // addEntry(new ReplyProcessor("getcommand")); addEntry(new ReplyProcessor("version")); addEntry(new ReplyProcessor("versionnew")); addEntry(new ReplyProcessor("versionnum")); // Populate mString2InfoMap (the tuple should be unique) for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList) { mString2InfoMap.insert(std::make_pair(std::make_pair(bhvr_info_p->getBehaviour(), static_cast(bhvr_info_p->getParamTypeMask())), bhvr_info_p)); } // Populate m_Bhvr2InfoMap (there can be multiple entries per ERlvBehaviour) for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList) { if ((bhvr_info_p->getParamTypeMask() & to_underlying(EParamType::AddRem)) && !bhvr_info_p->isSynonym()) { #ifdef RLV_DEBUG for (const auto& itBhvr : boost::make_iterator_range(mBhvr2InfoMap.lower_bound(bhvr_info_p->getBehaviourType()), mBhvr2InfoMap.upper_bound(bhvr_info_p->getBehaviourType()))) { RLV_ASSERT((itBhvr.first != bhvr_info_p->getBehaviourType()) || (itBhvr.second->getBehaviourFlags() != bhvr_info_p->getBehaviourFlags())); } #endif // RLV_DEBUG mBhvr2InfoMap.insert(std::pair(bhvr_info_p->getBehaviourType(), bhvr_info_p)); } } } BehaviourDictionary::~BehaviourDictionary() { for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList) { delete bhvr_info_p; } mBhvrInfoList.clear(); } void BehaviourDictionary::addEntry(const BehaviourInfo* entry_p) { // Filter experimental commands (if disabled) static LLCachedControl sEnableExperimental(gSavedSettings, Settings::EnableExperimentalCommands); if (!entry_p || (!sEnableExperimental && entry_p->isExperimental())) { return; } // Sanity check for duplicate entries #if LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG std::for_each(mBhvrInfoList.begin(), mBhvrInfoList.end(), [&entry_p](const BehaviourInfo* bhvr_info_p) { RLV_ASSERT_DBG((bhvr_info_p->getBehaviour() != entry_p->getBehaviour()) || ((bhvr_info_p->getParamTypeMask() & entry_p->getParamTypeMask()) == 0)); }); #endif // LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG mBhvrInfoList.push_back(entry_p); } const BehaviourInfo* BehaviourDictionary::getBehaviourInfo(EBehaviour eBhvr, EParamType eParamType) const { const BehaviourInfo* bhvr_info_p = nullptr; for (auto itBhvrLower = mBhvr2InfoMap.lower_bound(eBhvr), itBhvrUpper = mBhvr2InfoMap.upper_bound(eBhvr); std::find_if(itBhvrLower, itBhvrUpper, [eParamType](const auto& bhvrEntry) { return bhvrEntry.second->getParamTypeMask() == to_underlying(eParamType); }) != itBhvrUpper; ++itBhvrLower) { if (bhvr_info_p) return nullptr; bhvr_info_p = itBhvrLower->second; } return bhvr_info_p; } const BehaviourInfo* BehaviourDictionary::getBehaviourInfo(const std::string& strBhvr, EParamType eParamType, bool* is_strict_p) const { size_t idxBhvrLastPart = strBhvr.find_last_of('_'); std::string strBhvrLastPart((std::string::npos != idxBhvrLastPart) && (idxBhvrLastPart < strBhvr.size()) ? strBhvr.substr(idxBhvrLastPart + 1) : LLStringUtil::null); bool isStrict = (strBhvrLastPart.compare("sec") == 0); if (is_strict_p) *is_strict_p = isStrict; auto itBhvr = mString2InfoMap.find(std::make_pair((!isStrict) ? strBhvr : strBhvr.substr(0, strBhvr.size() - 4), (has_flag(eParamType, EParamType::AddRem)) ? EParamType::AddRem : eParamType)); if ((mString2InfoMap.end() == itBhvr) && (!isStrict) && (!strBhvrLastPart.empty()) && (EParamType::Force == eParamType)) { // No match found but it could still be a local scope modifier auto itBhvrMod = mString2InfoMap.find(std::make_pair(strBhvr.substr(0, idxBhvrLastPart), EParamType::AddRem)); } return ((itBhvr != mString2InfoMap.end()) && ((!isStrict) || (itBhvr->second->hasStrict()))) ? itBhvr->second : nullptr; } EBehaviour BehaviourDictionary::getBehaviourFromString(const std::string& strBhvr, EParamType eParamType, bool* pisStrict) const { const BehaviourInfo* bhvr_info_p = getBehaviourInfo(strBhvr, eParamType, pisStrict); // Filter out locally scoped modifier commands since they don't actually have a unique behaviour value of their own return bhvr_info_p->getBehaviourType(); } bool BehaviourDictionary::getCommands(const std::string& strMatch, EParamType eParamType, std::list& cmdList) const { cmdList.clear(); for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList) { if ((bhvr_info_p->getParamTypeMask() & to_underlying(eParamType)) || (EParamType::Unknown == eParamType)) { std::string strCmd = bhvr_info_p->getBehaviour(); if ((std::string::npos != strCmd.find(strMatch)) || (strMatch.empty())) cmdList.push_back(strCmd); if ((bhvr_info_p->hasStrict()) && ((std::string::npos != strCmd.append("_sec").find(strMatch)) || (strMatch.empty()))) cmdList.push_back(strCmd); } } return !cmdList.empty(); } bool BehaviourDictionary::getHasStrict(EBehaviour eBhvr) const { for (const auto& itBhvr : boost::make_iterator_range(mBhvr2InfoMap.lower_bound(eBhvr), mBhvr2InfoMap.upper_bound(eBhvr))) { // Only restrictions can be strict if (to_underlying(EParamType::AddRem) != itBhvr.second->getParamTypeMask()) continue; return itBhvr.second->hasStrict(); } RLV_ASSERT(false); return false; } void BehaviourDictionary::toggleBehaviourFlag(const std::string& strBhvr, EParamType eParamType, BehaviourInfo::EBehaviourFlags eBhvrFlag, bool fEnable) { auto itBhvr = mString2InfoMap.find(std::make_pair(strBhvr, (has_flag(eParamType, EParamType::AddRem)) ? EParamType::AddRem : eParamType)); if (mString2InfoMap.end() != itBhvr) { const_cast(itBhvr->second)->toggleBehaviourFlag(eBhvrFlag, fEnable); } } // ============================================================================ // RlvCommmand // RlvCommand::RlvCommand(const LLUUID& idObj, const std::string& strCmd) : mObjId(idObj) { if (parseCommand(strCmd, mBehaviour, mOption, mParam)) { if ("n" == mParam || "add" == mParam) mParamType = EParamType::Add; else if ("y" == mParam || "rem" == mParam) mParamType = EParamType::Remove; else if ("clear" == mBehaviour) // clear is the odd one out so just make it its own type mParamType = EParamType::Clear; else if ("force" == mParam) mParamType = EParamType::Force; else if (S32 nTemp; LLStringUtil::convertToS32(mParam, nTemp)) // Assume it's a reply command if we can convert to an S32 mParamType = EParamType::Reply; } mIsValid = mParamType != EParamType::Unknown; if (!mIsValid) { mOption.clear(); mParam.clear(); return; } mBhvrInfo = BehaviourDictionary::instance().getBehaviourInfo(mBehaviour, mParamType, &mIsStrict); } RlvCommand::RlvCommand(const RlvCommand& rlvCmd, EParamType eParamType) : mIsValid(rlvCmd.mIsValid), mObjId(rlvCmd.mObjId), mBehaviour(rlvCmd.mBehaviour), mBhvrInfo(rlvCmd.mBhvrInfo) , mParamType( (EParamType::Unknown == eParamType) ? rlvCmd.mParamType : eParamType) , mIsStrict(rlvCmd.mIsStrict), mOption(rlvCmd.mOption), mParam(rlvCmd.mParam), mIsRefCounted(rlvCmd.mIsRefCounted) { } bool RlvCommand::parseCommand(const std::string& strCmd, std::string& strBhvr, std::string& strOption, std::string& strParam) { // Format: [: