neo_doxygen: Add a missing getter in `Entity`.
[nit.git] / contrib / neo_doxygen / src / model / graph.nit
index 43edfa4..198b8fb 100644 (file)
@@ -16,6 +16,7 @@
 module model::graph
 
 import neo4j
+import more_collections
 import location
 
 # A Neo4j graph.
@@ -34,10 +35,29 @@ class NeoGraph
        end
 end
 
-# The project’s graph.
+# 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
 
+       # The project’s name.
+       var project_name: String
+
        # The node reperesenting the project.
        #
        # Once the project’s graph is initialized, this node must not be edited.
@@ -46,22 +66,43 @@ class ProjectGraph
        # 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.
-       init(name: String) do
-               project.labels.add(name)
+       init do
+               project.labels.add(project_name)
                project.labels.add("MEntity")
                project.labels.add("MProject")
-               project["name"] = name
+               project["name"] = project_name
                all_nodes.add(project)
 
                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[""])
@@ -91,7 +132,7 @@ abstract class Entity
        var doc = new JsonArray is writable
 
        init do
-               self.labels.add(graph.project["name"].to_s)
+               self.labels.add(graph.project_name)
                self.labels.add("MEntity")
        end
 
@@ -124,7 +165,7 @@ abstract class Entity
        #
        # Also set `name` using `name_separator`.
        fun full_name=(full_name: String) do
-               var m: nullable Match = full_name.search_last(name_separator)
+               var m = full_name.search_last(name_separator)
 
                self["full_name"] = full_name
                if m == null then
@@ -143,7 +184,11 @@ abstract class Entity
 
        # 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
+               if parent_name.is_empty then
+                       self["full_name"] = name
+               else
+                       self["full_name"] = parent_name + name_separator + name
+               end
        end
 
        # Set the location of the entity in the source code.
@@ -151,6 +196,11 @@ abstract class Entity
                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.
@@ -209,25 +259,29 @@ abstract class Compound
        # 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
+       # * `full_name`: qualified name of the inner class. Ignored in practice.
+       # * `prot`: visibility (proctection).
+       #
+       # TODO: Handle cases where only the `full_name` is available.
+       fun declare_class(id: String, full_name: String, prot: String) do end
 
        # Declare a base compound (usually, a base class).
        #
@@ -257,18 +311,28 @@ end
 class Namespace
        super Compound
 
-       # Inner namespaces (IDs).
+       # The inner namespaces.
        #
        # Left empty for the root namespace.
-       var inner_namespaces: SimpleCollection[String] = new Array[String]
+       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: String, full_name: String, prot: String) do
+               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
@@ -281,13 +345,42 @@ class Namespace
                        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 `""`.
@@ -298,7 +391,7 @@ class RootNamespace
        init do
                super
                self["full_name"] = ""
-               self["name"] = graph.project["name"]
+               self["name"] = graph.project_name
        end
 
        redef fun declare_namespace(id: String, name: String) do end