Merge: Nitsmell : Adding new code smells and print console updated
[nit.git] / lib / poset.nit
index 096c128..767b27d 100644 (file)
@@ -17,6 +17,8 @@
 # Pre order sets and partial order set (ie hierarchies)
 module poset
 
+import serialization
+
 # Pre-order set graph.
 # This class models an incremental pre-order graph where new nodes and edges can be added (but not removed).
 # Pre-order graph has two characteristics:
@@ -75,6 +77,7 @@ class POSet[E]
        super Collection[E]
        super Comparator
        super Cloneable
+       super Serializable
 
        redef type COMPARED: E is fixed
 
@@ -254,7 +257,7 @@ class POSet[E]
                        ids[x] = ids.length
                end
                for x in elements.keys do
-                       var xstr = x.to_s.escape_to_dot
+                       var xstr = (x or else "null").to_s.escape_to_dot
                        var nx = "n{ids[x]}"
                        f.write "{nx}[label=\"{xstr}\"];\n"
                        var xe = self.elements[x]
@@ -309,7 +312,7 @@ class POSet[E]
        # ~~~~
        #
        # Note that the linear extension is stable, unless a new node or a new edge is added.
-       redef fun compare(a, b: E): Int
+       redef fun compare(a, b)
        do
                var ae = self.elements[a]
                var be = self.elements[b]
@@ -417,6 +420,14 @@ class POSet[E]
        #
        # assert pos2["B"].direct_greaters.has_exactly(["D", "Y"])
        # ~~~
+       #
+       # If the `elements` contains all then the result is a clone of self.
+       #
+       # ~~~
+       # var pos3 = pos.sub(pos)
+       # assert pos3 == pos
+       # assert pos3 == pos.clone
+       # ~~~
        fun sub(elements: Collection[E]): POSet[E]
        do
                var res = new POSet[E]
@@ -432,9 +443,70 @@ class POSet[E]
                end
                return res
        end
+
+       # Two posets are equal if they contain the same elements and edges.
+       #
+       # ~~~
+       # var pos1 = new POSet[String]
+       # pos1.add_chain(["A", "B", "C", "D", "E"])
+       # pos1.add_chain(["A", "X", "C", "Y", "E"])
+       #
+       # var pos2 = new POSet[Object]
+       # pos2.add_edge("Y", "E")
+       # pos2.add_chain(["A", "X", "C", "D", "E"])
+       # pos2.add_chain(["A", "B", "C", "Y"])
+       #
+       # assert pos1 == pos2
+       #
+       # pos1.add_edge("D", "Y")
+       # assert pos1 != pos2
+       #
+       # pos2.add_edge("D", "Y")
+       # assert pos1 == pos2
+       #
+       # pos1.add_node("Z")
+       # assert pos1 != pos2
+       # ~~~
+       redef fun ==(other) do
+               if not other isa POSet[nullable Object] then return false
+               if not self.elements.keys.has_exactly(other.elements.keys) then return false
+               for e, ee in elements do
+                       if ee.direct_greaters != other[e].direct_greaters then return false
+               end
+               assert hash == other.hash
+               return true
+       end
+
+       redef fun hash
+       do
+               var res = 0
+               for e, ee in elements do
+                       if e == null then continue
+                       res += e.hash
+                       res += ee.direct_greaters.length
+               end
+               return res
+       end
+
+       redef fun core_serialize_to(serializer)
+       do
+               # Optimize written data because this structure has duplicated data
+               # For example, serializing the class hierarchy of a simple program where E is String
+               # result is before: 200k, after: 56k.
+               serializer.serialize_attribute("elements", elements)
+       end
+
+       redef init from_deserializer(deserializer)
+       do
+               deserializer.notify_of_creation self
+               var elements = deserializer.deserialize_attribute("elements")
+               if elements isa HashMap[E, POSetElement[E]] then
+                       self.elements = elements
+               end
+       end
 end
 
-# View of an objet in a poset
+# View of an object in a poset
 # This class is a helper to handle specific queries on a same object
 #
 # For instance, one common usage is to add a specific attribute for each poset a class belong.
@@ -449,6 +521,8 @@ end
 # t.in_some_relation.greaters
 # ~~~
 class POSetElement[E]
+       super Serializable
+
        # The poset self belong to
        var poset: POSet[E]
 
@@ -564,6 +638,175 @@ class POSetElement[E]
                        end
                end
                return min
+       end
 
