File indexing completed on 2024-05-05 14:17:19
0001 #include "introspection.h" 0002 0003 #include "arguments.h" 0004 #include "stringtools.h" 0005 0006 #include <tinyxml2.h> 0007 0008 #include <cassert> 0009 #include <cstring> // for strcmp... 0010 #include <memory> 0011 0012 namespace tx2 = tinyxml2; 0013 0014 // strcmp()'s return value is easy to misinterpret, especially for equality... 0015 static bool strequal(const char *c1, const char *c2) 0016 { 0017 return !strcmp(c1, c2); 0018 } 0019 0020 // This function exists to avoid making the many copies that a naive recursive implementation would make. 0021 // That it makes it possible to reserve() is just a nice extra. 0022 static void introspectionNodePathHelper(const IntrospectionNode *node, uint32 length, std::string *out) 0023 { 0024 if (node->parent) { 0025 introspectionNodePathHelper(node->parent, length + 1 + node->name.length(), out); 0026 *out += '/'; 0027 *out += node->name; 0028 } else { 0029 // root node; reserve just enough room in output string before we start filling it in 0030 out->reserve(length); 0031 } 0032 } 0033 0034 std::string IntrospectionNode::path() const 0035 { 0036 if (!parent) { 0037 return std::string("/"); 0038 } 0039 std::string ret; 0040 introspectionNodePathHelper(this, 0, &ret); 0041 return ret; 0042 } 0043 0044 IntrospectionTree::IntrospectionTree() 0045 : m_rootNode(new IntrospectionNode) 0046 { 0047 m_rootNode->parent = nullptr; 0048 // name stays empty for the root 0049 } 0050 0051 IntrospectionTree::~IntrospectionTree() 0052 { 0053 delete m_rootNode; 0054 m_rootNode = nullptr; 0055 } 0056 0057 bool IntrospectionTree::mergeXml(const char *xmlData, const char *_path) 0058 { 0059 // TODO use path to replace an empty root node name in the XML document 0060 // TODO is it OK to add interfaces to the root node? if so, review/test the code to ensure that it works! 0061 // TODO what about adding interfaces to other existing nodes? (this is not taken care of in current code!) 0062 // TODO check the path names (absolute / relative according to spec) 0063 // If things turn out to be complicated, it might be better to first create a detached tree from the XML 0064 // document, then check if it can be merged without conflicts, then merge it. That requires not rollback. 0065 tx2::XMLDocument doc; 0066 if (doc.Parse(xmlData) != tx2::XML_SUCCESS) { 0067 return false; 0068 } 0069 tx2::XMLElement *el = doc.RootElement(); 0070 if (!el || !strequal(el->Name(), "node")) { 0071 return false; 0072 } 0073 std::string path = _path; 0074 const tx2::XMLAttribute *attr = el->FirstAttribute(); 0075 if (attr && strequal(attr->Name(), "name")) { 0076 std::string intrinsicPath = attr->Value(); 0077 if (!path.empty() && path != intrinsicPath) { 0078 return false; 0079 } 0080 path = intrinsicPath; 0081 } 0082 0083 // ### Should we do this? if (path.empty()) { path = "/"; } 0084 0085 std::string leafName; 0086 IntrospectionNode *parent = findOrCreateParent(path.c_str(), &leafName); 0087 if (!parent || parent->children.count(leafName)) { 0088 // the second condition makes sure that we don't overwrite existing nodes 0089 return false; 0090 } 0091 0092 if (!addNode(parent, el)) { 0093 // TODO undo creation of parent nodes by findOrCreateParent() here 0094 0095 return false; 0096 } 0097 return true; 0098 } 0099 0100 IntrospectionNode *IntrospectionTree::findOrCreateParent(const char *path, std::string *leafName) 0101 { 0102 cstring csPath(path); 0103 if (!Arguments::isObjectPathValid(csPath)) { 0104 return nullptr; 0105 } 0106 std::string strPath(path, csPath.length); // prevent another strlen() 0107 std::vector<std::string> elements = split(strPath, '/', false); 0108 0109 IntrospectionNode *node = m_rootNode; 0110 0111 // the leaf node is to be created later, hence we omit the last path element 0112 for (size_t i = 0; i + 1 < elements.size(); i++) { 0113 std::map<std::string, IntrospectionNode *>::iterator it = node->children.find(elements[i]); 0114 if (it != node->children.end()) { 0115 node = it->second; 0116 } else { 0117 IntrospectionNode *newNode = new IntrospectionNode; 0118 newNode->name = elements[i]; 0119 node->children[elements[i]] = newNode; 0120 node = newNode; 0121 } 0122 } 0123 if (leafName) { 0124 if (elements.empty()) { 0125 leafName->clear(); 0126 } else { 0127 *leafName = elements.back(); 0128 } 0129 } 0130 return node; 0131 } 0132 0133 // careful with this one when not calling it from findOrCreateParent() or mergeXml() - if applied to a node 0134 // that was created earlier, it might delete children that shouldn't be deleted. 0135 void IntrospectionTree::pruneBranch(IntrospectionNode *node) 0136 { 0137 IntrospectionNode *const parent = node->parent; 0138 // remove the node itself, including any children 0139 removeNode(node); 0140 0141 // remove all now empty parents (except the root node) 0142 for (node = parent; node != m_rootNode; ) { 0143 assert(node->parent->children.count(node->name) == 1); 0144 if (node->children.size() == 1 && node->interfaces.empty()) { 0145 // we must have deleted that child while ascending; just remove the current node 0146 IntrospectionNode *exNode = node; 0147 node = node->parent; 0148 delete exNode; 0149 } else { 0150 // this node has other children, so we are going to keep it and update its children map 0151 // (we want to do this for the root node, too, so break here and do it after the loop) 0152 break; 0153 } 0154 } 0155 } 0156 0157 void IntrospectionTree::removeNode(IntrospectionNode *node) 0158 { 0159 std::map<std::string, IntrospectionNode *>::iterator it = node->children.begin(); 0160 for (; it != node->children.end(); ++it) { 0161 removeNode(it->second); 0162 } 0163 delete node; 0164 } 0165 0166 bool IntrospectionTree::addNode(IntrospectionNode *parent, const tx2::XMLElement *el) 0167 { 0168 const tx2::XMLAttribute *attr = el->FirstAttribute(); 0169 if (!attr || !strequal(attr->Name(), "name") || attr->Next()) { 0170 return false; 0171 } 0172 0173 // const bool isRootOfDocument = el == el->GetDocument()->RootElement(); 0174 0175 std::unique_ptr<IntrospectionNode> node(new IntrospectionNode); 0176 node->parent = parent; 0177 node->name = attr->Value(); 0178 0179 for (const tx2::XMLElement *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) { 0180 if (strequal(child->Name(), "node")) { 0181 if (!addNode(node.get(), child)) { 0182 return false; 0183 } 0184 } else if (strequal(child->Name(), "interface")) { 0185 if (!addInterface(node.get(), child)) { 0186 return false; 0187 } 0188 } 0189 } 0190 parent->children.emplace(node->name, node.get()); 0191 node.release(); 0192 return true; 0193 } 0194 0195 bool IntrospectionTree::addInterface(IntrospectionNode *node, const tx2::XMLElement *el) 0196 { 0197 const tx2::XMLAttribute *attr = el->FirstAttribute(); 0198 if (!attr || !strequal(attr->Name(), "name") || attr->Next()) { 0199 return false; 0200 } 0201 0202 Interface iface; 0203 iface.name = attr->Value(); 0204 0205 for (const tx2::XMLElement *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) { 0206 if (strequal(child->Name(), "method")) { 0207 if (!addMethod(&iface, child, Message::MethodCallMessage)) { 0208 return false; 0209 } 0210 } else if (strequal(child->Name(), "signal")) { 0211 if (!addMethod(&iface, child, Message::SignalMessage)) { 0212 return false; 0213 } 0214 } else if (strequal(child->Name(), "property")) { 0215 if (!addProperty(&iface, child)) { 0216 return false; 0217 } 0218 } else { 0219 return false; 0220 } 0221 } 0222 node->interfaces[iface.name] = iface; 0223 return true; 0224 } 0225 0226 bool IntrospectionTree::addMethod(Interface *iface, const tx2::XMLElement *el, Message::Type messageType) 0227 { 0228 const tx2::XMLAttribute *attr = el->FirstAttribute(); 0229 if (!attr || !strequal(attr->Name(), "name") || attr->Next()) { 0230 return false; 0231 } 0232 0233 Method method; 0234 method.type = messageType; 0235 method.name = attr->Value(); 0236 0237 for (const tx2::XMLElement *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) { 0238 if (strequal(child->Name(), "arg")) { 0239 if (!addArgument(&method, child, messageType)) { 0240 return false; 0241 } 0242 } else if (strequal(child->Name(), "annotation")) { 0243 // annotations are allowed, but we don't use them 0244 continue; 0245 } else { 0246 return false; 0247 } 0248 } 0249 iface->methods[method.name] = method; 0250 return true; 0251 } 0252 0253 bool IntrospectionTree::addArgument(Method *method, const tx2::XMLElement *el, Message::Type messageType) 0254 { 0255 Argument arg; 0256 arg.isDirectionOut = messageType == Message::SignalMessage; 0257 for (const tx2::XMLAttribute *attr = el->FirstAttribute(); attr; attr = attr->Next()) { 0258 if (strequal(attr->Name(), "name")) { 0259 arg.name = attr->Value(); 0260 } else if (strequal(attr->Name(), "type")) { 0261 arg.type = attr->Value(); 0262 // TODO validate (single complete type!) 0263 } else if (strequal(attr->Name(), "direction")) { 0264 if (strequal(attr->Value(), "in")) { 0265 if (messageType == Message::SignalMessage) { 0266 return false; 0267 } 0268 arg.isDirectionOut = false; 0269 } else if (strequal(attr->Value(), "out")) { 0270 arg.isDirectionOut = true; 0271 } else { 0272 return false; 0273 } 0274 } else { 0275 return false; 0276 } 0277 } 0278 if (arg.type.empty()) { 0279 return false; 0280 } 0281 method->arguments.push_back(arg); 0282 return true; 0283 } 0284 0285 bool IntrospectionTree::addProperty(Interface *iface, const tx2::XMLElement *el) 0286 { 0287 Property prop; 0288 prop.access = Property::Invalid; 0289 for (const tx2::XMLAttribute *attr = el->FirstAttribute(); attr; attr = attr->Next()) { 0290 if (strequal(attr->Name(), "name")) { 0291 // TODO validate 0292 prop.name = attr->Value(); 0293 } else if (strequal(attr->Name(), "type")) { 0294 // TODO validate - don't forget to check it's a single complete type 0295 prop.type = attr->Value(); 0296 } else if (strequal(attr->Name(), "access")) { 0297 if (strequal(attr->Value(), "readwrite")) { 0298 prop.access = Property::ReadWrite; 0299 } else if (strequal(attr->Value(), "read")) { 0300 prop.access = Property::Read; 0301 } else if (strequal(attr->Value(), "write")) { 0302 prop.access = Property::Write; 0303 } else { 0304 return false; 0305 } 0306 } else { 0307 return false; 0308 } 0309 } 0310 if (prop.name.empty() || prop.type.empty() || prop.access == Property::Invalid) { 0311 return false; 0312 } 0313 iface->properties[prop.name] = prop; 0314 return true; 0315 }