20736ad02742b623668e2f770263caebf534449b
[nit.git] / lib / neo4j / graph / json_graph_store.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
9 # another product.
10
11 # Provides JSON as a mean to store graphs.
12 module neo4j::graph::json_graph_store
13
14 import graph
15
16 # Save or load a graph using a JSON document.
17 #
18 # The graph (or the specified part of it) is stored as a JSON object with the
19 # following properties:
20 #
21 # * `"nodes"`: An array with all nodes. Each node is an object with the
22 # following properties:
23 # * `"labels"`: An array of all applied labels.
24 # * `"properties"`: An object mapping each defined property to its value.
25 # * `"edges"`: An array with all relationships. Each relationship is an object
26 # with the following properties:
27 # * `"type"`: The type (`String`) of the relationship.
28 # * `"properties"`: An object mapping each defined property to its value.
29 # * `"from"`: The local ID of the source node.
30 # * `"to"`: The local ID of the destination node.
31 #
32 # ~~~nit
33 # import neo4j::graph::sequential_id
34 #
35 # var graph = new NeoGraph(new SequentialNodeCollection("nid"))
36 # var a = new NeoNode
37 # a.labels.add "Foo"
38 # a["answer"] = 42
39 # a["Ultimate question of"] = new JsonArray.from(["life",
40 # "the Universe", "and Everything."])
41 # graph.nodes.register a
42 # var b = graph.create_node
43 # b.labels.add "Foo"
44 # b.labels.add "Bar"
45 # graph.edges.add new NeoEdge(a, "BAZ", b)
46 #
47 # var ostream = new StringOStream
48 # var store = new JsonGraphStore(graph)
49 # store.ostream = ostream
50 # store.save
51 # assert ostream.to_s == """{"nodes":[""" + """
52 # {"labels":["Foo"],"properties":{"answer":42,""" + """
53 # "Ultimate question of":["life","the Universe","and Everything."],""" + """
54 # "nid":1}},""" + """
55 # {"labels":["Foo","Bar"],"properties":{"nid":2}}],""" + """
56 # "edges":[{"type":"BAZ","properties":{},"from":1,"to":2}]}"""
57 #
58 # graph.nodes.clear
59 # graph.edges.clear
60 # store.istream = new StringIStream(ostream.to_s)
61 # store.load
62 # assert 1 == graph.edges.length
63 # for edge in graph.edges do
64 # assert "BAZ" == edge.rel_type
65 # assert a.labels == edge.from.labels
66 # for k, v in a.properties do assert v == edge.from.properties[k]
67 # assert b.labels == edge.to.labels
68 # for k, v in b.properties do assert v == edge.to.properties[k]
69 # end
70 # assert 2 == graph.nodes.length
71 # ~~~
72 class JsonGraphStore
73 super GraphStore
74
75 # The stream to use for `load`.
76 var istream: nullable IStream = null is writable
77
78 # The stream to use for `save` and `save_part`.
79 var ostream: nullable OStream = null is writable
80
81 # Use the specified `IOStream`.
82 init from_io(graph: NeoGraph, iostream: IOStream) do
83 init(graph)
84 istream = iostream
85 ostream = iostream
86 end
87
88 # Use the specified string to load the graph.
89 init from_string(graph: NeoGraph, string: String) do
90 init(graph)
91 istream = new StringIStream(string)
92 end
93
94 redef fun isolated_save do return true
95
96 redef fun load do
97 var istream = self.istream
98 assert istream isa IStream
99 fire_started
100 graph.load_json(istream.read_all)
101 fire_done
102 end
103
104 redef fun save_part(nodes, edges) do
105 var ostream = self.ostream
106 assert ostream isa OStream
107 fire_started
108 ostream.write(graph.to_json)
109 fire_done
110 end
111 end
112
113 redef class NeoGraph
114 super Jsonable
115
116 # Retrieve the graph from the specified JSON document.
117 #
118 # For the expected format, see `JsonGraphStore`.
119 #
120 # ~~~nit
121 # import neo4j::graph::sequential_id
122 #
123 # var graph = new NeoGraph(new SequentialNodeCollection("node_id"))
124 # var a = new NeoNode
125 # a.labels.add "Foo"
126 # a["answer"] = 42
127 # a["Ultimate question of"] = new JsonArray.from(["life",
128 # "the Universe", "and Everything."])
129 # graph.nodes.register a
130 # var b = graph.create_node
131 # b.labels.add "Foo"
132 # b.labels.add "Bar"
133 # graph.edges.add new NeoEdge(a, "BAZ", b)
134 #
135 # graph = new NeoGraph.from_json(
136 # new SequentialNodeCollection("node_id"), graph.to_json)
137 # assert 1 == graph.edges.length
138 # for edge in graph.edges do
139 # assert "BAZ" == edge.rel_type
140 # assert a.labels == edge.from.labels
141 # for k, v in a.properties do assert v == edge.from.properties[k]
142 # assert b.labels == edge.to.labels
143 # for k, v in b.properties do assert v == edge.to.properties[k]
144 # end
145 # assert 2 == graph.nodes.length
146 # ~~~
147 init from_json(nodes: NeoNodeCollection, t: Text) do
148 from_json_object(nodes, t.parse_json.as(JsonObject))
149 end
150
151 # Retrieve the graph from the specified JSON object.
152 #
153 # For the expected format, see `JsonGraphStore`.
154 init from_json_object(nodes: NeoNodeCollection, o: JsonObject) do
155 init(nodes)
156 load_json_object(o)
157 end
158
159 # Retrieve a part of the graph from the specified JSON document.
160 #
161 # For the expected format, see `JsonGraphStore`.
162 fun load_json(t: Text) do
163 load_json_object(t.parse_json.as(JsonObject))
164 end
165
166 # Retrieve a part of the graph from the specified JSON object.
167 #
168 # For the expected format, see `JsonGraphStore`.
169 fun load_json_object(o: JsonObject) do
170 var json_nodes = o["nodes"].as(JsonArray)
171 for json_node in json_nodes do
172 assert json_node isa JsonObject
173 var node = new NeoNode.from_json_object(json_node)
174 nodes.add node
175 end
176
177 var json_edges = o["edges"].as(JsonArray)
178 for json_edge in json_edges do
179 assert json_edge isa JsonObject
180 var from = nodes[nodes.id_from_jsonable(json_edge["from"])]
181 var to = nodes[nodes.id_from_jsonable(json_edge["to"])]
182 var rel_type = json_edge["type"].as(String)
183 var json_properties = json_edge["properties"].as(JsonObject)
184 var edge = new NeoEdge(from, rel_type, to)
185 edge.properties.recover_with(json_properties)
186 edges.add edge
187 end
188 end
189
190 redef fun to_json do return to_json_by_append
191
192 # Append the JSON representation of `self` to the specified buffer.
193 #
194 # For a description of the format, see `JsonGraphStore`.
195 #
196 # SEE: `to_json`
197 redef fun append_json(b) do
198 b.append "\{\"nodes\":["
199 append_entities_json(nodes, b)
200 b.append "],\"edges\":["
201 append_entities_json(edges, b)
202 b.append "]\}"
203 end
204
205 # Encode `self` in JSON.
206 #
207 # For a description of the format, see `JsonGraphStore`.
208 #
209 # SEE: `append_json`
210 private fun append_entities_json(entities: Collection[NeoEntity],
211 b: Buffer) do
212 var i = entities.iterator
213 if i.is_ok then
214 i.item.append_json_for(self, b)
215 i.next
216 for entity in i do
217 b.add ','
218 entity.append_json_for(self, b)
219 end
220 end
221 end
222 end
223
224 redef class NeoNodeCollection
225 # Convert the specified JSON value into a local ID.
226 fun id_from_jsonable(id: nullable Jsonable): ID_TYPE do return id.as(ID_TYPE)
227 end
228
229 redef class NeoEntity
230
231 # Append the JSON representation of the entity to the specified buffer.
232 fun append_json_for(graph: NeoGraph, buffer: Buffer) is abstract
233 end
234
235 # Make `NeoNode` `Jsonable`.
236 redef class NeoNode
237 super Jsonable
238
239 # Retrieve the node from the specified JSON value.
240 #
241 # Note: Here, the `"id"` is optional and ignored.
242 #
243 # SEE: `JsonGraph`
244 #
245 # var node = new NeoNode.from_json("""
246 # {
247 # "labels": ["foo", "Bar"],
248 # "properties": {
249 # "baz": 42
250 # }
251 # }
252 # """)
253 # assert ["foo", "Bar"] == node.labels
254 # assert 42 == node["baz"]
255 init from_json(t: Text) do
256 from_json_object(t.parse_json.as(JsonObject))
257 end
258
259 # Retrieve the node from the specified JSON value.
260 #
261 # Note: Here, the `"id"` is optional and ignored.
262 #
263 # SEE: `JsonGraph`
264 init from_json_object(o: JsonObject) do
265 init
266 var labels = o["labels"].as(JsonArray)
267 for lab in labels do self.labels.add(lab.as(String))
268 var json_properties = o["properties"].as(JsonObject)
269 properties.recover_with(json_properties)
270 end
271
272 redef fun to_json do return to_json_by_append
273
274 # Append the JSON representation of the node to the specified buffer.
275 #
276 # SEE: `JsonGraph`
277 redef fun append_json(b) do
278 b.append "\{\"labels\":["
279 var i = labels.iterator
280 if i.is_ok then
281 i.item.append_json(b)
282 i.next
283 for lab in i do
284 b.add ','
285 lab.append_json(b)
286 end
287 end
288 b.append "],\"properties\":"
289 properties.append_json(b)
290 b.add '}'
291 end
292
293 redef fun to_s do return to_json
294
295 # Append the JSON representation of the node to the specified buffer.
296 redef fun append_json_for(graph: NeoGraph, buffer: Buffer) do
297 append_json(buffer)
298 end
299 end
300
301 redef class NeoEdge
302
303 # Append the JSON representation of the relationship to the specified buffer.
304 #
305 # Use the IDs specfied by `graph.nodes`.
306 redef fun append_json_for(graph: NeoGraph, buffer: Buffer) do
307 buffer.append "\{\"type\":"
308 rel_type.append_json(buffer)
309 buffer.append ",\"properties\":"
310 properties.append_json(buffer)
311 buffer.append ",\"from\":"
312 graph.nodes.id_of(from).append_json(buffer)
313 buffer.append ",\"to\":"
314 graph.nodes.id_of(to).append_json(buffer)
315 buffer.append "}"
316 end
317 end