Skip to content

Commit 5503e9c

Browse files
committed
CommonUtils: testing
Signed-off-by: Felix Schlepper <[email protected]>
1 parent ee4bb3b commit 5503e9c

File tree

6 files changed

+363
-15
lines changed

6 files changed

+363
-15
lines changed

Common/Utils/include/CommonUtils/ConfigurableParam.h

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
// granted to it by virtue of its status as an Intergovernmental Organization
1010
// or submit itself to any jurisdiction.
1111

12-
//first version 8/2018, Sandro Wenzel
12+
// first version 8/2018, Sandro Wenzel
1313

1414
#ifndef COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAM_H_
1515
#define COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAM_H_
1616

1717
#include <vector>
1818
#include <cassert>
19+
#include <concepts>
1920
#include <map>
21+
#include <sstream>
2022
#include <unordered_map>
2123
#include <boost/property_tree/ptree_fwd.hpp>
2224
#include <typeinfo>
@@ -136,6 +138,170 @@ class EnumRegistry
136138
std::unordered_map<std::string, EnumLegalValues> entries;
137139
};
138140

141+
template <typename T>
142+
concept Container = requires(T t) {
143+
typename T::value_type;
144+
typename T::iterator;
145+
{ t.begin() } -> std::same_as<typename T::iterator>;
146+
{ t.end() } -> std::same_as<typename T::iterator>;
147+
};
148+
149+
template <typename T>
150+
concept MapLike = Container<T> && requires {
151+
typename T::key_type;
152+
typename T::mapped_type;
153+
};
154+
155+
template <typename T>
156+
concept SequenceContainer = Container<T> && !MapLike<T>;
157+
158+
class ContainerParser
159+
{
160+
public:
161+
template <typename T>
162+
static T parse(const std::string& str)
163+
{
164+
if constexpr (MapLike<T>) {
165+
return parseMap<T>(str);
166+
} else if constexpr (SequenceContainer<T>) {
167+
return parseSet<T>(str);
168+
} else if constexpr (Container<T>) {
169+
return parseSequence<T>(str);
170+
} else {
171+
return parseScalar<T>(str);
172+
}
173+
}
174+
175+
private:
176+
// Parse vector, list, deque, array
177+
template <SequenceContainer SequenceT>
178+
static SequenceT parseSequence(const std::string& str)
179+
{
180+
SequenceT result;
181+
using ValueType = typename SequenceT::value_type;
182+
std::string cleaned = str;
183+
if (!cleaned.empty() && cleaned.front() == '[' && cleaned.back() == ']') { // removed brackets [1,2,3] -> 1,2,3
184+
cleaned = cleaned.substr(1, cleaned.length() - 2);
185+
}
186+
if (cleaned.empty() || cleaned == "{}") { // noting to do
187+
return result;
188+
}
189+
auto tokens = split(cleaned, ',');
190+
for (const auto& token : tokens) {
191+
std::string trimmed = trim(token);
192+
result.insert(result.end(), parse<ValueType>(trimmed)); // handles nested containers
193+
}
194+
return result;
195+
}
196+
197+
// Parse map, unordered_map, multimap
198+
template <MapLike MapT>
199+
static MapT parseMap(const std::string& str)
200+
{
201+
MapT result;
202+
using KeyType = typename MapT::key_type;
203+
using ValueType = typename MapT::mapped_type;
204+
std::string cleaned = str;
205+
if (!cleaned.empty() && cleaned.front() == '{' && cleaned.back() == '}') { // stip braces {a:1,b:2} -> a:1,b:2
206+
cleaned = cleaned.substr(1, cleaned.length() - 2);
207+
}
208+
if (cleaned.empty()) { // nothing to do
209+
return result;
210+
}
211+
auto pairs = split(cleaned, ',');
212+
for (const auto& pair_str : pairs) {
213+
auto kv = split(pair_str, ':');
214+
if (kv.size() != 2) {
215+
throw std::runtime_error("Invalid map syntax: " + pair_str + ". Expected 'key:value' format, got ");
216+
}
217+
KeyType key = parseScalar<KeyType>(trim(kv[0]));
218+
result[key] = parse<ValueType>(trim(kv[1]));
219+
}
220+
return result;
221+
}
222+
223+
// Parse set containers
224+
template <SequenceContainer SetT>
225+
static SetT parseSet(const std::string& str)
226+
{
227+
SetT result;
228+
using ValueType = typename SetT::value_type;
229+
auto vec = parseSequence<std::vector<ValueType>>(str);
230+
for (const auto& val : vec) {
231+
result.push_back(val);
232+
}
233+
return result;
234+
}
235+
236+
// Parse scalar types
237+
template <typename T>
238+
static T parseScalar(const std::string& str)
239+
{
240+
if constexpr (std::is_same_v<T, std::string>) {
241+
return str;
242+
} else if constexpr (std::is_same_v<T, bool>) {
243+
std::string lower = str;
244+
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
245+
if (lower == "true" || lower == "1") {
246+
return true;
247+
}
248+
if (lower == "false" || lower == "0") {
249+
return false;
250+
}
251+
throw std::runtime_error("Invalid boolean value: " + str);
252+
} else {
253+
std::istringstream iss(str);
254+
T value;
255+
iss >> value;
256+
if (iss.fail()) {
257+
throw std::runtime_error("Failed to parse '" + str + "' as " + typeid(T).name());
258+
}
259+
return value;
260+
}
261+
}
262+
263+
// Split respecting nested brackets and braces
264+
static std::vector<std::string> split(const std::string& str, char delimiter)
265+
{
266+
std::vector<std::string> tokens;
267+
std::string current;
268+
int bracket_depth = 0;
269+
int brace_depth = 0;
270+
for (char c : str) {
271+
if (c == '[') {
272+
bracket_depth++;
273+
} else if (c == ']') {
274+
bracket_depth--;
275+
} else if (c == '{') {
276+
brace_depth++;
277+
} else if (c == '}') {
278+
brace_depth--;
279+
} else if (c == delimiter && bracket_depth == 0 && brace_depth == 0) {
280+
if (!current.empty()) {
281+
tokens.push_back(current);
282+
current.clear();
283+
}
284+
continue;
285+
}
286+
current += c;
287+
}
288+
if (!current.empty()) {
289+
tokens.push_back(current);
290+
}
291+
return tokens;
292+
}
293+
294+
static std::string trim(const std::string& str)
295+
{
296+
auto start = str.find_first_not_of(" \t\n\r\f\v");
297+
if (start == std::string::npos) {
298+
return "";
299+
}
300+
auto end = str.find_last_not_of(" \t\n\r\f\v");
301+
return str.substr(start, end - start + 1);
302+
}
303+
};
304+
139305
class ConfigurableParam
140306
{
141307
public:
@@ -244,14 +410,15 @@ class ConfigurableParam
244410
static void setValue(std::string const& key, std::string const& valuestring);
245411
static void setEnumValue(const std::string&, const std::string&);
246412
static void setArrayValue(const std::string&, const std::string&);
413+
static void setContainerValue(const std::string&, const std::string&);
247414

248415
// update the storagemap from a vector of key/value pairs, calling setValue for each pair
249416
static void setValues(std::vector<std::pair<std::string, std::string>> const& keyValues);
250417

251418
// initializes the parameter database
252419
static void initialize();
253420

254-
// create CCDB snapsnot
421+
// create CCDB snapshot
255422
static void toCCDB(std::string filename);
256423
// load from (CCDB) snapshot
257424
static void fromCCDB(std::string filename);

Common/Utils/include/CommonUtils/ConfigurableParamHelper.h

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@
99
// granted to it by virtue of its status as an Intergovernmental Organization
1010
// or submit itself to any jurisdiction.
1111

12-
//first version 8/2018, Sandro Wenzel
12+
// first version 8/2018, Sandro Wenzel
1313

1414
#ifndef COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAMHELPER_H_
1515
#define COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAMHELPER_H_
1616

1717
#include "CommonUtils/ConfigurableParam.h"
18-
#include "TClass.h"
18+
#include <TClass.h>
19+
#include <TFile.h>
20+
#include <TDataMember.h>
1921
#include <type_traits>
2022
#include <typeinfo>
21-
#include "TFile.h"
2223

2324
namespace o2
2425
{
@@ -339,6 +340,23 @@ class ConfigurableParamPromoter : public Base, virtual public ConfigurableParam
339340
}
340341
};
341342

