[gnue] r9963 - trunk/gnue-common/src/base

[email protected]
Newsgroups gmane.comp.cms.gnue.cvs
Message-ID <[email protected]>
Author: reinhard
Date: 2009-10-12 17:17:24 -0500 (Mon, 12 Oct 2009)
New Revision: 9963

Modified:
   trunk/gnue-common/src/base/tree.py
Log:
Added methods to create a deep copy of a node (including all children), and to
merge two trees. Added some module documentation.


Modified: trunk/gnue-common/src/base/tree.py
===================================================================
--- trunk/gnue-common/src/base/tree.py	2009-10-12 20:32:31 UTC (rev 9962)
+++ trunk/gnue-common/src/base/tree.py	2009-10-12 22:17:24 UTC (rev 9963)
@@ -24,7 +24,28 @@
 """
 Classes representing a node in a tree structure.
 
-TODO: This module is work in progress.
+Object trees in GNU Enterprise
+==============================
+
+The classes in this module facilitate the creation of an in-memory object tree
+made of objects of different classes.  An XML tree from a form or report
+definition file is typically represented as such a tree.
+
+The simplest variant for such an object is the L{Node} class which just
+maintains a link to its parent and its children, which also have to be
+descendants of that class.
+
+The L{NamedNode} adds a __node_name__ attribute which can be searched for in a
+tree, for example set through a C{name=} attribute in an XML element.
+
+The L{AttribNode} introduces I{attributes}, key/value pairs where the possible
+keys are predefined, and for each key, the allowed values are also predefined.
+Attributes mirror the XML attributes of a form/report definition file.
+
+For the sake of intuitive usability, both child nodes and node attributes can
+be accessed as object attributes.  For example, if the node "foo" has a child
+"bar", and that child has an attribute "baz", this attribute can be accessed
+with C{foo.bar.baz}.
 """
 
 from gnue.common.base import errors, i18n
@@ -33,7 +54,8 @@
 __all__ = ['Node', 'NamedNode', 'AttribNode',
         'ChildNotAllowedError', 'DuplicateSingletonError',
         'CircularReferenceError', 'DuplicateChildNameError',
-        'DuplicateDescendantNameError', 'NodeDictNotAvailableError']
+        'DuplicateDescendantNameError', 'NodeDictNotAvailableError',
+        'AttribRequiredError']
 
 
 # =============================================================================
@@ -57,7 +79,8 @@
     the parent.
 
     Instances of this class support searching L{ancestors
-    <__find_ancestor_of_class__>} and L{descendants
+    <__find_ancestor_of_class__>}, L{direct children
+    <__find_child_of_class__>}, and L{descendants
     <__find_descendant_of_class__>} of a given class.
 
     @cvar _allowed_children_: Dictionary with valid child node classes, where
@@ -221,6 +244,30 @@
 
 
     # -------------------------------------------------------------------------
+    # Find first child of a given class
+    # -------------------------------------------------------------------------
+
+    def __find_child_of_class__(self, child_class):
+        """
+        Return the first child with the given class.
+
+        If none of the children is of the given class, None is returned.
+        
+        @param child_class: Node class to search for.
+        @type child_class: type
+
+        @return: Child node of the given class.
+        @rtype: Node or None
+        """
+
+        checktype(child_class, type)
+
+        for child in self.__children__:
+            if isinstance(child, child_class):
+                return child
+
+
+    # -------------------------------------------------------------------------
     # Find first descendant of a given class
     # -------------------------------------------------------------------------
 
@@ -239,7 +286,7 @@
         @param descendant_class: Node class to search for.
         @type descendant_class: type
 
-        @return: Descendant node of the given node type.
+        @return: Descendant node of the given class.
         @rtype: Node or None
         """
 
@@ -271,7 +318,7 @@
                 # Must test with isinstance to also match descendant classes.
                 if isinstance(self, allowed_class):
                     if info.get('singleton', False):
-                        if parent.__find_descendant_of_class__(allowed_class):
+                        if parent.__find_child_of_class__(allowed_class):
                             raise DuplicateSingletonError(parent,
                                     allowed_class)
                     break
@@ -761,14 +808,14 @@
         @type children: list
         """
 
-        NamedNode.__init__(self, parent=parent, children=children)
+        NamedNode.__init__(self, children=children)
 
         #: Attributes
         self.__attribs = {}
 
         # Set name attribute first so the object already has a nice string
-        # representation in case setting any of the other attributes causes an
-        # exception.
+        # representation in case setting any of the other attributes causes
+        # an exception.
         if name is not None:
             self.name = name
 
@@ -782,7 +829,11 @@
                 if self.__get_attrib__(attrib) is None:
                     raise AttribRequiredError(self, attrib)
 
