# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Deserialize full Nit objects from MessagePack format # # See the package `msgpack` for more details on the serialization # of Nit objects. module serialization_read import serialization::caching import serialization::safe private import json # for class_inheritance_metamodel private import serialization::engine_tools import serialization_common private import read import ext # --- # Easy services redef class Bytes # Deserialize full Nit `nullable Object` from MessagePack formated data # # The dynamic type of the deserialized object can be limited to `static_type`. # # Warning: Deserialization errors are reported with `print_error`, # the returned object may be partial or fall back on `null`. # To handle the errors programmatically, use a `MsgPackDeserializer`. fun deserialize_msgpack(static_type: nullable String): nullable Object do var stream = new BytesReader(self) var res = stream.deserialize_msgpack(static_type) stream.close return res end end redef class Reader # Deserialize full Nit `nullable Object` from MessagePack formated data # # This method use metadata in the MessagePack source to recreate full # Nit objects serialized by `Writer::serialize_msgpack` or # `MsgPackSerializer`. # # The dynamic type of the deserialized object can be limited to `static_type`. # # Warning: Deserialization errors are reported with `print_error`, # the returned object may be partial or fall back on `null`. # To handle the errors programmatically, use a `MsgPackDeserializer`. fun deserialize_msgpack(static_type: nullable String): nullable Object do var deserializer = new MsgPackDeserializer(self) var res = deserializer.deserialize(static_type) if deserializer.errors.length == 1 then print_error deserializer.errors.join("") else if deserializer.errors.not_empty then print_error "Deserialization Errors:\n* {deserializer.errors.join("\n* ")}" end return res end end # --- # Engine # Deserialize MessagePack format to full Nit objects class MsgPackDeserializer super CachingDeserializer super MsgPackEngine super SafeDeserializer # Source stream var stream: Reader # Map of attributes from the root deserialized object to the current object private var path = new Array[Map[nullable Serializable, nullable Serializable]] # Metadata arrays with from the root deserialized object to the current object var path_arrays = new Array[nullable Array[nullable Object]] # Names of the attributes from the root to the object currently being deserialized var attributes_path = new Array[String] # Last encountered object reference id. # # See `id_to_object`. private var just_opened_id: nullable Int = null redef fun deserialize_attribute(name, static_type) do if path.is_empty then # The was a parsing error or the root is not an object deserialize_attribute_missing = false return null end var current = path.last var serialized_value = null var serialized_value_found = false if current.keys.has(name) then # Non-cached string serialized_value = current[name] serialized_value_found = true else # It may be cached, deserialize all keys until we find it for key in current.keys.to_a do if key isa Array[nullable Serializable] or key isa MsgPackExt then var str = convert_object(key, "String") if str isa String then var value = current[key] current.keys.remove key current[str] = value if str == name then serialized_value = value serialized_value_found = true break end end end end end if not serialized_value_found then # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error deserialize_attribute_missing = true return null end attributes_path.add name var res = convert_object(serialized_value, static_type) attributes_path.pop deserialize_attribute_missing = false return res end # This may be called multiple times by the same object from defs of a same constructor redef fun notify_of_creation(new_object) do var id = just_opened_id if id == null then return cache[id] = new_object end # Convert the simple JSON `object` to a Nit object private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object do #print "convert_object {if object != null then object.class_name else "null"}" if object isa Array[nullable Object] and object.length >= 1 then # Serialized object? var first = object.first if first isa MsgPackExt then if first.typ == ext_typ_obj then # An array starts with a *ext*, it must be a serialized object # New object declaration var id = first.data.to_i if cache.has_id(id) then # FIXME use Warning errors.add new Error("Deserialization Error: object with id {id} is deserialized twice.") # Keep going end var type_name = null var i = 1 # Read dynamic type if object.length >= 2 then # Try to get the type name as a string var o = object[i] if o isa String and static_type == "String" and object.length == 2 then cache[id] = o return o else var typ = convert_object(object[i], "String") if typ isa String then type_name = typ i += 1 end end end if type_name == null then # There was no dynamic type # We could use a `class_name_heuristic` here... # Fallback to the static type if static_type != null then type_name = static_type.strip_nullable end if type_name == null then errors.add new Error("Deserialization Error: could not determine dynamic type of `{object}`.") return null end end if not accept(type_name, static_type) then return null var attributes = null if object.length > i then attributes = object[i] if not attributes isa Map[nullable Serializable, nullable Serializable] then # Some other type (could be an error), or there's no attributes attributes = new Map[nullable Serializable, nullable Serializable] end # advance on path path.push attributes path_arrays.push object just_opened_id = id var value = deserialize_class(type_name) just_opened_id = null # revert on path path.pop path_arrays.pop return value else errors.add new Error("Deserialization Error: unknown MessagePack ext '{first.typ}'.") end end # Plain array? Try to convert it to the desired static_type if static_type != null then return deserialize_class(static_type.strip_nullable) end return object end if object isa Map[nullable Serializable, nullable Serializable] then # Plain map # TODO parse it as an instance of `static_type` if static_type != null then path.push object path_arrays.push null just_opened_id = null var value = deserialize_class(static_type.strip_nullable) path.pop path_arrays.pop return value end return object end if object isa MsgPackExt then # First try the custom extensions var custom = deserialize_ext(object, static_type) if custom == null then # No custom, go for deser standard references if object.typ == ext_typ_ref then # Reference to an object var id = object.data.to_i if not cache.has_id(id) then errors.add new Error("Deserialization Error: object reference id unknown.") return object end return cache.object_for(id) else if object.typ == ext_typ_char then # Char return object.data.to_s.first else if object.typ == ext_typ_byte then # Byte return object.data.first end end end if object isa String and object.length == 1 and static_type == "Char" then # Char serialized as a string return object.chars.first end if object isa Int and static_type == "Byte" then # Byte serialized as an integer return object.to_b end return object end redef fun deserialize(static_type) do errors.clear var root = stream.read_msgpack return convert_object(root, static_type) end # Hook to customize the deserialization of MessagePack extensions # # Redefine this method in subclasses to return custom Nit objects from # an application specific extension. # # This method is invoked before dealing with the extensions used by the # Nit serialization metadata [0x40..0x43]. In general, you should ignore # them by returning `null`, but they can also be intercepted to comply to # a format from a remote server. protected fun deserialize_ext(ext: MsgPackExt, static_type: nullable String): nullable Object do return null end end redef class SimpleCollection[E] redef init from_deserializer(v) do super if v isa MsgPackDeserializer then v.notify_of_creation self init var open_array = v.path_arrays.last var msgpack_items = null if open_array != null then msgpack_items = open_array.last if not msgpack_items isa Array[nullable Serializable] then v.errors.add new Error("Deserialization Error: no items in source of `{class_name}`") return end # Name of the dynamic name of E var items_type_name = (new GetName[E]).to_s # Fill array for o in msgpack_items do var obj = v.convert_object(o, items_type_name) if obj isa E then add obj else v.errors.add new AttributeTypeError(self, "items", obj, items_type_name) end end end end redef class Map[K, V] redef init from_deserializer(v) do super if v isa MsgPackDeserializer then v.notify_of_creation self init var open_object = v.path_arrays.last var msgpack_items if open_object != null then # Metadata available msgpack_items = open_object.last else msgpack_items = v.path.last end if not msgpack_items isa Map[nullable Object, nullable Object] then v.errors.add new Error("Deserialization Error: no key/values in source of `{class_name}`") return end var keys_type_name = (new GetName[K]).to_s var values_type_name = (new GetName[V]).to_s for key_src, value_src in msgpack_items do var key = v.convert_object(key_src, keys_type_name) if not key isa K then v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name) continue end var value = v.convert_object(value_src, values_type_name) if not value isa V then v.errors.add new AttributeTypeError(self, "values", value, values_type_name) continue end self[key] = value end end end end