phases: add support for deserialization
authorAlexis Laferrière <alexis.laf@xymus.net>
Sat, 1 Feb 2014 19:09:14 +0000 (14:09 -0500)
committerAlexis Laferrière <alexis.laf@xymus.net>
Thu, 20 Feb 2014 15:55:04 +0000 (10:55 -0500)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/json_serialization.nit
lib/serialization.nit
src/serialization_phase.nit

index 4b47eff..b4b1062 100644 (file)
@@ -17,6 +17,7 @@
 module json_serialization
 
 import serialization
+import simple_json_reader
 
 class JsonSerializer
        super Serializer
@@ -66,6 +67,97 @@ class JsonSerializer
        end
 end
 
+# Deserializer from a Json string
+class JsonDeserializer
+       super Deserializer
+
+       var root: nullable Object
+       var path = new Array[HashMap[String, nullable Object]]
+       var id_to_object = new HashMap[Int, Object]
+
+       var just_opened_id: nullable Int = null
+
+       init(text: String)
+       do
+               var root = text.json_to_nit_object
+               if root isa HashMap[String, nullable Object] then path.add(root)
+               self.root = root
+       end
+
+       redef fun deserialize_attribute(name)
+       do
+               assert not path.is_empty
+               var current = path.last
+
+               assert current.keys.has(name)
+               var value = current[name]
+               
+               return convert_object(value)
+       end
+
+       # This may be called multiple times by the same object from constructors
+       # in different nclassdef
+       redef fun notify_of_creation(new_object)
+       do
+               var id = just_opened_id
+               assert id != null
+               id_to_object[id] = new_object
+       end
+
+       # Convert from simple Json object to Nit object
+       private fun convert_object(object: nullable Object): nullable Object
+       do
+               if object isa HashMap[String, nullable Object] then
+                       assert object.keys.has("__kind")
+                       var kind = object["__kind"]
+
+                       # ref?
+                       if kind == "ref" then
+                               assert object.keys.has("__id")
+                               var id = object["__id"]
+                               assert id isa Int
+
+                               assert id_to_object.keys.has(id)
+                               return id_to_object[id]
+                       end
+
+                       # obj?
+                       if kind == "obj" then
+                               assert object.keys.has("__id")
+                               var id = object["__id"]
+                               assert id isa Int
+
+                               assert object.keys.has("__class")
+                               var class_name = object["__class"]
+                               assert class_name isa String
+
+                               assert not id_to_object.keys.has(id) else print "Error: Object with id '{id}' is deserialized twice."
+
+                               # advance on path
+                               path.push object
+
+                               just_opened_id = id
+                               var value = deserialize_class(class_name)
+                               just_opened_id = null
+
+                               # revert on path
+                               path.pop
+
+                               return value
+                       end
+
+                       # char? TODO
+
+                       print "Malformed Json string: unexpected Json Object kind '{kind}'"
+                       abort
+               end
+
+               return object
+       end
+
+       redef fun deserialize do return convert_object(root)
+end
+
 redef class Serializable
        private fun serialize_to_json(v: JsonSerializer)
        do
index 1ed25a8..ae971c9 100644 (file)
@@ -49,6 +49,25 @@ interface Serializer
        fun warn(msg: String) do print "Serialization warning: {msg}"
 end
 
+# Abstract deserialization service
+#
+# After initialization of one of its sub-classes, call `deserialize`
+interface Deserializer
+       # Main method of this class, returns a Nit object
+       fun deserialize: nullable Object is abstract
+
+       # Internal method to be implemented by sub-classes
+       fun deserialize_attribute(name: String): nullable Object is abstract
+
+       # Internal method called by objects in creation,
+       # to be implemented by sub-classes
+       fun notify_of_creation(new_object: Object) is abstract
+
+       # Mainly generated method to return the next instance of the givent
+       # class by name
+       fun deserialize_class(class_name: String): Object do abort
+end
+
 # Instances of this class can be passed to `Serializer::serialize`
 interface Serializable
        # Full or true serialization
index d17a40a..7bdd2d4 100644 (file)
@@ -42,6 +42,24 @@ private class SerializationPhase
                end
 
                generate_serialization_method(nclassdef)
