rename `NativeString` to `CString`
[nit.git] / lib / serialization / serialization.nit
index 2795d80..f94445e 100644 (file)
@@ -16,7 +16,7 @@
 
 # Abstract services to serialize Nit objects to different formats
 #
 
 # Abstract services to serialize Nit objects to different formats
 #
-# This module declares the `auto_serializable` annotation to mark Nit classes as serializable.
+# This module declares the `serialize` annotation to mark Nit classes as serializable.
 # For an introduction to this service, refer to the documentation of the `serialization` group.
 # This documentation provides more technical information on interesting entitie of this module.
 #
 # For an introduction to this service, refer to the documentation of the `serialization` group.
 # This documentation provides more technical information on interesting entitie of this module.
 #
@@ -44,6 +44,9 @@
 #   `notify_of_creation` must be redefined.
 module serialization is
        new_annotation auto_serializable
 #   `notify_of_creation` must be redefined.
 module serialization is
        new_annotation auto_serializable
+       new_annotation serialize
+       new_annotation noserialize
+       new_annotation serialize_as
 end
 
 # Abstract serialization service to be sub-classed by specialized services.
 end
 
 # Abstract serialization service to be sub-classed by specialized services.
@@ -85,28 +88,98 @@ end
 
 # Abstract deserialization service
 #
 
 # 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
+# The main service is `deserialize`.
+abstract class Deserializer
+       # Deserialize and return an object, storing errors in the attribute `errors`
+       #
+       # If a `static_type` is given, only subtypes of the `static_type` are accepted.
+       #
+       # This method behavior varies according to the implementation engines.
+       fun deserialize(static_type: nullable String): nullable Object is abstract
 
 
-       # Internal method to be implemented by sub-classes
-       fun deserialize_attribute(name: String): nullable Object is abstract
+       # Deserialize the attribute with `name` from the object open for deserialization
+       #
+       # The `static_type` can be used as last resort if the deserialized object
+       # desn't have any metadata declaring the dynamic type.
+       #
+       # Return the deserialized value or null on error, and set
+       # `deserialize_attribute_missing` to whether the attribute was missing.
+       #
+       # Internal method to be implemented by the engines.
+       fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
+
+       # Was the attribute queried by the last call to `deserialize_attribute` missing?
+       var deserialize_attribute_missing = false
 
 
-       # Internal method called by objects in creation,
-       # to be implemented by sub-classes
+       # Register a newly allocated object (even if not completely built)
+       #
+       # Internal method called by objects in creation, to be implemented by the engines.
        fun notify_of_creation(new_object: Object) is abstract
 
        # Deserialize the next available object as an instance of `class_name`
        #
        fun notify_of_creation(new_object: Object) is abstract
 
        # Deserialize the next available object as an instance of `class_name`
        #
-       # Returns the deserialized object on success, aborts on error.
+       # Return the deserialized object on success and
+       # record in `errors` if `class_name` is unknown.
        #
        # This method should be redefined for each custom subclass of `Serializable`.
        # All refinement should look for a precise `class_name` and call super
        # on unsupported classes.
        #
        # This method should be redefined for each custom subclass of `Serializable`.
        # All refinement should look for a precise `class_name` and call super
        # on unsupported classes.
