From 5ceb7a4b436b92e3c6538b6a091a5fcde5c5f8be Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Mon, 30 Jun 2014 14:08:01 +0200 Subject: [PATCH] data: added Graph data structure for storing code semantics in Storage This change adds graph data structures Token, Node, Edge and Graph for holding code semantics in the Storage and fills them with the parsed data. Things to note: * Token is the base for the classes Edge and Node and holds a unique id. * Graph is the owner of all Nodes and Edges. * A Node saves the Edges it is endpoint of. * An Edge saves the Nodes that are endpoints. * Nodes and Edges are not subclassed. The different Node and Edge types are distinguished by the enums NodeType and EdgeType. Type specific fields are therefore available for all types, a check when assigning them prohibits faulty usage. * Storage uses the Graph::getNodeHierarchy method, which is not strict about knowing type names beforehand. E.g. if A::B is passed in and A doesn't exist so far, then node A gets created as NODE_UNDEFINED with B as it's child. * The Graph methods addNodeAsPlainCopy and addEdgeAsPlainCopy can be used to create a sub-graph without reference to the original, but with the same ids. review id = 19 fortune cookie message = Your cheerful outlook is one of your best assets. --- src/lib/CMakeLists.txt | 20 +- src/lib/Project.cpp | 1 + src/lib/data/Edge.cpp | 13 -- src/lib/data/Edge.h | 22 -- src/lib/data/Element.cpp | 1 - src/lib/data/Element.h | 8 - src/lib/data/Graph.cpp | 9 - src/lib/data/Graph.h | 21 -- src/lib/data/Node.cpp | 1 - src/lib/data/Node.h | 11 - src/lib/data/Storage.cpp | 88 ++++++++ src/lib/data/Storage.h | 5 +- src/lib/data/TextLocation.cpp | 6 +- src/lib/data/TextLocation.h | 6 +- src/lib/data/graph/Edge.cpp | 146 +++++++++++++ src/lib/data/graph/Edge.h | 71 +++++++ src/lib/data/graph/Graph.cpp | 341 ++++++++++++++++++++++++++++++ src/lib/data/graph/Graph.h | 62 ++++++ src/lib/data/graph/Node.cpp | 248 ++++++++++++++++++++++ src/lib/data/graph/Node.h | 83 ++++++++ src/lib/data/graph/Token.cpp | 22 ++ src/lib/data/graph/Token.h | 30 +++ src/lib/utility/types.h | 6 + src/lib/utility/utilityString.cpp | 26 +++ src/lib/utility/utilityString.h | 13 ++ src/test/CMakeLists.txt | 2 + src/test/GraphTestSuite.h | 293 +++++++++++++++++++++++++ src/test/UtilityStringTestSuite.h | 53 +++++ 28 files changed, 1507 insertions(+), 101 deletions(-) delete mode 100644 src/lib/data/Edge.cpp delete mode 100644 src/lib/data/Edge.h delete mode 100644 src/lib/data/Element.cpp delete mode 100644 src/lib/data/Element.h delete mode 100644 src/lib/data/Graph.cpp delete mode 100644 src/lib/data/Graph.h delete mode 100644 src/lib/data/Node.cpp delete mode 100644 src/lib/data/Node.h create mode 100644 src/lib/data/graph/Edge.cpp create mode 100644 src/lib/data/graph/Edge.h create mode 100644 src/lib/data/graph/Graph.cpp create mode 100644 src/lib/data/graph/Graph.h create mode 100644 src/lib/data/graph/Node.cpp create mode 100644 src/lib/data/graph/Node.h create mode 100644 src/lib/data/graph/Token.cpp create mode 100644 src/lib/data/graph/Token.h create mode 100644 src/lib/utility/types.h create mode 100644 src/lib/utility/utilityString.cpp create mode 100644 src/lib/utility/utilityString.h create mode 100644 src/test/GraphTestSuite.h create mode 100644 src/test/UtilityStringTestSuite.h diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index c906d4c0..42447cfe 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -44,6 +44,15 @@ add_files( data/access/GraphAccess.cpp data/access/GraphAccess.h + data/graph/Edge.cpp + data/graph/Edge.h + data/graph/Graph.cpp + data/graph/Graph.h + data/graph/Node.cpp + data/graph/Node.h + data/graph/Token.cpp + data/graph/Token.h + data/parser/ParseLocation.cpp data/parser/ParseLocation.h data/parser/Parser.cpp @@ -53,16 +62,8 @@ add_files( data/parser/ParseVariable.cpp data/parser/ParseVariable.h - data/Edge.cpp - data/Edge.h - data/Element.cpp - data/Element.h data/ElementIndex.cpp data/ElementIndex.h - data/Graph.cpp - data/Graph.h - data/Node.cpp - data/Node.h data/SearchIndex.cpp data/SearchIndex.h data/Storage.cpp @@ -124,6 +125,9 @@ add_files( utility/ConfigManager.h utility/FileSystem.cpp utility/FileSystem.h + utility/types.h + utility/utilityString.cpp + utility/utilityString.h utility/Vector.h Application.cpp diff --git a/src/lib/Project.cpp b/src/lib/Project.cpp index fe03438a..df11ae24 100644 --- a/src/lib/Project.cpp +++ b/src/lib/Project.cpp @@ -46,5 +46,6 @@ void Project::parseCode() parser.parseFiles( FileSystem::getSourceFilesFromDirectory(ProjectSettings::getInstance()->getSourcePath(), extension) ); + m_storage->logGraph(); } } diff --git a/src/lib/data/Edge.cpp b/src/lib/data/Edge.cpp deleted file mode 100644 index 8d25aebe..00000000 --- a/src/lib/data/Edge.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "data/Edge.h" - -#include "data/Node.h" - -Edge::Edge(std::weak_ptr from, std::weak_ptr to) -: m_from(from) -, m_to(to) -{ -} - -Edge::~Edge() -{ -} diff --git a/src/lib/data/Edge.h b/src/lib/data/Edge.h deleted file mode 100644 index 7e7c06f2..00000000 --- a/src/lib/data/Edge.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef EDGE_H -#define EDGE_H - -#include - -#include "data/Element.h" - -class Node; - -class Edge: public Element -{ -public: - Edge(std::weak_ptr from, std::weak_ptr to); - ~Edge(); - -private: - const std::weak_ptr m_from; - const std::weak_ptr m_to; -}; - - -#endif // EDGE_H diff --git a/src/lib/data/Element.cpp b/src/lib/data/Element.cpp deleted file mode 100644 index b32eb083..00000000 --- a/src/lib/data/Element.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "data/Element.h" diff --git a/src/lib/data/Element.h b/src/lib/data/Element.h deleted file mode 100644 index a80b9b15..00000000 --- a/src/lib/data/Element.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef ELEMENT_H -#define ELEMENT_H - -class Element -{ -}; - -#endif // ELEMENT_H diff --git a/src/lib/data/Graph.cpp b/src/lib/data/Graph.cpp deleted file mode 100644 index 83d1235d..00000000 --- a/src/lib/data/Graph.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "data/Graph.h" - -Graph::Graph() -{ -} - -Graph::~Graph() -{ -} diff --git a/src/lib/data/Graph.h b/src/lib/data/Graph.h deleted file mode 100644 index 69b12edc..00000000 --- a/src/lib/data/Graph.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef GRAPH_H -#define GRAPH_H - -#include - -#include "data/Edge.h" -#include "data/Node.h" - -class Graph -{ -public: - Graph(); - ~Graph(); - -private: - std::vector > m_nodes; - std::vector > m_edges; -}; - - -#endif // GRAPH_H diff --git a/src/lib/data/Node.cpp b/src/lib/data/Node.cpp deleted file mode 100644 index 9ce0a575..00000000 --- a/src/lib/data/Node.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "data/Node.h" diff --git a/src/lib/data/Node.h b/src/lib/data/Node.h deleted file mode 100644 index de7365dc..00000000 --- a/src/lib/data/Node.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NODE_H -#define NODE_H - -#include "data/Element.h" - -class Node: public Element -{ -}; - - -#endif // NODE_H diff --git a/src/lib/data/Storage.cpp b/src/lib/data/Storage.cpp index afd6fb44..81caba2e 100644 --- a/src/lib/data/Storage.cpp +++ b/src/lib/data/Storage.cpp @@ -25,21 +25,50 @@ void Storage::onTypedefParsed( void Storage::onClassParsed(const ParseLocation& location, const std::string& fullName, AccessType access) { log("class", fullName, location); + + Node* node = m_graph.createNodeHierarchy(fullName); + node->setType(Node::NODE_CLASS); + node->setAccess(convertAccessType(access)); } void Storage::onStructParsed(const ParseLocation& location, const std::string& fullName, AccessType access) { log("struct", fullName, location); + + Node* node = m_graph.createNodeHierarchy(fullName); + node->setType(Node::NODE_STRUCT); + node->setAccess(convertAccessType(access)); } void Storage::onGlobalVariableParsed(const ParseLocation& location, const ParseVariable& variable) { log("global", variable.fullName, location); + + Node* node = m_graph.createNodeHierarchy(variable.fullName); + node->setType(Node::NODE_GLOBAL); + node->setConst(variable.isConst); + node->setStatic(variable.isStatic); + + m_graph.createEdge(Edge::EDGE_TYPE_OF, node, m_graph.createNodeHierarchy(variable.typeName)); } void Storage::onFieldParsed(const ParseLocation& location, const ParseVariable& variable, AccessType access) { log("field", variable.fullName, location); + + Node* node = m_graph.createNodeHierarchy(variable.fullName); + node->setType(Node::NODE_FIELD); + node->setConst(variable.isConst); + node->setStatic(variable.isStatic); + + if (access == ACCESS_NONE) + { + LOG_ERROR("Field needs to have access type [public, protected, private] but has none."); + return; + } + node->setAccess(convertAccessType(access)); + + m_graph.createEdge(Edge::EDGE_TYPE_OF, node, m_graph.createNodeHierarchy(variable.typeName)); } void Storage::onFunctionParsed( @@ -48,6 +77,15 @@ void Storage::onFunctionParsed( ) { log("function", fullName, location); + + Node* node = m_graph.createNodeHierarchy(fullName); + node->setType(Node::NODE_FUNCTION); + + m_graph.createEdge(Edge::EDGE_RETURN_TYPE_OF, node, m_graph.createNodeHierarchy(returnTypeName)); + for (const ParseVariable& var : parameters) + { + m_graph.createEdge(Edge::EDGE_PARAMETER_OF, node, m_graph.createNodeHierarchy(var.typeName)); + } } void Storage::onMethodParsed( @@ -57,21 +95,56 @@ void Storage::onMethodParsed( ) { log("method", fullName, location); + + Node* node = m_graph.createNodeHierarchy(fullName); + node->setType(Node::NODE_METHOD); + node->setConst(isConst); + node->setStatic(isStatic); + + if (access == ACCESS_NONE) + { + LOG_ERROR("Method needs to have access type [public, protected, private] but has none."); + return; + } + node->setAccess(convertAccessType(access)); + + m_graph.createEdge(Edge::EDGE_RETURN_TYPE_OF, node, m_graph.createNodeHierarchy(returnTypeName)); + for (const ParseVariable& parameter : parameters) + { + m_graph.createEdge(Edge::EDGE_PARAMETER_OF, node, m_graph.createNodeHierarchy(parameter.typeName)); + } } void Storage::onNamespaceParsed(const ParseLocation& location, const std::string& fullName) { log("namespace", fullName, location); + + Node* node = m_graph.createNodeHierarchy(fullName); + node->setType(Node::NODE_NAMESPACE); } void Storage::onEnumParsed(const ParseLocation& location, const std::string& fullName, AccessType access) { log("enum", fullName, location); + + Node* node = m_graph.createNodeHierarchy(fullName); + node->setType(Node::NODE_ENUM); + node->setAccess(convertAccessType(access)); } void Storage::onEnumFieldParsed(const ParseLocation& location, const std::string& fullName) { log("enum field", fullName, location); + + Node* node = m_graph.createNodeHierarchy(fullName); + node->setType(Node::NODE_FIELD); +} + +void Storage::logGraph() const +{ + std::stringstream str; + str << "\n" << m_graph; + LOG_INFO(str.str()); } void Storage::log(std::string type, std::string str, const ParseLocation& location) const @@ -80,3 +153,18 @@ void Storage::log(std::string type, std::string str, const ParseLocation& locati info << type << ": " << str << " <" << location.file << " " << location.line << ":" << location.column << ">"; LOG_INFO(info.str()); } + +Edge::AccessType Storage::convertAccessType(ParserClient::AccessType access) const +{ + switch (access) + { + case ACCESS_PUBLIC: + return Edge::ACCESS_PUBLIC; + case ACCESS_PROTECTED: + return Edge::ACCESS_PROTECTED; + case ACCESS_PRIVATE: + return Edge::ACCESS_PRIVATE; + case ACCESS_NONE: + return Edge::ACCESS_NONE; + } +} diff --git a/src/lib/data/Storage.h b/src/lib/data/Storage.h index 26d41f94..bdb6f748 100644 --- a/src/lib/data/Storage.h +++ b/src/lib/data/Storage.h @@ -4,7 +4,7 @@ #include #include -#include "data/Graph.h" +#include "data/graph/Graph.h" #include "data/parser/ParserClient.h" #include "data/TextLocationFile.h" @@ -37,8 +37,11 @@ public: virtual void onEnumParsed(const ParseLocation& location, const std::string& fullName, AccessType access); virtual void onEnumFieldParsed(const ParseLocation& location, const std::string& fullName); + void logGraph() const; + private: void log(std::string type, std::string str, const ParseLocation& location) const; + Edge::AccessType convertAccessType(ParserClient::AccessType access) const; Graph m_graph; diff --git a/src/lib/data/TextLocation.cpp b/src/lib/data/TextLocation.cpp index 8cb36f50..5c9597b2 100644 --- a/src/lib/data/TextLocation.cpp +++ b/src/lib/data/TextLocation.cpp @@ -1,14 +1,14 @@ #include "data/TextLocation.h" -#include "data/Element.h" +#include "data/graph/Token.h" #include "data/TextLocationLine.h" TextLocation::TextLocation( std::weak_ptr textLocationLine, - std::weak_ptr element, + std::weak_ptr token, unsigned int column ) - : m_element(element) + : m_token(token) , m_textLocationLine(textLocationLine) , m_column(column) { diff --git a/src/lib/data/TextLocation.h b/src/lib/data/TextLocation.h index 90332bd6..940fdc46 100644 --- a/src/lib/data/TextLocation.h +++ b/src/lib/data/TextLocation.h @@ -3,7 +3,7 @@ #include -class Element; +class Token; class TextLocationLine; class TextLocation @@ -11,7 +11,7 @@ class TextLocation public: TextLocation( std::weak_ptr textLocationLine, - std::weak_ptr element, + std::weak_ptr token, unsigned int column ); @@ -20,7 +20,7 @@ public: unsigned int getColumn() const; private: - const std::weak_ptr m_element; + const std::weak_ptr m_token; const std::weak_ptr m_textLocationLine; const unsigned int m_column; }; diff --git a/src/lib/data/graph/Edge.cpp b/src/lib/data/graph/Edge.cpp new file mode 100644 index 00000000..953c9abc --- /dev/null +++ b/src/lib/data/graph/Edge.cpp @@ -0,0 +1,146 @@ +#include "data/graph/Edge.h" + +#include + +#include "data/graph/Node.h" +#include "utility/logging/logging.h" + +Edge::Edge(EdgeType type, Node* from, Node* to) + : m_type(type) + , m_from(from) + , m_to(to) + , m_access(ACCESS_NONE) +{ + m_from->addEdge(this); + m_to->addEdge(this); +} + +Edge::~Edge() +{ + m_from->removeEdge(this); + m_to->removeEdge(this); +} + +std::shared_ptr Edge::createPlainCopy(Node* from, Node* to) const +{ + if (from == m_from || to == m_to || from->getId() != m_from->getId() || to->getId() != m_to->getId()) + { + LOG_ERROR("Nodes are not plain copies."); + return nullptr; + } + + std::shared_ptr edge(new Edge(getId(), m_type, from, to)); + + edge->setAccess(m_access); + + return edge; +} + +Edge::EdgeType Edge::getType() const +{ + return m_type; +} + +Node* Edge::getFrom() const +{ + return m_from; +} + +Node* Edge::getTo() const +{ + return m_to; +} + +bool Edge::isNode() const +{ + return false; +} + +bool Edge::isEdge() const +{ + return true; +} + +Edge::AccessType Edge::getAccess() const +{ + return m_access; +} + +void Edge::setAccess(AccessType access) +{ + if (access != ACCESS_NONE && m_type != EDGE_MEMBER && m_type != EDGE_INHERITANCE) + { + LOG_ERROR("Setting access on wrong edge type."); + return; + } + + if (m_access != ACCESS_NONE && access != m_access) + { + LOG_WARNING("Different AccessType was already set before."); + } + m_access = access; +} + +std::string Edge::getTypeString() const +{ + switch (m_type) + { + case EDGE_MEMBER: + return "has child"; + case EDGE_TYPE_OF: + return "is type of"; + case EDGE_RETURN_TYPE_OF: + return "has return type"; + case EDGE_PARAMETER_OF: + return "has parameter of type"; + default: + LOG_ERROR("TypeString not implemented for edge type."); + } + return ""; +} + +std::string Edge::getAccessString() const +{ + switch (m_access) + { + case ACCESS_PUBLIC: + return "public"; + case ACCESS_PROTECTED: + return "protected"; + case ACCESS_PRIVATE: + return "private"; + case ACCESS_NONE: + return ""; + } + return ""; +} + +std::string Edge::getAsString() const +{ + std::stringstream str; + str << "[" << getId() << "] \"" << m_from->getName() << "\" " << getTypeString() << " \"" + m_to->getName() << "\""; + + if (m_access != ACCESS_NONE) + { + str << " " << getAccessString(); + } + + return str.str(); +} + +Edge::Edge(Id id, EdgeType type, Node* from, Node* to) + : Token(id) + , m_type(type) + , m_from(from) + , m_to(to) + , m_access(ACCESS_NONE) +{ + m_from->addEdge(this); + m_to->addEdge(this); +} + +std::ostream& operator<<(std::ostream& ostream, const Edge& edge) +{ + ostream << edge.getAsString(); + return ostream; +} diff --git a/src/lib/data/graph/Edge.h b/src/lib/data/graph/Edge.h new file mode 100644 index 00000000..25c62e3e --- /dev/null +++ b/src/lib/data/graph/Edge.h @@ -0,0 +1,71 @@ +#ifndef EDGE_H +#define EDGE_H + +#include +#include + +#include "data/graph/Token.h" + +class Node; + +class Edge: public Token +{ +public: + enum EdgeType + { + EDGE_MEMBER, + EDGE_TYPE_OF, + EDGE_RETURN_TYPE_OF, + EDGE_PARAMETER_OF, + EDGE_USAGE, + EDGE_CALL, + EDGE_INHERITANCE + }; + + enum AccessType + { + ACCESS_PUBLIC, + ACCESS_PROTECTED, + ACCESS_PRIVATE, + ACCESS_NONE + }; + + Edge(EdgeType type, Node* from, Node* to); + virtual ~Edge(); + + std::shared_ptr createPlainCopy(Node* from, Node* to) const; + + EdgeType getType() const; + + Node* getFrom() const; + Node* getTo() const; + + // Token implementation + virtual bool isNode() const; + virtual bool isEdge() const; + + // Additional field accessors for different EdgeTypes. + AccessType getAccess() const; + void setAccess(AccessType access); + + // Logging. + std::string getTypeString() const; + std::string getAccessString() const; + std::string getAsString() const; + +private: + // Constructor for plain copies. + Edge(Id id, EdgeType type, Node* from, Node* to); + + const EdgeType m_type; + + Node* const m_from; + Node* const m_to; + + // Additional fields for different EdgeTypes. + AccessType m_access; +}; + +std::ostream& operator<<(std::ostream& ostream, const Edge& edge); + +#endif // EDGE_H diff --git a/src/lib/data/graph/Graph.cpp b/src/lib/data/graph/Graph.cpp new file mode 100644 index 00000000..dc8f6cfc --- /dev/null +++ b/src/lib/data/graph/Graph.cpp @@ -0,0 +1,341 @@ +#include "data/graph/Graph.h" + +#include "utility/logging/logging.h" +#include "utility/utilityString.h" + +Graph::Graph() +{ +} + +Graph::~Graph() +{ + m_edges.clear(); + m_nodes.clear(); +} + +Node* Graph::getNode(const std::string& fullName) const +{ + return findNode( + [fullName](Node* n) + { + return n->getName() == fullName; + } + ); +} + +Edge* Graph::getEdge(Edge::EdgeType type, Node* from, Node* to) const +{ + return from->findEdgeOfType(type, + [to](Edge* e) + { + return e->getTo() == to; + } + ); +} + +Node* Graph::getNodeById(Id id) const +{ + return findNode( + [id](Node* n) + { + return n->getId() == id; + } + ); +} + +Edge* Graph::getEdgeById(Id id) const +{ + return findEdge( + [id](Edge* e) + { + return e->getId() == id; + } + ); +} + +Token* Graph::getTokenById(Id id) const +{ + return findToken( + [id](Token* t) + { + return t->getId() == id; + } + ); +} + +Node* Graph::createNodeHierarchy(const std::string& fullName) +{ + Node* node = getNode(fullName); + if (node) + { + return node; + } + + return insertNodeHierarchy(fullName); +} + +Edge* Graph::createEdge(Edge::EdgeType type, Node* from, Node* to) +{ + Edge* edge = getEdge(type, from, to); + if (edge) + { + return edge; + } + + return insertEdge(type, from, to); +} + +void Graph::removeNode(Node* node) +{ + std::vector >::const_iterator it; + for (it = m_nodes.begin(); it != m_nodes.end(); it++) + { + if (it->get() == node) + { + break; + } + } + + if (it == m_nodes.end()) + { + LOG_WARNING("Node was not found in the graph."); + return; + } + + node->forEachEdgeOfType(Edge::EDGE_MEMBER, [this, node](Edge* e) + { + if (node == e->getFrom()) + { + this->removeNode(e->getTo()); + } + }); + + node->forEachEdge( + [this](Edge* e) + { + this->removeEdgeInternal(e); + } + ); + + if (node->getEdges().size()) + { + LOG_ERROR("Node has still edges."); + } + + m_nodes.erase(it); +} + +void Graph::removeEdge(Edge* edge) +{ + std::vector >::const_iterator it; + for (it = m_edges.begin(); it != m_edges.end(); it++) + { + if (it->get() == edge) + { + break; + } + } + + if (it == m_edges.end()) + { + LOG_WARNING("Edge was not found in the graph."); + } + + if (edge->getType() == Edge::EDGE_MEMBER) + { + LOG_ERROR("Can't remove member edge, without removing the child node."); + return; + } + + m_edges.erase(it); +} + +Node* Graph::findNode(std::function func) const +{ + std::vector >::const_iterator it = find_if(m_nodes.begin(), m_nodes.end(), + [&func](const std::shared_ptr& n) + { + return func(n.get()); + } + ); + + if (it != m_nodes.end()) + { + return it->get(); + } + + return nullptr; +} + +Edge* Graph::findEdge(std::function func) const +{ + std::vector >::const_iterator it = find_if(m_edges.begin(), m_edges.end(), + [func](const std::shared_ptr& e) + { + return func(e.get()); + } + ); + + if (it != m_edges.end()) + { + return it->get(); + } + + return nullptr; +} + +Token* Graph::findToken(std::function func) const +{ + Node* node = findNode(func); + if (node) + { + return node; + } + + Edge* edge = findEdge(func); + if (edge) + { + return edge; + } + + return nullptr; +} + +void Graph::forEachNode(std::function func) const +{ + for (const std::shared_ptr& node : m_nodes) + { + func(node.get()); + } +} + +void Graph::forEachEdge(std::function func) const +{ + for (const std::shared_ptr& edge : m_edges) + { + func(edge.get()); + } +} + +void Graph::forEachToken(std::function func) const +{ + forEachNode(func); + forEachEdge(func); +} + +const std::vector >& Graph::getNodes() const +{ + return m_nodes; +} + +const std::vector >& Graph::getEdges() const +{ + return m_edges; +} + +Node* Graph::addNodeAsPlainCopy(Node* node) +{ + Node* n = getNodeById(node->getId()); + if (n) + { + return n; + } + + std::shared_ptr copy = node->createPlainCopy(); + m_nodes.push_back(copy); + return copy.get(); +} + +Edge* Graph::addEdgeAsPlainCopy(Edge* edge) +{ + Edge* e = getEdgeById(edge->getId()); + if (e) + { + return e; + } + + Node* from = addNodeAsPlainCopy(edge->getFrom()); + Node* to = addNodeAsPlainCopy(edge->getTo()); + + std::shared_ptr copy = edge->createPlainCopy(from, to); + m_edges.push_back(copy); + return copy.get(); +} + +const std::string Graph::DELIMITER = "::"; + +Node* Graph::insertNodeHierarchy(const std::string& fullName) +{ + Node* node = nullptr; + std::vector names = utility::split(fullName, DELIMITER); + std::string name; + + for (std::vector::iterator it = names.begin(); it != names.end(); it++) + { + if (name.size() > 0) + { + name += DELIMITER; + } + name += *it; + + Node* childNode = getNode(name); + + if (!childNode) + { + std::shared_ptr childNodePtr = std::make_shared(Node::NODE_UNDEFINED, name); + m_nodes.push_back(childNodePtr); + childNode = childNodePtr.get(); + + if (node) + { + createEdge(Edge::EDGE_MEMBER, node, childNode); + } + } + + node = childNode; + } + + return node; +} + +Edge* Graph::insertEdge(Edge::EdgeType type, Node* from, Node* to) +{ + std::shared_ptr edgePtr = std::make_shared(type, from, to); + m_edges.push_back(edgePtr); + return edgePtr.get(); +} + +void Graph::removeEdgeInternal(Edge* edge) +{ + std::vector >::const_iterator it; + for (it = m_edges.begin(); it != m_edges.end(); it++) + { + if (it->get() == edge) + { + m_edges.erase(it); + return; + } + } +} + +std::ostream& operator<<(std::ostream& ostream, const Graph& graph) +{ + ostream << "Graph:\n"; + ostream << "nodes (" << graph.getNodes().size() << ")\n"; + graph.forEachNode( + [&ostream](Node* n) + { + ostream << *n << '\n'; + } + ); + + ostream << "edges (" << graph.getEdges().size() << ")\n"; + graph.forEachEdge( + [&ostream](Edge* e) + { + ostream << *e << '\n'; + } + ); + + return ostream; +} diff --git a/src/lib/data/graph/Graph.h b/src/lib/data/graph/Graph.h new file mode 100644 index 00000000..f178af4e --- /dev/null +++ b/src/lib/data/graph/Graph.h @@ -0,0 +1,62 @@ +#ifndef GRAPH_H +#define GRAPH_H + +#include +#include +#include + +#include "data/graph/Edge.h" +#include "data/graph/Node.h" + +class Graph +{ +public: + Graph(); + virtual ~Graph(); + + Node* getNode(const std::string& fullName) const; + Edge* getEdge(Edge::EdgeType type, Node* from, Node* to) const; + + Node* getNodeById(Id id) const; + Edge* getEdgeById(Id id) const; + Token* getTokenById(Id id) const; + + Node* createNodeHierarchy(const std::string& fullName); + Edge* createEdge(Edge::EdgeType type, Node* from, Node* to); + + void removeNode(Node* node); + void removeEdge(Edge* edge); + + Node* findNode(std::function func) const; + Edge* findEdge(std::function func) const; + Token* findToken(std::function func) const; + + void forEachNode(std::function func) const; + void forEachEdge(std::function func) const; + void forEachToken(std::function func) const; + + Node* addNodeAsPlainCopy(Node* node); + Edge* addEdgeAsPlainCopy(Edge* edge); + +protected: + const std::vector >& getNodes() const; + const std::vector >& getEdges() const; + +private: + static const std::string DELIMITER; + + Node* insertNodeHierarchy(const std::string& fullName); + Edge* insertEdge(Edge::EdgeType type, Node* from, Node* to); + void removeEdgeInternal(Edge* edge); + + const std::string m_delimiter; + + std::vector > m_nodes; + std::vector > m_edges; + + friend std::ostream& operator<<(std::ostream& ostream, const Graph& graph); +}; + +std::ostream& operator<<(std::ostream& ostream, const Graph& graph); + +#endif // GRAPH_H diff --git a/src/lib/data/graph/Node.cpp b/src/lib/data/graph/Node.cpp new file mode 100644 index 00000000..57f8f062 --- /dev/null +++ b/src/lib/data/graph/Node.cpp @@ -0,0 +1,248 @@ +#include "data/graph/Node.h" + +#include + +#include "utility/logging/logging.h" + +Node::Node(NodeType type, const std::string& name) + : m_type(type) + , m_name(name) + , m_isConst(false) + , m_isStatic(false) +{ +} + +Node::~Node() +{ +} + +std::shared_ptr Node::createPlainCopy() const +{ + std::shared_ptr node(new Node(getId(), m_type, m_name)); + + node->setConst(m_isConst); + node->setStatic(m_isStatic); + + return node; +} + +Node::NodeType Node::getType() const +{ + return m_type; +} + +void Node::setType(NodeType type) +{ + if (type != m_type && m_type != NODE_NAMESPACE && m_type != NODE_UNDEFINED) + { + LOG_WARNING( + "Changing NodeType after it was already set, from " + getTypeString(m_type) + " to " + getTypeString(type) + ); + } + m_type = type; +} + +const std::string& Node::getName() const +{ + return m_name; +} + +const std::vector& Node::getEdges() const +{ + return m_edges; +} + +void Node::addEdge(Edge* edge) +{ + m_edges.push_back(edge); +} + +void Node::removeEdge(Edge* edge) +{ + m_edges.erase(find(m_edges.begin(), m_edges.end(), edge)); +} + +Edge* Node::getMemberEdge() const +{ + Edge* edge = findEdgeOfType(Edge::EDGE_MEMBER, + [this](Edge* e) + { + return e->getTo() == this; + } + ); + + if (edge) + { + return edge; + } + else + { + LOG_WARNING("Child edge was not found, node " + getName() + " has no parent node."); + return nullptr; + } +} + +Edge* Node::findEdge(std::function func) const +{ + std::vector::const_iterator it = find_if(m_edges.begin(), m_edges.end(), func); + + if (it != m_edges.end()) + { + return *it; + } + + return NULL; +} + +Edge* Node::findEdgeOfType(Edge::EdgeType type, std::function func) const +{ + std::vector::const_iterator it = find_if(m_edges.begin(), m_edges.end(), + [type, func](Edge* e) + { + if (e->getType() == type) + { + return func(e); + } + return false; + } + ); + + if (it != m_edges.end()) + { + return *it; + } + + return NULL; +} + +void Node::forEachEdge(std::function func) const +{ + for_each(m_edges.begin(), m_edges.end(), func); +} + +void Node::forEachEdgeOfType(Edge::EdgeType type, std::function func) const +{ + for_each(m_edges.begin(), m_edges.end(), + [type, func](Edge* e) + { + if (e->getType() == type) + { + func(e); + } + } + ); +} + +bool Node::isNode() const +{ + return true; +} + +bool Node::isEdge() const +{ + return false; +} + +void Node::setAccess(Edge::AccessType access) +{ + if (access != Edge::ACCESS_NONE) + { + if (!getMemberEdge()) + { + LOG_WARNING("Can't set access on node " + getName() + ", because it is not a child."); + return; + } + getMemberEdge()->setAccess(access); + } +} + +bool Node::isConst() const +{ + return m_isConst; +} + +void Node::setConst(bool isConst) +{ + if (isConst && m_type != NODE_GLOBAL && m_type != NODE_FIELD && m_type != NODE_METHOD) + { + LOG_ERROR("Setting const on wrong node of type " + getTypeString(m_type)); + return; + } + + m_isConst = isConst; +} + +bool Node::isStatic() const +{ + return m_isStatic; +} + +void Node::setStatic(bool isStatic) +{ + if (isStatic && m_type != NODE_GLOBAL && m_type != NODE_FIELD && m_type != NODE_METHOD) + { + LOG_ERROR("Setting static on wrong node of type " + getTypeString(m_type)); + return; + } + + m_isStatic = isStatic; +} + +std::string Node::getTypeString(NodeType type) const +{ + switch (type) + { + case NODE_UNDEFINED: + return "type"; + case NODE_CLASS: + return "class"; + case NODE_STRUCT: + return "struct"; + case NODE_GLOBAL: + return "global"; + case NODE_FIELD: + return "field"; + case NODE_FUNCTION: + return "function"; + case NODE_METHOD: + return "method"; + case NODE_NAMESPACE: + return "namespace"; + case NODE_ENUM: + return "enum"; + } + return ""; +} + +std::string Node::getAsString() const +{ + std::stringstream str; + str << "[" << getId() << "] " << getTypeString(m_type) << ": " << "\"" << getName() << "\""; + + if (isStatic()) + { + str << " static"; + } + + if (isConst()) + { + str << " const"; + } + + return str.str(); +} + +Node::Node(Id id, NodeType type, const std::string& name) + : Token(id) + , m_type(type) + , m_name(name) + , m_isConst(false) + , m_isStatic(false) +{ +} + +std::ostream& operator<<(std::ostream& ostream, const Node& node) +{ + ostream << node.getAsString(); + return ostream; +} diff --git a/src/lib/data/graph/Node.h b/src/lib/data/graph/Node.h new file mode 100644 index 00000000..c7e76f39 --- /dev/null +++ b/src/lib/data/graph/Node.h @@ -0,0 +1,83 @@ +#ifndef NODE_H +#define NODE_H + +#include +#include +#include +#include + +#include "data/graph/Edge.h" +#include "data/graph/Token.h" + +class Node: public Token +{ +public: + enum NodeType + { + NODE_UNDEFINED, + NODE_CLASS, + NODE_STRUCT, + NODE_GLOBAL, + NODE_FIELD, + NODE_FUNCTION, + NODE_METHOD, + NODE_NAMESPACE, + NODE_ENUM + }; + + Node(NodeType type, const std::string& name); + virtual ~Node(); + + std::shared_ptr createPlainCopy() const; + + NodeType getType() const; + void setType(NodeType type); + + const std::string& getName() const; + const std::vector& getEdges() const; + + void addEdge(Edge* edge); + void removeEdge(Edge* edge); + + Edge* getMemberEdge() const; + + Edge* findEdge(std::function func) const; + Edge* findEdgeOfType(Edge::EdgeType type, std::function func) const; + + void forEachEdge(std::function func) const; + void forEachEdgeOfType(Edge::EdgeType type, std::function func) const; + + // Token implementation. + virtual bool isNode() const; + virtual bool isEdge() const; + + // Additional field accessors for different NodeTypes. + void setAccess(Edge::AccessType access); + + bool isConst() const; + void setConst(bool isConst); + + bool isStatic() const; + void setStatic(bool isStatic); + + // Logging. + std::string getTypeString(NodeType type) const; + std::string getAsString() const; + +private: + // Constructor for plain copies. + Node(Id id, NodeType type, const std::string& name); + + NodeType m_type; + std::string m_name; + + std::vector m_edges; + + // Additional fields for different NodeTypes. + bool m_isConst; + bool m_isStatic; +}; + +std::ostream& operator<<(std::ostream& ostream, const Node& node); + +#endif // NODE_H diff --git a/src/lib/data/graph/Token.cpp b/src/lib/data/graph/Token.cpp new file mode 100644 index 00000000..b56b851a --- /dev/null +++ b/src/lib/data/graph/Token.cpp @@ -0,0 +1,22 @@ +#include "data/graph/Token.h" + +Token::Token() + : m_id(s_nextId++) +{ +} + +Token::~Token() +{ +} + +Id Token::getId() const +{ + return m_id; +} + +Id Token::s_nextId = 1; + +Token::Token(Id id) + : m_id(id) +{ +} diff --git a/src/lib/data/graph/Token.h b/src/lib/data/graph/Token.h new file mode 100644 index 00000000..f2a2d3b8 --- /dev/null +++ b/src/lib/data/graph/Token.h @@ -0,0 +1,30 @@ +#ifndef TOKEN_H +#define TOKEN_H + +#include "utility/types.h" + +class Token +{ +public: + Token(); + virtual ~Token(); + + Id getId() const; + + virtual bool isNode() const = 0; + virtual bool isEdge() const = 0; + +protected: + // Constructor for plain copies of Node and Edge + Token(Id id); + +private: + static Id s_nextId; + + Token(const Token&); + void operator=(const Token&); + + const Id m_id; +}; + +#endif // TOKEN_H diff --git a/src/lib/utility/types.h b/src/lib/utility/types.h new file mode 100644 index 00000000..c322e8a5 --- /dev/null +++ b/src/lib/utility/types.h @@ -0,0 +1,6 @@ +#ifndef TYPES_H +#define TYPES_H + +typedef unsigned long Id; + +#endif // TYPES_H diff --git a/src/lib/utility/utilityString.cpp b/src/lib/utility/utilityString.cpp new file mode 100644 index 00000000..3f19589d --- /dev/null +++ b/src/lib/utility/utilityString.cpp @@ -0,0 +1,26 @@ +#include "utility/utilityString.h" + +namespace utility +{ + std::vector split(const std::string& str, char delimiter) + { + return split(str, std::string(1, delimiter)); + } + + std::vector split(const std::string& str, const std::string& delimiter) + { + size_t pos = 0; + size_t oldPos = 0; + std::vector vec; + + do + { + pos = str.find(delimiter, oldPos); + vec.push_back(str.substr(oldPos, pos - oldPos)); + oldPos = pos + delimiter.size(); + } + while (pos != std::string::npos); + + return vec; + } +} diff --git a/src/lib/utility/utilityString.h b/src/lib/utility/utilityString.h new file mode 100644 index 00000000..23c31f54 --- /dev/null +++ b/src/lib/utility/utilityString.h @@ -0,0 +1,13 @@ +#ifndef UTILITY_STRING_H +#define UTILITY_STRING_H + +#include +#include + +namespace utility +{ + std::vector split(const std::string& str, char delimiter); + std::vector split(const std::string& str, const std::string& delimiter); +} + +#endif // UTILITY_STRING_H diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index e46416d9..a7ecc21b 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -4,11 +4,13 @@ add_files( ConfigManagerTestSuite.h CxxParserTestSuite.h FileSystemTestSuite.h + GraphTestSuite.h LogManagerTestSuite.h MessageQueueTestSuite.h ProjectSettingsTestSuite.h TestSuiteFixture.cpp TestSuiteFixture.h TextAccessTestSuite.h + UtilityStringTestSuite.h Vector2TestSuite.h ) diff --git a/src/test/GraphTestSuite.h b/src/test/GraphTestSuite.h new file mode 100644 index 00000000..dffd6816 --- /dev/null +++ b/src/test/GraphTestSuite.h @@ -0,0 +1,293 @@ +#include "cxxtest/TestSuite.h" + +#include "data/graph/Graph.h" + +class GraphTestSuite : public CxxTest::TestSuite +{ +public: + void test_tokens_get_unique_id() + { + Node a(Node::NODE_UNDEFINED, "A"); + Node b(Node::NODE_UNDEFINED, "B"); + Node c(Node::NODE_UNDEFINED, "C"); + + TS_ASSERT_DIFFERS(a.getId(), c.getId()); + TS_ASSERT_DIFFERS(a.getId(), b.getId()); + TS_ASSERT_DIFFERS(b.getId(), c.getId()); + } + + void test_get_and_set_type_of_nodes() + { + Node n(Node::NODE_NAMESPACE, "A"); + TS_ASSERT_EQUALS(Node::NODE_NAMESPACE, n.getType()); + + n.setType(Node::NODE_CLASS); + TS_ASSERT_EQUALS(Node::NODE_CLASS, n.getType()); + } + + void test_get_type_of_edges() + { + Node a(Node::NODE_UNDEFINED, "A"); + Node b(Node::NODE_UNDEFINED, "B"); + Edge e(Edge::EDGE_TYPE_OF, &a, &b); + + TS_ASSERT_EQUALS(Edge::EDGE_TYPE_OF, e.getType()); + } + + void test_get_and_set_const_and_static_of_nodes() + { + Node n(Node::NODE_GLOBAL, "A"); + TS_ASSERT(!n.isConst()); + TS_ASSERT(!n.isStatic()); + + n.setConst(true); + TS_ASSERT(n.isConst()); + TS_ASSERT(!n.isStatic()); + + n.setStatic(true); + TS_ASSERT(n.isConst()); + TS_ASSERT(n.isStatic()); + } + + void test_graph_saves_nodes() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* b = graph.createNodeHierarchy("B"); + + TS_ASSERT_EQUALS(2, graph.getNodeCount()); + TS_ASSERT_EQUALS(0, graph.getEdgeCount()); + + TS_ASSERT_EQUALS(a, graph.getNode("A")); + TS_ASSERT_EQUALS("A", graph.getNode("A")->getName()); + + TS_ASSERT_EQUALS(b, graph.getNode("B")); + TS_ASSERT_EQUALS("B", graph.getNode("B")->getName()); + + TS_ASSERT(!graph.getNode("C")); + } + + void test_graph_saves_edges() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* b = graph.createNodeHierarchy("B"); + Edge* e = graph.createEdge(Edge::EDGE_TYPE_OF, a, b); + + TS_ASSERT_EQUALS(e, graph.getEdge(Edge::EDGE_TYPE_OF, a, b)); + TS_ASSERT(!graph.getEdge(Edge::EDGE_CALL, a, b)); + } + + void test_graph_finds_nodes_and_edges_by_id() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* b = graph.createNodeHierarchy("B"); + Edge* e = graph.createEdge(Edge::EDGE_TYPE_OF, a, b); + + TS_ASSERT(!graph.getEdgeById(a->getId())); + TS_ASSERT_EQUALS(a, graph.getNodeById(a->getId())); + TS_ASSERT_EQUALS(a, graph.getTokenById(a->getId())); + + TS_ASSERT(!graph.getNodeById(e->getId())); + TS_ASSERT_EQUALS(e, graph.getEdgeById(e->getId())); + TS_ASSERT_EQUALS(e, graph.getTokenById(e->getId())); + } + + void test_graph_creates_child_edges() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* ab = graph.createNodeHierarchy("A::B"); + + TS_ASSERT_EQUALS(2, graph.getNodeCount()); + TS_ASSERT_EQUALS(1, graph.getEdgeCount()); + + TS_ASSERT(graph.getEdge(Edge::EDGE_MEMBER, a, ab)); + } + + void test_graph_removes_nodes() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* b = graph.createNodeHierarchy("B"); + graph.createNodeHierarchy("A::C"); + graph.createNodeHierarchy("A::C::D"); + graph.createEdge(Edge::EDGE_TYPE_OF, a, b); + + graph.removeNode(a); + + TS_ASSERT_EQUALS(1, graph.getNodeCount()); + TS_ASSERT_EQUALS(0, graph.getEdgeCount()); + + TS_ASSERT(!graph.getNode("A")); + TS_ASSERT(!graph.getNode("A::C")); + TS_ASSERT(graph.getNode("B")); + } + + void test_graph_removes_edge() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* b = graph.createNodeHierarchy("B"); + Edge* e = graph.createEdge(Edge::EDGE_TYPE_OF, a, b); + + graph.removeEdge(e); + + TS_ASSERT_EQUALS(2, graph.getNodeCount()); + TS_ASSERT_EQUALS(0, graph.getEdgeCount()); + + TS_ASSERT(graph.getNode("A")); + TS_ASSERT(graph.getNode("B")); + } + + void test_graph_can_not_remove_member_edge() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* b = graph.createNodeHierarchy("B"); + Node* c = graph.createNodeHierarchy("A::C"); + graph.createEdge(Edge::EDGE_TYPE_OF, a, b); + + graph.removeEdge(c->getMemberEdge()); + + TS_ASSERT_EQUALS(3, graph.getNodeCount()); + TS_ASSERT_EQUALS(2, graph.getEdgeCount()); + + TS_ASSERT(graph.getNode("A")); + TS_ASSERT(graph.getNode("B")); + TS_ASSERT(graph.getNode("A::C")); + } + + void test_graph_creates_multiple_nodes_as_type_nodes() + { + TestGraph graph; + Node* abc = graph.createNodeHierarchy("A::B::C"); + + TS_ASSERT_EQUALS(3, graph.getNodeCount()); + TS_ASSERT_EQUALS(2, graph.getEdgeCount()); + + TS_ASSERT_EQUALS("A", graph.getNode("A")->getName()); + TS_ASSERT_EQUALS(Node::NODE_UNDEFINED, graph.getNode("A")->getType()); + + TS_ASSERT_EQUALS("A::B", graph.getNode("A::B")->getName()); + TS_ASSERT_EQUALS(Node::NODE_UNDEFINED, graph.getNode("A::B")->getType()); + + TS_ASSERT_EQUALS(abc, graph.getNode("A::B::C")); + TS_ASSERT_EQUALS("A::B::C", graph.getNode("A::B::C")->getName()); + TS_ASSERT_EQUALS(Node::NODE_UNDEFINED, graph.getNode("A::B::C")->getType()); + + Node* abcde = graph.createNodeHierarchy("A::B::C::D::E"); + + TS_ASSERT_EQUALS(5, graph.getNodeCount()); + TS_ASSERT_EQUALS(4, graph.getEdgeCount()); + + TS_ASSERT_EQUALS("A::B::C::D", graph.getNode("A::B::C::D")->getName()); + TS_ASSERT_EQUALS(Node::NODE_UNDEFINED, graph.getNode("A::B::C::D")->getType()); + + TS_ASSERT_EQUALS(abcde, graph.getNode("A::B::C::D::E")); + TS_ASSERT_EQUALS("A::B::C::D::E", graph.getNode("A::B::C::D::E")->getName()); + TS_ASSERT_EQUALS(Node::NODE_UNDEFINED, graph.getNode("A::B::C::D::E")->getType()); + } + + void test_set_access_on_child_edge_via_node() + { + TestGraph graph; + Node* b = graph.createNodeHierarchy("A::B"); + b->getMemberEdge()->setAccess(Edge::ACCESS_PRIVATE); + TS_ASSERT_EQUALS(Edge::ACCESS_PRIVATE, b->getMemberEdge()->getAccess()); + } + + void test_visit_each_token_on_graph() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* b = graph.createNodeHierarchy("B"); + Node* c = graph.createNodeHierarchy("C"); + Edge* e = graph.createEdge(Edge::EDGE_TYPE_OF, a, b); + Edge* f = graph.createEdge(Edge::EDGE_TYPE_OF, b, c); + + unsigned long idSum = a->getId() + b->getId() + c->getId() + e->getId() + f->getId(); + unsigned long checkSum = 0; + + graph.forEachToken( + [&checkSum](Token* t) + { + checkSum += t->getId(); + } + ); + + TS_ASSERT_EQUALS(idSum, checkSum); + } + + void test_visit_each_edge_of_type_on_node() + { + TestGraph graph; + Node* a = graph.createNodeHierarchy("A"); + Node* ab = graph.createNodeHierarchy("A::B"); + Node* ac = graph.createNodeHierarchy("A::C"); + + graph.createEdge(Edge::EDGE_TYPE_OF, a, ab); + graph.createEdge(Edge::EDGE_CALL, a, ac); + + unsigned int sum = 0; + a->forEachEdgeOfType(Edge::EDGE_MEMBER, [&sum](Edge* e) + { + TS_ASSERT_EQUALS(Edge::EDGE_MEMBER, e->getType()); + sum++; + }); + + TS_ASSERT_EQUALS(sum, 2); + } + + void test_creating_plain_copy_of_graph_part() + { + TestGraph graph; + Node* b = graph.createNodeHierarchy("A::B"); + b->getMemberEdge()->setAccess(Edge::ACCESS_PUBLIC); + graph.createNodeHierarchy("A::B::C"); + Node* d = graph.createNodeHierarchy("D"); + Node* e = graph.createNodeHierarchy("E"); + graph.createEdge(Edge::EDGE_TYPE_OF, d, b); + graph.createEdge(Edge::EDGE_TYPE_OF, d, e); + + TestGraph plainGraph; + Node* x = graph.getNode("A::B"); + plainGraph.addNodeAsPlainCopy(x); + + x->forEachEdge( + [&plainGraph](Edge* e) + { + plainGraph.addNodeAsPlainCopy(e->getFrom()); + plainGraph.addNodeAsPlainCopy(e->getTo()); + plainGraph.addEdgeAsPlainCopy(e); + } + ); + + TS_ASSERT_EQUALS(4, plainGraph.getNodeCount()); + TS_ASSERT_EQUALS(3, plainGraph.getEdgeCount()); + + TS_ASSERT(plainGraph.getNode("A")); + TS_ASSERT(plainGraph.getNode("A::B")); + TS_ASSERT(plainGraph.getNode("A::B::C")); + TS_ASSERT(plainGraph.getNode("D")); + TS_ASSERT(!plainGraph.getNode("E")); + TS_ASSERT_EQUALS(Edge::ACCESS_PUBLIC, plainGraph.getNode("A::B")->getMemberEdge()->getAccess()); + } + +private: + class TestGraph: public Graph + { + public: + size_t getNodeCount() const + { + return getNodes().size(); + } + + size_t getEdgeCount() const + { + return getEdges().size(); + } + }; + +}; diff --git a/src/test/UtilityStringTestSuite.h b/src/test/UtilityStringTestSuite.h new file mode 100644 index 00000000..ca502c75 --- /dev/null +++ b/src/test/UtilityStringTestSuite.h @@ -0,0 +1,53 @@ +#include "cxxtest/TestSuite.h" + +#include "utility/utilityString.h" + +class UtilityStringTestSuite : public CxxTest::TestSuite +{ +public: + void test_split_with_char_delimiter() + { + std::vector result = utility::split("A,B,C", ','); + + TS_ASSERT_EQUALS(result.size(), 3); + TS_ASSERT_EQUALS(result[0], "A"); + TS_ASSERT_EQUALS(result[1], "B"); + TS_ASSERT_EQUALS(result[2], "C"); + } + + void test_split_with_string_delimiter() + { + std::vector result = utility::split("A->B>C", "->"); + + TS_ASSERT_EQUALS(result.size(), 2); + TS_ASSERT_EQUALS(result[0], "A"); + TS_ASSERT_EQUALS(result[1], "B>C"); + } + + void test_split_on_empty_string() + { + std::vector result = utility::split("", "->"); + + TS_ASSERT_EQUALS(result.size(), 1); + TS_ASSERT_EQUALS(result[0], ""); + } + + void test_split_with_unused_delimiter() + { + std::vector result = utility::split("A:B:C", ";"); + + TS_ASSERT_EQUALS(result.size(), 1); + TS_ASSERT_EQUALS(result[0], "A:B:C"); + } + + void test_split_with_delimiters_next_to_each() + { + std::vector result = utility::split("A::B:C", ':'); + + TS_ASSERT_EQUALS(result.size(), 4); + TS_ASSERT_EQUALS(result[0], "A"); + TS_ASSERT_EQUALS(result[1], ""); + TS_ASSERT_EQUALS(result[2], "B"); + TS_ASSERT_EQUALS(result[3], "C"); + } +};