ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
UserInputValidator.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#pragma once
4
5#include "AccessorConcepts.h"
6#include "ArrayAccessor.h"
7#include "ScalarAccessor.h"
8
9#include <boost/circular_buffer.hpp>
10#include <boost/fusion/container.hpp>
11
12namespace ChimeraTK {
13
14 // Forward-declarations
15 class Module;
16
93 template<typename... ACCESSORTYPES>
94 void add(
95 const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, ACCESSORTYPES&... accessors);
96
101 template<std::ranges::input_range R>
103 void add(const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, const R& accessors);
104
113 template<typename UserType, template<typename> typename Accessor>
114 void setFallback(Accessor<UserType>& accessor, UserType value);
115
116 template<typename UserType, template<typename> typename Accessor>
117 void setFallback(Accessor<UserType>& accessor, std::vector<UserType> value);
118
124 void setErrorFunction(const std::function<void(const std::string&)>& errorFunction);
125
141 bool validate(const ChimeraTK::TransferElementID& change);
142
148 bool validateAll();
149
155 public:
156 virtual ~AccessorHook() = default;
157
162 virtual void onAddValidator([[maybe_unused]] UserInputValidator& validator) {};
163
168 virtual void onReject() {};
169
174 virtual void onAccept() {};
175 };
176
177 protected:
178 static constexpr std::string_view tagValidatedVariable{"__UserInputValidator"};
179
180 // Helper function to set up queue lengths of valid values. Will be called automatically for the first call to
181 // validate
182 void finalise();
183
184 // Helper function for internal book keeping of accessors (prevent unnecessary overwrite of map entry, which might
185 // result in loss of fallback values).
186 template<typename UserType, template<typename> typename Accessor>
187 void addAccessorIfNeeded(Accessor<UserType>& accessor);
188
189 // Type-independent base class representing a variable passed at least once to add() or setFallback().
192 virtual ~VariableBase() = default;
193 virtual void reject(RejectionType type) = 0;
194 virtual void accept() = 0;
195 virtual void setHistorySize(size_t size) = 0;
196 };
197
198 // Type-dependent representation of all known variables.
199 template<typename UserType, template<typename> typename Accessor>
201 explicit Variable(Accessor<UserType>& accessor);
202 Variable() = delete;
203 ~Variable() override = default;
204
205 // called when validation function returned false
206 void reject(RejectionType type) override;
207
208 // called when validation function returned true
209 void accept() override;
210
211 void setHistorySize(size_t) override;
212
213 // value to revert to if reject() is called. Updated through accept().
214 boost::circular_buffer<std::vector<UserType>> lastAcceptedValue{};
215 std::vector<UserType> fallbackValue{UserType()};
216
217 // Reference to the accessor.
218 Accessor<UserType>& accessor;
219
220 std::size_t historyLength{1};
221 };
222
223 // Represents a validation condition
224 struct Validator {
225 explicit Validator(std::function<bool(void)> isValidTest, std::string errorMessage);
226 Validator() = delete;
227 Validator(const Validator& other) = default;
228
229 std::function<bool(void)> isValidFunction;
230 std::string errorMessage;
231 };
232
233 Validator* addValidator(const std::function<bool(void)>& isValidFunction, const std::string& errorMessage);
234
235 template<typename UserType, template<typename> typename Accessor>
236 void registerAccessorWithValidator(Accessor<UserType>& accessor, Validator* validator);
237
238 // List of Validator objects
239 std::list<Validator> _validators; // must not use std::vector as resizing it invalidates pointers to objects
240
241 // Map to find Variable object for given TransferElementID
242 std::map<ChimeraTK::TransferElementID, std::shared_ptr<VariableBase>> _variableMap;
243
244 // Map to find all Validators associated with the given TransferElementID
245 std::map<ChimeraTK::TransferElementID, std::vector<Validator*>> _validatorMap;
246
247 // Function to be called for reporting validation errors
248 std::function<void(const std::string&)> _errorFunction{
249 [](const std::string& m) { logger(Logger::Severity::warning, "UserInputValidator") << m; }};
250
251 std::unordered_set<ChimeraTK::TransferElementID> _downstreamInvalidatingReturnChannels;
254 bool _finalised{false};
255
257 };
258
259 /********************************************************************************************************************/
260
261 template<typename UserType, template<typename> typename Accessor>
262 void UserInputValidator::registerAccessorWithValidator(Accessor<UserType>& accessor, Validator* validator) {
263 addAccessorIfNeeded(accessor);
264 _validatorMap[accessor.getId()].push_back(validator);
265 }
266
267 /********************************************************************************************************************/
268
269 template<typename... ACCESSORTYPES>
271 const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, ACCESSORTYPES&... accessors) {
272 boost::fusion::list<ACCESSORTYPES&...> accessorList{accessors...};
273 static_assert(boost::fusion::size(accessorList) > 0, "Must specify at least one accessor!");
274 assert(isValidFunction != nullptr);
275
276 auto* validator = addValidator(isValidFunction, errorMessage);
277
278 // create map of accessors to validators, also add accessors/variables to list
279 boost::fusion::for_each(accessorList, [&](auto& accessor) { registerAccessorWithValidator(accessor, validator); });
280 }
281
282 /********************************************************************************************************************/
283
284 template<std::ranges::input_range R>
287 const std::string& errorMessage, const std::function<bool(void)>& isValidFunction, const R& accessors) {
288 assert(isValidFunction != nullptr);
289
290 // create validator and store in list
291 _validators.emplace_back(isValidFunction, errorMessage);
292
293 // create map of accessors to validators, also add accessors/variables to list
294 for(auto& accessor : accessors) {
295 addAccessorIfNeeded(accessor);
296 _validatorMap[accessor.getId()].push_back(&_validators.back());
297 };
298 }
299
300 /********************************************************************************************************************/
301
302 template<typename UserType, template<typename> typename Accessor>
303 void UserInputValidator::setFallback(Accessor<UserType>& accessor, UserType value) {
304 addAccessorIfNeeded(accessor);
305 auto pv = std::dynamic_pointer_cast<Variable<UserType, Accessor>>(_variableMap.at(accessor.getId()));
306 assert(pv != nullptr);
307 if(pv->fallbackValue.size() != 1) {
308 throw ChimeraTK::logic_error(
309 "UserInputValidator::setFallback() with scalar value called for array-typed accessor '" + accessor.getName() +
310 "'.");
311 }
312 pv->fallbackValue[0] = value;
313 }
314
315 /********************************************************************************************************************/
316
317 template<typename UserType, template<typename> typename Accessor>
318 void UserInputValidator::setFallback(Accessor<UserType>& accessor, std::vector<UserType> value) {
319 addAccessorIfNeeded(accessor);
320 auto pv = std::dynamic_pointer_cast<Variable<UserType, Accessor>>(_variableMap.at(accessor.getId()));
321 assert(pv != nullptr);
322 if(pv->fallbackValue.size() != value.size()) {
323 throw ChimeraTK::logic_error(
324 "UserInputValidator::setFallback() with called with mismatching array length for accessor '" +
325 accessor.getName() + "'.");
326 }
327 pv->fallbackValue = value;
328 }
329 /********************************************************************************************************************/
330
331 template<typename UserType, template<typename> typename Accessor>
332 void UserInputValidator::addAccessorIfNeeded(Accessor<UserType>& accessor) {
333 if(_module == nullptr) {
334 _module = dynamic_cast<ApplicationModule*>(dynamic_cast<Module*>(accessor.getOwner())->findApplicationModule());
335 }
336 if(!_variableMap.count(accessor.getId())) {
337 accessor.addTag(std::string(tagValidatedVariable));
338 _variableMap[accessor.getId()] = std::make_shared<Variable<UserType, Accessor>>(accessor);
339
340 // Call the AccessorHook::onAddValidator() if present in the accessor
341 auto hook = boost::dynamic_pointer_cast<AccessorHook>(accessor.getImpl());
342 if(hook) {
343 hook->onAddValidator(*this);
344 }
345 }
346 }
347 /********************************************************************************************************************/
348
349 template<typename UserType, template<typename> typename Accessor>
351 : accessor(validatedAccessor) {
352 auto node = static_cast<VariableNetworkNode>(validatedAccessor);
353
354 if(node.getMode() != UpdateMode::push) {
355 throw ChimeraTK::logic_error("UserInputValidator can only be used with push-type inputs.");
356 }
357
358 if constexpr(std::derived_from<Accessor<UserType>, ChimeraTK::ScalarAccessor<UserType>>) {
359 fallbackValue.resize(1);
360 }
361 else {
362 fallbackValue.resize(accessor.getNElements());
363 }
364 lastAcceptedValue.set_capacity(1);
365 }
366
367 /********************************************************************************************************************/
368
369 template<typename UserType, template<typename> typename Accessor>
371 if(type == RejectionType::downstream && !lastAcceptedValue.empty()) {
372 lastAcceptedValue.pop_back();
373 }
374 if constexpr(std::derived_from<Accessor<UserType>, ChimeraTK::ScalarAccessor<UserType>>) {
375 if(lastAcceptedValue.empty()) {
376 accessor = fallbackValue[0];
377 }
378 else {
379 accessor = lastAcceptedValue.back()[0];
380 }
381 }
382 else {
383 if(lastAcceptedValue.empty()) {
384 accessor = fallbackValue;
385 }
386 else {
387 accessor = lastAcceptedValue.back();
388 }
389 }
390
391 // Call the AccessorHook::onReject() if present in the accessor
392 auto hook = boost::dynamic_pointer_cast<AccessorHook>(accessor.getImpl());
393 if(hook) {
394 hook->onReject();
395 }
396
397 if(accessor.isWriteable()) {
398 accessor.write();
399 }
400 }
401
402 /********************************************************************************************************************/
403 template<typename UserType, template<typename> typename Accessor>
405 if constexpr(std::derived_from<Accessor<UserType>, ChimeraTK::ScalarAccessor<UserType>>) {
406 auto savedValue = std::vector<UserType>(1);
407 savedValue[0] = accessor;
408 lastAcceptedValue.push_back(savedValue);
409 }
410 else {
411 auto savedValue = std::vector<UserType>(accessor.getNElements());
412 savedValue = accessor;
413 lastAcceptedValue.push_back(savedValue);
414 }
415
416 // Call the AccessorHook::onAccept() if present in the accessor
417 auto hook = boost::dynamic_pointer_cast<AccessorHook>(accessor.getImpl());
418 if(hook) {
419 hook->onAccept();
420 }
421 }
422 /********************************************************************************************************************/
423
424 template<typename UserType, template<typename> typename Accessor>
426 historyLength = 3 * size;
427 lastAcceptedValue.set_capacity(historyLength);
428 }
429
430 /********************************************************************************************************************/
431
432} // namespace ChimeraTK
void addTag(const std::string &tag)
Add a tag to all Application-type nodes inside this group.
Base class for ApplicationModule and DeviceModule, to have a common interface for these module types.
Definition Module.h:21
Accessor for scalar variables (i.e.
Accessors inheriting from this class (in addition to their accessor base class) can get informed abou...
virtual void onReject()
Called when UserInputValidator::validate() (or validateAll()) rejects an incoming or initial value.
virtual void onAddValidator(UserInputValidator &validator)
Called when the accessor is added to the validator, i.e.
virtual void onAccept()
Called when UserInputValidator::validate() (or validateAll()) accepts an incoming or initial value,...
Class describing a node of a variable network.
Concept requiring a type to be one of the ApplicationCore push input accessor types (scalar or array)...
InvalidityTracer application module.
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.
Definition Logger.h:124
Validator(const Validator &other)=default
virtual void reject(RejectionType type)=0
virtual void setHistorySize(size_t size)=0
void reject(RejectionType type) override
boost::circular_buffer< std::vector< UserType > > lastAcceptedValue
Class to realise the validation of user input values.
Validator * addValidator(const std::function< bool(void)> &isValidFunction, const std::string &errorMessage)
bool validate(const ChimeraTK::TransferElementID &change)
Execute all validations for the given change.
void setErrorFunction(const std::function< void(const std::string &)> &errorFunction)
Define how to report error messages to the user.
static constexpr std::string_view tagValidatedVariable
std::list< Validator > _validators
void addAccessorIfNeeded(Accessor< UserType > &accessor)
std::unordered_set< ChimeraTK::TransferElementID > _downstreamInvalidatingReturnChannels
std::map< ChimeraTK::TransferElementID, std::shared_ptr< VariableBase > > _variableMap
std::map< ChimeraTK::TransferElementID, std::vector< Validator * > > _validatorMap
void registerAccessorWithValidator(Accessor< UserType > &accessor, Validator *validator)
void add(const std::string &errorMessage, const std::function< bool(void)> &isValidFunction, ACCESSORTYPES &... accessors)
Add new condition to validate the given accessors against.
std::function< void(const std::string &)> _errorFunction
void setFallback(Accessor< UserType > &accessor, UserType value)
Provide fallback value for the given accessor.
bool validateAll()
Evaluate all validation conditions and correct all invalid values.