Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:01:39

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