module model::graph
import neo4j
+import more_collections
import location
+import descriptions
# A Neo4j graph.
class NeoGraph
end
# A project’s graph.
+#
+# Here is the usual steps to build a project graph:
+#
+# <ul>
+# <li>Instantiate `ProjectGraph` by giving the name that will label the project.</li>
+# <li>For each compound:
+# <ul>
+# <li>Instantiate the compound.</li>
+# <li>Provide all the related data.</li>
+# <li>Call the `put_in_graph` method of the compound.</li>
+# </ul></li>
+# <li>Call the `add_global_modules` method of the project’s graph (defined in
+# the `module_compound` module). This permits to take global classes into
+# account correctly.</li>
+# <li>Call the `put_edges` method of the project’s graph.</li>
+# </ul>
class ProjectGraph
super NeoGraph
# Entities by `model_id`.
var by_id: Map[String, Entity] = new HashMap[String, Entity]
+ # Namespaces by `full_name`.
+ var namespaces: Map[String, Namespace] = new HashMap[String, Namespace]
+
+ # For each `ClassCompound` in the graph, the mapping between its `model_id` and its namespace.
+ #
+ # Defaults to the root namespace. An entry is added each time
+ # `Namespace.declare_class` is called.
+ #
+ # Note: In the graph, there is no direct link between a namespace and a
+ # class. It is the role of a module (created internally by a `FileCompound`)
+ # to link a class with its namespace. So, this collection is used by modules
+ # to know which class in a file belong to their related namespace. It is
+ # also used by `FileCompound` to detect classes in the root namespace.
+ var class_to_ns: Map[String, Namespace] is noinit
+
# Initialize a new project graph using the specified project name.
#
# The specified name will label all nodes of the project’s graph.
var root = new RootNamespace(self)
root.put_in_graph
by_id[""] = root
+ class_to_ns = new DefaultMap[String, Namespace](root)
end
# Request to all nodes in the graph to add their related edges.
+ #
+ # Note: For the rare cases where a node need to wait the `put_edges` to add
+ # an implicit node, this method makes sure to call the `put_edges` method
+ # of the newly added nodes only after processing all the nodes that was
+ # already there.
fun put_edges do
all_edges.clear
add_edge(project, "ROOT", by_id[""])
# Is empty for entities without an ID.
var model_id: String = "" is writable
+ # The full (qualified) name, as presented by the original model.
+ #
+ # Fully independant of `name`. By default, equals to `""` for the root
+ # namespace.
+ var full_name: nullable String = null is writable
+
# Associated documentation.
- var doc = new JsonArray is writable
+ var doc = new Documentation is writable
init do
self.labels.add(graph.project_name)
end
# The short (unqualified) name.
- #
- # May be also set by `full_name=`.
fun name=(name: String) do
self["name"] = name
end
end
# The namespace separator of Nit/C++.
- fun ns_separator: String do return "::"
-
- # The name separator used when calling `full_name=`.
- fun name_separator: String do return ns_separator
-
- # The full (qualified) name.
#
- # Also set `name` using `name_separator`.
- fun full_name=(full_name: String) do
- var m = full_name.search_last(name_separator)
-
- self["full_name"] = full_name
- if m == null then
- name = full_name
- else
- name = full_name.substring_from(m.after)
- end
- end
-
- # The full (qualified) name.
- fun full_name: String do
- var full_name = self["full_name"]
- assert full_name isa String
- return full_name
- end
-
- # Set the full name using the current name and the specified parent name.
- fun parent_name=(parent_name: String) do
- self["full_name"] = parent_name + name_separator + self["name"].as(not null).to_s
- end
+ # Used to join two or more names when we need to work around some
+ # limitations of the Nit model.
+ fun ns_separator: String do return "::"
# Set the location of the entity in the source code.
fun location=(location: nullable Location) do
self["location"] = location
end
+ # Get the location of the entity in the source code.
+ fun location: nullable Location do
+ return self["location"].as(nullable Location)
+ end
+
# Put the entity in the graph.
#
# Called by the loader when it has finished to read the entity.
fun put_in_graph do
- if doc.length > 0 then
+ if not doc.is_empty then
set_mdoc
end
graph.all_nodes.add(self)
# Declare an inner namespace.
#
# Note: Althought Doxygen indicates that the name is optional,
- # declarations with an empty name are not supported yet.
+ # declarations with an empty name are not supported yet, except for the root
+ # namespace. For the root namespace, both arguments are empty.
#
# Parameters:
#
# * `id`: `model_id` of the inner namespace. May be empty.
- # * `full_name`: qualified name of the inner namespace.
+ # * `full_name`: qualified name of the inner namespace. Use an empty name
+ # for the root namespace.
fun declare_namespace(id: String, full_name: String) do end
# Declare an inner class.
#
# Note: Althought Doxygen indicates that both arguments are optional,
- # declarations with either an empty name or an empty ID are not
- # supported yet.
+ # declarations with an empty ID are not supported yet.
#
# Parameters:
#
# * `id`: `model_id` of the inner class.
- # * `full_name`: qualified name of the inner class.
- fun declare_class(id: String, full_name: String) do end
+ # * `name`: short name of the inner class.
+ # * `prot`: visibility (proctection).
+ fun declare_class(id: String, name: String, prot: String) do end
# Declare a base compound (usually, a base class).
#
class Namespace
super Compound
- # Inner namespaces (IDs).
- #
- # Left empty for the root namespace.
- var inner_namespaces: SimpleCollection[String] = new Array[String]
+ # The inner namespaces.
+ var inner_namespaces: SimpleCollection[NamespaceRef] = new Array[NamespaceRef]
init do
super
self.labels.add("MGroup")
end
- redef fun declare_namespace(id: String, name: String) do
- inner_namespaces.add(id)
+ redef fun declare_namespace(id: String, full_name: String) do
+ inner_namespaces.add new NamespaceRef(id, full_name)
+ end
+
+ redef fun declare_class(id, name, prot) do
+ assert not id.is_empty else
+ sys.stderr.write "Inner class declarations without ID are not yet supported.\n"
+ end
+ graph.class_to_ns[id] = self
+ end
+
+ redef fun put_in_graph do
+ super
+ var full_name = self.full_name
+ if full_name isa String then graph.namespaces[full_name] = self
end
redef fun put_edges do
super
graph.add_edge(self, "PROJECT", graph.project)
- if self["name"] == self["full_name"] and self["full_name"] != "" then
- # The root namespace does not know its children.
- var root = graph.by_id[""]
- graph.add_edge(self, "PARENT", root)
- graph.add_edge(root, "NESTS", self)
- end
for ns in inner_namespaces do
- var node = graph.by_id[ns]
+ var node = ns.seek_in(graph)
graph.add_edge(node, "PARENT", self)
graph.add_edge(self, "NESTS", node)
end
end
end
+# A reference to a namespace.
+class NamespaceRef
+ # The `model_id` of the target.
+ #
+ # Empty when unknown or for the root namespace.
+ var model_id: String
+
+ # The `full_name` of the target.
+ #
+ # Empty only for the root namespace.
+ var full_name: String
+
+ # Look for the targeted namespace in the specified graph.
+ fun seek_in(graph: ProjectGraph): Namespace do
+ var ns_compound: Namespace
+
+ if model_id.is_empty and not full_name.is_empty then
+ # ID unspecified. => We have to look by name
+ assert graph.namespaces.has_key(full_name) else
+ sys.stderr.write "Namespace `{full_name}` not found."
+ end
+ ns_compound = graph.namespaces[full_name]
+ else
+ ns_compound = graph.by_id[model_id].as(Namespace)
+ end
+ return ns_compound
+ end
+end
+
# The root namespace of a `ProjectGraph`.
#
# This the only entity in the graph whose `model_id` is really `""`.
init do
super
- self["full_name"] = ""
- self["name"] = graph.project["name"]
+ full_name = ""
+ name = graph.project_name
end
-
- redef fun declare_namespace(id: String, name: String) do end
end