Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-21 08:39:02

0001 
0002 #include "JWiringService.h"
0003 #include "toml.hpp"
0004 #include <exception>
0005 #include <memory>
0006 #include <set>
0007 
0008 namespace jana::services {
0009 
0010 JWiringService::JWiringService() {
0011     SetPrefix("jana");
0012 }
0013 
0014 void JWiringService::Init() {
0015     // User is _only_ allowed to specify wiring file via parameter
0016     // This way, we can restrict calling JWiringService::Init until inside JApplication::Init
0017     // Then we can load the wiring file exactly once. All WiredFactoryGenerators 
0018     // (recursively) load files
0019 
0020     if (!m_wirings_input_file().empty()) {
0021         LOG_INFO(GetLogger()) << "JWiringService: Using wiring file: " << *m_wirings_input_file << LOG_END;
0022         ApplyWiringSet(ParseWiringSetFromFilename(*m_wirings_input_file));
0023     }
0024     else {
0025         LOG_INFO(GetLogger()) << "JWiringService: No wiring file used" << LOG_END;
0026     }
0027 }
0028 
0029 void JWiringService::ApplyWiringSet(JWiringService::WiringSet&& wiring_set) {
0030 
0031     // Recursively overlay any included files
0032     OverlayAllIncludes(wiring_set);
0033 
0034     // Clear everything from before
0035     m_wiring_set = std::move(wiring_set);
0036     m_added_prefixes.clear();
0037 
0038     // Populate lookup table
0039     for (auto& it: m_wiring_set.wirings) {
0040         if (it.second->action == Action::Add) {
0041             auto& wiring = it.second;
0042             m_added_prefixes[{wiring->type_name, wiring->plugin_name}].push_back(wiring->prefix);
0043         }
0044     }
0045 }
0046 
0047 void JWiringService::OverlayAllIncludes(WiringSet &wiring_set) {
0048     for (auto& include_file_name : wiring_set.include_file_names) {
0049         auto include_wiring_set = ParseWiringSetFromFilename(include_file_name);
0050         OverlayAllIncludes(include_wiring_set);
0051         OverlayWiringSet(wiring_set, include_wiring_set);
0052     }
0053 }
0054 
0055 std::unique_ptr<JWiringService::Wiring> ParseWiring(const toml::table& f) {
0056 
0057     auto wiring = std::make_unique<JWiringService::Wiring>();
0058 
0059     auto unparsed_action = f["action"].value<std::string>().value_or("(missing)");
0060     if (unparsed_action == "update") {
0061         wiring->action = JWiringService::Action::Update;
0062     }
0063     else if (unparsed_action == "add") {
0064         wiring->action = JWiringService::Action::Add;
0065     }
0066     else if (unparsed_action == "remove") {
0067         wiring->action = JWiringService::Action::Remove;
0068     }
0069     else {
0070         throw JException("JWiringService: Invalid action '%s'! Valid values are {'update', 'add', 'remove'}", unparsed_action.c_str());
0071     }
0072     wiring->plugin_name = f["plugin_name"].value<std::string>().value_or("");
0073     wiring->type_name = f["type_name"].value<std::string>().value();
0074     wiring->prefix = f["prefix"].value<std::string>().value();
0075     wiring->level = parseEventLevel(f["level"].value_or<std::string>("None"));
0076 
0077     auto input_names = f["input_names"].as_array();
0078     if (input_names != nullptr) {
0079         if (wiring->action == JWiringService::Action::Remove) {
0080             throw JException("Removed wiring has superfluous input names");
0081         }
0082         for (const auto& input_name : *input_names) {
0083             wiring->input_names.push_back(input_name.value<std::string>().value());
0084         }
0085     }
0086 
0087     auto variadic_input_names = f["variadic_input_names"].as_array();
0088     if (variadic_input_names != nullptr) {
0089         if (wiring->action == JWiringService::Action::Remove) {
0090             throw JException("Removed wiring has superfluous variadic input names");
0091         }
0092         for (const auto& input_name_vec : *variadic_input_names) {
0093             std::vector<std::string> temp;
0094             for (const auto& input_name : *(input_name_vec.as_array())) {
0095                 temp.push_back(input_name.as_string()->get());
0096             }
0097             wiring->variadic_input_names.push_back(temp);
0098         }
0099     }
0100 
0101     auto input_levels = f["input_levels"].as_array();
0102     if (input_levels != nullptr) {
0103         if (wiring->action == JWiringService::Action::Remove) {
0104             throw JException("Removed wiring has superfluous input levels");
0105         }
0106         for (const auto& input_level : *input_levels) {
0107             wiring->input_levels.push_back(parseEventLevel(input_level.value<std::string>().value()));
0108         }
0109     }
0110 
0111     auto variadic_input_levels = f["variadic_input_levels"].as_array();
0112     if (variadic_input_levels != nullptr) {
0113         if (wiring->action == JWiringService::Action::Remove) {
0114             throw JException("Removed wiring has superfluous variadic input levels");
0115         }
0116         for (const auto& input_level : *variadic_input_levels) {
0117             wiring->variadic_input_levels.push_back(parseEventLevel(input_level.value<std::string>().value()));
0118         }
0119     }
0120 
0121     auto output_names = f["output_names"].as_array();
0122     if (output_names != nullptr) {
0123         if (wiring->action == JWiringService::Action::Remove) {
0124             throw JException("Removed wiring has superfluous output names");
0125         }
0126         for (const auto& output_name : *f["output_names"].as_array()) {
0127             wiring->output_names.push_back(output_name.value<std::string>().value());
0128         }
0129     }
0130 
0131     auto variadic_output_names = f["variadic_output_names"].as_array();
0132     if (variadic_output_names != nullptr) {
0133         if (wiring->action == JWiringService::Action::Remove) {
0134             throw JException("Removed wiring has superfluous variadic output names");
0135         }
0136         for (const auto& output_name_vec : *variadic_output_names) {
0137             std::vector<std::string> temp;
0138             for (const auto& output_name : *(output_name_vec.as_array())) {
0139                 temp.push_back(output_name.as_string()->get());
0140             }
0141             wiring->variadic_output_names.push_back(temp);
0142         }
0143     }
0144 
0145     auto configs = f["configs"].as_table();
0146     if (configs != nullptr) {
0147         for (const auto& config : *configs) {
0148             std::string config_name(config.first);
0149             // For now, parse all config values as strings. 
0150             // Later we may go for a deeper integration with toml types and/or with JParameterManager.
0151             wiring->configs[config_name] = config.second.value<std::string>().value();
0152         }
0153     }
0154 
0155     return wiring;
0156 }
0157 
0158 
0159 JWiringService::WiringSet JWiringService::ParseWiringSet(const toml::table& table) {
0160 
0161     WiringSet wiring_set;
0162 
0163     // Parse include file names
0164     auto includes = table["includes"].as_array();
0165     if (includes != nullptr) {
0166         for (const auto& include : *includes) {
0167             wiring_set.include_file_names.push_back(include.as<std::string>()->get());
0168         }
0169     }
0170 
0171     // Parse plugin names
0172     auto plugins = table["plugins"].as_array();
0173     if (plugins != nullptr) {
0174         for (const auto& plugin_name : *plugins) {
0175             wiring_set.plugin_names.push_back(plugin_name.as<std::string>()->get());
0176         }
0177     }
0178 
0179     // Parse shared parameters
0180     auto shared_params = table["configs"].as_table();
0181     if (shared_params != nullptr) {
0182         for (const auto& param : *shared_params) {
0183             std::string key(param.first);
0184             std::string val = *param.second.value<std::string>();
0185             wiring_set.shared_parameters[key] = val;
0186         }
0187     }
0188 
0189     // Parse use_short_names
0190     auto use_short_names = table["use_short_names"].as<bool>();
0191     if (use_short_names != nullptr) {
0192         wiring_set.use_short_names = use_short_names->get();
0193     }
0194 
0195     // Parse wirings
0196     auto wirings_array = table["wiring"].as_array();
0197     if (wirings_array != nullptr) {
0198         for (const auto& wiring_node : *wirings_array) {
0199             auto wiring_table = wiring_node.as_table();
0200             if (wiring_table != nullptr) {
0201                 auto wiring = ParseWiring(*wiring_table);
0202 
0203                 // Check for uniqueness before adding
0204                 auto it = wiring_set.wirings.find(wiring->prefix);
0205                 if (it != wiring_set.wirings.end()) {
0206                     throw JException("Duplicated prefix '%s' in wiring set", wiring->prefix.c_str());
0207                 }
0208 
0209                 // Add to wiring set
0210                 wiring_set.wirings[wiring->prefix] = std::move(wiring);
0211             }
0212         }
0213     }
0214 
0215     return wiring_set;
0216 }
0217 
0218 JWiringService::WiringSet JWiringService::ParseWiringSetFromFilename(const std::string& filename) {
0219     try {
0220         auto table = toml::parse_file(filename);
0221         return ParseWiringSet(table);
0222     }
0223     catch (const toml::parse_error& err) {
0224         auto e = JException("Error parsing TOML file: '%s'", filename.c_str());
0225         e.nested_exception = std::current_exception();
0226         throw e;
0227     }
0228 }
0229 
0230 JWiringService::Wiring* JWiringService::GetWiring(const std::string& prefix) const {
0231     auto it = m_wiring_set.wirings.find(prefix);
0232     if (it == m_wiring_set.wirings.end()) {
0233         return nullptr;
0234     }
0235     return it->second.get();
0236 }
0237 
0238 const std::vector<std::string>&
0239 JWiringService::GetPrefixesForAddedInstances(const std::string& plugin_name, const std::string& type_name) const {
0240 
0241     auto it = m_added_prefixes.find({type_name, plugin_name});
0242     if (it == m_added_prefixes.end()) {
0243         return m_no_added_prefixes;
0244     }
0245     return it->second;
0246 }
0247 
0248 void JWiringService::OverlayWiringSet(WiringSet &above, const WiringSet &below) {
0249 
0250     // TODO: Figure out merging of includes, plugins, use_short_names
0251 
0252     // Iterate over 'below' to ensure that every wiring from below propagates above
0253     for (auto& below_it: below.wirings) {
0254         auto& below_wiring = below_it.second;
0255         auto& prefix = below_wiring->prefix;
0256         const auto& above_it = above.wirings.find(prefix);
0257         if (above_it == above.wirings.end()) {
0258             // This is a new wiring, so we add it and we are done
0259             above.wirings[prefix] = std::make_unique<Wiring>(*below_wiring);
0260         }
0261         else {
0262             // Wiring shows up in both places, so we need to overlay the two
0263             auto& above_wiring = above_it->second;
0264 
0265             // First we validate that the two wirings are compatible
0266             if (above_wiring->type_name != below_wiring->type_name) {
0267                 throw JException("Wiring mismatch: type name '%s' vs '%s'",
0268                                  above_wiring->type_name.c_str(), below_wiring->type_name.c_str());
0269             }
0270             if (above_wiring->plugin_name != below_wiring->plugin_name) {
0271                 throw JException("Wiring mismatch: plugin name '%s' vs '%s'",
0272                                  above_wiring->plugin_name.c_str(), below_wiring->plugin_name.c_str());
0273             }
0274 
0275             // Next we do the overlay, which modifies above_wiring
0276             Overlay(*above_wiring, *below_wiring);
0277         }
0278     }
0279 }
0280 
0281 void JWiringService::Overlay(Wiring& above, const Wiring& below) {
0282 
0283     // In theory this should be handled by the caller, but let's check just in case
0284     if (above.plugin_name != below.plugin_name) throw JException("Plugin name mismatch!");
0285     if (above.type_name != below.type_name) throw JException("Type name mismatch!");
0286 
0287     // Overlay actions
0288     if (above.action == Action::Add) {
0289         if (below.action != Action::Remove) {
0290             throw JException("Attempted to add a wiring that is already present. plugin=%s, prefix=%s",
0291                              above.plugin_name.c_str(), above.prefix.c_str());
0292         }
0293         else {
0294             // Add(Remove(wiring)) = Update(wiring)
0295             above.action = Action::Update;
0296         }
0297     }
0298     else if (above.action == Action::Update) {
0299         if (below.action == Action::Add) {
0300             above.action = Action::Add;
0301         }
0302         else if (below.action == Action::Remove) {
0303             throw JException("Attempted to update a wiring that has been removed. plugin=%s, prefix=%s",
0304                              above.plugin_name.c_str(), above.prefix.c_str());
0305         }
0306         // Update(Update(wiring)) => Update(wiring)
0307     }
0308     else if (above.action == Action::Remove) {
0309         if (below.action == Action::Remove) {
0310             throw JException("Attempted to remove a wiring that has already been removed. plugin=%s, prefix=%s",
0311                              above.plugin_name.c_str(), above.prefix.c_str());
0312         }
0313         // Remove(Add(wiring)) => Remove(wiring)  // This one is odd, might reconsider
0314         // Remove(Update(wiring)) => Remove(wiring)
0315     }
0316 
0317     if (above.input_names.empty() && !below.input_names.empty()) {
0318         above.input_names = std::move(below.input_names);
0319     }
0320     if (above.input_levels.empty() && !below.input_levels.empty()) {
0321         above.input_levels = std::move(below.input_levels);
0322     }
0323     if (above.output_names.empty() && !below.output_names.empty()) {
0324         above.output_names = std::move(below.output_names);
0325     }
0326     for (const auto& [key, val] : below.configs) {
0327         if (above.configs.find(key) == above.configs.end()) {
0328             above.configs[key] = val;
0329         }
0330     }
0331 }
0332 
0333 const std::map<std::string, std::string>& JWiringService::GetSharedParameters() const {
0334     return m_wiring_set.shared_parameters;
0335 }
0336 
0337 void JWiringService::CheckAllWiringsAreUsed() {
0338     std::vector<JWiringService::Wiring*> m_unused_wirings;
0339     for (const auto& wiring : m_wiring_set.wirings) {
0340         if (wiring.second->is_used == false) {
0341             m_unused_wirings.push_back(wiring.second.get());
0342         }
0343     }
0344     if (m_unused_wirings.size() != 0) {
0345         LOG_ERROR(GetLogger()) << "Wirings were found but never used:";
0346         for (auto wiring : m_unused_wirings) {
0347             LOG_ERROR(GetLogger()) << "  " << wiring->type_name << " " << wiring->prefix;
0348         }
0349         throw JException("Unused wirings found");
0350     }
0351 }
0352 
0353 } // namespace jana::services