+
+               generate_deserialization_init(nclassdef)
+       end
+
+       redef fun process_nmodule(nmodule)
+       do
+               # collect all classes
+               var auto_serializable_nclassdefs = new Array[AStdClassdef]
+               for nclassdef in nmodule.n_classdefs do
+                       if nclassdef isa AStdClassdef and
+                          not nclassdef.collect_annotations_by_name("auto_serializable").is_empty then
+                               auto_serializable_nclassdefs.add nclassdef
+                       end
+               end
+
+               if not auto_serializable_nclassdefs.is_empty then
+                       generate_deserialization_method(nmodule, auto_serializable_nclassdefs)
+               end
        end
 
        private fun generate_serialization_method(nclassdef: AClassdef)
@@ -63,6 +81,71 @@ private class SerializationPhase
                # Create method Node and add it to the AST
                npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
        end
+
+       # Add a constructor to the automated nclassdef
+       private fun generate_deserialization_init(nclassdef: AClassdef)
+       do
+               var npropdefs = nclassdef.n_propdefs
+
+               var code = new Array[String]
+               code.add "init from_deserializer(v: Deserializer)"
+               code.add "do"
+               code.add "      v.notify_of_creation self"
+
+               for attribute in npropdefs do if attribute isa AAttrPropdef then
+                       var name = attribute.name
+                       var type_name = attribute.type_name
+                       code.add ""
+                       code.add "\tvar {name} = v.deserialize_attribute(\"{name}\")"
+                       code.add "\tassert {name} isa {type_name} else print \"Expected attribute '{name}' to be of type '{type_name}'\""
+                       code.add "\tself.{name} = {name}"
+               end
+
+               code.add "end"
+               npropdefs.add(toolcontext.parse_propdef(code.join("\n")))
+       end
+
+       # Added to the abstract serialization service
+       private fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
+       do
+               var code = new Array[String]
+
+               var deserializer_nclassdef = nmodule.deserializer_nclassdef
+               var deserializer_npropdef
+               if deserializer_nclassdef == null then
+                       # create the class
+                       code.add "redef class Deserializer"
+                       deserializer_npropdef = null
+               else
+                       deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
+               end
+
+               if deserializer_npropdef == null then
+                       # create the property
+                       code.add "      redef fun deserialize_class(name)"
+                       code.add "      do"
+               else
+                       toolcontext.error(deserializer_npropdef.location, "Annotation error: you cannont define Deserializer::deserialize_class in a module where you use \"auto_serializable\".")
+                       return
+               end
+
+               for nclassdef in nclassdefs do
+                       var name = nclassdef.n_id.text
+                       if not name.has('[') then # FIXME this is a temporary hack
+                               code.add "              if name == \"{name}\" then return new {name}.from_deserializer(self)"
+                       end
+               end
+
+               code.add "              return super"
+               code.add "      end"
+
+               if deserializer_nclassdef == null then
+                       code.add "end"
+                       nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
+               else
+                       deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
+               end
+       end
 end
 
 redef class AAttrPropdef
@@ -76,6 +159,8 @@ redef class AAttrPropdef
        do
                var name = n_type.n_id.text
 
+               if n_type.n_kwnullable != null then name = "nullable {name}"
+
                var types = n_type.n_types
                if not types.is_empty then
                        var params = new Array[String]
@@ -84,3 +169,30 @@ redef class AAttrPropdef
                else return name
        end
 end
+
+redef class AModule
+       private fun deserializer_nclassdef: nullable AStdClassdef
+       do
+               for nclassdef in n_classdefs do
+                       if nclassdef isa AStdClassdef and nclassdef.n_id.text == "Deserialization" then
+                               return nclassdef
+                       end
+               end
+
+               return null
+       end
+end
+
+redef class AStdClassdef
+       private fun deserializer_npropdef: nullable AMethPropdef
+       do
+               for npropdef in n_propdefs do if npropdef isa AMethPropdef then
+                       var id = npropdef.n_methid
+                       if id isa AIdMethid and id.n_id.text == "deserialize_class" then
+                               return npropdef
+                       end
+               end
+
+               return null
+       end
+end