Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:35

0001 
0002 // Copyright 2020, Jefferson Science Associates, LLC.
0003 // Subject to the terms in the LICENSE file found in the top-level directory.
0004 
0005 #pragma once
0006 
0007 #include <limits>
0008 #include <string>
0009 #include <algorithm>
0010 #include <vector>
0011 #include <array>
0012 #include <map>
0013 #include <cmath>
0014 #include <iomanip>
0015 #include <cstring>
0016 
0017 #include <JANA/JLogger.h>
0018 #include <JANA/JException.h>
0019 #include <JANA/Services/JServiceLocator.h>
0020 
0021 class JParameter {
0022 
0023     std::string m_name;             // A token (no whitespace, colon-prefixed), e.g. "my_plugin:use_mc"
0024     std::string m_value;            // Stringified value, e.g. "1". Can handle comma-separated vectors of strings e.g. "abc, def"
0025     std::string m_default_value;    // Optional default value, which may or may not equal value
0026     std::string m_description;      // One-liner
0027 
0028     bool m_has_default = false;     // Indicates that a default value is present. Does not indicate whether said value is in use.
0029                                     //   We need this because "" is a valid default value.
0030     bool m_is_default = false;      // Indicates that we are actually using this default value.
0031                                     //   We need this because we want to know if the user provided a value that happened to match the default value.
0032     bool m_is_deprecated = false;   // This lets us print a warning if a user provides a deprecated parameter,
0033                                     //   It also lets us suppress printing this parameter in the parameter list.
0034     bool m_is_advanced = false;       // Some JANA parameters control internal details that aren't part of the contract between JANA and its users.
0035                                     //   We want to differentiate these from the parameters that users are meant to control and understand.
0036     bool m_is_used = false;         // If a parameter hasn't been used, it probably contains a typo, and we should warn the user.
0037 
0038     bool m_is_conflicted = false;   // Whether or not this parameter has been registered with inconsistent default values
0039 
0040 public:
0041 
0042     JParameter(std::string key, std::string value, std::string defaultValue, std::string description, bool hasDefault, bool isDefault)
0043     : m_name(std::move(key)),
0044       m_value(std::move(value)),
0045       m_default_value(std::move(defaultValue)),
0046       m_description(std::move(description)),
0047       m_has_default(hasDefault),
0048       m_is_default(isDefault) {}
0049 
0050     inline const std::string& GetKey() const { return m_name; }
0051     inline const std::string& GetValue() const { return m_value; }
0052     inline const std::string& GetDefault() const { return m_default_value; }
0053     inline const std::string& GetDescription() const { return m_description; }
0054 
0055     inline bool HasDefault() const { return m_has_default; }
0056     inline bool IsDefault() const { return m_is_default; }
0057     inline bool IsAdvanced() const { return m_is_advanced; }
0058     inline bool IsUsed() const { return m_is_used; }
0059     inline bool IsDeprecated() const { return m_is_deprecated; }
0060     inline bool IsConflicted() const { return m_is_conflicted; }
0061 
0062     inline void SetKey(std::string key) { m_name = std::move(key); }
0063     inline void SetValue(std::string val) { m_value = std::move(val); }
0064     inline void SetDefault(std::string defaultValue) { m_default_value = std::move(defaultValue); }
0065     inline void SetDescription(std::string desc) { m_description = std::move(desc); }
0066     inline void SetHasDefault(bool hasDefault) { m_has_default = hasDefault; }
0067     inline void SetIsDefault(bool isDefault) { m_is_default = isDefault; }
0068     inline void SetIsAdvanced(bool isHidden) { m_is_advanced = isHidden; }
0069     inline void SetIsUsed(bool isUsed) { m_is_used = isUsed; }
0070     inline void SetIsDeprecated(bool isDeprecated) { m_is_deprecated = isDeprecated; }
0071     inline void SetIsConflicted(bool isConflicted) { m_is_conflicted = isConflicted; }
0072 
0073 };
0074 
0075 class JParameterManager : public JService {
0076 public:
0077 
0078     JParameterManager();
0079 
0080     JParameterManager(const JParameterManager&);
0081 
0082     ~JParameterManager() override;
0083 
0084     inline void SetLogger(JLogger logger) { m_logger = logger; }
0085 
0086     bool Exists(std::string name);
0087 
0088     JParameter* FindParameter(std::string);
0089 
0090     void PrintParameters();
0091     
0092     void PrintParameters(int verbosity, int strictness);
0093 
0094     std::map<std::string, JParameter*> GetAllParameters();
0095 
0096     template<typename T>
0097     JParameter* GetParameter(std::string name, T& val);
0098 
0099     template<typename T>
0100     T GetParameterValue(std::string name);
0101 
0102     template<typename T>
0103     JParameter* SetParameter(std::string name, T val);
0104 
0105     template<typename T>
0106     JParameter* SetDefaultParameter(std::string name, T& val, std::string description = "");
0107 
0108     template <typename T>
0109     T RegisterParameter(std::string name, const T default_val, std::string description = "");
0110 
0111     void FilterParameters(std::map<std::string,std::string> &parms, std::string filter="");
0112 
0113     void ReadConfigFile(std::string name);
0114 
0115     void WriteConfigFile(std::string name);
0116 
0117     template<typename T>
0118     static inline void Parse(const std::string& value, T& out);
0119 
0120     template<typename T>
0121     static inline void Parse(const std::string& value, std::vector<T>& out);
0122 
0123     template<typename T, size_t arrSize>
0124     static inline void Parse(const std::string& value, std::array<T,arrSize>& out); 
0125 
0126     /*
0127     Template specialization done for double and float numbers in order to match the 
0128     precision of the number with the string
0129     */
0130     template<typename T>
0131     static std::string Stringify(const T& value);
0132     
0133     template<typename T>
0134     static std::string Stringify(const std::vector<T>& values);
0135 
0136     template<typename T,size_t N>
0137     static std::string Stringify(const std::array<T,N>& values);
0138 
0139     template<typename T>
0140     static bool Equals(const T& lhs, const T& rhs);
0141 
0142 
0143     static std::string ToLower(const std::string& name);
0144 
0145     JLogger GetLogger(const std::string& prefix);
0146 
0147 private:
0148 
0149     std::map<std::string, JParameter*> m_parameters;
0150 
0151     int m_strictness = 1;
0152     int m_verbosity = 1;
0153 
0154     std::mutex m_mutex;
0155 };
0156 
0157 
0158 
0159 /// @brief Retrieves a JParameter and stores its value
0160 ///
0161 /// @param [in] name    The name of the parameter to retrieve
0162 /// @param [out] val    Reference to where the parameter value should be stored
0163 /// @returns            The corresponding JParameter representation, which provides us with
0164 ///                     additional information such as the default value, or nullptr if not found.
0165 ///
0166 template<typename T>
0167 JParameter* JParameterManager::GetParameter(std::string name, T& val) {
0168 
0169     auto result = m_parameters.find(ToLower(name));
0170     if (result == m_parameters.end()) {
0171         return nullptr;
0172     }
0173     Parse(result->second->GetValue(),val);
0174     result->second->SetIsUsed(true);
0175     return result->second;
0176 }
0177 
0178 
0179 /// @brief Retrieves a parameter value
0180 ///
0181 /// @param [in] name    The name of the parameter to retrieve
0182 /// @returns            The parameter value
0183 /// @throws JException  in case the parameter is not found
0184 ///
0185 template<typename T>
0186 T JParameterManager::GetParameterValue(std::string name) {
0187     T t;
0188     auto result = m_parameters.find(ToLower(name));
0189     if (result == m_parameters.end()) {
0190         throw JException("Unknown parameter \"%s\"", name.c_str());
0191     }
0192     result->second->SetIsUsed(true);
0193     Parse(result->second->GetValue(),t);
0194     return t;
0195 }
0196 
0197 
0198 /// @brief Sets a configuration parameter
0199 /// @param [in] name        The parameter name
0200 /// @param [in] val         The parameter value. This may be typed, or it may be a string.
0201 /// @return                 Pointer to the JParameter that was either created or updated.
0202 ///                         Note that this is owned by this JParameterManager, so do not delete.
0203 template<typename T>
0204 JParameter* JParameterManager::SetParameter(std::string name, T val) {
0205 
0206     auto result = m_parameters.find(ToLower(name));
0207 
0208     if (result == m_parameters.end()) {
0209         auto* param = new JParameter {name, Stringify(val), "", "", false, false};
0210         m_parameters[ToLower(name)] = param;
0211         return param;
0212     }
0213     result->second->SetValue(Stringify(val));
0214     result->second->SetIsDefault(false);
0215     return result->second;
0216 }
0217 
0218 
0219 
0220 /// @brief Retrieve a configuration parameter, if necessary creating it with the provided default value
0221 ///
0222 /// @param [in] name            The parameter name. Must not contain spaces.
0223 /// @param [in,out] val         Reference to a variable which has been set to the desired default value.
0224 /// @param [in] description     Optional description, e.g. units, set or range of valid values, etc.
0225 ///
0226 /// @details Upon entry, the value in "val" should be set to the desired default value. It
0227 /// will be overwritten if a value for the parameter already exists because
0228 /// it was given by the user either on the command line or in a configuration
0229 /// file. If the parameter does not already exist, it is created and its value set
0230 /// to that of "val". Upon exit, "val" will always contain the value that should be used
0231 /// for event processing.
0232 ///
0233 /// If a parameter with the given name already exists, it will be checked to see
0234 /// if the parameter already has a default value assigned (this is kept separate
0235 /// from the actual value of the parameter used and is maintained purely for
0236 /// bookkeeping purposes). If it does not have a default value, then the value
0237 /// of "val" upon entry is saved as the default. If it does have a default, then
0238 /// the value of the default is compared to the value of "val" upon entry. If the
0239 /// two do not match, then a warning message is printed to indicate to the user
0240 /// that two different default values are being set for this parameter.
0241 ///
0242 /// Parameters specified on the command line using the "-Pkey=value" syntax will
0243 /// not have a default value at the time the parameter is created.
0244 ///
0245 template<typename T>
0246 JParameter* JParameterManager::SetDefaultParameter(std::string name, T& val, std::string description) {
0247 
0248     std::lock_guard<std::mutex> lock(m_mutex);
0249     JParameter* param = nullptr;
0250     T t;
0251     auto result = m_parameters.find(ToLower(name));
0252     if (result != m_parameters.end()) {
0253         // We already have a value stored for this parameter
0254         param = result->second;
0255 
0256         if (!param->HasDefault()) {
0257             // We don't have a default value yet. Our existing value is a non-default value.
0258             param->SetHasDefault(true);
0259             param->SetDefault(Stringify(val));
0260             param->SetDescription(std::move(description));
0261         }
0262         else {
0263             // We tried to set the same default parameter twice. This is fine; this happens all the time
0264             // because we construct lots of copies of JFactories, each of which calls SetDefaultParameter on its own.
0265             // However, we still want to warn the user if the same parameter was declared with different values.
0266             Parse(param->GetDefault(),t);
0267             if (!Equals(val, t)) {
0268                 if (!param->IsConflicted()) {
0269                     // Only show this warning once per parameter
0270                     LOG_WARN(m_logger) << "Parameter '" << name << "' has conflicting defaults: '"
0271                                     << Stringify(val) << "' vs '" << param->GetDefault() << "'"
0272                                     << LOG_END;
0273                     param->SetIsConflicted(true);
0274                 }
0275                 // If we tried to set the same default parameter twice with different values, and there is no
0276                 // existing non-default value, we remember the _latest_ default value. This way, we return the
0277                 // default provided by the caller, instead of the default provided by the mysterious interloper.
0278                 param->SetDefault(Stringify(val));
0279             }
0280             if (param->IsDefault()) {
0281                 param->SetValue(Stringify(val)); // Use _latest_ default value
0282             }
0283         }
0284     }
0285     else {
0286         // We are storing a value for this parameter for the first time
0287         auto valstr = Stringify(val);
0288         param = new JParameter {name, valstr, valstr, std::move(description), true, true};
0289 
0290         // Test whether parameter is one-to-one with its string representation.
0291         // If not, warn the user they may have a problem.
0292         
0293         Parse(valstr,t);
0294         if (!Equals(val, t)) {
0295             LOG_WARN(m_logger) << "Parameter '" << name << "' with value '" << valstr
0296                                << "' loses equality with itself after stringification" << LOG_END;
0297         }
0298         m_parameters[ToLower(name)] = param;
0299     }
0300 
0301     // Always put val through the stringification/parsing cycle to be consistent with
0302     // values passed in from config file, accesses from other threads
0303     Parse(param->GetValue(),t);
0304     val = t;
0305     param->SetIsUsed(true);
0306     return param;
0307 }
0308 
0309 /// @brief Retrieve a configuration parameter, if necessary creating it with the provided default value.
0310 ///
0311 /// @param [in] name            The parameter name. Must not contain spaces.
0312 /// @param [in,out] default_val Value to set the desired default value to (returned if no value yet set for parameter).
0313 /// @param [in] description     Optional description, e.g. units, set or range of valid values, etc.
0314 /// @return                     Current value of parameter
0315 ///
0316 /// @details This is a convenience method that wraps SetDefaultParameter. The difference
0317 /// is that this has the default passed by value (not by reference) and the value of the parameter is returned
0318 /// by value. This allows a slightly different form for declaring configuration parameters with a default value.
0319 /// e.g.
0320 ///     auto thresh = jpp->RegisterParameter("SystemA:threshold", 1.3, "threshold in MeV");
0321 ///
0322 template <typename T>
0323 inline T JParameterManager::RegisterParameter(std::string name, const T default_val, std::string description){
0324     T val = default_val;
0325     SetDefaultParameter(name.c_str(), val, description);
0326     return val;
0327 }
0328 
0329 
0330 #if __cplusplus >= 201703L
0331 /// @brief Basic implementation of Parse for C++17 and newer. Provides a helpful error message when attempting to parse a type that doesn't come with a stream operator.
0332 template <typename T>
0333 void JParameterManager::Parse(const std::string& s, T& out) {
0334     constexpr bool parseable = JTypeInfo::is_parseable<T>::value;
0335     static_assert(parseable, "Type is not automatically parseable by JParameterManager. To use, provide a template specialization for JParameterManager::Parse(std::string in, T& out).");
0336     if constexpr (parseable) {
0337         std::stringstream ss(s);
0338         ss >> out;
0339     }
0340 }
0341 #else
0342 /// @brief Basic implementation of Parse for C++14 and earlier.
0343 template <typename T>
0344 void JParameterManager::Parse(const std::string& s, T& out) {
0345     std::stringstream ss(s);
0346     ss >> out;
0347 }
0348 #endif
0349 
0350 
0351 /// @brief Specialization for string. 
0352 /// The stream operator is not only redundant here, but it also splits the string (see Issue #191)
0353 template <>
0354 inline void JParameterManager::Parse(const std::string& value, std::string& out) {
0355     out = value;
0356 }
0357 
0358 
0359 /// @brief Specialization for bool
0360 template <>
0361 inline void JParameterManager::Parse(const std::string& value, bool& val) {
0362     if (value == "0" || value =="false" || value == "off") val = false;
0363     else if (value == "1" || value == "true" || value == "on") val = true;
0364     else throw JException("'%s' not parseable as bool", value.c_str());
0365 }
0366 
0367 // @brief Template to parse a string and return in an array
0368 template<typename T, size_t N>
0369 inline void JParameterManager::Parse(const std::string& value,std::array<T,N> &val) {
0370     std::string s;
0371     std::stringstream ss(value);
0372     int indx = 0;
0373     while (getline(ss, s, ',')) {
0374         T t;
0375         Parse(s, t);
0376         val[indx++]= t;
0377     }    
0378 }
0379 
0380 /// @brief Specialization for std::vector<std::string>
0381 template<typename T>
0382 inline void JParameterManager::Parse(const std::string& value, std::vector<T> &val) {
0383     std::stringstream ss(value);
0384     std::string s;
0385     val.clear(); // clearing the input vector to ensure no dulication which can be caused due to val.push_back(t);
0386     while (getline(ss, s, ',')) {
0387         T t;
0388         Parse(s, t);
0389         val.push_back(t);
0390     }
0391 }
0392 
0393 /// @brief Specialization for JLogger::Level enum
0394 template <>
0395 inline void JParameterManager::Parse(const std::string& in, JLogger::Level& out) {
0396     std::string token(in);
0397     std::transform(in.begin(), in.end(), token.begin(), ::tolower);
0398     if (std::strcmp(token.c_str(), "trace") == 0) { 
0399         out = JLogger::Level::TRACE;
0400     }
0401     else if (std::strcmp(token.c_str(), "debug") == 0) { 
0402         out = JLogger::Level::DEBUG;
0403     }
0404     else if (std::strcmp(token.c_str(), "info") == 0) { 
0405         out = JLogger::Level::INFO;
0406     }
0407     else if (std::strcmp(token.c_str(), "warn") == 0) { 
0408         out = JLogger::Level::WARN;
0409     }
0410     else if (std::strcmp(token.c_str(), "error") == 0) { 
0411         out = JLogger::Level::ERROR;
0412     }
0413     else if (std::strcmp(token.c_str(), "fatal") == 0) { 
0414         out = JLogger::Level::FATAL;
0415     }
0416     else if (std::strcmp(token.c_str(), "off") == 0) { 
0417         out = JLogger::Level::OFF;
0418     }
0419     else {
0420         throw JException("Unable to parse log level: '%s'. Options are: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF", in.c_str());
0421     }
0422 }
0423 
0424 #if __cplusplus >= 201703L
0425 /// @brief Basic implementation of Stringify for C++17 and newer. Provides a helpful error message when attempting to stringify a type that doesn't come with a stream operator.
0426 template <typename T>
0427 inline std::string JParameterManager::Stringify(const T& value) {
0428     constexpr bool serializable = JTypeInfo::is_serializable<T>::value;
0429     static_assert(serializable, "Type is not automatically serializable by JParameterManager. To use, provide a template specialization for JParameterManager::Stringify(const T& val) -> std::string.");
0430     if constexpr (serializable) {
0431         std::stringstream ss;
0432         ss << value;
0433         return ss.str();
0434     }
0435 }
0436 #else
0437 /// @brief Basic implementation of Parse for C++14 and earlier.
0438 template <typename T>
0439 inline std::string JParameterManager::Stringify(const T& value) {
0440     std::stringstream ss;
0441     ss << value;
0442     return ss.str();
0443 }
0444 #endif
0445 
0446 
0447 template <>
0448 inline std::string JParameterManager::Stringify(const float& value) {
0449     std::stringstream ss;
0450     // Use enough precision to make sure that the "float -> string -> float" roundtrip is exact. 
0451     // The stringstream << operator is smart enough to give us a clean representation when one is available.
0452     ss << std::setprecision(std::numeric_limits<float>::max_digits10) << value;
0453     return ss.str();
0454 }
0455 template <>
0456 inline std::string JParameterManager::Stringify(const double& value) {
0457     std::stringstream ss;
0458     // Use enough precision to make sure that the "double -> string -> double" roundtrip is exact. 
0459     // The stringstream << operator is smart enough to give us a clean representation when one is available.
0460     ss << std::setprecision(std::numeric_limits<double>::max_digits10) << value;
0461     return ss.str();
0462 }
0463 template <>
0464 inline std::string JParameterManager::Stringify(const long double& value) {
0465     std::stringstream ss;
0466     // Use enough precision to make sure that the "long double -> string -> long double" roundtrip is exact. 
0467     // The stringstream << operator is smart enough to give us a clean representation when one is available.
0468     ss << std::setprecision(std::numeric_limits<long double>::max_digits10) << value;
0469     return ss.str();
0470 }
0471 
0472 // @brief Specialization for strings. The stream operator is not only redundant here, but it also splits the string (see Issue #191)
0473 template <>
0474 inline std::string JParameterManager::Stringify(const std::string& value) {
0475     return value;
0476 }
0477 
0478 
0479 template <typename T>
0480 inline std::string JParameterManager::Stringify(const std::vector<T> &values) {
0481     std::stringstream ss;
0482     size_t len = values.size();
0483     for (size_t i = 0; i+1 < len; ++i) {
0484         ss << values[i];
0485         ss << ",";
0486     }
0487     if (len != 0) {
0488         ss << values[len-1];
0489     }
0490     return ss.str();
0491 }
0492 
0493 
0494 // @brief Template for generically stringifying an array
0495 template <typename T, size_t N>
0496 inline std::string JParameterManager::Stringify(const std::array<T,N> &values) {
0497     std::stringstream ss;
0498     size_t len = values.size();
0499     for (size_t i = 0; i+1 < N; ++i) {
0500         ss << values[i];
0501         ss << ",";
0502     }
0503     if (len != 0) {
0504         ss << values[len-1];
0505     }
0506     return ss.str();
0507 }
0508 
0509 // @brief Equals() is called internally by SetDefaultParameter. This allows the user to define equivalence relations on custom data types so that
0510 // they can silence any spurious "loses equality with itself after stringification" warnings. Generally you should try specializing Stringify and Parse
0511 // first to normalize your data representation.
0512 template <typename T>
0513 inline bool JParameterManager::Equals(const T& lhs, const T& rhs) {
0514     return lhs == rhs;
0515 }
0516 
0517 
0518 
0519