198b8fbff97b73b636e98d545c65b6f833f14be1
[nit.git] / contrib / neo_doxygen / src / model / graph.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Graphs and basic entities.
16 module model::graph
17
18 import neo4j
19 import more_collections
20 import location
21
22 # A Neo4j graph.
23 class NeoGraph
24 # All the nodes in the graph.
25 var all_nodes: SimpleCollection[NeoNode] = new Array[NeoNode]
26
27 # All the edges in the graph.
28 var all_edges: SimpleCollection[NeoEdge] = new Array[NeoEdge]
29
30 # Add a relationship between two nodes.
31 #
32 # Parameters are the same than for the constructor of `NeoEdge`.
33 fun add_edge(from: NeoNode, rel_type: String, to: NeoNode) do
34 all_edges.add(new NeoEdge(from, rel_type, to))
35 end
36 end
37
38 # A project’s graph.
39 #
40 # Here is the usual steps to build a project graph:
41 #
42 # <ul>
43 # <li>Instantiate `ProjectGraph` by giving the name that will label the project.</li>
44 # <li>For each compound:
45 # <ul>
46 # <li>Instantiate the compound.</li>
47 # <li>Provide all the related data.</li>
48 # <li>Call the `put_in_graph` method of the compound.</li>
49 # </ul></li>
50 # <li>Call the `add_global_modules` method of the project’s graph (defined in
51 # the `module_compound` module). This permits to take global classes into
52 # account correctly.</li>
53 # <li>Call the `put_edges` method of the project’s graph.</li>
54 # </ul>
55 class ProjectGraph
56 super NeoGraph
57
58 # The project’s name.
59 var project_name: String
60
61 # The node reperesenting the project.
62 #
63 # Once the project’s graph is initialized, this node must not be edited.
64 var project = new NeoNode
65
66 # Entities by `model_id`.
67 var by_id: Map[String, Entity] = new HashMap[String, Entity]
68
69 # Namespaces by `full_name`.
70 var namespaces: Map[String, Namespace] = new HashMap[String, Namespace]
71
72 # For each `ClassCompound` in the graph, the mapping between its `model_id` and its namespace.
73 #
74 # Defaults to the root namespace. An entry is added each time
75 # `Namespace.declare_class` is called.
76 #
77 # Note: In the graph, there is no direct link between a namespace and a
78 # class. It is the role of a module (created internally by a `FileCompound`)
79 # to link a class with its namespace. So, this collection is used by modules
80 # to know which class in a file belong to their related namespace. It is
81 # also used by `FileCompound` to detect classes in the root namespace.
82 var class_to_ns: Map[String, Namespace] is noinit
83
84 # Initialize a new project graph using the specified project name.
85 #
86 # The specified name will label all nodes of the project’s graph.
87 init do
88 project.labels.add(project_name)
89 project.labels.add("MEntity")
90 project.labels.add("MProject")
91 project["name"] = project_name
92 all_nodes.add(project)
93
94 var root = new RootNamespace(self)
95 root.put_in_graph
96 by_id[""] = root
97 class_to_ns = new DefaultMap[String, Namespace](root)
98 end
99
100 # Request to all nodes in the graph to add their related edges.
101 #
102 # Note: For the rare cases where a node need to wait the `put_edges` to add
103 # an implicit node, this method makes sure to call the `put_edges` method
104 # of the newly added nodes only after processing all the nodes that was
105 # already there.
106 fun put_edges do
107 all_edges.clear
108 add_edge(project, "ROOT", by_id[""])
109 for n in all_nodes do
110 if n isa Entity then
111 n.put_edges
112 end
113 end
114 end
115 end
116
117 # A model’s entity.
118 #
119 # In practice, this is the base class of every node in a `ProjectGraph`.
120 abstract class Entity
121 super NeoNode
122
123 # Graph that will embed the entity.
124 var graph: ProjectGraph
125
126 # ID of the entity in the model.
127 #
128 # Is empty for entities without an ID.
129 var model_id: String = "" is writable
130
131 # Associated documentation.
132 var doc = new JsonArray is writable
133
134 init do
135 self.labels.add(graph.project_name)
136 self.labels.add("MEntity")
137 end
138
139 # The short (unqualified) name.
140 #
141 # May be also set by `full_name=`.
142 fun name=(name: String) do
143 self["name"] = name
144 end
145
146 # The short (unqualified) name.
147 fun name: String do
148 var name = self["name"]
149 assert name isa String
150 return name
151 end
152
153 # Include the documentation of `self` in the graph.
154 protected fun set_mdoc do
155 self["mdoc"] = doc
156 end
157
158 # The namespace separator of Nit/C++.
159 fun ns_separator: String do return "::"
160
161 # The name separator used when calling `full_name=`.
162 fun name_separator: String do return ns_separator
163
164 # The full (qualified) name.
165 #
166 # Also set `name` using `name_separator`.
167 fun full_name=(full_name: String) do
168 var m = full_name.search_last(name_separator)
169
170 self["full_name"] = full_name
171 if m == null then
172 name = full_name
173 else
174 name = full_name.substring_from(m.after)
175 end
176 end
177
178 # The full (qualified) name.
179 fun full_name: String do
180 var full_name = self["full_name"]
181 assert full_name isa String
182 return full_name
183 end
184
185 # Set the full name using the current name and the specified parent name.
186 fun parent_name=(parent_name: String) do
187 if parent_name.is_empty then
188 self["full_name"] = name
189 else
190 self["full_name"] = parent_name + name_separator + name
191 end
192 end
193
194 # Set the location of the entity in the source code.
195 fun location=(location: nullable Location) do
196 self["location"] = location
197 end
198
199 # Get the location of the entity in the source code.
200 fun location: nullable Location do
201 return self["location"].as(nullable Location)
202 end
203
204 # Put the entity in the graph.
205 #
206 # Called by the loader when it has finished to read the entity.
207 fun put_in_graph do
208 if doc.length > 0 then
209 set_mdoc
210 end
211 graph.all_nodes.add(self)
212 if model_id != "" then graph.by_id[model_id] = self
213 end
214
215 # Put the related edges in the graph.
216 #
217 # This method is called on each node by `ProjectGraph.put_edges`.
218 #
219 # Note: Even at this step, the entity may modify its own attributes and
220 # inner entities’ ones because some values are only known once the entity
221 # know its relationships with the rest of the graph.
222 fun put_edges do end
223 end
224
225 # An entity whose the location is mandatory.
226 abstract class CodeBlock
227 super Entity
228
229 init do
230 self["location"] = new Location
231 end
232
233 redef fun location=(location: nullable Location) do
234 if location == null then
235 super(new Location)
236 else
237 super
238 end
239 end
240 end
241
242 # A compound.
243 #
244 # Usually corresponds to a `<compounddef>` element in of the XML output of
245 # Doxygen.
246 abstract class Compound
247 super Entity
248
249 # Set the declared visibility (the proctection) of the compound.
250 fun visibility=(visibility: String) do
251 self["visibility"] = visibility
252 end
253
254 # Set the specific kind of the compound.
255 fun kind=(kind: String) do
256 self["kind"] = kind
257 end
258
259 # Declare an inner namespace.
260 #
261 # Note: Althought Doxygen indicates that the name is optional,
262 # declarations with an empty name are not supported yet, except for the root
263 # namespace. For the root namespace, both arguments are empty.
264 #
265 # Parameters:
266 #
267 # * `id`: `model_id` of the inner namespace. May be empty.
268 # * `full_name`: qualified name of the inner namespace. Use an empty name
269 # for the root namespace.
270 fun declare_namespace(id: String, full_name: String) do end
271
272 # Declare an inner class.
273 #
274 # Note: Althought Doxygen indicates that both arguments are optional,
275 # declarations with an empty ID are not supported yet.
276 #
277 # Parameters:
278 #
279 # * `id`: `model_id` of the inner class.
280 # * `full_name`: qualified name of the inner class. Ignored in practice.
281 # * `prot`: visibility (proctection).
282 #
283 # TODO: Handle cases where only the `full_name` is available.
284 fun declare_class(id: String, full_name: String, prot: String) do end
285
286 # Declare a base compound (usually, a base class).
287 #
288 # Parameters:
289 #
290 # * `id`: `model_id` of the base compound. May be empty.
291 # * `full_name`: qualified name of the base compound. May be empty.
292 # * `prot`: visibility (proctection) of the relationship.
293 # * `virt`: level of virtuality of the relationship.
294 fun declare_super(id: String, full_name: String, prot: String,
295 virt: String) do end
296 end
297
298 # An unrecognized compound.
299 #
300 # Used to simplify the handling of ignored entities.
301 class UnknownCompound
302 super Compound
303
304 redef fun put_in_graph do end
305 redef fun put_edges do end
306 end
307
308 # A namespace.
309 #
310 # Corresponds to a group in Nit.
311 class Namespace
312 super Compound
313
314 # The inner namespaces.
315 #
316 # Left empty for the root namespace.
317 var inner_namespaces: SimpleCollection[NamespaceRef] = new Array[NamespaceRef]
318
319 init do
320 super
321 self.labels.add("MGroup")
322 end
323
324 redef fun declare_namespace(id: String, full_name: String) do
325 inner_namespaces.add new NamespaceRef(id, full_name)
326 end
327
328 redef fun declare_class(id: String, full_name: String, prot: String) do
329 graph.class_to_ns[id] = self
330 end
331
332 redef fun put_in_graph do
333 super
334 var full_name = self["full_name"]
335 if full_name isa String then graph.namespaces[full_name] = self
336 end
337
338 redef fun put_edges do
339 super
340 graph.add_edge(self, "PROJECT", graph.project)
341 if self["name"] == self["full_name"] and self["full_name"] != "" then
342 # The root namespace does not know its children.
343 var root = graph.by_id[""]
344 graph.add_edge(self, "PARENT", root)
345 graph.add_edge(root, "NESTS", self)
346 end
347 for ns in inner_namespaces do
348 var node = ns.seek_in(graph)
349 graph.add_edge(node, "PARENT", self)
350 graph.add_edge(self, "NESTS", node)
351 end
352 end
353 end
354
355 # A reference to a namespace.
356 class NamespaceRef
357 # The `model_id` of the target.
358 #
359 # Empty when unknown or for the root namespace.
360 var model_id: String
361
362 # The `full_name` of the target.
363 #
364 # Empty only for the root namespace.
365 var full_name: String
366
367 # Look for the targeted namespace in the specified graph.
368 fun seek_in(graph: ProjectGraph): Namespace do
369 var ns_compound: Namespace
370
371 if model_id.is_empty and not full_name.is_empty then
372 # ID unspecified. => We have to look by name
373 assert graph.namespaces.has_key(full_name) else
374 sys.stderr.write "Namespace `{full_name}` not found."
375 end
376 ns_compound = graph.namespaces[full_name]
377 else
378 ns_compound = graph.by_id[model_id].as(Namespace)
379 end
380 return ns_compound
381 end
382 end
383
384 # The root namespace of a `ProjectGraph`.
385 #
386 # This the only entity in the graph whose `model_id` is really `""`.
387 # Added automatically at the initialization of a `ProjectGraph`.
388 class RootNamespace
389 super Namespace
390
391 init do
392 super
393 self["full_name"] = ""
394 self["name"] = graph.project_name
395 end
396
397 redef fun declare_namespace(id: String, name: String) do end
398 end