AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
node_manifest.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "auto_apms_behavior_tree_core/node/node_manifest.hpp"
16
17#include <fstream>
18#include <map>
19
20#include "auto_apms_behavior_tree_core/exceptions.hpp"
21#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
22#include "auto_apms_util/resource.hpp"
23#include "auto_apms_util/string.hpp"
24
26{
27
29{
30 if (identity.empty()) {
31 throw auto_apms_util::exceptions::ResourceIdentityFormatError(
32 "Cannot create node manifest resource identity from empty string. Must be <package_name>" +
33 std::string(_AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_IDENTITY_ALIAS_SEP) + "<metadata_id>.");
34 }
35 if (std::size_t pos = identity.find(_AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_IDENTITY_ALIAS_SEP);
36 pos == std::string::npos) {
37 // If only a single token is given, assume it's metadata_id
38 package_name = "";
40 } else {
41 package_name = identity.substr(0, pos);
42 metadata_id = identity.substr(pos + std::string(_AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_IDENTITY_ALIAS_SEP).size());
43 }
44 if (metadata_id.empty()) {
45 throw auto_apms_util::exceptions::ResourceIdentityFormatError(
46 "Node manifest resource identity string '" + identity + "' is invalid. Metadata ID must not be empty.");
47 }
48}
49
51: NodeManifestResourceIdentity(std::string(identity))
52{
53}
54
55bool NodeManifestResourceIdentity::operator==(const NodeManifestResourceIdentity & other) const
56{
57 return str() == other.str();
58}
59
60bool NodeManifestResourceIdentity::operator<(const NodeManifestResourceIdentity & other) const
61{
62 return str() < other.str();
63}
64
66{
67 return package_name + _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_IDENTITY_ALIAS_SEP + metadata_id;
68}
69
70bool NodeManifestResourceIdentity::empty() const { return package_name.empty() && metadata_id.empty(); }
71
72std::set<NodeManifestResourceIdentity> getNodeManifestResourceIdentities(const std::set<std::string> & exclude_packages)
73{
74 std::set<NodeManifestResourceIdentity> identities;
76 _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_TYPE_NAME__NODE_MANIFEST, exclude_packages)) {
77 std::string content;
78 std::string base_path;
79 if (ament_index_cpp::get_resource(
80 _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_TYPE_NAME__NODE_MANIFEST, p, content, &base_path)) {
81 for (const auto & line :
82 auto_apms_util::splitString(content, _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_MARKER_FILE_LINE_SEP)) {
83 const std::vector<std::string> parts = auto_apms_util::splitString(
84 line, _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_MARKER_FILE_FIELD_PER_LINE_SEP, false);
85 if (parts.size() > 0) {
87 i.package_name = p;
88 i.metadata_id = parts[0];
89 identities.insert(i);
90 }
91 }
92 }
93 }
94 return identities;
95}
96
98
100
101NodeManifest NodeManifest::fromFiles(const std::vector<std::string> & paths)
102{
103 NodeManifest manifest;
104 for (const auto & path : paths) {
105 try {
106 manifest.merge(fromFile(path));
107 } catch (const std::exception & e) {
108 throw exceptions::NodeManifestError("Error creating node manifest from multiple files: " + std::string(e.what()));
109 }
110 }
111 return manifest;
112}
113
114namespace
115{
116std::map<NodeManifestResourceIdentity, NodeManifest> s_local_manifests;
117} // namespace
118
120 const NodeManifestResourceIdentity & id, const NodeManifest & manifest, bool allow_override)
121{
122 if (!allow_override) {
123 if (s_local_manifests.count(id)) {
124 throw exceptions::NodeManifestError(
125 "Cannot register local manifest '" + id.str() +
126 "': resource is already registered locally. Pass allow_override=true to override it.");
127 }
128 const auto existing_identities = getNodeManifestResourceIdentities();
129 if (existing_identities.count(id)) {
130 throw exceptions::NodeManifestError(
131 "Cannot register local manifest '" + id.str() +
132 "': resource is already registered in the ament index. Pass allow_override=true to override it.");
133 }
134 }
135 s_local_manifests[id] = manifest;
136}
137
139{
140 // Check the process-local registry first
141 if (const auto it = s_local_manifests.find(search_identity); it != s_local_manifests.end()) {
142 return it->second;
143 }
144 return NodeManifestResource(search_identity).getNodeManifest();
145}
146
147void NodeManifest::toFile(const std::string & file_path) const
148{
149 std::ofstream out_stream(file_path);
150 if (out_stream.is_open()) {
151 out_stream << this->encode();
152 out_stream.close();
153 } else {
154 throw exceptions::NodeManifestError("Error opening node manifest output file '" + file_path + "'.");
155 }
156}
157
158bool NodeManifest::contains(const std::string & node_name) const { return map_.find(node_name) != map_.end(); }
159
160NodeManifest::RegistrationOptions & NodeManifest::operator[](const std::string & node_name)
161{
162 if (contains(node_name)) return map_[node_name];
163 throw std::out_of_range{
164 "Node '" + node_name + "' doesn't exist in node manifest (Size: " + std::to_string(map_.size()) +
165 "). Use the add() method to add an entry."};
166}
167
168const NodeManifest::RegistrationOptions & NodeManifest::operator[](const std::string & node_name) const
169{
170 if (contains(node_name)) return map_.at(node_name);
171 throw std::out_of_range{
172 "Node '" + node_name + "' doesn't exist in node manifest (Size: " + std::to_string(map_.size()) + ")."};
173}
174
175NodeManifest & NodeManifest::add(const std::string & node_name, const RegistrationOptions & opt)
176{
177 if (contains(node_name)) {
178 throw exceptions::NodeManifestError{
179 "Node '" + node_name + "' already exists in node manifest (Size: " + std::to_string(map_.size()) + ")."};
180 }
181
182 if (!opt.valid()) {
183 throw exceptions::NodeManifestError(
184 "Cannot add node '" + node_name + "' to manifest. Parameter class_name must not be empty.");
185 }
186 map_[node_name] = opt;
187 return *this;
188}
189
190NodeManifest & NodeManifest::remove(const std::string & node_name)
191{
192 if (!contains(node_name)) {
193 throw std::out_of_range{
194 "Node '" + node_name + "' doesn't exist in node manifest, so the corresponding entry cannot be removed."};
195 }
196 map_.erase(node_name);
197 return *this;
198}
199
200NodeManifest & NodeManifest::merge(const NodeManifest & other, bool replace)
201{
202 for (const auto & [node_name, params] : other.map()) {
203 if (contains(node_name)) {
204 if (replace) {
205 map_.erase(node_name);
206 } else {
207 throw exceptions::NodeManifestError(
208 "Cannot merge node manifests, because node '" + node_name +
209 "' already exists and argument replace is false (Won't replace existing entries).");
210 }
211 }
212 add(node_name, params);
213 }
214 return *this;
215}
216
218 const NodeManifest & other, const std::string & with_namespace, const std::string & sep)
219{
220 for (const auto & [node_name, params] : other.map()) {
221 // Apply namespace prefix
222 const std::string final_name = with_namespace + sep + node_name;
223 if (contains(final_name)) {
224 throw exceptions::NodeManifestError(
225 "Cannot merge node manifests, because node '" + final_name + "' already exists.");
226 }
227 add(final_name, params);
228 }
229 return *this;
230}
231
232NodeManifest & NodeManifest::applyNodeNamespace(const std::string & ns, const std::string & sep)
233{
234 const NodeManifest::Map old_map = map_;
235 map_.clear();
236 for (const auto & [node_name, params] : old_map) {
237 const std::string final_name = ns + sep + node_name;
238 map_[final_name] = params;
239 }
240 return *this;
241}
242
243std::vector<std::string> NodeManifest::getNodeNames()
244{
245 std::vector<std::string> names;
246 names.reserve(map_.size());
247 for (const auto & [name, _] : map_) names.push_back(name);
248 return names;
249}
250
251size_t NodeManifest::size() const { return map_.size(); }
252
253bool NodeManifest::empty() const { return map_.empty(); }
254
255const NodeManifest::Map & NodeManifest::map() const { return map_; }
256
257NodeManifestResource::NodeManifestResource(const Identity & search_identity)
258{
259 std::set<std::string> search_packages;
260 if (!search_identity.package_name.empty()) {
261 search_packages.insert(search_identity.package_name);
262 } else {
263 search_packages =
264 auto_apms_util::getPackagesWithResourceType(_AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_TYPE_NAME__NODE_MANIFEST);
265 }
266
267 size_t matching_count = 0;
268 for (const auto & p : search_packages) {
269 std::string content;
270 std::string base_path;
271 if (ament_index_cpp::get_resource(
272 _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_TYPE_NAME__NODE_MANIFEST, p, content, &base_path)) {
273 std::vector<std::string> lines =
274 auto_apms_util::splitString(content, _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_MARKER_FILE_LINE_SEP);
275 for (const std::string & line : lines) {
276 std::vector<std::string> parts = auto_apms_util::splitString(
277 line, _AUTO_APMS_BEHAVIOR_TREE_CORE__RESOURCE_MARKER_FILE_FIELD_PER_LINE_SEP, false);
278 if (parts.size() != 3) {
279 throw auto_apms_util::exceptions::ResourceError(
280 "Invalid node manifest resource file (Package: '" + p + "'). Invalid line: " + line + ".");
281 }
282
283 // Determine if resource is matching
284 std::string found_metadata_id = parts[0];
285 if (found_metadata_id != search_identity.metadata_id) continue;
286
287 // Found matching resource - Increase counter
288 matching_count++;
289
290 // Now fill the other member variables in case the resource matches (if match is not unique, error is thrown
291 // later and the object is discarded)
292
293 unique_identity_.package_name = p;
294 unique_identity_.metadata_id = found_metadata_id;
295 node_manifest_file_path_ = base_path + "/" + parts[1];
296 node_model_file_path_ = base_path + "/" + parts[2];
297 node_manifest_ = NodeManifest::fromFile(node_manifest_file_path_);
298
299 tinyxml2::XMLDocument model_doc;
300 if (model_doc.LoadFile(node_model_file_path_.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
301 throw exceptions::TreeDocumentError(
302 "Error parsing the node model associated with node manifest " + unique_identity_.str());
303 }
304 node_model_ = TreeDocument::getNodeModel(model_doc, node_manifest_);
305 }
306 }
307 }
308
309 if (matching_count == 0) {
310 throw auto_apms_util::exceptions::ResourceError(
311 "No node manifest resource was found using identity '" + search_identity.str() + "'.");
312 }
313 if (matching_count > 1) {
314 throw auto_apms_util::exceptions::ResourceError(
315 "There are multiple node manifest resources with metadata ID '" + search_identity.str() + "'.");
316 }
317}
318
319NodeManifestResource::NodeManifestResource(const std::string & search_identity)
321{
322}
323
325: NodeManifestResource(std::string(search_identity))
326{
327}
328
329const NodeManifestResource::Identity & NodeManifestResource::getIdentity() const { return unique_identity_; }
330
331const NodeManifest & NodeManifestResource::getNodeManifest() const { return node_manifest_; }
332
333const NodeModelMap & NodeManifestResource::getNodeModel() const { return node_model_; }
334
335} // namespace auto_apms_behavior_tree::core
Class containing behavior tree node manifest resource data.
NodeManifestResource(const Identity &search_identity)
Constructor of a node manifest resource object.
const NodeModelMap & getNodeModel() const
Get the node model object associated with this resource.
const Identity & getIdentity() const
Get the unique identity for this resource.
const NodeManifest & getNodeManifest() const
Get the node manifest object associated with this resource.
Data structure for information about which behavior tree node plugin to load and how to configure the...
NodeManifest & applyNodeNamespace(const std::string &ns, const std::string &sep=_AUTO_APMS_BEHAVIOR_TREE_CORE__NODE_NAMESPACE_DEFAULT_SEP)
Apply a namespace prefix to all node names in this manifest.
std::map< std::string, NodeRegistrationOptions > Map
Mapping of a node's name and its registration parameters.
static NodeManifest fromFiles(const std::vector< std::string > &paths)
Create a node plugin manifest from multiple files. They are loaded in the given order.
size_t size() const
Get the number of behavior tree nodes this manifest holds registration options for.
NodeManifest & add(const std::string &node_name, const RegistrationOptions &opt)
Add registration options for a behavior tree node to the manifest.
bool contains(const std::string &node_name) const
Determine if a behavior tree node has been added to the manifest.
NodeManifest & mergeWithNamespace(const NodeManifest &other, const std::string &with_namespace, const std::string &sep=_AUTO_APMS_BEHAVIOR_TREE_CORE__NODE_NAMESPACE_DEFAULT_SEP)
Merges another NodeManifest with this one using a namespace for the registration names.
bool empty() const
Determine whether any node registration options have been added to the manifest.
NodeManifest & merge(const NodeManifest &other, bool replace=false)
Merges another NodeManifest with this one.
const Map & map() const
Get a view of the internal map.
std::vector< std::string > getNodeNames()
Get all names of the behavior tree nodes specified by the manifest.
RegistrationOptions & operator[](const std::string &node_name)
Access the node manifest and retrieve registration options for a specific behavior tree node.
void toFile(const std::string &file_path) const
Write the node manifest to a file.
NodeManifest(const Map &map={})
Constructor of a NodeManifest data structure.
static NodeManifest fromResource(const NodeManifestResourceIdentity &search_identity)
Create a node manifest from a resource insinde the process-local lookup table or the ament index.
static void registerLocalManifest(const NodeManifestResourceIdentity &id, const NodeManifest &manifest, bool allow_override=false)
Register a manifest in the process-local lookup table.
NodeManifest & remove(const std::string &node_name)
Remove registration options for a behavior tree node.
Document Object Model (DOM) for the behavior tree XML schema. This class offers a programmatic approa...
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc, const NodeManifest &manifest)
Convert a behavior tree node model document to the corresponding data structure.
std::set< NodeManifestResourceIdentity > getNodeManifestResourceIdentities(const std::set< std::string > &exclude_packages={})
Get all registered behavior tree node manifest resource identities.
std::vector< std::string > splitString(const std::string &str, const std::string &delimiter, bool remove_empty=true)
Split a string into multiple tokens using a specific delimiter string (Delimiter may consist of multi...
Definition string.cpp:24
std::set< std::string > getPackagesWithResourceType(const std::string &resource_type, const std::set< std::string > &exclude_packages={})
Get a list of all package names that register a certain type of ament_index resources.
Definition resource.cpp:28
Core API for AutoAPMS's behavior tree implementation.
Definition behavior.hpp:32
NodeModelMap getNativeNodeModel()
Get a model of the native BehaviorTree.CPP nodes.
std::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
Struct that encapsulates the identity string for a registered behavior tree node manifest.
bool empty() const
Determine whether this node manifest resource identity object is considered empty.
NodeManifestResourceIdentity(const std::string &identity)
Constructor of a node manifest resource identity object.
NodeManifestResourceIdentity()=default
Constructor of an empty node manifest resource identity object.
std::string package_name
Name of the package that registers the behavior resource.
std::string metadata_id
Metadata ID determined when regitering the corresponding node manifest resource.
std::string str() const
Create the corresponding identity string.
bool valid() const
Verify that the options are valid (e.g. all required values are set).