+       redef fun core_serialize_to(serializer)
+       do
+               serializer.serialize_attribute("poset", poset)
+               serializer.serialize_attribute("element", element)
+               serializer.serialize_attribute("tos", tos)
+               serializer.serialize_attribute("froms", froms)
+               serializer.serialize_attribute("dtos", dtos)
+               serializer.serialize_attribute("dfroms", dfroms)
+               serializer.serialize_attribute("count", count)
+
+               # Don't serialize `froms`, `dtos` and `tos` as they duplicate information.
+               # TODO serialize them if a flag for extra info is set on `serializer`.
+       end
+
+       redef init from_deserializer(v)
+       do
+               # Code generated by the serialization_phase from the compiler frontend,
+               # copied here for compatibility with nith.
+
+               super
+               v.notify_of_creation self
+
+               var poset = v.deserialize_attribute("poset", "POSet[nullable Object]")
+               if v.deserialize_attribute_missing then
+                       v.errors.add new Error("Deserialization Error: attribute `{class_name}::poset` missing from JSON object")
+               else if not poset isa POSet[E] then
+                       v.errors.add new AttributeTypeError(self, "poset", poset, "POSet[nullable Object]")
+                       if v.keep_going == false then return
+               else
+                       self.poset = poset
+               end
+
+               var element = v.deserialize_attribute("element", "nullable Object")
+               if v.deserialize_attribute_missing then
+                       v.errors.add new Error("Deserialization Error: attribute `{class_name}::element` missing from JSON object")
+               else if not element isa E then
+                       v.errors.add new AttributeTypeError(self, "element", element, "nullable Object")
+                       if v.keep_going == false then return
+               else
+                       self.element = element
+               end
+
+               var tos = v.deserialize_attribute("tos", "HashSet[nullable Object]")
+               if v.deserialize_attribute_missing then
+               else if not tos isa HashSet[E] then
+                       v.errors.add new AttributeTypeError(self, "tos", tos, "HashSet[nullable Object]")
+                       if v.keep_going == false then return
+               else
+                       self.tos = tos
+               end
+
+               var froms = v.deserialize_attribute("froms", "HashSet[nullable Object]")
+               if v.deserialize_attribute_missing then
+               else if not froms isa HashSet[E] then
+                       v.errors.add new AttributeTypeError(self, "froms", froms, "HashSet[nullable Object]")
+                       if v.keep_going == false then return
+               else
+                       self.froms = froms
+               end
+
+               var dtos = v.deserialize_attribute("dtos", "HashSet[nullable Object]")
+               if v.deserialize_attribute_missing then
+               else if not dtos isa HashSet[E] then
+                       v.errors.add new AttributeTypeError(self, "dtos", dtos, "HashSet[nullable Object]")
+                       if v.keep_going == false then return
+               else
+                       self.dtos = dtos
+               end
+
+               var dfroms = v.deserialize_attribute("dfroms", "HashSet[nullable Object]")
+               if v.deserialize_attribute_missing then
+               else if not dfroms isa HashSet[E] then
+                       v.errors.add new AttributeTypeError(self, "dfroms", dfroms, "HashSet[nullable Object]")
+                       if v.keep_going == false then return
+               else
+                       self.dfroms = dfroms
+               end
+
+               var count = v.deserialize_attribute("count", "Int")
+               if v.deserialize_attribute_missing then
+                       v.errors.add new Error("Deserialization Error: attribute `{class_name}::count` missing from JSON object")
+               else if not count isa Int then
+                       v.errors.add new AttributeTypeError(self, "count", count, "Int")
+                       if v.keep_going == false then return
+               else
+                       self.count = count
+               end
+       end
+end
+
+redef class MapRead[K, V]
+       # Return all elements of `keys` that have a value.
+       #
+       # ~~~
+       # var map = new Map[String, String]
+       # map["A"] = "a"
+       # map["B"] = "b"
+       # map["C"] = "c"
+       #
+       # assert map.filter_keys(["B"]) == ["B"]
+       # assert map.filter_keys(["A", "Z", "C"]) == ["A", "C"]
+       # assert map.filter_keys(["X", "Y", "Z"]).is_empty
+       # ~~~
+       #
+       # `has_key` is used to filter.
+       fun filter_keys(keys: Collection[nullable Object]): Array[K]
+       do
+               var res = new Array[K]
+               for e in keys do
+                       if has_key(e) then res.add e
+               end
+               return res
+       end
+
+       # Search all the values in `pe.greaters`.
+       #
+       # Elements without values are ignored.
+       #
+       # Basically, values defined in all greater elements of `pe` are inherited.
+       #
+       # ~~~
+       # var pos = new POSet[String]
+       # pos.add_chain(["E", "D", "C", "B", "A"])
+       # pos.add_chain(["D", "X", "B"])
+       #
+       # var map = new HashMap[String, String]
+       # map["A"] = "a"
+       # map["C"] = "c"
+       # map["X"] = "x"
+       # map["E"] = "e"
+       #
+       # assert map.lookup_all_values(pos["B"]).has_exactly(["a"])
+       # assert map.lookup_all_values(pos["C"]).has_exactly(["a", "c"])
+       # assert map.lookup_all_values(pos["D"]).has_exactly(["a", "c", "x"])
+       # ~~~
+       fun lookup_all_values(pe: POSetElement[K]): Set[V]
+       do
+               var res = new Set[V]
+               for k in filter_keys(pe.greaters) do res.add self[k]
+               return res
+       end
+
+       # Combine the values in `pe.greaters` from the most smaller elements that have a value.
+       #
+       # Elements without values are ignored.
+       #
+       # Basically, values defined in nearest greater elements of `pe` are inherited.
+       #
+       # ~~~
+       # var pos = new POSet[String]
+       # pos.add_chain(["E", "D", "C", "B", "A"])
+       # pos.add_chain(["D", "X", "B"])
+       #
+       # var map = new HashMap[String, String]
+       # map["A"] = "a"
+       # map["C"] = "c"
+       # map["X"] = "x"
+       # map["E"] = "e"
+       #
+       # assert map.lookup_values(pos["B"]).has_exactly(["a"])
+       # assert map.lookup_values(pos["C"]).has_exactly(["c"])
+       # assert map.lookup_values(pos["D"]).has_exactly(["c", "x"])
+       # ~~~
+       fun lookup_values(pe: POSetElement[K]): Set[V]
+       do
+               var res = new Set[V]
+               for k in pe.poset.select_smallest(filter_keys(pe.greaters)) do res.add self[k]
+               return res
        end
 end