[gxml] Gom-collections: moved to its own file



commit f2cfaab6ce3ef9e38b115de876471d39cfe11120
Author: Daniel Espinosa <esodan gmail com>
Date:   Tue Mar 19 13:20:44 2019 -0600

    Gom-collections: moved to its own file

 gxml/GomArrayList.vala      |  54 +++
 gxml/GomBaseCollection.vala | 223 +++++++++++
 gxml/GomCollections.vala    | 906 --------------------------------------------
 gxml/GomHashMap.vala        | 172 +++++++++
 gxml/GomHashPairedMap.vala  | 243 ++++++++++++
 gxml/GomHashThreeMap.vala   | 313 +++++++++++++++
 gxml/meson.build            |   8 +-
 test/GomElementTest.vala    |   1 -
 8 files changed, 1011 insertions(+), 909 deletions(-)
---
diff --git a/gxml/GomArrayList.vala b/gxml/GomArrayList.vala
new file mode 100644
index 0000000..80f0d1c
--- /dev/null
+++ b/gxml/GomArrayList.vala
@@ -0,0 +1,54 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ * GomArrayList.vala
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using Gee;
+
+/**
+ * A class impementing {@link Collection} to store references to
+ * child {@link DomElement} of {@link Collection.element}, using an index.
+ *
+ * {{{
+ *   public class YourObject : GomElement {
+ *    [Description (nick="::Name")]
+ *    public string name { get; set; }
+ *   }
+ *   public class YourList : GomArrayList {
+ *    construct {
+ *      try { initialize (typeof (YourObject)); }
+ *      catch (GLib.Error e) {
+ *        warning ("Initialization error for collection type: %s : %s"
+ *             .printf (get_type ().name(), e.message));
+ *      }
+ *    }
+ *   }
+ * }}}
+ */
+public class GXml.GomArrayList : GXml.BaseCollection, GXml.List {
+  public override bool validate_append (int index, DomElement element) throws GLib.Error {
+#if DEBUG
+    GLib.message ("Adding node:"+element.node_name);
+#endif
+    return true;
+  }
+}
+
diff --git a/gxml/GomBaseCollection.vala b/gxml/GomBaseCollection.vala
new file mode 100644
index 0000000..f5557ba
--- /dev/null
+++ b/gxml/GomBaseCollection.vala
@@ -0,0 +1,223 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/* GomBaseCollection.vala
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using Gee;
+
+/**
+ * Base class for collections implemeting {@link Collection}, priving basic
+ * infrastructure.
+ *
+ * Collections properties should be initialized with current container element
+ * in order to be able to add new references to elements. Use {@link initialize_element}
+ * to set parent element and {@link search} to find elements for collection.
+ */
+public abstract class GXml.BaseCollection : Object, Traversable<DomElement>, Iterable<DomElement>, 
Collection {
+  /**
+   * A collection of node's index refered. Don't modify it manually.
+   */
+  protected GLib.Queue<int> _nodes_index = new GLib.Queue<int> ();
+  /**
+   * Element used to refer of containier element. You should define it at construction time
+   * our set it as a construction property.
+   */
+  protected GomElement _element;
+  /**
+   * Local name of {@link DomElement} objects of {@link element}, which could be
+   * contained in this collection.
+   *
+   * Used when reading to add elements to collection. You can set it at construction time,
+   * by, for example, instantaiting a object of the type {@link Collection.items_type}
+   * then use {@link GomElement.local_name}'s value.
+   */
+  protected string _items_name = "";
+  /**
+   * Objects' type to be referenced by this collection and to deserialize objects.
+   * Derived classes, can initilize this value at constructor or as construct property.
+   *
+   * Used when reading and at initialization time, to know {@link GomElement.local_name}
+   * at runtime.
+   */
+  protected GLib.Type _items_type = GLib.Type.INVALID;
+  /**
+   * {@inheritDoc}
+   */
+  public string items_name { get { return _items_name; } }
+  /**
+   * {@inheritDoc}
+   */
+  public Type items_type {
+    get { return _items_type; } construct set { _items_type = value; }
+  }
+  /**
+   * {@inheritDoc}
+   */
+  public GLib.Queue<int> nodes_index { get { return _nodes_index; } }
+  /**
+   * {@inheritDoc}
+   */
+  public DomElement element {
+    get { return _element as DomElement; }
+    construct set {
+      if (value is GomElement)
+        _element = value as GomElement;
+    }
+  }
+  /**
+   * {@inheritDoc}
+   */
+  public void initialize (GLib.Type items_type) throws GLib.Error {
+    if (!items_type.is_a (typeof (GomElement))) {
+      throw new DomError.INVALID_NODE_TYPE_ERROR
+                (_("Invalid attempt to initialize a collection using an unsupported type. Only 
GXmlGomElement is supported"));
+    }
+    var o = Object.new (items_type) as GomElement;
+    _items_name = o.local_name;
+    _items_type = items_type;
+  }
+  /**
+   * Initialize an {@link Collection} to use an element as children's parent.
+   * Searchs for all nodes, calling {@link Collection.search}
+   * with {@link Collection.items_type}, using its
+   * {@link DomElement.local_name} to find it.
+   *
+   * Implemenation classes, should initialize collection to hold a {@link GomElement}
+   * derived type using {@link Collection.initialize}.
+   */
+  public void initialize_element (GomElement e) throws GLib.Error {
+    _element = e;
+  }
+
+  /**
+   * Adds an {@link DomElement} of type {@link GomObject} as a child of
+   * {@link element}.
+   *
+   * Object is always added as a child of {@link element}
+   * but just added to collection if {@link validate_append} returns true;
+   */
+  public void append (DomElement node) throws GLib.Error {
+    if (_element == null)
+      throw new DomError.INVALID_NODE_TYPE_ERROR
+                (_("Parent Element is invalid"));
+    if (!(node is GomElement))
+      throw new DomError.INVALID_NODE_TYPE_ERROR
+                (_("Invalid attempt to set unsupported type. Only GXmlGomElement is supported"));
+    if (node.owner_document != _element.owner_document)
+      throw new DomError.HIERARCHY_REQUEST_ERROR
+                (_("Invalid attempt to set a node with a different parent document"));
+    if (node.parent_node == null)
+      _element.append_child (node);
+    if (_element.child_nodes.size == 0)
+      throw new DomError.QUOTA_EXCEEDED_ERROR
+                (_("Node element not appended as child of parent. No node added to collection"));
+    var index = _element.child_nodes.size - 1;
+    if (!validate_append (index, node)) return;
+    _nodes_index.push_tail (index);
+  }
+  /**
+   * Search for all child nodes in {@link element} of type {@link GomElement}
+   * with a {@link GomElement.local_name} equal to {@link Collection.items_name},
+   * to add it to collection. This method calls {@link clear} first.
+   *
+   * Implementations could add additional restrictions to add element to collection.
+   */
+  public void search () throws GLib.Error {
+    _nodes_index.clear ();
+    clear ();
+    if (_element == null)
+      throw new DomError.INVALID_NODE_TYPE_ERROR
+                (_("Parent Element is invalid"));
+    for (int i = 0; i < _element.child_nodes.size; i++) {
+      var n = _element.child_nodes.get (i);
+      if (n is GomObject) {
+        if ((n as DomElement).local_name.down () == items_name.down ()) {
+          if (validate_append (i, n as DomElement))
+            _nodes_index.push_tail (i);
+        }
+      }
+    }
+  }
+  /**
+   * {@inheritDoc}
+   */
+  public abstract bool validate_append (int index, DomElement element) throws GLib.Error;
+  /**
+   * {@inheritDoc}
+   */
+  public virtual void clear () throws GLib.Error {}
+
+  // Traversable Interface
+  public bool @foreach (ForallFunc<DomElement> f) {
+    var i = iterator ();
+    return i.foreach (f);
+  }
+  // Itarable Interface
+  public Iterator<DomElement> iterator () { return new CollectionIterator (this); }
+  // For Iterable interface implementation
+  private class CollectionIterator : Object, Traversable<DomElement>, Iterator<DomElement> {
+    private int pos;
+    private Collection _collection;
+    public bool read_only { get { return false; } }
+    public bool valid { get { return (pos >= 0 && pos < _collection.length); } }
+
+    public CollectionIterator (Collection col) {
+      _collection = col;
+      pos = -1;
+    }
+
+    public new DomElement @get ()
+      requires (pos >= 0 && pos < _collection.length) {
+      DomElement e = null;
+      try {
+        e = _collection.get_item (pos);
+      } catch (GLib.Error e) {
+        warning (_("Error: %s").printf (e.message));
+      }
+      return e;
+    }
+    public bool has_next () { return (pos + 1 < _collection.length); }
+    public bool next () {
+      if (!has_next ()) return false;
+      pos++;
+      return true;
+    }
+    public void remove () {
+      DomElement e = null;
+      try {
+        e = _collection.get_item (pos);
+        if (e == null) return;
+        e.remove ();
+        _collection.search ();
+      } catch (GLib.Error e) {
+        warning (_("Error: %s").printf (e.message));
+      }
+    }
+
+    public bool @foreach (ForallFunc<DomElement> f) {
+      while (has_next ()) {
+        next ();
+        if (!f (get ())) return false;
+      }
+      return true;
+    }
+  }
+}
+
diff --git a/gxml/GomHashMap.vala b/gxml/GomHashMap.vala
new file mode 100644
index 0000000..6da2a4c
--- /dev/null
+++ b/gxml/GomHashMap.vala
@@ -0,0 +1,172 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ * GomHashMap.vala
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using Gee;
+
+/**
+ * A class impementing {@link Collection} to store references to
+ * child {@link DomElement} of {@link Collection.element}, using an attribute in
+ * items as key or {@link MappeableElement.get_map_key} method if implemented
+ * by items to be added. If key is not defined in node, it is not added; but
+ * keeps it as a child node of actual {@link Collection.element}.
+ *
+ * If {@link GomElement} to be added is of type {@link Collection.items_type}
+ * and implements {@link MappeableElement}, you should set {@link GomHashMap.attribute_key}
+ * to null in order to use returned value of {@link MappeableElement.get_map_key}
+ * as key.
+ *
+ * {{{
+ *   public class YourObject : GomElement {
+ *    [Description (nick="::Name")]
+ *    public string name { get; set; }
+ *   }
+ *   public class YourList : GomHashMap {
+ *    construct {
+ *      try { initialize_with_key (typeof (YourObject),"Name"); }
+ *      catch (GLib.Error e) {
+ *        warning ("Initialization error for collection type: %s : %s"
+ *             .printf (get_type ().name(), e.message));
+ *      }
+ *    }
+ *   }
+ * }}}
+ */
+public class GXml.GomHashMap : GXml.BaseCollection, GXml.Map {
+  /**
+   * A hashtable with all keys as string to node's index refered. Don't modify it manually.
+   */
+  protected Gee.HashMap<string,int> _hashtable = new Gee.HashMap<string,int> ();
+  /**
+   * Element's attribute name used to refer of container's element.
+   * You should define it at construction time
+   * our set it as a construction property.
+   */
+  protected string _attribute_key;
+  /**
+   * An attribute's name in items to be added and used to retrieve elements
+   * as key.
+   */
+  public string attribute_key {
+    get { return _attribute_key; } construct set { _attribute_key = value; }
+  }
+  /**
+   * Convenient function to initialize a {@link GomHashMap} collection, using
+   * given element, items' type and name.
+   */
+  public void initialize_element_with_key (GomElement element,
+                                  GLib.Type items_type,
+                                  string attribute_key) throws GLib.Error
+  {
+    initialize (items_type);
+    initialize_element (element);
+    _attribute_key = attribute_key;
+  }
+
+  /**
+   * Convenient function to initialize a {@link GomHashMap} collection, using
+   * given element, items' type and name.
+   *
+   * Using this method at construction time of derived classes.
+   */
+  public void initialize_with_key (GLib.Type items_type,
+                                  string attribute_key) throws GLib.Error
+  {
+    initialize (items_type);
+    _attribute_key = attribute_key;
+  }
+  /**
+   * Returns an {@link DomElement} in the collection using a string key.
+   */
+  public new DomElement? get (string key) {
+    if (!_hashtable.has_key (key)) return null;
+    var i = _hashtable.get (key);
+#if DEBUG
+    GLib.message ("Key:"+key+" item:"+i.to_string ());
+#endif
+    return _element.child_nodes.get (i) as DomElement;
+  }
+  /**
+   * Returns true if @key is used in collection.
+   */
+  public bool has_key (string key) {
+    if (_hashtable.has_key (key)) return true;
+    return false;
+  }
+  /**
+   * Returns list of keys used in collection.
+   */
+  public GLib.List<string> get_keys () {
+    var l = new GLib.List<string> ();
+    foreach (string k in _hashtable.keys) { l.append (k); }
+    return l;
+  }
+  /**
+   * Validates if given element has a {@link GomHashMap.attribute_key} set,
+   * if so adds a new key pointing to given index and returns true.
+   *
+   * Attribute should be a valid {@link DomElement} attribute or
+   * a {@link GomObject} property identified using a nick with a '::' prefix.
+   *
+   * If there are more elements with same key, they are kept as child nodes
+   * but the one in collection will be the last one to be found.
+   *
+   * Return: false if element should not be added to collection.
+   */
+  public override bool validate_append (int index, DomElement element) throws GLib.Error {
+    if (!(element is GomElement)) return false;
+#if DEBUG
+    message ("Validating HashMap Element..."
+            +(element as GomElement).write_string ()
+            +" Attrs:"+(element as GomElement).attributes.length.to_string());
+#endif
+    string key = null;
+    if (attribute_key != null) {
+      key = (element as DomElement).get_attribute (attribute_key);
+      if (key == null)
+      key = (element as DomElement).get_attribute (attribute_key.down ());
+    } else {
+      if (items_type.is_a (typeof(MappeableElement))) {
+        if (!(element is MappeableElement)) return false;
+        key = ((MappeableElement) element).get_map_key ();
+      }
+    }
+    if (key == null) return false;
+#if DEBUG
+    message ("Attribute key value: "+key);
+#endif
+    _hashtable.set (key, index);
+    return true;
+  }
+  public override void clear () {
+    _hashtable = new HashMap<string,int> ();
+  }
+  public DomElement? item (string key) { return get (key); }
+  public Gee.Set<string> keys_set {
+    owned get {
+      var l = new HashSet<string> ();
+      foreach (string k in _hashtable.keys) { l.add (k); }
+      return l;
+    }
+  }
+}
+
diff --git a/gxml/GomHashPairedMap.vala b/gxml/GomHashPairedMap.vala
new file mode 100644
index 0000000..7161ed9
--- /dev/null
+++ b/gxml/GomHashPairedMap.vala
@@ -0,0 +1,243 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ * GomHashPairedMap.vala
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using Gee;
+
+/**
+ * A class impementing {@link Collection} to store references to
+ * child {@link DomElement} of {@link Collection.element}, using two attributes in
+ * items as primary and secondary keys or {@link MappeableElementPairKey.get_map_primary_key}
+ * and {@link MappeableElementPairKey.get_map_secondary_key} methods if
+ * {@link MappeableElementPairKey} are implemented
+ * by items to be added. If one or both keys are not defined in node,
+ * it is not added; but keeps it as a child node of actual
+ * {@link Collection.element}.
+ *
+ * If {@link GomElement} to be added is of type {@link Collection.items_type}
+ * and implements {@link MappeableElementPairKey}, you should set
+ * {@link attribute_primary_key} and {@link attribute_secondary_key}
+ * to null in order to use returned value of {@link MappeableElementPairKey.get_map_primary_key}
+ * and {@link MappeableElementPairKey.get_map_secondary_key}
+ * as keys.
+ *
+ * {{{
+ *   public class YourObject : GomElement, MappeableElementPairKey {
+ *    [Description (nick="::Name")]
+ *    public string name { get; set; }
+ *    public string code { get; set; }
+ *    public string get_map_primary_key () { return code; }
+ *    public string get_map_secondary_key () { return name; }
+ *   }
+ *   public class YourList : GomHashPairedMap {
+ *    construct {
+ *      try { initialize_with (typeof (YourObject)); }
+ *      catch (GLib.Error e) {
+ *        warning ("Initialization error for collection type: %s : %s"
+ *             .printf (get_type ().name(), e.message));
+ *      }
+ *    }
+ *   }
+ * }}}
+ */
+public class GXml.GomHashPairedMap : GXml.BaseCollection, GXml.PairedMap {
+  /**
+   * A hashtable with all keys as string to node's index refered. Don't modify it manually.
+   */
+  protected Gee.HashMap<string,HashMap<string,int>> _hashtable = new HashMap<string,HashMap<string,int>> ();
+  /**
+   * Element's attribute name used to refer of container's element as primery key.
+   * You should define it at construction time
+   * our set it as a construction property.
+   */
+  protected string _attribute_primary_key;
+  /**
+   * Element's attribute name used to refer of container's element as secondary key.
+   * You should define it at construction time
+   * our set it as a construction property.
+   */
+  protected string _attribute_secondary_key;
+  /**
+   * An attribute's name in items to be added and used to retrieve elements
+   * as primary key.
+   */
+  public string attribute_primary_key {
+    get { return _attribute_primary_key; } construct set { _attribute_primary_key = value; }
+  }
+  /**
+   * An attribute's name in items to be added and used to retrieve elements
+   * as secondary key.
+   */
+  public string attribute_secondary_key {
+    get { return _attribute_secondary_key; } construct set { _attribute_secondary_key = value; }
+  }
+  /**
+   * Convenient function to initialize a {@link GomHashMap} collection, using
+   * given element, items' type and name.
+   */
+  public void initialize_element_with_keys (GomElement element,
+                                  GLib.Type items_type,
+                                  string attribute_primary_key,
+                                  string attribute_secondary_key) throws GLib.Error
+  {
+    initialize (items_type);
+    initialize_element (element);
+    _attribute_primary_key = attribute_primary_key;
+    _attribute_secondary_key = attribute_secondary_key;
+  }
+
+  /**
+   * Convenient function to initialize a {@link GomHashMap} collection, using
+   * given element, items' type and name.
+   *
+   * Using this method at construction time of derived classes.
+   */
+  public void initialize_with_keys (GLib.Type items_type,
+                                  string attribute_primary_key,
+                                  string attribute_secondary_key) throws GLib.Error
+  {
+    initialize (items_type);
+    _attribute_primary_key = attribute_primary_key;
+    _attribute_secondary_key = attribute_secondary_key;
+  }
+  /**
+   * Returns an {@link DomElement} in the collection using given string keys.
+   */
+  public new DomElement? get (string primary_key, string secondary_key) {
+    if (!_hashtable.has_key (primary_key)) return null;
+    var ht = _hashtable.get (primary_key);
+    if (ht == null) return null;
+    if (!ht.has_key (secondary_key)) return null;
+    var i = ht.get (secondary_key);
+    return _element.child_nodes.get (i) as DomElement;
+  }
+  /**
+   * Returns true if @key is used in collection as primery key.
+   */
+  public bool has_primary_key (string key) {
+    if (_hashtable.has_key (key)) return true;
+    return false;
+  }
+  /**
+   * Returns true if @key is used in collection as secondary key
+   * with @pkey as primary.
+   */
+  public bool has_secondary_key (string pkey, string key) {
+    if (!(_hashtable.has_key (pkey))) return false;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return false;
+    if (ht.has_key (key)) return true;
+    return false;
+  }
+  /**
+   * Returns list of primary keys used in collection.
+   */
+  public GLib.List<string> get_primary_keys () {
+    var l = new GLib.List<string> ();
+    foreach (string k in _hashtable.keys) {
+      l.append (k);
+    }
+    return l;
+  }
+  /**
+   * Returns list of secondary keys used in collection with @pkey as primary key.
+   */
+  public GLib.List<string> get_secondary_keys (string pkey) {
+    var l = new GLib.List<string> ();
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return l;
+    foreach (string k in ht.keys) {
+      l.append (k);
+    }
+    return l;
+  }
+  /**
+   * Validates if given element has a {@link attribute_primary_key}
+   * and {@link attribute_secondary_key} set,
+   * if so adds a new keys pointing to given index and returns true.
+   *
+   * Attribute should be a valid {@link DomElement} attribute or
+   * a {@link GomObject} property identified using a nick with a '::' prefix.
+   *
+   * If there are more elements with same keys, they are kept as child nodes
+   * but the one in collection will be the last one to be found.
+   *
+   * Return: false if element should not be added to collection.
+   */
+  public override bool validate_append (int index, DomElement element) throws GLib.Error {
+    if (!(element is GomElement)) return false;
+#if DEBUG
+    message ("Validating HashMap Element..."
+            +(element as GomElement).write_string ()
+            +" Attrs:"+(element as GomElement).attributes.length.to_string());
+#endif
+    string pkey = null;
+    string skey = null;
+    if (attribute_primary_key != null && attribute_secondary_key != null) {
+      pkey = (element as DomElement).get_attribute (attribute_primary_key);
+      skey = (element as DomElement).get_attribute (attribute_secondary_key);
+      if (pkey == null || skey == null) {
+        pkey = (element as DomElement).get_attribute (attribute_primary_key.down ());
+        skey = (element as DomElement).get_attribute (attribute_secondary_key.down ());
+      }
+    } else {
+      if (items_type.is_a (typeof(MappeableElementPairKey))) {
+        if (!(element is MappeableElementPairKey)) return false;
+        pkey = ((MappeableElementPairKey) element).get_map_primary_key ();
+        skey = ((MappeableElementPairKey) element).get_map_secondary_key ();
+      }
+    }
+    if (pkey == null || skey == null)
+      throw new DomError.NOT_FOUND_ERROR (_("No primary key and/or secondary key was found"));
+    var ht = _hashtable.get (pkey);
+    if (ht == null) {
+      ht = new HashMap<string,int> ();
+      _hashtable.set (pkey, ht);
+    }
+    ht.set (skey, index);
+    return true;
+  }
+  public override void clear () {
+    _hashtable = new HashMap<string,HashMap<string,int>> ();
+  }
+  public DomElement? item (string primary_key, string secondary_key) {
+    return get (primary_key, secondary_key);
+  }
+  public Set<string> primary_keys_set {
+    owned get {
+      var l = new HashSet<string> ();
+      foreach (string k in _hashtable.keys) {
+        l.add (k);
+      }
+      return l;
+    }
+  }
+  public Set<string> secondary_keys_set (string pkey) {
+    var l = new HashSet<string> ();
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return l;
+    foreach (string k in ht.keys) {
+      l.add (k);
+    }
+    return l;
+  }
+}
diff --git a/gxml/GomHashThreeMap.vala b/gxml/GomHashThreeMap.vala
new file mode 100644
index 0000000..f836d69
--- /dev/null
+++ b/gxml/GomHashThreeMap.vala
@@ -0,0 +1,313 @@
+/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
+/*
+ * GomHashThreeMap.vala
+ *
+ * Copyright (C) 2016  Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *      Daniel Espinosa <esodan gmail com>
+ */
+
+using Gee;
+
+/**
+ * A class impementing {@link Collection} to store references to
+ * child {@link DomElement} of {@link Collection.element}, using three attributes in
+ * items as primary, secondary tertiary keys or {@link MappeableElementThreeKey.get_map_pkey},
+ * {@link MappeableElementThreeKey.get_map_skey}
+ * and {@link MappeableElementThreeKey.get_map_tkey}
+ * methods if {@link MappeableElementThreeKey} are implemented
+ * by items to be added. All keys should be defined in node, otherwise
+ * it is not added; but keeps it as a child node of actual
+ * {@link Collection.element}.
+ *
+ * If {@link GomElement} to be added is of type {@link Collection.items_type}
+ * and implements {@link MappeableElementThreeKey}, you should set
+ * {@link attribute_primary_key}, {@link attribute_secondary_key}
+ * and  {@link attribute_third_key}
+ * to null in order to use returned value of {@link MappeableElementThreeKey.get_map_pkey},
+ * {@link MappeableElementThreeKey.get_map_skey}
+ * and {@link MappeableElementThreeKey.get_map_tkey}
+ * as keys.
+ *
+ * {{{
+ *   public class YourObject : GomElement, MappeableElementThirdKey {
+ *    [Description (nick="::Name")]
+ *    public string name { get; set; }
+ *    public string code { get; set; }
+ *    public string category { get; set; }
+ *    public string get_map_primary_key () { return code; }
+ *    public string get_map_secondary_key () { return name; }
+ *    public string get_map_third_key () { return category; }
+ *   }
+ *   public class YourList : GomHashPairedMap {
+ *    construct {
+ *      try { initialize_with (typeof (YourObject)); }
+ *      catch (GLib.Error e) {
+ *        warning ("Initialization error for collection type: %s : %s"
+ *             .printf (get_type ().name(), e.message));
+ *      }
+ *    }
+ *   }
+ * }}}
+ */
+public class GXml.GomHashThreeMap : GXml.BaseCollection, ThreeMap {
+  /**
+   * A hashtable with all keys as string to node's index refered. Don't modify it manually.
+   */
+  protected HashMap<string,HashMap<string,HashMap<string,int>>> _hashtable = new 
HashMap<string,HashMap<string,HashMap<string,int>>> ();
+  /**
+   * Element's attribute name used to refer of container's element as primery key.
+   * You should define it at construction time
+   * our set it as a construction property.
+   */
+  protected string _attribute_primary_key;
+  /**
+   * Element's attribute name used to refer of container's element as secondary key.
+   * You should define it at construction time
+   * our set it as a construction property.
+   */
+  protected string _attribute_secondary_key;
+  /**
+   * Element's attribute name used to refer of container's element as third key.
+   * You should define it at construction time
+   * our set it as a construction property.
+   */
+  protected string _attribute_third_key;
+  /**
+   * An attribute's name in items to be added and used to retrieve elements
+   * as primary key.
+   */
+  public string attribute_primary_key {
+    get { return _attribute_primary_key; } construct set { _attribute_primary_key = value; }
+  }
+  /**
+   * An attribute's name in items to be added and used to retrieve elements
+   * as secondary key.
+   */
+  public string attribute_secondary_key {
+    get { return _attribute_secondary_key; } construct set { _attribute_secondary_key = value; }
+  }
+  /**
+   * An attribute's name in items to be added and used to retrieve elements
+   * as third key.
+   */
+  public string attribute_third_key {
+    get { return _attribute_third_key; } construct set { _attribute_third_key = value; }
+  }
+  /**
+   * Convenient function to initialize a {@link GomHashMap} collection, using
+   * given element, items' type and name.
+   */
+  public void initialize_element_with_keys (GomElement element,
+                                  GLib.Type items_type,
+                                  string attribute_primary_key,
+                                  string attribute_secondary_key,
+                                  string attribute_third_key) throws GLib.Error
+  {
+    initialize (items_type);
+    initialize_element (element);
+    _attribute_primary_key = attribute_primary_key;
+    _attribute_secondary_key = attribute_secondary_key;
+    _attribute_third_key = attribute_third_key;
+  }
+
+  /**
+   * Convenient function to initialize a {@link GomHashMap} collection, using
+   * given element, items' type and name.
+   *
+   * Using this method at construction time of derived classes.
+   */
+  public void initialize_with_keys (GLib.Type items_type,
+                                  string attribute_primary_key,
+                                  string attribute_secondary_key,
+                                  string attribute_third_key) throws GLib.Error
+  {
+    initialize (items_type);
+    _attribute_primary_key = attribute_primary_key;
+    _attribute_secondary_key = attribute_secondary_key;
+    _attribute_third_key = attribute_third_key;
+  }
+  /**
+   * Returns an {@link DomElement} in the collection using given string keys.
+   */
+  public new DomElement? get (string primary_key, string secondary_key, string third_key) {
+    if (!_hashtable.has_key (primary_key)) return null;
+    var ht = _hashtable.get (primary_key);
+    if (ht == null) return null;
+    if (!ht.has_key (secondary_key)) return null;
+    var hte = ht.get (secondary_key);
+    if (hte == null) return null;
+    if (!hte.has_key (third_key)) return null;
+    var i = hte.get (secondary_key);
+    return _element.child_nodes.get (i) as DomElement;
+  }
+  /**
+   * Returns true if @key is used in collection as primery key.
+   */
+  public bool has_primary_key (string key) {
+    if (_hashtable.has_key (key)) return true;
+    return false;
+  }
+  /**
+   * Returns true if @key is used in collection as secondary key
+   * with @pkey as primary.
+   */
+  public bool has_secondary_key (string pkey, string key) {
+    if (!(_hashtable.has_key (pkey))) return false;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return false;
+    if (ht.has_key (key)) return true;
+    return false;
+  }
+  /**
+   * Returns true if @key is used in collection as third key with secondary key
+   * and pkey as primary.
+   */
+  public bool has_third_key (string pkey, string skey, string key) {
+    if (!(_hashtable.has_key (pkey))) return false;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return false;
+    var hte = ht.get (skey);
+    if (hte == null) return false;
+    if (hte.has_key (key)) return true;
+    return false;
+  }
+  /**
+   * Returns list of primary keys used in collection.
+   */
+  public GLib.List<string> get_primary_keys () {
+    var l = new GLib.List<string> ();
+    foreach (string k in _hashtable.keys) {
+      l.append (k);
+    }
+    return l;
+  }
+  /**
+   * Returns list of secondary keys used in collection with pkey as primary key.
+   */
+  public GLib.List<string> get_secondary_keys (string pkey) {
+    var l = new GLib.List<string> ();
+    if (!_hashtable.has_key (pkey)) return l;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return l;
+    foreach (string k in ht.keys) {
+      l.append (k);
+    }
+    return l;
+  }
+  /**
+   * Returns list of third keys used in collection with pkey as primary key
+   * and skey as secondary key.
+   */
+  public GLib.List<string> get_third_keys (string pkey, string skey) {
+    var l = new GLib.List<string> ();
+    if (!_hashtable.has_key (pkey)) return l;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return l;
+    var hte = ht.get (skey);
+    if (hte == null) return l;
+    foreach (string k in hte.keys) {
+      l.append (k);
+    }
+    return l;
+  }
+  /**
+   * Validates if given element has a {@link attribute_primary_key},
+   * {@link attribute_secondary_key} and
+   * {@link attribute_third_key} set,
+   * if so adds a new keys pointing to given index and returns true.
+   *
+   * Attribute should be a valid {@link DomElement} attribute or
+   * a {@link GomObject} property identified using a nick with a '::' prefix.
+   *
+   * If there are more elements with same keys, they are kept as child nodes
+   * but the one in collection will be the last one to be found.
+   *
+   * Return: false if element should not be added to collection.
+   */
+  public override bool validate_append (int index, DomElement element) throws GLib.Error {
+    if (!(element is GomElement)) return false;
+    string pkey = null;
+    string skey = null;
+    string tkey = null;
+    if (attribute_primary_key != null && attribute_secondary_key != null
+        && attribute_third_key != null) {
+      pkey = (element as DomElement).get_attribute (attribute_primary_key);
+      skey = (element as DomElement).get_attribute (attribute_secondary_key);
+      tkey = (element as DomElement).get_attribute (attribute_third_key);
+      if (pkey == null || skey == null || tkey == null) {
+        pkey = (element as DomElement).get_attribute (attribute_primary_key.down ());
+        skey = (element as DomElement).get_attribute (attribute_secondary_key.down ());
+        tkey = (element as DomElement).get_attribute (attribute_third_key.down ());
+      }
+    } else {
+      if (items_type.is_a (typeof(MappeableElementThreeKey))) {
+        if (!(element is MappeableElementThreeKey)) return false;
+        pkey = ((MappeableElementThreeKey) element).get_map_pkey ();
+        skey = ((MappeableElementThreeKey) element).get_map_skey ();
+        tkey = ((MappeableElementThreeKey) element).get_map_tkey ();
+      }
+    }
+    if (pkey == null || skey == null || tkey == null) return false;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) ht = new HashMap<string,HashMap<string,int>> ();
+    var hte = ht.get (skey);
+    if (hte == null) hte = new HashMap<string,int> ();
+    if (!_hashtable.has_key (pkey)) _hashtable.set (pkey, ht);
+    if (!ht.has_key (skey)) ht.set (skey, hte);
+    hte.set (tkey, index);
+    return true;
+  }
+  public override void clear () {
+    _hashtable = new HashMap<string,HashMap<string,HashMap<string,int>>> ();
+  }
+
+  public DomElement? item (string primary_key, string secondary_key, string third_key) {
+    return get (primary_key, secondary_key, third_key);
+  }
+  public Set<string> primary_keys_set {
+    owned get {
+      var l = new HashSet<string> ();
+      foreach (string k in _hashtable.keys) {
+        l.add (k);
+      }
+      return l;
+    }
+  }
+  public Set<string> secondary_keys_set (string pkey) {
+    var l = new HashSet<string> ();
+    if (!_hashtable.has_key (pkey)) return l;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return l;
+    foreach (string k in ht.keys) {
+      l.add (k);
+    }
+    return l;
+  }
+  public Set<string> third_keys_set (string pkey, string skey) {
+    var l = new HashSet<string> ();
+    if (!_hashtable.has_key (pkey)) return l;
+    var ht = _hashtable.get (pkey);
+    if (ht == null) return l;
+    var hte = ht.get (skey);
+    if (hte == null) return l;
+    foreach (string k in hte.keys) {
+      l.add (k);
+    }
+    return l;
+  }
+}
diff --git a/gxml/meson.build b/gxml/meson.build
index f04be68..cd16e8d 100644
--- a/gxml/meson.build
+++ b/gxml/meson.build
@@ -57,10 +57,14 @@ valasources = files ([
        'Element.vala',
        'Enumeration.vala',
        'GHtml.vala',
+       'GomArrayList.vala',
        'GomAttr.vala',
-       'GomCollections.vala',
+       'GomBaseCollection.vala',
        'GomDocument.vala',
        'GomElement.vala',
+       'GomHashMap.vala',
+       'GomHashPairedMap.vala',
+       'GomHashThreeMap.vala',
        'GomNode.vala',
        'GomObject.vala',
        'GomProperty.vala',
@@ -180,4 +184,4 @@ endif
 
 
 libgxml_dep = declare_dependency(include_directories : inc_rooth_dep,
-  link_with : libgxml)
\ No newline at end of file
+  link_with : libgxml)
diff --git a/test/GomElementTest.vala b/test/GomElementTest.vala
index 912c398..355962e 100644
--- a/test/GomElementTest.vala
+++ b/test/GomElementTest.vala
@@ -753,7 +753,6 @@ class GomElementTest : GXmlTest  {
                                assert (e.get_attribute ("prop") == "value_prop");
                                assert (e.text != null);
                                assert (e.prop != null);
-                               assert_not_reached ();
                        } catch (GLib.Error e) {
                    GLib.message ("Error: "+e.message);
                    assert_not_reached ();



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]