-       fun deserialize_class(class_name: String): Object do
-               print "Error: doesn't know how to deserialize class \"{class_name}\""
-               abort
+       protected fun deserialize_class(class_name: String): nullable Object do
+               if class_name == "Error" then return new Error.from_deserializer(self)
+               return deserialize_class_intern(class_name)
+       end
+
+       # Generated service to deserialize the next available object as an instance of `class_name`
+       #
+       # Refinements to this method will be generated by the serialization phase.
+       # To avoid conflicts, there should not be any other refinements to this method.
+       # You can instead use `deserialize_class`.
+       protected fun deserialize_class_intern(class_name: String): nullable Object do
+               errors.add new Error("Deserialization Error: Doesn't know how to deserialize class \"{class_name}\"")
+               return null
+       end
+
+       # Should `self` keep trying to deserialize an object after an error?
+       #
+       # This behavior takes effect after each attribute deserialization with
+       # errors such as a missing attribute or the value is of the wrong type.
+       # If `keep_going`, the attribute will be skipped but the engine will
+       # deserialize the next attribute.
+       # If `not keep_going`, the engine stops deserializing right away.
+       #
+       # When at `true`, this may cause the accumulation of a lot of entries in `errors`.
+       #
+       # Default at `true`.
+       var keep_going: nullable Bool = null is writable
+
+       # Errors encountered in the last call to `deserialize`
+       var errors = new Array[Error]
+end
+
+# Error on invalid dynamic type for a deserialized attribute
+class AttributeTypeError
+       super Error
+
+       autoinit receiver, attribute_name, attribute, expected_type
+
+       # Parent object of the problematic attribute
+       var receiver: Object
+
+       # Name of the problematic attribute in `receiver`
+       var attribute_name: String
+
+       # Deserialized object that isn't of the `expected_type`
+       var attribute: nullable Object
+
+       # Name of the type expected for `attribute`
+       var expected_type: String
+
+       redef var message is lazy do
+               var attribute = attribute
+               var found_type = if attribute != null then attribute.class_name else "null"
+
+               return "Deserialization Error: {
+               }Wrong type on `{receiver.class_name}::{attribute_name}` expected `{expected_type}`, got `{found_type}`"
        end
 end
 
        end
 end
 
@@ -138,7 +211,7 @@ interface Serializable
        # Create an instance of this class from the `deserializer`
        #
        # This constructor is refined by subclasses to correctly build their instances.
        # Create an instance of this class from the `deserializer`
        #
        # This constructor is refined by subclasses to correctly build their instances.
-       init from_deserializer(deserializer: Deserializer) do end
+       init from_deserializer(deserializer: Deserializer) is nosuper do end
 end
 
 redef interface Object
 end
 
 redef interface Object
@@ -146,6 +219,12 @@ redef interface Object
        #
        # Used to determine if an object has already been serialized.
        fun is_same_serialized(other: nullable Object): Bool do return is_same_instance(other)
        #
        # Used to determine if an object has already been serialized.
        fun is_same_serialized(other: nullable Object): Bool do return is_same_instance(other)
+
+       # Hash value use for serialization
+       #
+       # Used in combination with `is_same_serialized`. If two objects are the same
+       # in a serialization context, they must have the same `serialization_hash`.
+       fun serialization_hash: Int do return object_id
 end
 
 # Instances of this class are not delayed and instead serialized immediately
 end
 
 # Instances of this class are not delayed and instead serialized immediately
@@ -160,6 +239,63 @@ redef class Bool super DirectSerializable end
 redef class Char super DirectSerializable end
 redef class Int super DirectSerializable end
 redef class Float super DirectSerializable end
 redef class Char super DirectSerializable end
 redef class Int super DirectSerializable end
 redef class Float super DirectSerializable end
-redef class NativeString super DirectSerializable end
-redef class String super DirectSerializable end
-redef class Array[E] super Serializable end
+redef class CString super DirectSerializable end
+redef class Text super DirectSerializable end
+redef class SimpleCollection[E] super Serializable end
+redef class Map[K, V] super Serializable end
+
+redef class Couple[F, S]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var first = v.deserialize_attribute("first")
+               var second = v.deserialize_attribute("second")
+               init(first, second)
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("first", first)
+               v.serialize_attribute("second", second)
+       end
+end
+
+redef class Ref[E]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var item = v.deserialize_attribute("item")
+               init item
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("item", first)
+       end
+end
+
+redef class Error
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var message = v.deserialize_attribute("message")
+               if not message isa String then message = ""
+               init message
+
+               var cause = v.deserialize_attribute("cause")
+               if cause isa nullable Error then self.cause = cause
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("message", message)
+               v.serialize_attribute("cause", cause)
+       end
+end