+        # Set parent only now, in case an exception happens before we aren't
+        # linked in the tree.
+        self.__parent__ = parent
 
+
     # -------------------------------------------------------------------------
     # Access attribs as object attributes
     # -------------------------------------------------------------------------
@@ -818,6 +869,24 @@
     # Attribute access
     # -------------------------------------------------------------------------
 
+    def __get_attrib_list(self):
+
+        return self.__attribs.keys()
+
+    # -------------------------------------------------------------------------
+
+    __attrib_list__ = property(__get_attrib_list, None, None,
+            """
+            A list of the names of the attributes which have been explicitly
+            set. (readonly)
+
+            Attributes still containing the default value are not listed.
+
+            @type: [Node]
+            """)
+
+    # -------------------------------------------------------------------------
+
     def __get_attrib__(self, name):
         """
         Get an attribute value.
@@ -869,7 +938,7 @@
             target_type = unicode
 
         # Typecast if necessary.
-        if not isinstance(value, target_type):
+        if not isinstance(value, target_type) and value is not None:
             value = target_type(value)
 
         # Check if value is allowed.
@@ -892,9 +961,72 @@
     # Merge two nodes with all their children
     # -------------------------------------------------------------------------
 
-    # TODO: Steal from GParserHelpers
+    def __copy__(self):
+        """
+        Create a deep copy of the object tree.
+        """
 
+        # Copy the node itself.
+        result = self.__class__(**self.__attribs)
 
+        # Copy children.
+        for child in self.__children__:
+            child.__copy__().__parent__ = result
+
+        return result
+
+
+    # -------------------------------------------------------------------------
+    # Merge two nodes with all their children
+    # -------------------------------------------------------------------------
+
+    def __merge__(self, other):
+        """
+        Merge another object tree into this tree.
+
+        All attributes and child nodes from the other object are merged into this
+        object.  If any child node exists in both objects with the same name,
+        the merge is done recursively.
+
+        @param other: L{ParserObj} tree to be merged into this object tree
+        """
+
+        # First copy all explicitly set attributes from the other object.
+        # We access other.__attribs directly to avoid conversion from string to
+        # object and back for object references.
+        for (name, value) in other.__attribs.items():
+            if name not in self.__attribs:
+                self.__set_attrib__(name, value)
+
+        # Now copy/merge all children.
+        for child in other.__children__:
+            if child.name is None:
+                # Unnamed children: check if singleton.
+                for allowed_class, info in self._allowed_children_.items():
+                    if isinstance(self, allowed_class):
+                        my_child = self.__find_child_of_class__(allowed_class)
+                        if my_child and info.get('singleton', False):
+                            # If singleton and already there: merge.
+                            my_child.__merge__(child)
+                        else:
+                            # Otherwise: copy.
+                            child.__copy__().__parent__ = self
+                        break
+                else:
+                    # Not found in self._allowed_children_: Copy it, this will
+                    # create a meaningful exception.
+                    child.__copy__().__parent__ = self
+            else:
+                # Named children: check if already there.
+                my_child = self.__get_child__(child.name)
+                if my_child:
+                    # If already there: merge.
+                    my_child.__merge__(child)
+                else:
+                    # Otherwise: copy.
+                    child.__copy__().__parent__ = self
+
+
 # =============================================================================
 # Exceptions
 # =============================================================================
@@ -1271,8 +1403,7 @@
                 rank="Captain"),
             Member(
                 name="Leonard McCoy",
-                rank="Medic",
-                supervisor="James T. Kirk"),
+                rank="Medic"),
             Member(
                 name="Nyota Uhura",
                 rank="Communication Officer")])
@@ -1307,7 +1438,30 @@
     print "Spock's supervisor is", repr(crew.Spock.supervisor)
     print "Uhura's supervisor is", repr(uhura.supervisor)
 
+    # Test __copy__ method.
+    new_crew = crew.__copy__()
+    print "Copy of the crew:", new_crew.__children__
+    print "IDs of the original:", [id(c) for c in crew.__children__]
+    print "IDs of the copy:", [id(c) for c in new_crew.__children__]
 
+    # Test __merge__ method.
+    merge_crew = Crew(
+        name="U.S.S. Enterprise",
+        children=[
+            Member(
+                name="Scotty",
+                rank="Chief Technican",
+                supervisor="James T. Kirk"),
+            Member(
+                name="Leonard McCoy",
+                rank="Medic",
+                supervisor="James T. Kirk")])
+    crew.__merge__(merge_crew)
+    print "Merged crew:", crew.__children__
+    print "McCoy's supervisor is now",
+    print repr(crew.__get_child__("Leonard McCoy").supervisor)
+
+
 # -----------------------------------------------------------------------------
 # Run tests
 # -----------------------------------------------------------------------------
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.