commit-gnue
[Top][All Lists]
Advanced

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

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


From: reinhard
Subject: [gnue] r9963 - trunk/gnue-common/src/base
Date: Mon, 12 Oct 2009 17:17:25 -0500 (CDT)

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
 # -----------------------------------------------------------------------------





reply via email to

[Prev in Thread] Current Thread [Next in Thread]