343+
inline bool isContainer(const std::string& typeName)
344+
{
345+
return (typeName.starts_with("vector") ||
346+
typeName.starts_with("map") ||
347+
typeName.starts_with("set") ||
348+
typeName.starts_with("list") ||
349+
typeName.starts_with("deque") ||
350+
typeName.starts_with("unordered_map") ||
351+
typeName.starts_with("unordered_set"));
352+
}
353+
354+
inline bool isContainer(TDataMember const& dm)
355+
{
356+
const std::string typeName = dm.GetTrueTypeName();
357+
return isContainer(typeName);
358+
}
359+
342360
} // namespace conf
343361
} // namespace o2
344362

Common/Utils/include/CommonUtils/ConfigurableParamTest.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#include "CommonUtils/ConfigurableParam.h"
1616
#include "CommonUtils/ConfigurableParamHelper.h"
1717

18+
#include <vector>
19+
#include <array>
20+
#include <map>
21+
1822
namespace o2::conf::test
1923
{
2024
struct TestParam : public o2::conf::ConfigurableParamHelper<TestParam> {
@@ -37,6 +41,8 @@ struct TestParam : public o2::conf::ConfigurableParamHelper<TestParam> {
3741
int iValueProvenanceTest{0};
3842
TestEnum eValue = TestEnum::C;
3943
int caValue[3] = {0, 1, 2};
44+
std::vector<int> vec;
45+
std::map<int, unsigned int> map;
4046

4147
O2ParamDef(TestParam, "TestParam");
4248
};

Common/Utils/src/ConfigurableParam.cxx

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ EnumRegistry* ConfigurableParam::sEnumRegistry = nullptr;
5454
bool ConfigurableParam::sIsFullyInitialized = false;
5555
bool ConfigurableParam::sRegisterMode = true;
5656

57+
namespace
58+
{
59+
std::map<std::string, std::function<void(const std::string&)>>* sContainerParsers = nullptr;
60+
std::map<std::string, std::function<std::string()>>* sContainerSerializers = nullptr;
61+
} // namespace
62+
5763
// ------------------------------------------------------------------
5864

5965
std::ostream& operator<<(std::ostream& out, ConfigurableParam const& param)
@@ -77,7 +83,7 @@ bool keyInTree(boost::property_tree::ptree* pt, const std::string& key)
7783
return reply;
7884
}
7985

80-
// Convert a type info to the appropiate literal suffix
86+
// Convert a type info to the appropriate literal suffix
8187
std::string getLiteralSuffixFromType(const std::type_info& type)
8288
{
8389
if (type == typeid(float)) {
@@ -101,6 +107,26 @@ std::string getLiteralSuffixFromType(const std::type_info& type)
101107
return "";
102108
}
103109

110+
bool isSTLContainer(const std::string& key,
111+
std::map<std::string, std::pair<std::type_info const&, void*>>* storageMap)
112+
{
113+
auto iter = storageMap->find(key);
114+
if (iter == storageMap->end()) {
115+
return false;
116+
}
117+
TClass* cl = TClass::GetClass(iter->second.first);
118+
if (!cl) {
119+
return false;
120+
}
121+
std::string typeName = cl->GetName();
122+
return typeName.find("vector") != std::string::npos ||
123+
typeName.find("map") != std::string::npos ||
124+
typeName.find("set") != std::string::npos ||
125+
typeName.find("array") != std::string::npos ||
126+
typeName.find("deque") != std::string::npos ||
127+
typeName.find("list") != std::string::npos;
128+
}
129+
104130
// ------------------------------------------------------------------
105131

106132
void EnumRegistry::add(const std::string& key, const TDataMember* dm)
@@ -417,7 +443,7 @@ void ConfigurableParam::printAllRegisteredParamNames()
417443
// If nonempty comma-separated paramsList is provided, only those params will
418444
// be updated, absence of data for any of requested params will lead to fatal
419445
// If unchangedOnly is true, then only those parameters whose provenance is kCODE will be updated
420-
// (to allow prefernce of run-time settings)
446+
// (to allow preference of run-time settings)
421447
void ConfigurableParam::updateFromFile(std::string const& configFile, std::string const& paramsList, bool unchangedOnly)
422448
{
423449
if (!sIsFullyInitialized) {
@@ -559,6 +585,9 @@ void ConfigurableParam::setValues(std::vector<std::pair<std::string, std::string
559585
auto isArray = [](std::string& el) {
560586
return el.size() > 0 && (el.at(0) == '[') && (el.at(el.size() - 1) == ']');
561587
};
588+
auto isMap = [](std::string& el) {
589+
return el.size() > 0 && (el.at(0) == '{') && (el.at(el.size() - 1) == '}');
590+
};
562591

563592
bool nonFatal = getenv("ALICEO2_CONFIGURABLEPARAM_WRONGKEYISNONFATAL") != nullptr;
564593

@@ -578,7 +607,19 @@ void ConfigurableParam::setValues(std::vector<std::pair<std::string, std::string
578607
LOG(warn) << "Ignoring non-existent ConfigurableParam key: " << key;
579608
continue;
580609
}
581-
LOG(fatal) << "Inexistant ConfigurableParam key: " << key;
610+
LOG(fatal) << "Inexistent ConfigurableParam key: " << key;
611+
}
612+
613+
auto iter = sKeyToStorageMap->find(key);
614+
if (iter != sKeyToStorageMap->end()) {
615+
TClass* cl = TClass::GetClass(iter->second.first);
616+
if (cl) {
617+
std::string typeName = cl->GetName();
618+
if (isContainer(typeName)) {
619+
setContainerValue(key, value);
620+
continue;
621+
}
622+
}
582623
}
583624

584625
if (sEnumRegistry->contains(key)) {
@@ -619,6 +660,36 @@ void ConfigurableParam::setArrayValue(const std::string& key, const std::string&
619660
}
620661
}
621662

663+
void ConfigurableParam::setContainerValue(const std::string& key, const std::string& value)
664+
{
665+
auto iter = sKeyToStorageMap->find(key);
666+
if (iter == sKeyToStorageMap->end()) {
667+
LOG(error) << "Container parameter " << key << " not found";
668+
return;
669+
}
670+
void* targetAddress = iter->second.second;
671+
const std::type_info& tinfo = iter->second.first;
672+
TClass* cl = TClass::GetClass(tinfo);
673+
if (!cl) {
674+
LOG(error) << "Cannot get TClass for container " << key;
675+
return;
676+
}
677+
std::string typeName = cl->GetName();
678+
try {
679+
if (typeName.starts_with("vector<int>")) {
680+
auto parsed = ContainerParser::parse<std::vector<int>>(value);
681+
*(std::vector<int>*)targetAddress = parsed;
682+
} else if (typeName.starts_with("map<int,unsigned int>")) {
683+
auto parsed = ContainerParser::parse<std::map<int, unsigned int>>(value);
684+
*(std::map<int, unsigned int>*)targetAddress = parsed;
685+
} else {
686+
LOG(error) << "Unknown container type " << typeName;
687+
}
688+
} catch (const std::exception& e) {
689+
LOG(error) << "Failed to parse container " << key << ": " << e.what();
690+
}
691+
}
692+
622693
void ConfigurableParam::setEnumValue(const std::string& key, const std::string& value)
623694
{
624695
int val = (*sEnumRegistry)[key]->getIntValue(value);

0 commit comments

Comments
 (0)