neo_doxygen: Read the `prot` attribute of the `innerclass` elements.
[nit.git] / contrib / neo_doxygen / src / model / graph.nit
index 7607620..0d8f511 100644 (file)
 module model::graph
 
 import neo4j
+import more_collections
 import location
 
 # A Neo4j graph.
 class NeoGraph
+       # All the nodes in the graph.
        var all_nodes: SimpleCollection[NeoNode] = new Array[NeoNode]
+
+       # All the edges in the graph.
        var all_edges: SimpleCollection[NeoEdge] = new Array[NeoEdge]
 
        # Add a relationship between two nodes.
@@ -31,34 +35,74 @@ 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.
-       var project: NeoNode = new NeoNode
+       var project = new NeoNode
 
        # 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[""])
@@ -85,10 +129,10 @@ abstract class Entity
        var model_id: String = "" is writable
 
        # Associated documentation.
-       var doc: JsonArray = new JsonArray is writable
+       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
 
@@ -140,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.
@@ -205,29 +253,41 @@ 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, except for the root
+       # namespace. For the root namespace, both arguments are empty.
+       #
        # Parameters:
        #
        # * `id`: `model_id` of the inner namespace. May be empty.
-       # * `name`: string identifying the inner namespace. May be empty.
-       fun declare_namespace(id: String, name: String) do end
+       # * `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 an empty ID are not supported yet.
+       #
        # Parameters:
        #
-       # * `id`: `model_id` of the inner class. May be empty.
-       # * `name`: string identifying the inner class. May be empty.
-       fun declare_class(id: String, name: String) do end
+       # * `id`: `model_id` of the inner class.
+       # * `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).
        #
        # Parameters:
        #
        # * `id`: `model_id` of the base compound. May be empty.
-       # * `name`: string identifying the base compound. May be empty.
+       # * `full_name`: qualified name of the base compound. May be empty.
        # * `prot`: visibility (proctection) of the relationship.
        # * `virt`: level of virtuality of the relationship.
-       fun declare_super(id: String, name: String, prot: String, virt: String) do end
+       fun declare_super(id: String, full_name: String, prot: String,
+                       virt: String) do end
 end
 
 # An unrecognized compound.
@@ -246,18 +306,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
@@ -270,13 +340,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 `""`.