diff --git a/core/coreobjects/include/coreobjects/property_object_class_builder_impl.h b/core/coreobjects/include/coreobjects/property_object_class_builder_impl.h index dc2fa8fc14..6a4f98aad2 100644 --- a/core/coreobjects/include/coreobjects/property_object_class_builder_impl.h +++ b/core/coreobjects/include/coreobjects/property_object_class_builder_impl.h @@ -54,7 +54,6 @@ class PropertyObjectClassBuilderImpl : public ImplementationOf customOrder; WeakRefPtr manager; - bool hasDuplicateReferences(const PropertyPtr& prop) const; ListPtr getProperties() const; }; diff --git a/core/coreobjects/include/coreobjects/property_object_class_impl.h b/core/coreobjects/include/coreobjects/property_object_class_impl.h index d3009220cd..8d03bf0437 100644 --- a/core/coreobjects/include/coreobjects/property_object_class_impl.h +++ b/core/coreobjects/include/coreobjects/property_object_class_impl.h @@ -68,8 +68,6 @@ class PropertyObjectClassImpl : public ImplementationOf& properties) const; ErrCode serializeProperties(ISerializer* serializer); - - bool hasDuplicateReferences(const PropertyPtr& prop); }; diff --git a/core/coreobjects/include/coreobjects/property_object_helpers.h b/core/coreobjects/include/coreobjects/property_object_helpers.h new file mode 100644 index 0000000000..0c175e537b --- /dev/null +++ b/core/coreobjects/include/coreobjects/property_object_helpers.h @@ -0,0 +1,678 @@ +/* + * Copyright 2022-2026 openDAQ d.o.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_NAMESPACE_OPENDAQ + +// Stateless helpers used by GenericPropertyObjectImpl. Internal to openDAQ; not part of the public API surface. +namespace details +{ + +struct PropertyNameInfo +{ + StringPtr name; + Int index = -1; // -1 when the queried name carries no "[index]" suffix +}; + +#if defined(__GNUC__) && __GNUC__ >= 12 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdangling-pointer" +#endif + +// Child property handling - Used when a property is queried in the "parent.child" format + +inline bool isChildProperty(const StringPtr& name) +{ + return strchr(name.getCharPtr(), '.') != nullptr; +} + +inline void splitOnLastDot(const StringPtr& input, StringPtr& head, StringPtr& tail) +{ + const std::string inputStr = input; + head = input; + + size_t pos = inputStr.rfind('.'); + if (pos == std::string::npos) + return; + + head = inputStr.substr(0, pos); + tail = inputStr.substr(pos + 1); +} + +// Gets the index integer value between two square brackets +inline int parseIndex(char const* lBracket) +{ + auto last = strchr(lBracket, ']'); + if (last != nullptr) + { + char* end; + int index = strtol(lBracket + 1, &end, 10); + + if (end != last) + DAQ_THROW_EXCEPTION(InvalidParameterException, "Could not parse the property index."); + + return index; + } + DAQ_THROW_EXCEPTION(InvalidParameterException, "No matching ] found."); +} + +// Gets the property name without the index as the `propName` output parameter +// Returns the index in the form of [index], eg. [0] +inline ConstCharPtr getPropNameWithoutIndex(const StringPtr& name, StringPtr& propName) +{ + auto propNameData = name.getCharPtr(); + auto first = strchr(propNameData, '['); + + if (first == nullptr) + propName = String(propNameData); + else + propName = String(propNameData, first - propNameData); + return first; +} + +inline PropertyNameInfo getPropertyNameInfo(const StringPtr& name) +{ + PropertyNameInfo nameInfo; + const ConstCharPtr bracket = getPropNameWithoutIndex(name, nameInfo.name); + if (bracket != nullptr) + nameInfo.index = parseIndex(bracket); + return nameInfo; +} + +#if defined(__GNUC__) && __GNUC__ >= 12 + #pragma GCC diagnostic pop +#endif + +// Checks if the value is a container type, or base `IPropertyObject`. Only such values can be set in `setProperty` +inline ErrCode checkContainerType(const PropertyPtr& prop, const BaseObjectPtr& value) +{ + if (!value.assigned()) + return OPENDAQ_SUCCESS; + + auto coreType = value.getCoreType(); + if (coreType == ctObject) + { + auto inspect = value.asPtrOrNull(true); + if (inspect.assigned() && !inspect.getInterfaceIds().empty()) + return inspect.getInterfaceIds()[0] == IPropertyObject::Id; + + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, "Only base Property Object object-type values are allowed"); + } + + auto iterate = [](const IterablePtr& it, CoreType type) + { + for (const auto& key : it) + { + if (key.getCoreType() != type) + return false; + } + return true; + }; + + const auto propInternal = prop.asPtr(true); + if (coreType == ctDict) + { + const auto dict = value.asPtr(); + const auto keyType = propInternal.getKeyTypeNoLock(); + const auto itemType = propInternal.getItemTypeNoLock(); + + IterablePtr it; + dict->getKeys(&it); + if (!iterate(it, keyType)) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Invalid dictionary key type for property "{}")", prop.getName())); + + dict->getValues(&it); + if (!iterate(it, itemType)) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Invalid dictionary item type for property "{}")", prop.getName())); + } + else if (coreType == ctList) + { + const auto itemType = propInternal.getItemTypeNoLock(); + + if (itemType != ctUndefined && !iterate(value, itemType)) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Invalid list item type for property "{}")", prop.getName())); + } + + return OPENDAQ_SUCCESS; +} + +// Checks if the property is a struct type, and checks its fields for type/name compatibility +inline ErrCode checkStructType(const PropertyPtr& prop, const BaseObjectPtr& value) +{ + if (prop.getValueType() != ctStruct) + return OPENDAQ_SUCCESS; + + auto structPtr = value.asPtrOrNull(); + if (!structPtr.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value is not a struct for property "{}")", prop.getName())); + + StructTypePtr structType = prop.asPtr().getStructTypeNoLock(); + StructTypePtr valueStructType = structPtr.getStructType(); + + if (structType != valueStructType) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value StructureType is different from the default for property "{}")", prop.getName())); + + return OPENDAQ_SUCCESS; +} + +// Checks if the property is a enumeration type and checks for type/name compatibility +inline ErrCode checkEnumerationType(const PropertyPtr& prop, const BaseObjectPtr& value) +{ + const auto propInternal = prop.asPtr(); + if (propInternal.getValueTypeNoLock() != ctEnumeration) + return OPENDAQ_SUCCESS; + + auto enumerationPtr = value.asPtrOrNull(); + if (!enumerationPtr.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value is not an enumeration for property "{}")", prop.getName())); + + auto propEnumerationPtr = propInternal.getDefaultValueNoLock().asPtrOrNull(); + if (!propEnumerationPtr.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Property default value is not an enumeration for property "{}")", prop.getName())); + + EnumerationTypePtr valueEnumerationType = enumerationPtr.getEnumerationType(); + EnumerationTypePtr propEnumerationType = propEnumerationPtr.getEnumerationType(); + + if (propEnumerationType != valueEnumerationType) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value EnumerationType is different from the default for property "{}")", prop.getName())); + + return OPENDAQ_SUCCESS; +} + +// Checks if value is a correct key into the list/dictionary of selection values +inline ErrCode checkSelectionValues(const PropertyPtr& prop, const BaseObjectPtr& value) +{ + const auto selectionValues = prop.asPtr(true).getSelectionValuesNoLock(); + if (!selectionValues.assigned()) + return OPENDAQ_SUCCESS; + + const PropertyType propType = prop.getPropertyType(); + if (propType == PropertyType::IndexSelection) + { + if (const auto list = selectionValues.asPtrOrNull(true); list.assigned()) + { + const SizeT key = value; + if (key < list.getCount()) + return OPENDAQ_SUCCESS; + } + } + else if (propType == PropertyType::Selection) + { + if (const auto list = selectionValues.asPtrOrNull(true); list.assigned()) + { + if (prop.getValueType() == ctFloat) + { + const double valueDouble = value; + const double preScale = std::max(1.0, std::abs(valueDouble)); + for (const double& item : list) + { + const double scale = std::max(preScale, std::abs(item)); + if (std::abs(item - valueDouble) <= std::numeric_limits::epsilon() * scale) + return OPENDAQ_SUCCESS; + } + } + else + { + for (const auto& item : list) + { + if (item == value) + return OPENDAQ_SUCCESS; + } + } + } + } + else if (propType == PropertyType::SparseSelection) + { + if (const auto dict = selectionValues.asPtrOrNull(true); dict.assigned() && dict.hasKey(value)) + return OPENDAQ_SUCCESS; + } + + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Value is not a key/index of selection values for property "{}")", prop.getName())); +} + +// Checks if property and value type match. If not, attempts to convert the value +inline ErrCode checkPropertyTypeAndConvert(const PropertyPtr& prop, BaseObjectPtr& value) +{ + if (!prop.assigned() || !value.assigned()) + return OPENDAQ_SUCCESS; + + if (value.supportsInterface()) + return OPENDAQ_SUCCESS; + + const ErrCode errCode = daqTry([&]() + { + const auto propInternal = prop.asPtr(); + const auto propCoreType = propInternal.getValueTypeNoLock(); + const auto valueCoreType = value.getCoreType(); + + if (propCoreType != valueCoreType) + { + if (propCoreType == ctEnumeration) + { + const auto enumVal = propInternal.getDefaultValueNoLock().asPtrOrNull(); + if (!enumVal.assigned()) + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, + fmt::format(R"(Default value of enumeration property {} is not assigned)", prop.getName())); + } + + const auto type = enumVal.getEnumerationType(); + const Int intVal = value.convertTo(ctInt); + value = EnumerationWithIntValueAndType(type, intVal); + } + else + value = value.convertTo(propCoreType); + } + return OPENDAQ_SUCCESS; + }); + + OPENDAQ_RETURN_IF_FAILED(errCode, fmt::format(R"(Value type is different than Property type and conversion failed for property "{}")", prop.getName())); + return errCode; +} + +// Computes the name under which a property's value is stored/looked up, given the resolved +// property and the raw queried name. Reference properties store under the referenced name; +// a "[index]" suffix from the raw name is preserved. +inline StringPtr buildResolvedPropertyName(const StringPtr& parsedName, + const StringPtr& rawName, + const PropertyPtr& resolvedProperty, + bool isReferenced, + ConstCharPtr bracket) +{ + if (bracket != nullptr) + return isReferenced ? StringPtr(resolvedProperty.getName() + std::string(bracket)) : rawName; + + if (isReferenced) + return resolvedProperty.getName(); + + return parsedName; +} + +// Reads the property's default value, indexing into it when a "[index]" suffix was queried. +// A missing default is not an error; value is simply left unassigned. +inline ErrCode readDefaultPropertyValue(const PropertyPtr& property, const StringPtr& propName, ConstCharPtr bracket, BaseObjectPtr& value) +{ + const auto propInternal = property.asPtr(); + const ErrCode res = propInternal->getDefaultValueNoLock(&value); + + if (OPENDAQ_FAILED(res)) + daqClearErrorInfo(); + + if (!value.assigned()) + return OPENDAQ_SUCCESS; + + if (value.getCoreType() == ctList && bracket != nullptr) + { + const int index = parseIndex(bracket); + ListPtr list = value; + if (index >= static_cast(list.getCount())) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_OUTOFRANGE, fmt::format(R"(The index parameter is out of bounds of the list for property "{}")", propName)); + value = list[std::size_t(index)]; + } + + return OPENDAQ_SUCCESS; +} + +// Container values are cloned on read so the stored value cannot be mutated through the returned reference +inline BaseObjectPtr cloneIfContainerValue(const BaseObjectPtr& value) +{ + if (!value.assigned()) + return value; + + const CoreType coreType = value.getCoreType(); + if (coreType == ctList || coreType == ctDict) + { + BaseObjectPtr clonedValue; + value.asPtr()->clone(&clonedValue); + return clonedValue; + } + return value; +} + +// Maps a user-facing selection value to the index/key that is stored as the property value +inline ErrCode selectionValueToKey(const PropertyPtr& prop, const BaseObjectPtr& valuePtr, BaseObjectPtr& indexOrKey) +{ + const auto propInternal = prop.asPtr(true); + const auto selectionValues = propInternal.getSelectionValuesNoLock(); + const PropertyType propType = prop.getPropertyType(); + const auto propName = prop.getName(); + + if (propType == PropertyType::IndexSelection) + { + if (!selectionValues.assigned()) + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, + fmt::format(R"(Index selection property "{}" has no selection values assigned)", propName)); + } + + const auto valuesList = selectionValues.asPtrOrNull(true); + if (!valuesList.assigned()) + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, + fmt::format(R"(Index selection property "{}" values is not a list)", propName)); + } + + for (SizeT i = 0; i < valuesList.getCount(); ++i) + { + if (valuesList.getItemAt(i) == valuePtr) + { + indexOrKey = Int(i); + break; + } + } + + if (!indexOrKey.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Value not found in selection values of property "{}")", propName)); + } + else if (propType == PropertyType::SparseSelection) + { + if (!selectionValues.assigned()) + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, + fmt::format(R"(Sparse selection property "{}" has no selection values assigned)", propName)); + } + + const auto valuesDict = selectionValues.asPtrOrNull(true); + if (!valuesDict.assigned()) + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, + fmt::format(R"(Sparse selection property "{}" values is not a dictionary)", propName)); + } + + for (const auto& [key, value] : valuesDict) + { + if (value == valuePtr) + { + indexOrKey = key; + break; + } + } + + if (!indexOrKey.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Value not found in sparse selection values of property "{}")", propName)); + } + else if (propType == PropertyType::Selection) + indexOrKey = valuePtr; + else + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, fmt::format(R"(Property "{}" is not an index selection or sparse selection property)", propName)); + + return OPENDAQ_SUCCESS; +} + +// Resolves the stored index/key of a selection property to the corresponding selection value, in place +inline ErrCode selectionKeyToValue(const PropertyPtr& prop, BaseObjectPtr& valuePtr) +{ + const auto propInternal = prop.asPtr(true); + const auto values = propInternal.getSelectionValuesNoLock(); + const auto propName = prop.getName(); + + if (!values.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, fmt::format(R"(Selection property "{}" has no selection values assigned)", propName)); + + const PropertyType propType = prop.getPropertyType(); + if (propType == PropertyType::IndexSelection) + { + const auto valuesList = values.asPtrOrNull(true); + if (!valuesList.assigned()) + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, + fmt::format(R"(Index selection property "{}" values is not a list)", propName)); + } + valuePtr = valuesList.getItemAt(valuePtr); + } + else if (propType == PropertyType::SparseSelection) + { + const auto valuesDict = values.asPtrOrNull(true); + if (!valuesDict.assigned()) + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, + fmt::format(R"(Sparse selection property "{}" values is not a dictionary)", propName)); + } + valuePtr = valuesDict.get(valuePtr); + } + else if (propType == PropertyType::Selection) + { + if (propInternal.getValueTypeNoLock() != valuePtr.getCoreType()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Selection item type mismatch for property "{}")", propName)); + + return OPENDAQ_SUCCESS; + } + else + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, + fmt::format(R"(Property "{}" is not an index selection or sparse selection property)", propName)); + } + + if (propInternal.getItemTypeNoLock() != valuePtr.getCoreType()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Selection value type mismatch for property "{}")", propName)); + + return OPENDAQ_SUCCESS; +} + +// Coercion/Validation + +inline void coercePropertyWrite(const PropertyPtr& prop, ObjectPtr& valuePtr, const PropertyObjectPtr& objPtr) +{ + if (!prop.assigned() || !valuePtr.assigned()) + return; + + const auto coercer = prop.asPtr().getCoercerNoLock(); + if (!coercer.assigned()) + return; + + try + { + valuePtr = coercer.coerceNoLock(objPtr, valuePtr); + } + catch (const DaqException&) + { + throw; + } + catch (...) + { + DAQ_THROW_EXCEPTION(CoerceFailedException); + } +} + +inline void validatePropertyWrite(const PropertyPtr& prop, ObjectPtr& valuePtr, const PropertyObjectPtr& objPtr) +{ + if (!prop.assigned() || !valuePtr.assigned()) + return; + + const auto validator = prop.asPtr().getValidatorNoLock(); + if (!validator.assigned()) + return; + + try + { + validator.validateNoLock(objPtr, valuePtr); + } + catch (const DaqException&) + { + throw; + } + catch (...) + { + DAQ_THROW_EXCEPTION(ValidateFailedException); + } +} + +inline void coerceMinMax(const PropertyPtr& prop, ObjectPtr& valuePtr) +{ + if (!prop.assigned() || !valuePtr.assigned()) + return; + + const auto propInternal = prop.asPtr(); + const auto min = propInternal.getMinValueNoLock(); + if (min.assigned()) + { + try + { + if (valuePtr < min) + valuePtr = min; + } + catch (...) + { + } + } + + const auto max = propInternal.getMaxValueNoLock(); + if (max.assigned()) + { + try + { + if (valuePtr > max) + valuePtr = max; + } + catch (...) + { + } + } +} + +// Validates and converts `value` for writing to `prop`: type conversion and compatibility +// checks, write coercion/validation, min/max clamping, and a defensive clone of container +// values so the caller stores a private copy. +inline ErrCode checkAndCoerceWrite(const PropertyPtr& prop, ObjectPtr& value, const PropertyObjectPtr& objPtr) +{ + OPENDAQ_RETURN_IF_FAILED(checkPropertyTypeAndConvert(prop, value)); + OPENDAQ_RETURN_IF_FAILED(checkContainerType(prop, value)); + OPENDAQ_RETURN_IF_FAILED(checkSelectionValues(prop, value)); + OPENDAQ_RETURN_IF_FAILED(checkStructType(prop, value)); + OPENDAQ_RETURN_IF_FAILED(checkEnumerationType(prop, value)); + + coercePropertyWrite(prop, value, objPtr); + validatePropertyWrite(prop, value, objPtr); + coerceMinMax(prop, value); + + const auto ct = prop.asPtr(true).getValueTypeNoLock(); + if (ct == ctList || ct == ctDict) + { + BaseObjectPtr cloned; + OPENDAQ_RETURN_IF_FAILED(value.asPtr()->clone(&cloned)); + value = cloned.detach(); + } + + return OPENDAQ_SUCCESS; +} + +// Reference property handling + +inline PropertyPtr checkForRefPropAndGetBoundProp(const PropertyPtr& prop, const PropertyObjectPtr& objPtr, bool* isReferenced = nullptr) +{ + if (!prop.assigned()) + return prop; + + PropertyInternalPtr boundProp = prop.asPtr(true).cloneWithOwner(objPtr); + auto refProp = boundProp.getReferencedPropertyNoLock(); + if (refProp.assigned()) + { + CoreType ct = refProp.getCoreType(); + + if (ct != ctObject) + throw std::invalid_argument("Invalid reference to property"); + + if (isReferenced) + *isReferenced = true; + + return checkForRefPropAndGetBoundProp(refProp, objPtr); + } + + if (isReferenced) + *isReferenced = false; + return boundProp; +} + +// Checks whether the property is a reference property that references an already referenced property +inline bool hasDuplicateReferences(const PropertyPtr& prop, const PropertyObjectPtr& objPtr) +{ + const auto refEval = prop.asPtr().getReferencedPropertyUnresolved(); + if (!refEval.assigned()) + return false; + + for (const auto& refPropName : refEval.getPropertyReferences()) + { + if (objPtr.hasProperty(refPropName) && objPtr.getProperty(refPropName).getIsReferenced()) + return true; + } + + return false; +} + +// Checks whether the property's reference targets overlap with those of any property in the list +inline bool hasDuplicateReferences(const PropertyPtr& prop, const ListPtr& properties) +{ + const auto refEval = prop.asPtr().getReferencedPropertyUnresolved(); + if (!refEval.assigned()) + return false; + + std::unordered_set refNamesSet; + for (const auto& refName : refEval.getPropertyReferences()) + refNamesSet.insert(refName); + + for (const auto& ownProp : properties) + { + if (const auto refEvalOwn = ownProp.asPtr().getReferencedPropertyUnresolved(); refEvalOwn.assigned()) + { + for (const auto& refPropName : refEvalOwn.getPropertyReferences()) + { + if (refNamesSet.count(refPropName)) + return true; + } + } + } + + return false; +} + +inline bool checkIsReferenced(const StringPtr& referencedPropName, const PropertyInternalPtr& prop) +{ + const auto refProp = prop.getReferencedPropertyUnresolved(); + if (!refProp.assigned()) + return false; + + for (const auto& propName : refProp.getPropertyReferences()) + { + if (propName == referencedPropName) + return true; + } + + return false; +} + +} + +END_NAMESPACE_OPENDAQ diff --git a/core/coreobjects/include/coreobjects/property_object_impl.h b/core/coreobjects/include/coreobjects/property_object_impl.h index 8b302b94c3..d9a878fdbc 100644 --- a/core/coreobjects/include/coreobjects/property_object_impl.h +++ b/core/coreobjects/include/coreobjects/property_object_impl.h @@ -54,15 +54,28 @@ #include #include #include +#include BEGIN_NAMESPACE_OPENDAQ using PropertyOrderedMap = tsl::ordered_map; -struct PropertyNameInfo -{ - StringPtr name; - Int index{}; +using PropertyValueEventEmitter = EventEmitter; +using EndUpdateEventEmitter = EventEmitter; + +// Snapshot of the members a clone copies from its source. Namespace-scoped (not nested) so that +// all GenericPropertyObjectImpl instantiations share the type and can configure clones of one another. +struct PropertyObjectCloneParameters +{ + const std::unordered_map& valueWriteEvents; + const std::unordered_map& valueReadEvents; + const EndUpdateEventEmitter& endUpdateEvent; + const ProcedurePtr& triggerCoreEvent; + const PropertyOrderedMap& localProperties; + const std::unordered_map& propValues; + const std::vector& customOrder; + const PermissionManagerPtr& permissionManager; + const std::set& corePropertyNames; }; namespace config_protocol @@ -176,32 +189,9 @@ class GenericPropertyObjectImpl : public ImplementationOfWeak; - using EndUpdateEventEmitter = EventEmitter; - - struct CloneParameters - { - const std::unordered_map& valueWriteEvents; - const std::unordered_map& valueReadEvents; - const EndUpdateEventEmitter& endUpdateEvent; - const ProcedurePtr& triggerCoreEvent; - const PropertyOrderedMap& localProperties; - const std::unordered_map& propValues; - const std::vector& customOrder; - const PermissionManagerPtr& permissionManager; - const std::set& corePropertyNames; - }; + using CloneParameters = PropertyObjectCloneParameters; void configureClonedMembers(const CloneParameters& parameters); - void configureClonedMembers(const std::unordered_map& valueWriteEvents, - const std::unordered_map& valueReadEvents, - const EndUpdateEventEmitter& endUpdateEvent, - const ProcedurePtr& triggerCoreEvent, - const PropertyOrderedMap& localProperties, - const std::unordered_map& propValues, - const std::vector& customOrder, - const PermissionManagerPtr& permissionManager, - const std::set& corePropertyNames); // TODO: Make remove friend classes once private methods are properly exposed in protected scope. template @@ -341,7 +331,7 @@ class GenericPropertyObjectImpl : public ImplementationOfWeak owner; int updateCount; - UpdatingActions updatingPropsAndValues; + UpdatingActions batchedUpdates; WeakRefPtr manager; std::vector customOrder; StringPtr path; @@ -349,6 +339,9 @@ class GenericPropertyObjectImpl : public ImplementationOfWeak + void forEachUnfrozenChildObject(Handler&& handler) + { + for (const auto& [_, propValue] : propValues) + { + const auto propObj = propValue.template asPtrOrNull(true); + if (!propObj.assigned()) + continue; + + const auto freezable = propObj.template asPtrOrNull(true); + if (freezable.assigned() && freezable.isFrozen()) + continue; + + handler(propObj); + } + } + static void DeserializePropertyValues(const SerializedObjectPtr& serialized, const BaseObjectPtr& context, const FunctionPtr& factoryCallback, @@ -380,18 +391,14 @@ class GenericPropertyObjectImpl : public ImplementationOfWeak collectAllProperties(Bool includeCoreProperties) const; + // Binds the collected properties to this object and drops invisible/referenced ones unless requested + ErrCode bindAndFilterProperties(const std::vector& allProperties, Bool includeInvisible, Bool bind, PropertyOrderedMap& lookup); + // Consumes `lookup`: properties named in customOrder first, then the rest in default order + ListPtr applyCustomPropertyOrder(PropertyOrderedMap& lookup) const; // Gets the property value, if stored in local value dictionary (propValues) // Parses brackets, if the property is a list ErrCode readLocalValue(const StringPtr& name, BaseObjectPtr& value) const; - static PropertyNameInfo getPropertyNameInfo(const StringPtr& name); - - // Checks if the value is a container type, or base `IPropertyObject`. Only such values can be set in `setProperty` - static ErrCode checkContainerType(const PropertyPtr& prop, const BaseObjectPtr& value); - - // Checks if the property is a struct type, and checks its fields for type/name compatibility - static ErrCode checkStructType(const PropertyPtr& prop, const BaseObjectPtr& value); - - // Checks if the property is a enumeration type and checks for type/name compatibility - static ErrCode checkEnumerationType(const PropertyPtr& prop, const BaseObjectPtr& value); - - // Checks if value is a correct key into the list/dictionary of selection values - static ErrCode checkSelectionValues(const PropertyPtr& prop, const BaseObjectPtr& value); // Called when `setPropertyValue` successfully sets a new value [[maybe_unused]] @@ -430,32 +440,22 @@ class GenericPropertyObjectImpl : public ImplementationOfWeak& valuePtr, const PropertyObjectPtr& objPtr); - static void validatePropertyWrite(const PropertyPtr& prop, ObjectPtr& valuePtr, const PropertyObjectPtr& objPtr); - static void coerceMinMax(const PropertyPtr& prop, ObjectPtr& valuePtr); - static Bool checkIsReferenced(const StringPtr& referencedPropName, const PropertyInternalPtr& prop); // Update ErrCode updateObjectProperties(const PropertyObjectPtr& propObj, @@ -528,9 +528,7 @@ GenericPropertyObjectImpl::GenericPropertyObjec for (const auto& prop : objectClass.getProperties(true)) { if (checkIsChildObjectProperty(prop)) - { setChildPropertyObject(prop.getName(), cloneChildPropertyObject(prop)); - } } } } @@ -612,102 +610,49 @@ ErrCode GenericPropertyObjectImpl::getClassName OPENDAQ_PARAM_NOT_NULL(className); if (this->className.assigned()) - { *className = this->className.addRefAndReturn(); - } else - { *className = String("").detach(); - } return OPENDAQ_SUCCESS; } -#if defined(__GNUC__) && __GNUC__ >= 12 - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdangling-pointer" -#endif - -template -bool GenericPropertyObjectImpl::isChildProperty(const StringPtr& name) -{ - auto chr = strchr(name.getCharPtr(), '.'); - return chr != nullptr; -} - -template -void GenericPropertyObjectImpl::splitOnFirstDot(const StringPtr& input, - StringPtr& head, - StringPtr& tail) -{ - const std::string inputStr = input; - head = input; - - size_t pos = inputStr.find('.'); - if (pos == std::string::npos) - return; - - head = inputStr.substr(0, pos); - tail = inputStr.substr(pos + 1); -} - template -void GenericPropertyObjectImpl::splitOnLastDot(const StringPtr& input, - StringPtr& head, - StringPtr& tail) +ErrCode GenericPropertyObjectImpl::getParentObject(const StringPtr& path, + PropertyObjectPtr& parentObj, + StringPtr& leafName) { - const std::string inputStr = input; - head = input; + StringPtr parentPath; + details::splitOnLastDot(path, parentPath, leafName); - size_t pos = inputStr.rfind('.'); - if (pos == std::string::npos) - return; + const std::string parentPathStr = parentPath; + PropertyObjectPtr current; + size_t start = 0; - head = inputStr.substr(0, pos); - tail = inputStr.substr(pos + 1); -} - -template -ErrCode GenericPropertyObjectImpl::getChildPropertyValue(const StringPtr& childName, - const StringPtr& subName, - BaseObjectPtr& value) -{ - PropertyPtr prop; - StringPtr name; - - auto err = daqTry([&]() -> auto + while (true) { - prop = getUnboundProperty(childName); + const size_t pos = parentPathStr.find('.', start); + const SizeT segmentLength = (pos == std::string::npos ? parentPathStr.size() : pos) - start; + const StringPtr segment = String(parentPathStr.c_str() + start, segmentLength); - prop = checkForRefPropAndGetBoundProp(prop, objPtr); - name = prop.getName(); - return OPENDAQ_SUCCESS; - }); + BaseObjectPtr childValue; + const ErrCode err = current.assigned() ? current->getPropertyValue(segment, &childValue) + : getPropertyValueInternal(segment, &childValue); + OPENDAQ_RETURN_IF_FAILED(err); - OPENDAQ_RETURN_IF_FAILED(err); + current = childValue.template asPtrOrNull(true); + if (!current.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOINTERFACE, fmt::format(R"(Property "{}" is not a property object)", segment)); - if (!prop.assigned()) - { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", name)); + if (pos == std::string::npos) + break; + start = pos + 1; } - BaseObjectPtr childProp; - err = getPropertyValueInternal(name, &childProp); - OPENDAQ_RETURN_IF_FAILED(err); - - err = daqTry([&]() -> auto - { - const auto childPropAsPropertyObject = childProp.template asPtr(true); - value = childPropAsPropertyObject.getPropertyValue(subName); - }); - OPENDAQ_RETURN_IF_FAILED(err); - return err; + parentObj = current; + return OPENDAQ_SUCCESS; } -#if defined(__GNUC__) && __GNUC__ >= 12 - #pragma GCC diagnostic pop -#endif - template ErrCode GenericPropertyObjectImpl::callPropertyValueWrite(const PropertyPtr& prop, BaseObjectPtr& newValue, @@ -720,8 +665,7 @@ ErrCode GenericPropertyObjectImpl::callProperty if (!updatePropertyStack.registerPropertyUpdating(name, newValue)) return OPENDAQ_IGNORED; - const bool isBaseStackLevel = updatePropertyStack.isBaseStackLevel(name); - if (isBaseStackLevel) + if (updatePropertyStack.isBaseStackLevel(name)) { if (newValue.assigned() && !shouldWriteLocalValue(name, newValue)) { @@ -730,180 +674,105 @@ ErrCode GenericPropertyObjectImpl::callProperty } } - BaseObjectPtr oldValue; - ErrCode errCode = readLocalValue(name, oldValue); - if (errCode == OPENDAQ_ERR_NOTFOUND) + bool unregistered = false; + try { - daqClearErrorInfo(); - oldValue = defaultValue; - } - errCode = OPENDAQ_SUCCESS; + BaseObjectPtr oldValue; + ErrCode errCode = readLocalValue(name, oldValue); + if (errCode == OPENDAQ_ERR_NOTFOUND) + { + daqClearErrorInfo(); + oldValue = defaultValue; + } - PropertyValueEventArgsPtr args; - if (changeType == PropertyEventType::Clear) - args = PropertyValueEventArgs(prop, defaultValue, oldValue, changeType, isUpdating); - else - args = PropertyValueEventArgs(prop, newValue, oldValue, changeType, isUpdating); + auto chosenValue = changeType == PropertyEventType::Clear ? defaultValue : newValue; + PropertyValueEventArgsPtr args = PropertyValueEventArgs(prop, chosenValue, oldValue, changeType, isUpdating); - if (!localProperties.count(name)) - { - const PropertyValueEventEmitter propEvent{prop.asPtr(true).getClassOnPropertyValueWrite()}; - if (propEvent.hasListeners()) - propEvent(objPtr, args); - } + errCode = firePropertyValueEvents(prop, args, true); - if (valueWriteEvents.find(name) != valueWriteEvents.end()) - { - if (valueWriteEvents[name].hasListeners()) - errCode = daqTry([&] { valueWriteEvents[name](objPtr, args); }); - } + const bool shouldUpdate = updatePropertyStack.unregisterPropertyUpdating(name); + unregistered = true; + // If the event execution failed, forward the error code + OPENDAQ_RETURN_IF_FAILED(errCode); - if (valueWriteEvents[AnyWriteEventName].hasListeners()) - { - valueWriteEvents[AnyWriteEventName](objPtr, args); - } + if (shouldUpdate) + { + // setting the final value is only done in the top level of the stack + if (changeType == PropertyEventType::Clear && args.getValue() == defaultValue) + return OPENDAQ_SUCCESS; - bool shouldUpdate = updatePropertyStack.unregisterPropertyUpdating(name); - // If the event execution failed, forward the error code - OPENDAQ_RETURN_IF_FAILED(errCode); + if (newValue == args.getValue()) + return OPENDAQ_SUCCESS; - if (shouldUpdate) + // if the value changed, we have to validate new value before setting it + newValue = args.getValue(); + return setPropertyValueInternal(name, newValue, false, true, false); + } + return OPENDAQ_IGNORED; + } + catch (...) { - // setting the final value is only done in the top level of the stack - if (changeType == PropertyEventType::Clear && args.getValue() == defaultValue) - return OPENDAQ_SUCCESS; - - if (newValue == args.getValue()) - return OPENDAQ_SUCCESS; - - // if the value changed, we have to validate new value before setting it - newValue = args.getValue(); - return setPropertyValueInternal(name, newValue, false, true, false); + // A registration leaked past a throw would permanently poison the update stack for this property + if (!unregistered) + updatePropertyStack.unregisterPropertyUpdating(name); + throw; } - return OPENDAQ_IGNORED; } template -BaseObjectPtr GenericPropertyObjectImpl::callPropertyValueRead(const PropertyPtr& prop, - const BaseObjectPtr& readValue) +ErrCode GenericPropertyObjectImpl::firePropertyValueEvents(const PropertyPtr& prop, + PropertyValueEventArgsPtr& args, + bool valueWrite) { - if (!prop.assigned()) - { - return readValue; - } - - auto args = PropertyValueEventArgs(prop, readValue, readValue, PropertyEventType::Read, False); + ErrCode errCode = OPENDAQ_SUCCESS; + const auto name = prop.getName(); + auto& events = valueWrite ? valueWriteEvents : valueReadEvents; - if (!localProperties.count(prop.getName())) + // Write tiers are guarded so a throwing handler surfaces as an error instead of unwinding + // past the caller's update-stack bookkeeping; all tiers still fire, the first error wins. + const auto fire = [&](const PropertyValueEventEmitter& emitter) { - const PropertyValueEventEmitter propEvent{prop.asPtr().getClassOnPropertyValueRead()}; - if (propEvent.hasListeners()) - { - propEvent(objPtr, args); - } - } + if (!emitter.hasListeners()) + return; - const auto name = prop.getName(); - if (valueReadEvents.find(name) != valueReadEvents.end()) - { - if (valueReadEvents[name].hasListeners()) + // Does it make sense to only protect the write events? + if (!valueWrite) { - valueReadEvents[name](objPtr, args); + emitter(objPtr, args); + return; } - } - if (valueReadEvents[AnyReadEventName].hasListeners()) + const ErrCode err = daqTry([&] { emitter(objPtr, args); }); + if (OPENDAQ_FAILED(err) && OPENDAQ_SUCCEEDED(errCode)) + errCode = err; + }; + + if (!localProperties.count(name)) { - valueReadEvents[AnyReadEventName](objPtr, args); + const auto propInternal = prop.asPtr(true); + const PropertyValueEventEmitter classEvent{valueWrite ? propInternal.getClassOnPropertyValueWrite() + : propInternal.getClassOnPropertyValueRead()}; + fire(classEvent); } - return args.getValue(); -} + if (const auto it = events.find(name); it != events.end()) + fire(it->second); -template -void GenericPropertyObjectImpl::coercePropertyWrite(const PropertyPtr& prop, - ObjectPtr& valuePtr, - const PropertyObjectPtr& objPtr) -{ - if (prop.assigned() && valuePtr.assigned()) - { - const auto coercer = prop.asPtr().getCoercerNoLock(); - if (coercer.assigned()) - { - try - { - valuePtr = coercer.coerceNoLock(objPtr, valuePtr); - } - catch (const DaqException&) - { - throw; - } - catch (...) - { - DAQ_THROW_EXCEPTION(CoerceFailedException); - } - } - } -} + fire(events[valueWrite ? AnyWriteEventName : AnyReadEventName]); -template -void GenericPropertyObjectImpl::validatePropertyWrite(const PropertyPtr& prop, - ObjectPtr& valuePtr, - const PropertyObjectPtr& objPtr) -{ - if (prop.assigned() && valuePtr.assigned()) - { - const auto validator = prop.asPtr().getValidatorNoLock(); - if (validator.assigned()) - { - try - { - validator.validateNoLock(objPtr, valuePtr); - } - catch (const DaqException&) - { - throw; - } - catch (...) - { - DAQ_THROW_EXCEPTION(ValidateFailedException); - } - } - } + return errCode; } -template -void GenericPropertyObjectImpl::coerceMinMax(const PropertyPtr& prop, ObjectPtr& valuePtr) +template +BaseObjectPtr GenericPropertyObjectImpl::callPropertyValueRead(const PropertyPtr& prop, + const BaseObjectPtr& readValue) { - if (!prop.assigned() || !valuePtr.assigned()) - return; - - const auto propInternal = prop.asPtr(); - const auto min = propInternal.getMinValueNoLock(); - if (min.assigned()) - { - try - { - if (valuePtr < min) - valuePtr = min; - } - catch (...) - { - } - } + if (!prop.assigned()) + return readValue; - const auto max = propInternal.getMaxValueNoLock(); - if (max.assigned()) - { - try - { - if (valuePtr > max) - valuePtr = max; - } - catch (...) - { - } - } + auto args = PropertyValueEventArgs(prop, readValue, readValue, PropertyEventType::Read, False); + firePropertyValueEvents(prop, args, false); + return args.getValue(); } template @@ -932,158 +801,6 @@ ErrCode GenericPropertyObjectImpl::setPropertyV return setPropertyValueInternal(propertyName, value, true, false, updateCount > 0); } -template -ErrCode GenericPropertyObjectImpl::checkContainerType(const PropertyPtr& prop, const BaseObjectPtr& value) -{ - if (!value.assigned()) - return OPENDAQ_SUCCESS; - - auto coreType = value.getCoreType(); - if (coreType == ctObject) - { - auto inspect = value.asPtrOrNull(true); - if (inspect.assigned() && !inspect.getInterfaceIds().empty()) - { - return inspect.getInterfaceIds()[0] == IPropertyObject::Id; - } - - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, "Only base Property Object object-type values are allowed"); - } - - auto iterate = [](const IterablePtr& it, CoreType type) - { - for (const auto& key : it) - { - if (key.getCoreType() != type) - return false; - } - return true; - }; - - const auto propInternal = prop.asPtr(true); - if (coreType == ctDict) - { - const auto dict = value.asPtr(); - const auto keyType = propInternal.getKeyTypeNoLock(); - const auto itemType = propInternal.getItemTypeNoLock(); - - IterablePtr it; - dict->getKeys(&it); - if (!iterate(it, keyType)) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Invalid dictionary key type for property "{}")", prop.getName())); - - dict->getValues(&it); - if (!iterate(it, itemType)) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Invalid dictionary item type for property "{}")", prop.getName())); - } - else if (coreType == ctList) - { - const auto itemType = propInternal.getItemTypeNoLock(); - - if (itemType != ctUndefined && !iterate(value, itemType)) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Invalid list item type for property "{}")", prop.getName())); - } - - return OPENDAQ_SUCCESS; -} - -template -ErrCode GenericPropertyObjectImpl::checkStructType(const PropertyPtr& prop, const BaseObjectPtr& value) -{ - if (prop.getValueType() != ctStruct) - return OPENDAQ_SUCCESS; - - auto structPtr = value.asPtrOrNull(); - if (!structPtr.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value is not a struct for property "{}")", prop.getName())); - - StructTypePtr structType = prop.asPtr().getStructTypeNoLock(); - StructTypePtr valueStructType = structPtr.getStructType(); - - if (structType != valueStructType) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value StructureType is different from the default for property "{}")", prop.getName())); - - return OPENDAQ_SUCCESS; -} - -template -ErrCode GenericPropertyObjectImpl::checkEnumerationType(const PropertyPtr& prop, - const BaseObjectPtr& value) -{ - const auto propInternal = prop.asPtr(); - if (propInternal.getValueTypeNoLock() != ctEnumeration) - return OPENDAQ_SUCCESS; - - auto enumerationPtr = value.asPtrOrNull(); - if (!enumerationPtr.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value is not an enumeration for property "{}")", prop.getName())); - - auto propEnumerationPtr = propInternal.getDefaultValueNoLock().asPtrOrNull(); - if (!propEnumerationPtr.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Property default value is not an enumeration for property "{}")", prop.getName())); - - EnumerationTypePtr valueEnumerationType = enumerationPtr.getEnumerationType(); - EnumerationTypePtr propEnumerationType = propEnumerationPtr.getEnumerationType(); - - if (propEnumerationType != valueEnumerationType) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, fmt::format(R"(Set value EnumerationType is different from the default for property "{}")", prop.getName())); - - return OPENDAQ_SUCCESS; -} - -template -ErrCode GenericPropertyObjectImpl::checkSelectionValues(const PropertyPtr& prop, - const BaseObjectPtr& value) -{ - const auto selectionValues = prop.asPtr(true).getSelectionValuesNoLock(); - if (selectionValues.assigned()) - { - const PropertyType propType = prop.getPropertyType(); - if (propType == PropertyType::IndexSelection) - { - if (const auto list = selectionValues.asPtrOrNull(true); list.assigned()) - { - const SizeT key = value; - if (key < list.getCount()) - return OPENDAQ_SUCCESS; - } - } - else if (propType == PropertyType::Selection) - { - if (const auto list = selectionValues.asPtrOrNull(true); list.assigned()) - { - if (prop.getValueType() == ctFloat) - { - const double valueDouble = value; - const double preScale = std::max({1.0, std::abs(valueDouble)}); - for (const double& item : list) - { - const double scale = std::max({preScale, std::abs(item)}); - if (std::abs(item - valueDouble) <= std::numeric_limits::epsilon() * scale) - return OPENDAQ_SUCCESS; - } - } - else - { - for (const auto& item : list) - { - if (item == value) - return OPENDAQ_SUCCESS; - } - } - } - } - else if (propType == PropertyType::SparseSelection) - { - if (const auto dict = selectionValues.asPtrOrNull(true); dict.assigned() && dict.hasKey(value)) - return OPENDAQ_SUCCESS; - } - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Value is not a key/index of selection values for property "{}")", prop.getName())); - } - - return OPENDAQ_SUCCESS; -} - template ErrCode GenericPropertyObjectImpl::setPropertyValueInternal(IString* name, IBaseObject* value, @@ -1103,205 +820,133 @@ ErrCode GenericPropertyObjectImpl::setPropertyV const ErrCode errCode = daqTry([&]() { - const auto isChildProp = isChildProperty(propName); + const auto isChildProp = details::isChildProperty(propName); if (batch && !isChildProp) { - updatingPropsAndValues.emplace_back(std::make_pair(propName, UpdatingAction{true, protectedAccess, valuePtr})); + batchedUpdates.emplace_back(std::make_pair(propName, UpdatingAction{true, protectedAccess, valuePtr})); return OPENDAQ_SUCCESS; } - StringPtr subName; if (isChildProp) { - splitOnFirstDot(propName, propName, subName); - } - - PropertyPtr prop = getUnboundProperty(propName); - prop = checkForRefPropAndGetBoundProp(prop, objPtr); - - if (!prop.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" not found.)", propName)); - - propName = prop.getName(); - - if (isChildProp) - { - BaseObjectPtr childProp; - const ErrCode err = getPropertyValueInternal(propName, &childProp); - OPENDAQ_RETURN_IF_FAILED(err); + PropertyObjectPtr parentObj; + StringPtr leafName; + OPENDAQ_RETURN_IF_FAILED(getParentObject(propName, parentObj, leafName)); if (protectedAccess) { - const auto childPropAsPropertyObject = childProp.template asPtr(true); - childPropAsPropertyObject.setProtectedPropertyValue(subName, valuePtr); + const auto parentObjProtected = parentObj.template asPtr(true); + parentObjProtected.setProtectedPropertyValue(leafName, valuePtr); } else - { - const auto childPropAsPropertyObject = childProp.template asPtr(true); - childPropAsPropertyObject.setPropertyValue(subName, valuePtr); - } + parentObj.setPropertyValue(leafName, valuePtr); return OPENDAQ_SUCCESS; } - const auto propInternal = prop.asPtr(); - // TODO: If function type, check if return value is correct type. - if (!protectedAccess) - { - if (propInternal.getReadOnlyNoLock() || propInternal.getValueTypeNoLock() == ctObject) - { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED, fmt::format(R"(Property "{}" is read only)", propName)); - } - } - - OPENDAQ_RETURN_IF_FAILED(checkPropertyTypeAndConvert(prop, valuePtr)); - OPENDAQ_RETURN_IF_FAILED(checkContainerType(prop, valuePtr)); - OPENDAQ_RETURN_IF_FAILED(checkSelectionValues(prop, valuePtr)); - OPENDAQ_RETURN_IF_FAILED(checkStructType(prop, valuePtr)); - OPENDAQ_RETURN_IF_FAILED(checkEnumerationType(prop, valuePtr)); - - coercePropertyWrite(prop, valuePtr, objPtr); - validatePropertyWrite(prop, valuePtr, objPtr); - coerceMinMax(prop, valuePtr); - - const auto ct = propInternal.getValueTypeNoLock(); - if (ct == ctList || ct == ctDict) - { - BaseObjectPtr clonedValue; - OPENDAQ_RETURN_IF_FAILED(valuePtr.asPtr()->clone(&clonedValue)); - - valuePtr = clonedValue.detach(); - } - else if (ct == ctObject) - { - configureClonedObj(propName, valuePtr); - } - - if (triggerEvent) - { - BaseObjectPtr newValue = valuePtr; - ErrCode err = callPropertyValueWrite(prop, newValue, PropertyEventType::Update, isUpdating); - OPENDAQ_RETURN_IF_FAILED(err); - - if (err == OPENDAQ_IGNORED) - return OPENDAQ_SUCCESS; - - if (valuePtr == newValue) - { - writeLocalValue(propName, newValue); - setOwnerToPropertyValue(newValue); - } - - if (!isUpdating) - triggerCoreEventInternal(CoreEventArgsPropertyValueChanged(objPtr, propName, newValue, path)); - } - else - { - if (!writeLocalValue(propName, valuePtr)) - return OPENDAQ_IGNORED; - setOwnerToPropertyValue(valuePtr); - } + PropertyPtr prop; + OPENDAQ_RETURN_IF_FAILED(bindProperty(propName, prop)); - return OPENDAQ_SUCCESS; + return checkAndSetPropertyValue(prop, propName, valuePtr, triggerEvent, protectedAccess, isUpdating); }); OPENDAQ_RETURN_IF_FAILED(errCode, fmt::format(R"(Failed to set property value "{}")", propName)); return errCode; } -template -ErrCode GenericPropertyObjectImpl::checkPropertyTypeAndConvert(const PropertyPtr& prop, - BaseObjectPtr& value) +template +ErrCode GenericPropertyObjectImpl::checkAndSetPropertyValue(const PropertyPtr& prop, + const StringPtr& propName, + BaseObjectPtr& valuePtr, + bool triggerEvent, + bool protectedAccess, + bool isUpdating) { - if (!prop.assigned() || !value.assigned()) - return OPENDAQ_SUCCESS; + const auto propInternal = prop.asPtr(); + // TODO: If function type, check if return value is correct type. + if (!protectedAccess) + { + if (propInternal.getReadOnlyNoLock() || propInternal.getValueTypeNoLock() == ctObject) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED, fmt::format(R"(Property "{}" is read only)", propName)); + } - if (value.supportsInterface()) - return OPENDAQ_SUCCESS; + OPENDAQ_RETURN_IF_FAILED(details::checkAndCoerceWrite(prop, valuePtr, objPtr)); - const ErrCode errCode = daqTry([&]() + if (propInternal.getValueTypeNoLock() == ctObject) + configureClonedObj(propName, valuePtr); + + if (triggerEvent) { - const auto propInternal = prop.asPtr(); - const auto propCoreType = propInternal.getValueTypeNoLock(); - const auto valueCoreType = value.getCoreType(); + BaseObjectPtr newValue = valuePtr; + ErrCode err = callPropertyValueWrite(prop, newValue, PropertyEventType::Update, isUpdating); + OPENDAQ_RETURN_IF_FAILED(err); - if (propCoreType != valueCoreType) + if (err == OPENDAQ_IGNORED) + return OPENDAQ_SUCCESS; + + if (valuePtr == newValue) { - if (propCoreType == ctEnumeration) - { - const auto enumVal = propInternal.getDefaultValueNoLock().asPtrOrNull(); - if (!enumVal.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDSTATE, - fmt::format(R"(Default value of enumeration property {} is not assigned)", prop.getName())); - - const auto type = enumVal.getEnumerationType(); - const Int intVal = value.convertTo(ctInt); - value = EnumerationWithIntValueAndType(type, intVal); - } - else - value = value.convertTo(propCoreType); + writeLocalValue(propName, newValue); + setOwnerToPropertyValue(newValue); } - return OPENDAQ_SUCCESS; - }); - OPENDAQ_RETURN_IF_FAILED(errCode, fmt::format(R"(Value type is different than Property type and conversion failed for property "{}")", prop.getName())); - return errCode; + if (!isUpdating) + triggerCoreEventInternal(CoreEventArgsPropertyValueChanged(objPtr, propName, newValue, path)); + } + else + { + if (!writeLocalValue(propName, valuePtr)) + return OPENDAQ_IGNORED; + setOwnerToPropertyValue(valuePtr); + } + + return OPENDAQ_SUCCESS; } + template -bool GenericPropertyObjectImpl::shouldWriteLocalValue(const StringPtr& name, const BaseObjectPtr& value) const +bool GenericPropertyObjectImpl::differsFromDefaultValue(const StringPtr& name, const BaseObjectPtr& value) const { - auto it = propValues.find(name); - if (it != propValues.end()) + try { - return it->second != value; + return objPtr.getProperty(name).template asPtr().getDefaultValueNoLock() != value; } - else + catch (...) { - try - { - return objPtr.getProperty(name).template asPtr().getDefaultValueNoLock() != value; - } - catch(...) - { - } } return true; } +template +bool GenericPropertyObjectImpl::shouldWriteLocalValue(const StringPtr& name, const BaseObjectPtr& value) const +{ + const auto it = propValues.find(name); + if (it != propValues.end()) + return it->second != value; + + return differsFromDefaultValue(name, value); +} + template bool GenericPropertyObjectImpl::writeLocalValue(const StringPtr& name, const BaseObjectPtr& value, bool forceWrite) { - auto it = propValues.find(name); + const auto it = propValues.find(name); if (it != propValues.end()) { if (it->second == value) return false; it->second = value; + return true; } - else if (forceWrite) + + if (forceWrite || differsFromDefaultValue(name, value)) { propValues.emplace(name, value); - } - else - { - bool shouldWrite = true; - try - { - shouldWrite = objPtr.getProperty(name).template asPtr().getDefaultValueNoLock() != value; - } - catch (...) - { - } - - if (shouldWrite) - propValues.emplace(name, value); - else - return false; + return true; } - return true; + return false; } template @@ -1322,21 +967,6 @@ void GenericPropertyObjectImpl::setOwnerToPrope } } -template -PropertyPtr GenericPropertyObjectImpl::getUnboundProperty(const StringPtr& name) -{ - const auto res = localProperties.find(name); - if (res == localProperties.end()) - { - if (objectClass == nullptr) - DAQ_THROW_EXCEPTION(NotFoundException, R"(Property with name {} does not exist.)", name); - - return objectClass.getProperty(name); - } - - return res->second; -} - template PropertyPtr GenericPropertyObjectImpl::getUnboundPropertyOrNull(const StringPtr& name) const { @@ -1359,36 +989,6 @@ PropertyPtr GenericPropertyObjectImpl::getUnbou return property; } -template -PropertyPtr GenericPropertyObjectImpl::checkForRefPropAndGetBoundProp(PropertyPtr& prop, - const PropertyObjectPtr& objPtr, - bool* isReferenced) -{ - if (!prop.assigned()) - { - return prop; - } - - PropertyInternalPtr boundProp = prop.asPtr(true).cloneWithOwner(objPtr); - auto refProp = boundProp.getReferencedPropertyNoLock(); - if (refProp.assigned()) - { - CoreType ct = refProp.getCoreType(); - - if (ct != ctObject) - throw std::invalid_argument("Invalid reference to property"); - - if (isReferenced) - *isReferenced = true; - - return checkForRefPropAndGetBoundProp(refProp, objPtr); - } - - if (isReferenced) - *isReferenced = false; - return boundProp; -} - template PropertyObjectPtr GenericPropertyObjectImpl::cloneChildPropertyObject(const PropertyPtr& prop) { @@ -1441,27 +1041,10 @@ void GenericPropertyObjectImpl::configureCloned } } -template -bool GenericPropertyObjectImpl::hasDuplicateReferences(const PropertyPtr& prop, const PropertyObjectPtr& objPtr) -{ - auto refEval = prop.asPtr().getReferencedPropertyUnresolved(); - if (refEval.assigned()) - { - auto refNames = refEval.getPropertyReferences(); - for (auto refPropName : refNames) - { - if (objPtr.hasProperty(refPropName) && objPtr.getProperty(refPropName).getIsReferenced()) - return true; - } - } - - return false; -} - template ErrCode GenericPropertyObjectImpl::readLocalValue(const StringPtr& name, BaseObjectPtr& value) const { - PropertyNameInfo info = getPropertyNameInfo(name); + details::PropertyNameInfo info = details::getPropertyNameInfo(name); const auto it = propValues.find(info.name); if (it != propValues.cend()) @@ -1469,186 +1052,109 @@ ErrCode GenericPropertyObjectImpl::readLocalVal if (info.index != -1) { if (it->second.getCoreType() != ctList) - { return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPARAMETER, fmt::format(R"(Could not access the index as the value is not a list for property "{}")", name)); - } ListPtr list = it->second; if (info.index >= (int) list.getCount()) - { return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_OUTOFRANGE, fmt::format(R"(The index parameter is out of bounds of the list for property "{}")", name)); - } value = list[std::size_t(info.index)]; } else - { value = it->second; - } return OPENDAQ_SUCCESS; } return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property value "{}" not found)", name)); } -template -int GenericPropertyObjectImpl::parseIndex(char const* lBracket) -{ - auto last = strchr(lBracket, ']'); - if (last != nullptr) - { - char* end; - int index = strtol(lBracket + 1, &end, 10); - - if (end != last) - { - DAQ_THROW_EXCEPTION(InvalidParameterException, "Could not parse the property index."); - } - - return index; - } - DAQ_THROW_EXCEPTION(InvalidParameterException, "No matching ] found."); -} - #if defined(__GNUC__) && __GNUC__ >= 12 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdangling-pointer" #endif -template -PropertyNameInfo GenericPropertyObjectImpl::getPropertyNameInfo(const StringPtr& name) -{ - PropertyNameInfo nameInfo; - - auto propNameData = name.getCharPtr(); - auto first = strchr(propNameData, '['); - if (first != nullptr) - { - nameInfo.index = parseIndex(first); - nameInfo.name = String(propNameData, first - propNameData); - } - else - { - nameInfo.index = -1; - nameInfo.name = name; - } - - return nameInfo; -} - -template -ConstCharPtr GenericPropertyObjectImpl::getPropNameWithoutIndex(const StringPtr& name, - StringPtr& propName) +template +void GenericPropertyObjectImpl::triggerCoreEventInternal(const CoreEventArgsPtr& args) { - auto propNameData = name.getCharPtr(); - auto first = strchr(propNameData, '['); - - if (first == nullptr) - { - propName = String(propNameData); - } - else - { - propName = String(propNameData, first - propNameData); - } - return first; + if (!coreEventMuted && triggerCoreEvent.assigned()) + triggerCoreEvent(args); } -template -void GenericPropertyObjectImpl::triggerCoreEventInternal(const CoreEventArgsPtr& args) +template +ErrCode GenericPropertyObjectImpl::bindProperty(StringPtr& propName, PropertyPtr& prop) { - if (!coreEventMuted && triggerCoreEvent.assigned()) - triggerCoreEvent(args); + prop = getUnboundPropertyOrNull(propName); + prop = details::checkForRefPropAndGetBoundProp(prop, objPtr); + + if (!prop.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", propName)); + + propName = prop.getName(); + return OPENDAQ_SUCCESS; } template -ErrCode GenericPropertyObjectImpl::getPropertyAndValueInternal(const StringPtr& name, - BaseObjectPtr& value, - PropertyPtr& property, - bool triggerEvent, - bool retrieveUpdatingValue) +ErrCode GenericPropertyObjectImpl::getBoundPropertyInternal(const StringPtr& name, + PropertyPtr& property, + StringPtr& resolvedName, + ConstCharPtr& bracket) { StringPtr propName; - ConstCharPtr bracket = getPropNameWithoutIndex(name, propName); + bracket = details::getPropNameWithoutIndex(name, propName); property = getUnboundPropertyOrNull(propName); if (!property.assigned()) - { return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", propName)); - } bool isRef; - property = checkForRefPropAndGetBoundProp(property, objPtr, &isRef); - - // TODO: Extract this to own function - if (bracket != nullptr) - { - if (isRef) - { - propName = property.getName() + std::string(bracket); - } - else - { - propName = name; - } - } - else if (isRef) - { - propName = property.getName(); - } + property = details::checkForRefPropAndGetBoundProp(property, objPtr, &isRef); + resolvedName = details::buildResolvedPropertyName(propName, name, property, isRef, bracket); + return OPENDAQ_SUCCESS; +} +template +ErrCode GenericPropertyObjectImpl::readPropertyValueInternal(const PropertyPtr& property, + const StringPtr& resolvedName, + ConstCharPtr bracket, + bool retrieveUpdatingValue, + BaseObjectPtr& value) +{ ErrCode res = OPENDAQ_SUCCESS; - if (retrieveUpdatingValue && updatePropertyStack.getPropertyValue(propName, value)) + if (retrieveUpdatingValue && updatePropertyStack.getPropertyValue(resolvedName, value)) { if (!value.assigned()) value = property.getDefaultValue(); } else - { - res = readLocalValue(propName, value); - } + res = readLocalValue(resolvedName, value); OPENDAQ_RETURN_IF_FAILED_EXCEPT(res, OPENDAQ_ERR_NOTFOUND); if (res == OPENDAQ_ERR_NOTFOUND) { daqClearErrorInfo(); - const auto propInternal = property.asPtr(); - res = propInternal->getDefaultValueNoLock(&value); - - if (OPENDAQ_FAILED(res)) - daqClearErrorInfo(); + OPENDAQ_RETURN_IF_FAILED(details::readDefaultPropertyValue(property, resolvedName, bracket, value)); if (!value.assigned()) return OPENDAQ_SUCCESS; - - CoreType coreType = value.getCoreType(); - if (coreType == ctList && bracket != nullptr) - { - int index = parseIndex(bracket); - ListPtr list = value; - if (index >= static_cast(list.getCount())) - { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_OUTOFRANGE, fmt::format(R"(The index parameter is out of bounds of the list for property "{}")", propName)); - } - value = list[std::size_t(index)]; - } - } - - CoreType coreType = value.getCoreType(); - if (coreType == ctList || coreType == ctDict) - { - BaseObjectPtr clonedValue; - value.asPtr()->clone(&clonedValue); - value = clonedValue.detach(); } - if (triggerEvent) - value = callPropertyValueRead(property, value); - + value = details::cloneIfContainerValue(value); return OPENDAQ_SUCCESS; } +template +ErrCode GenericPropertyObjectImpl::readPropertyValueInternal(const StringPtr& name, + bool retrieveUpdatingValue, + PropertyPtr& property, + BaseObjectPtr& value) +{ + StringPtr resolvedName; + ConstCharPtr bracket; + OPENDAQ_RETURN_IF_FAILED(getBoundPropertyInternal(name, property, resolvedName, bracket)); + return readPropertyValueInternal(property, resolvedName, bracket, retrieveUpdatingValue, value); +} + #if defined(__GNUC__) && __GNUC__ >= 12 #pragma GCC diagnostic pop #endif @@ -1707,95 +1213,38 @@ ErrCode GenericPropertyObjectImpl::setPropertyS const auto propName = StringPtr::Borrow(propertyName); const auto valuePtr = BaseObjectPtr::Borrow(value); - if (isChildProperty(propName)) + if (details::isChildProperty(propName)) { - StringPtr childName; - StringPtr subName; - splitOnFirstDot(propName, childName, subName); - - BaseObjectPtr childProp; - const ErrCode err = getPropertyValueInternal(childName, &childProp); - OPENDAQ_RETURN_IF_FAILED(err); + PropertyObjectPtr parentObj; + StringPtr leafName; + OPENDAQ_RETURN_IF_FAILED(getParentObject(propName, parentObj, leafName)); if (protectedAccess) { - const auto childPropAsPropertyObject = childProp.template asPtr(true); - return childPropAsPropertyObject->setProtectedPropertySelectionValue(subName, value); + const auto parentObjProtected = parentObj.template asPtr(true); + return parentObjProtected->setProtectedPropertySelectionValue(leafName, value); } else - { - const auto childPropAsPropertyObject = childProp.template asPtr(true); - return childPropAsPropertyObject->setPropertySelectionValue(subName, value); - } + return parentObj->setPropertySelectionValue(leafName, value); } - PropertyPtr prop = getUnboundProperty(propName); - prop = checkForRefPropAndGetBoundProp(prop, objPtr); + StringPtr boundName = propName; + PropertyPtr prop; + OPENDAQ_RETURN_IF_FAILED(bindProperty(boundName, prop)); - if (!prop.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" not found)", propName)); - - const auto propInternal = prop.asPtr(true); - const auto selectionValues = propInternal.getSelectionValuesNoLock(); BaseObjectPtr indexOrKey; - PropertyType propType = prop.getPropertyType(); - - if (propType == PropertyType::IndexSelection) - { - if (!selectionValues.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, - fmt::format(R"(Index selection property "{}" has no selection values assigned)", propName)); - - const auto valuesList = selectionValues.template asPtrOrNull(true); - if (!valuesList.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, - fmt::format(R"(Index selection property "{}" values is not a list)", propName)); - - for (SizeT i = 0; i < valuesList.getCount(); ++i) - { - if (valuesList.getItemAt(i) == valuePtr) - { - indexOrKey = Int(i); - break; - } - } - - if (!indexOrKey.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Value not found in selection values of property "{}")", propName)); - } - else if (propType == PropertyType::SparseSelection) - { - if (!selectionValues.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, - fmt::format(R"(Sparse selection property "{}" has no selection values assigned)", propName)); + OPENDAQ_RETURN_IF_FAILED(details::selectionValueToKey(prop, valuePtr, indexOrKey)); - const auto valuesDict = selectionValues.template asPtrOrNull(true); - if (!valuesDict.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, - fmt::format(R"(Sparse selection property "{}" values is not a dictionary)", propName)); - - for (const auto& [key, value] : valuesDict) - { - if (value == valuePtr) - { - indexOrKey = key; - break; - } - } + if (frozen) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_FROZEN); - if (!indexOrKey.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Value not found in sparse selection values of property "{}")", propName)); - } - else if (propType == PropertyType::Selection) - { - indexOrKey = valuePtr; - } - else + if (updateCount > 0) { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, fmt::format(R"(Property "{}" is not an index selection or sparse selection property)", propName)); + batchedUpdates.emplace_back(std::make_pair(propName, UpdatingAction{true, protectedAccess, indexOrKey})); + return OPENDAQ_SUCCESS; } - return setPropertyValueInternal(propertyName, indexOrKey, true, protectedAccess, updateCount > 0); + return checkAndSetPropertyValue(prop, boundName, indexOrKey, true, protectedAccess, false); }); OPENDAQ_RETURN_IF_FAILED(errCode, "Failed to set property selection value"); return errCode; @@ -1810,40 +1259,17 @@ ErrCode GenericPropertyObjectImpl::clearProtect template void GenericPropertyObjectImpl::configureClonedMembers(const CloneParameters& parameters) -{ - configureClonedMembers(parameters.valueWriteEvents, - parameters.valueReadEvents, - parameters.endUpdateEvent, - parameters.triggerCoreEvent, - parameters.localProperties, - parameters.propValues, - parameters.customOrder, - parameters.permissionManager, - parameters.corePropertyNames); -} - -template -void GenericPropertyObjectImpl::configureClonedMembers( - const std::unordered_map& valueWriteEvents, - const std::unordered_map& valueReadEvents, - const EndUpdateEventEmitter& endUpdateEvent, - const ProcedurePtr& triggerCoreEvent, - const PropertyOrderedMap& localProperties, - const std::unordered_map& propValues, - const std::vector& customOrder, - const PermissionManagerPtr& permissionManager, - const std::set& corePropertyNames) { this->valueWriteEvents.clear(); - for (const auto& [name, srcEmitter] : valueWriteEvents) + for (const auto& [name, srcEmitter] : parameters.valueWriteEvents) { BaseObjectPtr cloned; srcEmitter.template asPtr(true)->clone(&cloned); this->valueWriteEvents.emplace(name, cloned); } - + this->valueReadEvents.clear(); - for (const auto& [name, srcEmitter] : valueReadEvents) + for (const auto& [name, srcEmitter] : parameters.valueReadEvents) { BaseObjectPtr cloned; srcEmitter.template asPtr(true)->clone(&cloned); @@ -1851,19 +1277,19 @@ void GenericPropertyObjectImpl::configureCloned } BaseObjectPtr cloned; - endUpdateEvent.template asPtr(true)->clone(&cloned); + parameters.endUpdateEvent.template asPtr(true)->clone(&cloned); this->endUpdateEvent = cloned; - this->triggerCoreEvent = triggerCoreEvent; - this->localProperties = localProperties; - this->customOrder = customOrder; - this->corePropertyNames = corePropertyNames; + this->triggerCoreEvent = parameters.triggerCoreEvent; + this->localProperties = parameters.localProperties; + this->customOrder = parameters.customOrder; + this->corePropertyNames = parameters.corePropertyNames; BaseObjectPtr permissionManagerClone; - permissionManager.template asPtr()->clone(&permissionManagerClone); + parameters.permissionManager.template asPtr()->clone(&permissionManagerClone); this->permissionManager = permissionManagerClone; - for (const auto& val : propValues) + for (const auto& val : parameters.propValues) { const auto& propName = val.first; const auto& prop = val.second; @@ -1901,32 +1327,28 @@ void GenericPropertyObjectImpl::configureCloned } } else - { this->propValues.insert(val); - } } } +// The non-"2" lock accessors are the legacy generation, kept for derived classes still on std lock types; +// each is defined in terms of its "2" counterpart or the shared local mutex. template std::unique_ptr GenericPropertyObjectImpl::getRecursiveConfigLock() { - LockGuardPtr lockGuard; - checkErrorInfo(getRecursiveLockGuard(&lockGuard)); - return std::make_unique(lockGuard); + return std::make_unique(getRecursiveConfigLock2()); } template std::lock_guard GenericPropertyObjectImpl::getAcquisitionLock() { - std::mutex* mutexPtr = getLocalMutex(); - return std::lock_guard(*mutexPtr); + return std::lock_guard(*getLocalMutex()); } template std::unique_lock GenericPropertyObjectImpl::getUniqueLock() { - std::mutex* mutexPtr = getLocalMutex(); - return std::unique_lock(*mutexPtr); + return std::unique_lock(*getLocalMutex()); } template @@ -1999,9 +1421,9 @@ ErrCode GenericPropertyObjectImpl::clearPropert if (prop.getValueType() == ctObject) { - PropertyPtr propPtr; + // `prop` is already bound and cannot be a reference here; read the value directly BaseObjectPtr valuePtr; - ErrCode err = getPropertyAndValueInternal(prop.getName(), valuePtr, propPtr, false); + const ErrCode err = readPropertyValueInternal(prop, prop.getName(), nullptr, false, valuePtr); OPENDAQ_RETURN_IF_FAILED(err); if (const auto freezable = valuePtr.asPtrOrNull(true); freezable.assigned() && freezable.isFrozen()) @@ -2046,57 +1468,42 @@ ErrCode GenericPropertyObjectImpl::clearPropert const ErrCode errCode = daqTry([&]() { auto propName = StringPtr::Borrow(name); + const auto isChildProp = details::isChildProperty(propName); - if (batch) + if (batch && !isChildProp) { - updatingPropsAndValues.emplace_back(std::make_pair(propName, UpdatingAction{false, protectedAccess, nullptr})); + batchedUpdates.emplace_back(std::make_pair(propName, UpdatingAction{false, protectedAccess, nullptr})); return OPENDAQ_SUCCESS; } - StringPtr subName; - const auto isChildProp = isChildProperty(propName); if (isChildProp) { - splitOnFirstDot(propName, propName, subName); - } + PropertyObjectPtr parentObj; + StringPtr leafName; + OPENDAQ_RETURN_IF_FAILED(getParentObject(propName, parentObj, leafName)); - PropertyPtr prop = getUnboundPropertyOrNull(propName); - prop = checkForRefPropAndGetBoundProp(prop, objPtr); + if (protectedAccess) + { + const auto parentObjProtected = parentObj.template asPtr(true); + parentObjProtected.clearProtectedPropertyValue(leafName); + } + else + parentObj.clearPropertyValue(leafName); - if (!prop.assigned()) - { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", propName)); + return OPENDAQ_SUCCESS; } - propName = prop.getName(); + PropertyPtr prop; + OPENDAQ_RETURN_IF_FAILED(bindProperty(propName, prop)); + const auto propInternal = prop.asPtr(); if (!protectedAccess) { - if (propInternal.getReadOnlyNoLock() && !isChildProp) - { + if (propInternal.getReadOnlyNoLock()) return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED, fmt::format(R"(Property "{}" is read only)", propName)); - } } - if (isChildProp) - { - BaseObjectPtr childProp; - const ErrCode err = getPropertyValueInternal(propName, &childProp); - OPENDAQ_RETURN_IF_FAILED(err); - - if (protectedAccess) - { - const auto childPropAsPropertyObject = childProp.template asPtr(true); - childPropAsPropertyObject.clearProtectedPropertyValue(subName); - } - else - { - const auto childPropAsPropertyObject = childProp.template asPtr(true); - childPropAsPropertyObject.clearPropertyValue(subName); - } - } - else { if (propValues.find(prop.getName()) == propValues.end()) return OPENDAQ_IGNORED; @@ -2110,24 +1517,18 @@ ErrCode GenericPropertyObjectImpl::clearPropert freezable.assigned() && freezable.isFrozen()) return OPENDAQ_IGNORED; + // Delegate to the nested object so the semantics (skipping read-only and + // reference properties) match clearPropertyValuesInternal if (protectedAccess) { - auto objProtected = it->second.template asPtr(true); - auto obj = it->second.template asPtr(true); - for (const auto& childProp: obj.getAllProperties()) - { - objProtected.clearProtectedPropertyValue(childProp.getName()); - } + const auto nested = it->second.template asPtr(true); + OPENDAQ_RETURN_IF_FAILED(nested->clearProtectedPropertyValues()); } else { - auto obj = it->second.template asPtr(true); - for (const auto& childProp: obj.getAllProperties()) - { - obj.clearPropertyValue(childProp.getName()); - } + const auto nested = it->second.template asPtr(true); + OPENDAQ_RETURN_IF_FAILED(nested->clearPropertyValues()); } - } } else @@ -2168,16 +1569,21 @@ ErrCode GenericPropertyObjectImpl::getPropertyV BaseObjectPtr valuePtr; ErrCode err; - if (isChildProperty(propName)) + if (details::isChildProperty(propName)) { - StringPtr subName; - splitOnFirstDot(propName, propName, subName); - err = getChildPropertyValue(propName, subName, valuePtr); + PropertyObjectPtr parentObj; + StringPtr leafName; + err = getParentObject(propName, parentObj, leafName); + OPENDAQ_RETURN_IF_FAILED(err); + err = parentObj->getPropertyValue(leafName, &valuePtr); } else { PropertyPtr prop; - err = getPropertyAndValueInternal(propName, valuePtr, prop, true, retrieveUpdatingValue); + err = readPropertyValueInternal(propName, retrieveUpdatingValue, prop, valuePtr); + OPENDAQ_RETURN_IF_FAILED(err); + if (valuePtr.assigned()) + valuePtr = callPropertyValueRead(prop, valuePtr); } OPENDAQ_RETURN_IF_FAILED(err); @@ -2203,56 +1609,21 @@ ErrCode GenericPropertyObjectImpl::getPropertyS BaseObjectPtr valuePtr; PropertyPtr prop; - if (isChildProperty(propName)) + if (details::isChildProperty(propName)) { - const ErrCode errCode = getProperty(propName, &prop); - OPENDAQ_RETURN_IF_FAILED(errCode, OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Selection property "{}" not found)", propName)); + const ErrCode err = getProperty(propName, &prop); + OPENDAQ_RETURN_IF_FAILED(err, OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Selection property "{}" not found)", propName)); valuePtr = prop.getValue(); } else { - const ErrCode errCode = getPropertyAndValueInternal(propName, valuePtr, prop, true, retrieveUpdatingValue); - OPENDAQ_RETURN_IF_FAILED(errCode, OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Selection property "{}" not found)", propName)); - } - - const auto propInternal = prop.asPtr(true); - auto values = propInternal.getSelectionValuesNoLock(); - if (!values.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, fmt::format(R"(Selection property "{}" has no selection values assigned)", propName)); - - const PropertyType propType = prop.getPropertyType(); - if (propType == PropertyType::IndexSelection) - { - const auto valuesList = values.asPtrOrNull(true); - if (!valuesList.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, - fmt::format(R"(Index selection property "{}" values is not a list)", propName)); - valuePtr = valuesList.getItemAt(valuePtr); - } - else if (propType == PropertyType::SparseSelection) - { - const auto valuesDict = values.asPtrOrNull(true); - if (!valuesDict.assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, - fmt::format(R"(Sparse selection property "{}" values is not a dictionary)", propName)); - valuePtr = valuesDict.get(valuePtr); - } - else if (propType == PropertyType::Selection) - { - if (propInternal.getValueTypeNoLock() != valuePtr.getCoreType()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(Selection item type mismatch for property "{}")", propName)); - - *value = valuePtr.detach(); - return OPENDAQ_SUCCESS; - } - else - { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPROPERTY, - fmt::format(R"(Property "{}" is not an index selection or sparse selection property)", propName)); + const ErrCode err = readPropertyValueInternal(propName, retrieveUpdatingValue, prop, valuePtr); + OPENDAQ_RETURN_IF_FAILED(err, OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Selection property "{}" not found)", propName)); + if (valuePtr.assigned()) + valuePtr = callPropertyValueRead(prop, valuePtr); } - if (propInternal.getItemTypeNoLock() != valuePtr.getCoreType()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDTYPE, fmt::format(R"(List item type mismatch for property "{}")", propName)); + OPENDAQ_RETURN_IF_FAILED(details::selectionKeyToValue(prop, valuePtr)); *value = valuePtr.detach(); return OPENDAQ_SUCCESS; @@ -2272,28 +1643,25 @@ ErrCode GenericPropertyObjectImpl::getProperty( StringPtr propName = propertyName; PropertyPtr prop; - if (isChildProperty(propName)) + if (details::isChildProperty(propName)) { - StringPtr subName; - BaseObjectPtr childProp; - - splitOnFirstDot(propName, propName, subName); - const ErrCode err = getPropertyValueInternal(propName, &childProp); - OPENDAQ_RETURN_IF_FAILED(err); + PropertyObjectPtr parentObj; + StringPtr leafName; + OPENDAQ_RETURN_IF_FAILED(getParentObject(propName, parentObj, leafName)); - const auto childPropAsPropertyObject = childProp.template asPtr(true); - prop = childPropAsPropertyObject.getProperty(subName); + prop = parentObj.getProperty(leafName); } else { - prop = getUnboundProperty(propName); + prop = getUnboundPropertyOrNull(propName); + if (!prop.assigned()) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", propName)); + prop = prop.asPtr().cloneWithOwner(objPtr); } if (const auto freezable = prop.template asPtrOrNull(true); freezable.assigned()) - { OPENDAQ_RETURN_IF_FAILED(freezable->freeze()); - } *property = prop.detach(); return OPENDAQ_SUCCESS; @@ -2321,9 +1689,11 @@ ErrCode GenericPropertyObjectImpl::addPropertyI if (!propName.assigned()) return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDVALUE, fmt::format(R"(Property "{}" does not have an assigned name.)", propName)); - if (hasDuplicateReferences(propPtr, objPtr)) + if (details::hasDuplicateReferences(propPtr, objPtr)) + { return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDVALUE, fmt::format(R"(Reference property "{}" references a property that is already referenced by another.)", propName)); + } propPtr.asPtr(true).setOwner(objPtr); @@ -2399,23 +1769,17 @@ ErrCode GenericPropertyObjectImpl::removeProper OPENDAQ_PARAM_NOT_NULL(propertyName); if (frozen) - { return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_FROZEN); - } auto lock = getRecursiveConfigLock2(); auto namePtr = StringPtr::Borrow(propertyName); if (localProperties.find(propertyName) == localProperties.cend()) - { return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", namePtr)); - } localProperties.erase(propertyName); if (propValues.find(propertyName) != propValues.cend()) - { propValues.erase(propertyName); - } if (auto it = corePropertyNames.find(namePtr); it != corePropertyNames.end()) corePropertyNames.erase(it); @@ -2472,6 +1836,18 @@ ErrCode GenericPropertyObjectImpl::getPropertie if (!includeInvisible && !bind) return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDPARAMETER); + const std::vector allProperties = collectAllProperties(includeCoreProperties); + + PropertyOrderedMap lookup; + OPENDAQ_RETURN_IF_FAILED(bindAndFilterProperties(allProperties, includeInvisible, bind, lookup)); + + *list = applyCustomPropertyOrder(lookup).detach(); + return OPENDAQ_SUCCESS; +} + +template +std::vector GenericPropertyObjectImpl::collectAllProperties(Bool includeCoreProperties) const +{ std::vector allProperties; if (objectClass.assigned()) { @@ -2481,9 +1857,7 @@ ErrCode GenericPropertyObjectImpl::getPropertie allProperties.push_back(prop); } else - { allProperties.reserve(localProperties.size()); - } for (const auto& [propName, prop] : localProperties) { @@ -2491,8 +1865,16 @@ ErrCode GenericPropertyObjectImpl::getPropertie allProperties.push_back(prop); } - PropertyOrderedMap lookup; - for (auto& prop : allProperties) + return allProperties; +} + +template +ErrCode GenericPropertyObjectImpl::bindAndFilterProperties(const std::vector& allProperties, + Bool includeInvisible, + Bool bind, + PropertyOrderedMap& lookup) +{ + for (const auto& prop : allProperties) { if (!bind) { @@ -2502,22 +1884,16 @@ ErrCode GenericPropertyObjectImpl::getPropertie auto boundProp = prop.asPtr(true).cloneWithOwner(objPtr); if (!includeInvisible && boundProp.getIsReferenced()) - { continue; - } try { if (!includeInvisible && !boundProp.getVisible()) - { continue; - } auto freezable = boundProp.template asPtrOrNull(true); if (freezable.assigned()) - { freezable.freeze(); - } lookup.insert_or_assign(boundProp.getName(), boundProp); } @@ -2533,116 +1909,79 @@ ErrCode GenericPropertyObjectImpl::getPropertie } } + return OPENDAQ_SUCCESS; +} + +template +ListPtr GenericPropertyObjectImpl::applyCustomPropertyOrder(PropertyOrderedMap& lookup) const +{ auto properties = List(); - if (!customOrder.empty()) - { - // Add properties with explicit order - for (auto& propName : customOrder) - { - const auto iter = lookup.find(propName); - if (iter != lookup.cend()) - { - properties.unsafePushBack(iter->second); - lookup.erase(iter); - } - } - // Add the rest of without set order - for (auto& prop : lookup) + for (const auto& propName : customOrder) + { + const auto iter = lookup.find(propName); + if (iter != lookup.cend()) { - properties.unsafePushBack(prop.second); + properties.unsafePushBack(iter->second); + lookup.erase(iter); } } - else + + for (const auto& prop : lookup) { - for (auto& prop : lookup) - { - properties.unsafePushBack(prop.second); - } + properties.unsafePushBack(prop.second); } - *list = properties.detach(); - return OPENDAQ_SUCCESS; + return properties; } template -ErrCode GenericPropertyObjectImpl::getOnPropertyValueWrite(IString* propertyName, IEvent** event) +ErrCode GenericPropertyObjectImpl::getPropertyValueEventInternal(IString* propertyName, IEvent** event, bool valueWrite) { OPENDAQ_PARAM_NOT_NULL(propertyName); OPENDAQ_PARAM_NOT_NULL(event); - StringPtr name = StringPtr::Borrow( propertyName); + StringPtr name = StringPtr::Borrow(propertyName); - if (isChildProperty(name)) + if (details::isChildProperty(name)) { - StringPtr subName; - splitOnFirstDot(name, name, subName); - - BaseObjectPtr childProp; - ErrCode errCode = getPropertyValueInternal(name, &childProp); + PropertyObjectPtr parentObj; + StringPtr leafName; + const ErrCode errCode = getParentObject(name, parentObj, leafName); OPENDAQ_RETURN_IF_FAILED(errCode); - const auto childPropAsPropertyObject = childProp.template asPtr(true); - errCode = childPropAsPropertyObject->getOnPropertyValueWrite(subName, event); - OPENDAQ_RETURN_IF_FAILED(errCode); - return errCode; + return valueWrite ? parentObj->getOnPropertyValueWrite(leafName, event) + : parentObj->getOnPropertyValueRead(leafName, event); } - Bool hasProp; - ErrCode err = this->hasProperty(name, &hasProp); - OPENDAQ_RETURN_IF_FAILED(err); - - if (!hasProp) + PropertyInternalPtr prop = getUnboundPropertyOrNull(name); + if (!prop.assigned()) return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", name)); - PropertyInternalPtr prop = getUnboundProperty(name); if (prop.getReferencedPropertyUnresolved().assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALID_OPERATION, fmt::format(R"(getOnPropertyValueWrite is not allowed for the reference properties "{}")", name)); + { + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALID_OPERATION, + fmt::format(R"({} is not allowed for the reference properties "{}")", + valueWrite ? "getOnPropertyValueWrite" : "getOnPropertyValueRead", + name)); + } - auto [it, _] = valueWriteEvents.try_emplace(name); + auto& events = valueWrite ? valueWriteEvents : valueReadEvents; + auto [it, _] = events.try_emplace(name); *event = it->second.addRefAndReturn(); return OPENDAQ_SUCCESS; } template -ErrCode GenericPropertyObjectImpl::getOnPropertyValueRead(IString* propertyName, IEvent** event) +ErrCode GenericPropertyObjectImpl::getOnPropertyValueWrite(IString* propertyName, IEvent** event) { - OPENDAQ_PARAM_NOT_NULL(propertyName); - OPENDAQ_PARAM_NOT_NULL(event); - - StringPtr name = StringPtr::Borrow(propertyName); - - if (isChildProperty(name)) - { - StringPtr subName; - splitOnFirstDot(name, name, subName); - - BaseObjectPtr childProp; - ErrCode errCode = getPropertyValueInternal(name, &childProp); - OPENDAQ_RETURN_IF_FAILED(errCode); - - const auto childPropAsPropertyObject = childProp.template asPtr(true); - errCode = childPropAsPropertyObject->getOnPropertyValueRead(subName, event); - OPENDAQ_RETURN_IF_FAILED(errCode); - return errCode; - } - - Bool hasProp; - ErrCode err = this->hasProperty(name, &hasProp); - OPENDAQ_RETURN_IF_FAILED(err); - - if (!hasProp) - { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Property "{}" does not exist)", name)); - } - - PropertyInternalPtr prop = getUnboundProperty(name); - if (prop.getReferencedPropertyUnresolved().assigned()) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALID_OPERATION, fmt::format(R"(getOnPropertyValueRead is not allowed for the reference properties "{}")", name)); + return getPropertyValueEventInternal(propertyName, event, true); +} - auto [it, _] = valueReadEvents.try_emplace(name); - *event = it->second.addRefAndReturn(); - return OPENDAQ_SUCCESS; +template +ErrCode GenericPropertyObjectImpl::getOnPropertyValueRead(IString* propertyName, IEvent** event) +{ + return getPropertyValueEventInternal(propertyName, event, false); } template @@ -2673,35 +2012,13 @@ ErrCode GenericPropertyObjectImpl::beginUpdate( template void GenericPropertyObjectImpl::callBeginUpdateOnChildren() { - for (const auto& [_, propValue] : propValues) - { - const auto propObj = propValue.template asPtrOrNull(true); - if (!propObj.assigned()) - continue; - - auto freezable = propObj.template asPtrOrNull(true); - if (freezable.assigned() && freezable.isFrozen()) - continue; - - propObj.beginUpdate(); - } + forEachUnfrozenChildObject([](const PropertyObjectPtr& propObj) { propObj.beginUpdate(); }); } template void GenericPropertyObjectImpl::callEndUpdateOnChildren() { - for (const auto& [_, propValue] : propValues) - { - const auto propObj = propValue.template asPtrOrNull(true); - if (!propObj.assigned()) - continue; - - auto freezable = propObj.template asPtrOrNull(true); - if (freezable.assigned() && freezable.isFrozen()) - continue; - - propObj.endUpdate(); - } + forEachUnfrozenChildObject([](const PropertyObjectPtr& propObj) { propObj.endUpdate(); }); } template @@ -2785,9 +2102,7 @@ template std::mutex* GenericPropertyObjectImpl::getLocalMutex() { if (this->lockingStrategy == LockingStrategy::InheritLock) - { DAQ_THROW_EXCEPTION(daq::InvalidStateException, "Can't acquire local mutex if locking strategy is set to inherit"); - } MutexImpl* mutexImpl = dynamic_cast(this->sync.getObject()); return &mutexImpl->mutex; @@ -2796,13 +2111,13 @@ std::mutex* GenericPropertyObjectImpl::getLocal template void GenericPropertyObjectImpl::beginApplyUpdate() { - beginApplyProperties(updatingPropsAndValues, isParentUpdating()); + beginApplyProperties(batchedUpdates, isParentUpdating()); } template void GenericPropertyObjectImpl::endApplyUpdate() { - UpdatingActions localUpdates = std::move(updatingPropsAndValues); + UpdatingActions localUpdates = std::move(batchedUpdates); UpdatingActions appliedUpdates; appliedUpdates.reserve(localUpdates.size()); @@ -2824,7 +2139,17 @@ void GenericPropertyObjectImpl::endApplyUpdate( if (err != OPENDAQ_IGNORED) { PropertyPtr prop; - getPropertyAndValueInternal(name, action.value, prop); + const ErrCode refreshErr = readPropertyValueInternal(name, false, prop, action.value); + if (OPENDAQ_FAILED(refreshErr)) + { + // Child-path names cannot be refreshed on this object; keep the queued value + daqClearErrorInfo(); + } + else if (action.value.assigned()) + { + // TODO: firing read events while applying updates is likely unintended; kept for behavior parity + action.value = callPropertyValueRead(prop, action.value); + } appliedUpdates.emplace_back(name, action); } } @@ -2920,25 +2245,6 @@ ErrCode GenericPropertyObjectImpl::findProperti return errCode; } -template -Bool GenericPropertyObjectImpl::checkIsReferenced(const StringPtr& referencedPropName, - const PropertyInternalPtr& prop) -{ - const auto refProp = prop.getReferencedPropertyUnresolved(); - if (!refProp.assigned()) - return false; - - for (auto propName : refProp.getPropertyReferences()) - { - if (propName == referencedPropName) - { - return true; - } - } - - return false; -} - template ErrCode GenericPropertyObjectImpl::checkForReferencesInternal(IProperty* property, Bool* isReferenced) { @@ -2954,14 +2260,14 @@ ErrCode GenericPropertyObjectImpl::checkForRefe { for (const auto& prop : objectClass.getProperties(True)) { - if (*isReferenced = checkIsReferenced(name, prop); *isReferenced) + if (*isReferenced = details::checkIsReferenced(name, prop); *isReferenced) return OPENDAQ_SUCCESS; } } for (const auto& prop : localProperties) { - if (*isReferenced = checkIsReferenced(name, prop.second); *isReferenced) + if (*isReferenced = details::checkIsReferenced(name, prop.second); *isReferenced) return OPENDAQ_SUCCESS; } return OPENDAQ_SUCCESS; @@ -3016,24 +2322,6 @@ ErrCode GenericPropertyObjectImpl::disableCoreE } } - for (const auto& item : localProperties) - { - if (item.second.assigned()) - { - const auto propInternal = item.second.template asPtr(); - if (propInternal.getValueTypeUnresolved() == ctObject) - { - const auto defaultVal = item.second.getDefaultValue(); - if (defaultVal.assigned()) - { - const auto objInternal = defaultVal.template asPtrOrNull(); - if (objInternal.assigned()) - objInternal.disableCoreEventTrigger(); - } - } - } - } - return OPENDAQ_SUCCESS; } @@ -3066,15 +2354,7 @@ ErrCode GenericPropertyObjectImpl::clone(IPrope const ErrCode errCode = daqTry([this, &obj, &cloned]() { auto implPtr = static_cast(obj.getObject()); - implPtr->configureClonedMembers(valueWriteEvents, - valueReadEvents, - endUpdateEvent, - triggerCoreEvent, - localProperties, - propValues, - customOrder, - permissionManager, - corePropertyNames); + implPtr->configureClonedMembers(getCloneParameters()); *cloned = obj.detach(); return OPENDAQ_SUCCESS; @@ -3481,9 +2761,7 @@ void GenericPropertyObjectImpl::DeserializeLoca const auto propName = prop.getName(); if (!propObjPtr.hasProperty(propName)) - { propObjPtr.addProperty(prop); - } } } @@ -3498,9 +2776,7 @@ template ErrCode GenericPropertyObjectImpl::toString(CharPtr* str) { if (str == nullptr) - { return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ARGUMENT_NULL, "Parameter must not be null"); - } std::ostringstream stream; stream << "PropertyObject"; @@ -3577,28 +2853,20 @@ ErrCode GenericPropertyObjectImpl::hasProperty( auto propName = StringPtr::Borrow(propertyName); - if (isChildProperty(propName)) + if (details::isChildProperty(propName)) { - BaseObjectPtr val; - StringPtr childStr; - splitOnLastDot(propName, propName, childStr); - - ErrCode err = getPropertyValue(propName, &val); - OPENDAQ_RETURN_IF_FAILED_EXCEPT(err, OPENDAQ_ERR_NOTFOUND, fmt::format(R"(Failed to retrieve child object with name {})", propName)); - if (err == OPENDAQ_ERR_NOTFOUND) - { - *hasProperty = False; - return OPENDAQ_SUCCESS; - } - - PropertyObjectPtr obj = val.asPtrOrNull(true); - if (!obj.assigned()) + PropertyObjectPtr parentObj; + StringPtr leafName; + const ErrCode err = getParentObject(propName, parentObj, leafName); + if (err == OPENDAQ_ERR_NOTFOUND || err == OPENDAQ_ERR_NOINTERFACE) { + daqClearErrorInfo(); *hasProperty = False; return OPENDAQ_SUCCESS; } + OPENDAQ_RETURN_IF_FAILED(err, fmt::format(R"(Failed to retrieve child object for property "{}")", propName)); - return obj->hasProperty(childStr, hasProperty); + return parentObj->hasProperty(leafName, hasProperty); } if (localProperties.find(propertyName) != localProperties.cend()) @@ -3631,9 +2899,7 @@ ErrCode GenericPropertyObjectImpl::setPropertyF const TypeManagerPtr& typeManager) { if (!serialized.assigned()) - { return propObj->clearPropertyValue(propName); - } BaseObjectPtr propValue; diff --git a/core/coreobjects/src/CMakeLists.txt b/core/coreobjects/src/CMakeLists.txt index 3f5cd9f240..eac1b915ed 100644 --- a/core/coreobjects/src/CMakeLists.txt +++ b/core/coreobjects/src/CMakeLists.txt @@ -112,6 +112,7 @@ source_group("property_object" FILES ${SDK_HEADERS_DIR}/property_object.h ${SDK_HEADERS_DIR}/property_object_core.h ${SDK_HEADERS_DIR}/property_object_core_impl.h ${SDK_HEADERS_DIR}/property_object_utils.h + ${SDK_HEADERS_DIR}/property_object_helpers.h property_object_core_impl.cpp property_object_impl.cpp property_object_utils.cpp @@ -300,6 +301,7 @@ set(SRC_PublicHeaders coreobjects.h mutex_impl.h property_object_core.h property_object_utils.h + property_object_helpers.h permissions_internal.h ) diff --git a/core/coreobjects/src/property_object_class_builder_impl.cpp b/core/coreobjects/src/property_object_class_builder_impl.cpp index c08beb83d3..aa19c2ebbd 100644 --- a/core/coreobjects/src/property_object_class_builder_impl.cpp +++ b/core/coreobjects/src/property_object_class_builder_impl.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +73,7 @@ ErrCode PropertyObjectClassBuilderImpl::addProperty(IProperty* property) { auto p = PropertyPtr::Borrow(property); - if (hasDuplicateReferences(p)) + if (details::hasDuplicateReferences(p, getProperties())) return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_INVALIDVALUE, "Reference property references a property that is already referenced by another."); if (props.hasKey(p.getName())) @@ -151,32 +152,6 @@ ErrCode PropertyObjectClassBuilderImpl::getManager(ITypeManager** manager) return OPENDAQ_SUCCESS; } -bool PropertyObjectClassBuilderImpl::hasDuplicateReferences(const PropertyPtr& prop) const -{ - if (const auto refEval = prop.asPtr().getReferencedPropertyUnresolved(); refEval.assigned()) - { - const auto refNames = refEval.getPropertyReferences(); - std::unordered_set refNamesSet; - for (auto refName : refNames) - refNamesSet.insert(refName); - - for (auto ownProp : getProperties()) - { - if (auto refEvalOwn = ownProp.asPtr().getReferencedPropertyUnresolved(); refEvalOwn.assigned()) - { - auto refNamesOwn = refEvalOwn.getPropertyReferences(); - for (auto refPropName : refNamesOwn) - { - if (refNamesSet.count(refPropName)) - return true; - } - } - } - } - - return false; -} - ListPtr PropertyObjectClassBuilderImpl::getProperties() const { ListPtr properties = List(); diff --git a/core/coreobjects/src/property_object_class_impl.cpp b/core/coreobjects/src/property_object_class_impl.cpp index fc1a768db7..47343fdd47 100644 --- a/core/coreobjects/src/property_object_class_impl.cpp +++ b/core/coreobjects/src/property_object_class_impl.cpp @@ -266,33 +266,6 @@ ErrCode PropertyObjectClassImpl::serializeProperties(ISerializer* serializer) return OPENDAQ_SUCCESS; } -bool PropertyObjectClassImpl::hasDuplicateReferences(const PropertyPtr& prop) -{ - if (const auto refEval = prop.asPtr().getReferencedPropertyUnresolved(); refEval.assigned()) - { - const auto refNames = refEval.getPropertyReferences(); - std::unordered_set refNamesSet; - for (auto refName : refNames) - refNamesSet.insert(refName); - - const auto thisPtr = this->borrowPtr(); - for (auto ownProp : thisPtr.getProperties(true)) - { - if (auto refEvalOwn = ownProp.asPtr().getReferencedPropertyUnresolved(); refEvalOwn.assigned()) - { - auto refNamesOwn = refEvalOwn.getPropertyReferences(); - for (auto refPropName : refNamesOwn) - { - if (refNamesSet.count(refPropName)) - return true; - } - } - } - } - - return false; -} - ErrCode PropertyObjectClassImpl::serialize(ISerializer* serializer) { serializer->startTaggedObject(this); diff --git a/core/coreobjects/tests/test_property_object.cpp b/core/coreobjects/tests/test_property_object.cpp index c2f0f58672..02f0115020 100644 --- a/core/coreobjects/tests/test_property_object.cpp +++ b/core/coreobjects/tests/test_property_object.cpp @@ -2197,6 +2197,68 @@ TEST_F(PropertyObjectTest, BeginEndUpdateNestedFromPropertyValueWrite) ASSERT_EQ(propObj.getPropertyValue("Property4"), "valuefromprop2"); } +TEST_F(PropertyObjectTest, ChildPropertyClearInChildUpdateScope) +{ + const auto childTemplate = PropertyObject(); + childTemplate.addProperty(StringProperty("Str", "-")); + + auto propObj = PropertyObject(); + propObj.addProperty(ObjectProperty("Child", childTemplate)); + propObj.setPropertyValue("Child.Str", "Value"); + + const PropertyObjectPtr child = propObj.getPropertyValue("Child"); + + bool clearSeen = false; + child.getOnPropertyValueWrite("Str") += [&clearSeen](PropertyObjectPtr&, PropertyValueEventArgsPtr& args) + { + if (args.getPropertyEventType() == PropertyEventType::Clear) + { + clearSeen = true; + // A child-path clear during an update applies within the child's own update scope, + // matching the behavior of child-path sets + ASSERT_TRUE(args.getIsUpdating()); + } + }; + + int childEndUpdateCount = 0; + child.getOnEndUpdate() += [&childEndUpdateCount](PropertyObjectPtr&, EndUpdateEventArgsPtr& args) + { + childEndUpdateCount++; + ASSERT_THAT(args.getProperties(), testing::ElementsAre("Str")); + }; + + propObj.beginUpdate(); + propObj.clearPropertyValue("Child.Str"); + ASSERT_EQ(propObj.getPropertyValue("Child.Str"), "Value"); + propObj.endUpdate(); + + ASSERT_TRUE(clearSeen); + ASSERT_EQ(childEndUpdateCount, 1); + ASSERT_EQ(propObj.getPropertyValue("Child.Str"), "-"); +} + +TEST_F(PropertyObjectTest, ClearObjectPropertyWithReadOnlyChild) +{ + const auto childTemplate = PropertyObject(); + childTemplate.addProperty(IntPropertyBuilder("RO", 1).setReadOnly(true).build()); + childTemplate.addProperty(IntProperty("RW", 1)); + + auto propObj = PropertyObject(); + propObj.addProperty(ObjectProperty("Child", childTemplate)); + + propObj.asPtr().setProtectedPropertyValue("Child.RO", 5); + propObj.setPropertyValue("Child.RW", 5); + + // Read-only children are skipped, matching clearPropertyValues on the nested object + ASSERT_NO_THROW(propObj.clearPropertyValue("Child")); + ASSERT_EQ(propObj.getPropertyValue("Child.RW"), 1); + ASSERT_EQ(propObj.getPropertyValue("Child.RO"), 5); + + // The protected clear still resets read-only children + propObj.asPtr().clearProtectedPropertyValue("Child"); + ASSERT_EQ(propObj.getPropertyValue("Child.RO"), 1); +} + TEST_F(PropertyObjectTest, TestContainerClone) { const auto propObj = PropertyObject(); diff --git a/core/coreobjects/tests/test_value_changed_events.cpp b/core/coreobjects/tests/test_value_changed_events.cpp index 851a39df9d..6b26c7419a 100644 --- a/core/coreobjects/tests/test_value_changed_events.cpp +++ b/core/coreobjects/tests/test_value_changed_events.cpp @@ -506,3 +506,31 @@ TEST_F(PropertyValueChangedEventsTest, AnyEventClone) ASSERT_EQ(callCount, 3); } + +TEST_F(PropertyValueChangedEventsTest, ThrowingWriteHandlerDoesNotPoisonProperty) +{ + const PropertyObjectPtr obj = PropertyObject(); + obj.addProperty(IntProperty("int", 0)); + + bool shouldThrow = true; + obj.getOnAnyPropertyValueWrite() += + [&shouldThrow](const PropertyObjectPtr&, const PropertyValueEventArgsPtr&) + { + if (shouldThrow) + throw GeneralErrorException("Handler failure"); + }; + + ASSERT_ANY_THROW(obj.setPropertyValue("int", 1)); + + // A failed write must not leave a phantom value on the update stack: reads consult + // the stack first, so a leaked entry makes every read return the never-written value + ASSERT_EQ(obj.getPropertyValue("int"), 0); + + // The same value must still be settable afterwards, and for real (visible to clones) + shouldThrow = false; + ASSERT_NO_THROW(obj.setPropertyValue("int", 1)); + ASSERT_EQ(obj.getPropertyValue("int"), 1); + + const PropertyObjectPtr objClone = obj.asPtr().clone(); + ASSERT_EQ(objClone.getPropertyValue("int"), 1); +} diff --git a/shared/libraries/config_protocol/include/config_protocol/config_client_property_object_impl.h b/shared/libraries/config_protocol/include/config_protocol/config_client_property_object_impl.h index acbf76851a..ee38274cf7 100644 --- a/shared/libraries/config_protocol/include/config_protocol/config_client_property_object_impl.h +++ b/shared/libraries/config_protocol/include/config_protocol/config_client_property_object_impl.h @@ -810,7 +810,7 @@ void ConfigClientPropertyObjectBaseImpl::endApplyUpdate() propsAndValuesEx = List(); auto ignoredProps = List(); - for (auto& item : this->updatingPropsAndValues) + for (auto& item : this->batchedUpdates) { auto itemEx = Dict(); itemEx.set("Name", String(item.first)); @@ -823,7 +823,7 @@ void ConfigClientPropertyObjectBaseImpl::endApplyUpdate() else applyUpdatingPropsAndValuesProtocolVer0(); - this->updatingPropsAndValues.clear(); + this->batchedUpdates.clear(); clientComm->endUpdate(remoteGlobalId, getPathInternal(), propsAndValuesEx); } @@ -831,7 +831,7 @@ void ConfigClientPropertyObjectBaseImpl::endApplyUpdate() template void ConfigClientPropertyObjectBaseImpl::applyUpdatingPropsAndValuesProtocolVer0() { - for (const auto& item : this->updatingPropsAndValues) + for (const auto& item : this->batchedUpdates) { if (item.second.setValue) {