import graph
import class_compound
+import namespace_members
# A source file.
#
# Creates one modules by inner namespace. The full name of the modules begin
# with the namespace’s full name, and end with the unqualified name of the file,
# without the extension.
+#
+# Note: If a module associated to the root namespace is needed, it is added to
+# the graph only when `put_edges` is called.
class FileCompound
super Compound
super CodeBlock
- # Mapping between inner namespace’s names and corresponding modules.
- private var inner_namespaces: Map[String, Module] = new HashMap[String, Module]
+ # Modules corresponding to the namespaces defined/redefined in the file.
+ private var inner_namespaces = new Array[Module]
+
+ # `model_id` of the classes declared in the file.
+ private var inner_classes = new Array[String]
# The last component of the path, without the extension.
#
super
end
- redef fun name_separator: String do return "/"
-
- redef fun location=(location: nullable Location) do
+ redef fun location=(location) do
super
- if location != null and location.path != null then
- full_name = location.path.as(not null)
- end
- for m in inner_namespaces.values do m.location = location
+ for m in inner_namespaces do m.location = location
end
- redef fun name=(name: String) do
+ redef fun name=(name) do
# Example: `MyClass.java`
super
var match = name.search_last(".")
basename = name.substring(0, match.from)
end
# Update the modules’ name.
- for ns, m in inner_namespaces do
- if ns.is_empty then
- m.full_name = basename
- else
- m.full_name = "{ns}{ns_separator}{basename}"
- end
- end
+ for m in inner_namespaces do m.update_name
end
- redef fun declare_namespace(id: String, full_name: String) do
+ redef fun declare_namespace(id, full_name) do
var m: Module
assert not full_name.is_empty or id.is_empty else
sys.stderr.write "Inner mamespace declarations without name are not yet supported (except for the root namespace).\n"
end
- if inner_namespaces.keys.has(full_name) then
- m = inner_namespaces[full_name]
- if id != "" then m.parent = id
- else
- m = new Module(graph)
- if full_name.is_empty then
- m.full_name = basename
- else
- m.full_name = "{full_name}{ns_separator}{basename}"
- end
- m.parent = id
- m.location = self["location"].as(nullable Location)
- inner_namespaces[full_name] = m
- end
+ m = new Module(graph, self, new NamespaceRef(id, full_name))
+ m.location = location
+ inner_namespaces.add m
end
- redef fun declare_class(id: String, full_name: String) do
+ 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
- assert not full_name.is_empty else
- sys.stderr.write "Inner class declarations without name are not yet supported.\n"
- end
- var match = full_name.search_last(ns_separator)
- var ns_name: String
- var m: Module
-
- if match == null then
- ns_name = ""
- else
- ns_name = full_name.substring(0, match.from)
- end
- if inner_namespaces.keys.has(ns_name) then
- m = inner_namespaces[ns_name]
- else
- declare_namespace("", ns_name)
- m = inner_namespaces[ns_name]
- end
- m.declare_class(id, full_name)
+ inner_classes.add id
end
redef fun put_in_graph do
# Do not add `self` to the Neo4j graph...
# ... but add its modules...
- for m in inner_namespaces.values do m.put_in_graph
- # ... and add `self` to the index.
+ for m in inner_namespaces do m.put_in_graph
+ # ... and add `self` to the indexes.
if model_id != "" then graph.by_id[model_id] = self
+ graph.files.add self
+ end
+
+ # If the file contains some classes in the root namespace, add an implicit
+ # module to handle them.
+ #
+ # This method is called by `ProjectGraph.add_global_modules` and assumes
+ # that all the namespaces are already fully set and put in the graph.
+ fun declare_root_namespace do
+ if has_globals then
+ declare_namespace("", "")
+ inner_namespaces.last.put_in_graph
+ end
+ end
+
+ # Does this file contain classes in the root namespace?
+ private fun has_globals: Bool do
+ var root = graph.by_id[""]
+ for c in inner_classes do
+ if graph.class_to_ns[c] == root then return true
+ end
+ return false
end
end
-# A module.
-class Module
+# A `MModule` node.
+#
+# For each file, there is one module by inner namespace.
+private class Module
super Compound
super CodeBlock
- # The `model_id` of the parent namespace.
- var parent: String = "" is writable
+ # The file that declares the module.
+ var file_compound: FileCompound
- # The classes defined in the module.
- var inner_classes: SimpleCollection[String] = new Array[String]
+ # The namespace defined or redefined by the module.
+ var namespace: NamespaceRef
init do
super
self.labels.add("MModule")
+ update_name
end
- redef fun declare_class(id: String, full_name: String) do
- assert not id.is_empty else
- sys.stderr.write "Inner class declarations without ID not supported yet.\n"
- end
- inner_classes.add(id)
- end
+ # Update the `name`.
+ #
+ # Update the short name of the module to the `basename` of the file that
+ # declares it.
+ fun update_name do name = file_compound.basename
redef fun put_edges do
- graph.add_edge(graph.by_id[parent], "DECLARES", self)
- for c in inner_classes do
- var node = graph.by_id[c].as(ClassCompound)
- graph.add_edge(self, "INTRODUCES", node)
- graph.add_edge(self, "DEFINES", node.class_def)
+ var ns_compound = namespace.seek_in(graph)
+ var self_class = ns_compound.self_class
+ var class_count = 0
+ var last_class: nullable ClassCompound = null
+
+ graph.add_edge(ns_compound, "DECLARES", self)
+
+ for c in file_compound.inner_classes do
+ if graph.class_to_ns[c] != ns_compound then continue
+ var class_compound = graph.by_id[c].as(ClassCompound)
+ last_class = class_compound
+ class_count += 1
+ graph.add_edge(self, "INTRODUCES", class_compound)
+ graph.add_edge(self, "DEFINES", class_compound.class_def)
+ end
+
+ if self_class isa SelfClass then
+ # We assume that only one file is linked to the namespace.
+ # TODO When Doxygen will provide a way to know which file defines which member, use it.
+ self_class.location = file_compound.location
+ graph.add_edge(self, "INTRODUCES", self_class)
+ graph.add_edge(self, "DEFINES", self_class.class_def)
+ end
+
+ if doc.is_empty and class_count == 1 then
+ doc = last_class.as(not null).doc
end
+ if doc.is_empty then doc = file_compound.doc
+ if doc.is_empty then doc = ns_compound.doc
+ if not doc.is_empty then set_mdoc
+ end
+end
+
+# Adds the `add_global_modules` phase to `ProjectGraph`.
+redef class ProjectGraph
+
+ # Project’s source files.
+ var files: SimpleCollection[FileCompound] = new Array[FileCompound]
+
+ # Add the modules that define the root namespace.
+ #
+ # **Must** be called before any call to `put_edges`, and after all the
+ # namespaces are fully set and put in the graph.
+ #
+ # Note: This method is not idempotent so it has to be called only once.
+ fun add_global_modules do
+ for f in files do f.declare_root_namespace
end
end