diff --git a/src/libraries/JANA/Services/JParameterManager.h b/src/libraries/JANA/Services/JParameterManager.h index 9f46463d0..6fea718f5 100644 --- a/src/libraries/JANA/Services/JParameterManager.h +++ b/src/libraries/JANA/Services/JParameterManager.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -104,19 +105,26 @@ class JParameterManager : public JService { void WriteConfigFile(std::string name); template - static T Parse(const std::string& value); + static inline void Parse(const std::string& value, T& out); template - static std::string Stringify(const T& value); + static inline void Parse(const std::string& value, std::vector& out); + + template + static inline void Parse(const std::string& value, std::array& out); template - static bool Equals(const T& lhs, const T& rhs); + static std::string Stringify(const T& value); template - static std::vector ParseVector(const std::string& value); + static std::string Stringify(const std::vector& values); + + template + static std::string Stringify(const std::array& values); template - static std::string StringifyVector(const std::vector& values); + static bool Equals(const T& lhs, const T& rhs); + static std::string ToLower(const std::string& name); @@ -145,7 +153,7 @@ JParameter* JParameterManager::GetParameter(std::string name, T& val) { if (result == m_parameters.end()) { return nullptr; } - val = Parse(result->second->GetValue()); + Parse(result->second->GetValue(),val); result->second->SetIsUsed(true); return result->second; } @@ -159,12 +167,14 @@ JParameter* JParameterManager::GetParameter(std::string name, T& val) { /// template T JParameterManager::GetParameterValue(std::string name) { + T t; auto result = m_parameters.find(ToLower(name)); if (result == m_parameters.end()) { throw JException("Unknown parameter \"%s\"", name.c_str()); } result->second->SetIsUsed(true); - return Parse(result->second->GetValue()); + Parse(result->second->GetValue(),t); + return t; } @@ -220,7 +230,7 @@ JParameter* JParameterManager::SetDefaultParameter(std::string name, T& val, std std::lock_guard lock(m_mutex); JParameter* param = nullptr; - + T t; auto result = m_parameters.find(ToLower(name)); if (result != m_parameters.end()) { // We already have a value stored for this parameter @@ -236,7 +246,8 @@ JParameter* JParameterManager::SetDefaultParameter(std::string name, T& val, std // We tried to set the same default parameter twice. This is fine; this happens all the time // because we construct lots of copies of JFactories, each of which calls SetDefaultParameter on its own. // However, we still want to warn the user if the same parameter was declared with different values. - if (!Equals(val, Parse(param->GetDefault()))) { + Parse(param->GetDefault(),t); + if (!Equals(val, t)) { LOG_WARN(m_logger) << "Parameter '" << name << "' has conflicting defaults: '" << Stringify(val) << "' vs '" << param->GetDefault() << "'" << LOG_END; @@ -256,7 +267,9 @@ JParameter* JParameterManager::SetDefaultParameter(std::string name, T& val, std // Test whether parameter is one-to-one with its string representation. // If not, warn the user they may have a problem. - if (!Equals(val, Parse(valstr))) { + + Parse(valstr,t); + if (!Equals(val, t)) { LOG_WARN(m_logger) << "Parameter '" << name << "' with value '" << valstr << "' loses equality with itself after stringification" << LOG_END; } @@ -265,7 +278,8 @@ JParameter* JParameterManager::SetDefaultParameter(std::string name, T& val, std // Always put val through the stringification/parsing cycle to be consistent with // values passed in from config file, accesses from other threads - val = Parse(param->GetValue()); + Parse(param->GetValue(),t); + val = t; param->SetIsUsed(true); return param; } @@ -293,89 +307,51 @@ inline T JParameterManager::RegisterParameter(std::string name, const T default_ /// @brief Logic for parsing different types in a generic way template -inline T JParameterManager::Parse(const std::string& value) { +void JParameterManager::Parse(const std::string& value, T& val) { std::stringstream ss(value); - T val; ss >> val; - return val; } -/// @brief Specialization for handling strings that don't need parsing + +/// @brief Specialization for string. +/// The stream operator is not only redundant here, but it also splits the string (see Issue #191) template <> -inline std::string JParameterManager::Parse(const std::string& value) { - return std::string(value); +inline void JParameterManager::Parse(const std::string& value, std::string& out) { + out = value; } + /// @brief Specialization for bool template <> -inline bool JParameterManager::Parse(const std::string& value) { - if (value == "0") return false; - if (value == "1") return true; - if (value == "false") return false; - if (value == "true") return true; - if (value == "off") return false; - if (value == "on") return true; - throw JException("'%s' not parseable as bool", value.c_str()); +inline void JParameterManager::Parse(const std::string& value, bool& val) { + if (value == "0" || value =="false" || value == "off") val = false; + else if (value == "1" || value == "true" || value == "on") val = true; + else throw JException("'%s' not parseable as bool", value.c_str()); } - -/// @brief Specialization for std::vector -template<> -inline std::vector JParameterManager::Parse(const std::string& value) { - std::stringstream ss(value); - std::vector result; +// @brief Template to parse a string and return in an array +template +inline void JParameterManager::Parse(const std::string& value,std::array &val) { std::string s; + std::stringstream ss(value); + int indx = 0; while (getline(ss, s, ',')) { - result.push_back(s); - } - return result; + T t; + Parse(s, t); + val[indx++]= t; + } } -template -inline std::vector JParameterManager::ParseVector(const std::string &value) { +/// @brief Specialization for std::vector +template +inline void JParameterManager::Parse(const std::string& value, std::vector &val) { std::stringstream ss(value); - std::vector result; std::string s; while (getline(ss, s, ',')) { - std::stringstream sss(s); T t; - sss >> t; - result.push_back(t); + Parse(s, t); + val.push_back(t); } - return result; -} - -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); -} -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); -} -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); -} -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); -} -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); -} -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); -} -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); -} -template <> -inline std::vector JParameterManager::Parse(const std::string& value) { - return ParseVector(value); } /// @brief Logic for stringifying different types in a generic way @@ -386,8 +362,15 @@ inline std::string JParameterManager::Stringify(const T& value) { return ss.str(); } +// @brief Specialization for strings. The stream operator is not only redundant here, but it also splits the string (see Issue #191) +template <> +inline std::string JParameterManager::Stringify(const std::string& value) { + return value; +} + + template -inline std::string JParameterManager::StringifyVector(const std::vector &values) { +inline std::string JParameterManager::Stringify(const std::vector &values) { std::stringstream ss; size_t len = values.size(); for (size_t i = 0; i+1 < len; ++i) { @@ -400,51 +383,19 @@ inline std::string JParameterManager::StringifyVector(const std::vector &valu return ss.str(); } -/// Specializations of Stringify - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); -} - -template<> -inline std::string JParameterManager::Stringify(const std::vector& values) { - return StringifyVector(values); +// @brief Template for generically stringifying an array +template +inline std::string JParameterManager::Stringify(const std::array &values) { + std::stringstream ss; + size_t len = values.size(); + for (size_t i = 0; i+1 < N; ++i) { + ss << values[i]; + ss << ","; + } + if (len != 0) { + ss << values[len-1]; + } + return ss.str(); } template @@ -464,3 +415,5 @@ inline bool JParameterManager::Equals(const double& lhs, const double& rhs) { #endif // _JParameterManager_h_ + + diff --git a/src/libraries/JANA/Utils/JInspector.h b/src/libraries/JANA/Utils/JInspector.h index 6b8796f15..672d2fe65 100644 --- a/src/libraries/JANA/Utils/JInspector.h +++ b/src/libraries/JANA/Utils/JInspector.h @@ -76,12 +76,12 @@ inline std::string JParameterManager::Stringify(const JInspector::Format& value) } template <> -inline JInspector::Format JParameterManager::Parse(const std::string& value) { +inline void JParameterManager::Parse(const std::string& value, JInspector::Format& val) { auto lowered = JParameterManager::ToLower(value); - if (lowered == "table") return JInspector::Format::Table; - if (lowered == "json") return JInspector::Format::Json; - if (lowered == "tsv") return JInspector::Format::Tsv; - else return JInspector::Format::Table; + if (lowered == "table") val = JInspector::Format::Table; + if (lowered == "json") val = JInspector::Format::Json; + if (lowered == "tsv") val = JInspector::Format::Tsv; + else val = JInspector::Format::Table; } #endif // _JIntrospection_h_ diff --git a/src/programs/tests/JParameterManagerTests.cc b/src/programs/tests/JParameterManagerTests.cc index 43cd0fc64..2ac86693a 100644 --- a/src/programs/tests/JParameterManagerTests.cc +++ b/src/programs/tests/JParameterManagerTests.cc @@ -11,7 +11,7 @@ TEST_CASE("JParameterManager::SetDefaultParameter") { JParameterManager jpm; - SECTION("Multiple calls to SetDefaultParameter with same defaults succeed") { + SECTION("Multiple calls to SetDefaultParameter with same defaults succeed") { jpm.SetParameter("testing:dummy_var", 22); @@ -31,33 +31,41 @@ TEST_CASE("JParameterManager::SetDefaultParameter") { jpm.SetDefaultParameter("small_float", x); float y = 1.1; jpm.SetDefaultParameter("small_float", y); - - REQUIRE(jpm.Equals(jpm.Parse(jpm.Stringify(1.1f)), 1.1f)); - REQUIRE(!jpm.Equals(jpm.Parse(jpm.Stringify(1.1f)), 1.10001f)); + float temp; + jpm.Parse(jpm.Stringify(1.1f), temp); + REQUIRE(jpm.Equals(temp, 1.1f)); + jpm.Parse(jpm.Stringify(1.1f), temp); + REQUIRE(!jpm.Equals(temp, 1.10001f)); float v = 1.1e20f; jpm.SetDefaultParameter("large_float", v); float w = 1.1e20f; - jpm.SetDefaultParameter("large_float", w); - REQUIRE(jpm.Equals(jpm.Parse(jpm.Stringify(1.1e20f)), 1.1e20f)); - REQUIRE(!jpm.Equals(jpm.Parse(jpm.Stringify(1.1e20f)), 1.100001e20f)); + jpm.SetDefaultParameter("large_float", w); + jpm.Parse(jpm.Stringify(1.1e20f), temp); + REQUIRE(jpm.Equals(temp, 1.1e20f)); + jpm.Parse(jpm.Stringify(1.1e20f), temp); + REQUIRE(!jpm.Equals(temp, 1.100001e20f)); double xx = 1.1; jpm.SetDefaultParameter("small_double", xx); double yy = 1.1; jpm.SetDefaultParameter("small_double", yy); - - REQUIRE(jpm.Equals(jpm.Parse(jpm.Stringify(1.1)), 1.1)); - REQUIRE(!jpm.Equals(jpm.Parse(jpm.Stringify(1.1)), 1.100001)); + + double tempD; + jpm.Parse(jpm.Stringify(1.1), tempD); + REQUIRE(jpm.Equals(tempD, 1.1)); + jpm.Parse(jpm.Stringify(1.1), tempD); + REQUIRE(!jpm.Equals(tempD, 1.100001)); double vv = 1.1e50; jpm.SetDefaultParameter("large_double", vv); double ww = 1.1e50; jpm.SetDefaultParameter("large_double", ww); - REQUIRE(jpm.Equals(jpm.Parse(jpm.Stringify(1.1e20)), 1.1e20)); - REQUIRE(!jpm.Equals(jpm.Parse(jpm.Stringify(1.1e20)), 1.1000000001e20)); + jpm.Parse(jpm.Stringify(1.1e20), tempD); + REQUIRE(jpm.Equals(tempD, 1.1e20)); + REQUIRE(!jpm.Equals(tempD, 1.1000000001e20)); } @@ -103,7 +111,9 @@ TEST_CASE("JParameterManager::SetDefaultParameter") { // Parse returns identical string std::string z = "My String Value With Spaces"; - REQUIRE( jpm.Parse(z) == "My String Value With Spaces" ); + std::string testString; + jpm.Parse(z,testString); + REQUIRE( testString == "My String Value With Spaces" ); } } @@ -247,12 +257,64 @@ TEST_CASE("JParameterManager::RegisterParameter") { jpm.SetParameter("testing:dummy_var", 22); auto x_actual = jpm.RegisterParameter("testing:dummy_var", 44); // this should set the default value to 44 while keeping value at 22 auto x_default_str = jpm.FindParameter("testing:dummy_var")->GetDefault(); - auto x_default = jpm.Parse(x_default_str); + int x_default; + jpm.Parse(x_default_str,x_default); REQUIRE(x_actual == 22); REQUIRE(x_default == 44); } } +TEST_CASE("JParameterManager_ArrayParams") { + JParameterManager jpm; + + SECTION("Reading a array of strings") { + jpm.SetParameter("test", "simple,whitespace in middle, also with whitespace padding "); + std::array vals; + jpm.GetParameter>("test", vals); + REQUIRE(vals[0] == "simple"); + REQUIRE(vals[1] == "whitespace in middle"); + REQUIRE(vals[2] == " also with whitespace padding "); + } + SECTION("Writing a array of strings") { + std::array inputs = {"first", "second one" , " third one "}; + jpm.SetDefaultParameter("test", inputs); + std::array outputs; + auto param = jpm.GetParameter("test", outputs); + REQUIRE(param->GetValue() == "first,second one, third one "); + } + SECTION("Reading a array of ints") { + jpm.SetParameter("test", "1,2, 3 "); + std::array vals; + jpm.GetParameter("test", vals); + + REQUIRE(vals[0] == 1); + REQUIRE(vals[1] == 2); + REQUIRE(vals[2] == 3); + } + SECTION("Writing a array of ints") { + std::array inputs = {22,49,42}; + jpm.SetDefaultParameter("test", inputs); + std::array outputs; + auto param = jpm.GetParameter("test", outputs); + REQUIRE(param->GetValue() == "22,49,42"); + } + SECTION("Reading a array of floats") { + jpm.SetParameter("test", "1,2,3"); + std::array vals; + jpm.GetParameter("test", vals); + + REQUIRE(vals[0] == 1.0f); + REQUIRE(vals[1] == 2.0f); + REQUIRE(vals[2] == 3.0f); + } + SECTION("Writing a array of floats") { + std::array inputs = {22.0,49.2,42.0}; + jpm.SetDefaultParameter("test", inputs); + std::array outputs; + auto param = jpm.GetParameter("test", outputs); + REQUIRE(param->GetValue() == "22,49.2,42"); + } +}