You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@gump.apache.org by le...@apache.org on 2005/05/01 19:55:39 UTC

svn commit: r165531 [1/4] - in /gump/branches/Gump3/pygump/python/gump: config.py engine/__init__.py engine/loader.py engine/modeller.py engine/normalizer.py engine/objectifier.py engine/verifier.py plugins/java/ test/testModeller.py

Author: leosimons
Date: Sun May  1 10:55:39 2005
New Revision: 165531

URL: http://svn.apache.org/viewcvs?rev=165531&view=rev
Log:
Extensive refactoring of engine code (GUMP-123).

* pygump/python/gump/engine/verifier.py,
  pygump/python/gump/engine/__init__.py,
  pygump/python/gump/engine/normalizer.py,
  pygump/python/gump/engine/modeller.py,
  pygump/python/gump/engine/objectifier.py,
  pygump/python/gump/engine/loader.py: extensive refactoring to break things out into multiple files, break classes up into small bits with many utility methods, clean things up, etc.

* pygump/python/gump/test/testModeller.py,
  pygump/python/gump/config.py: update to correspond with the refactoring of the engine code.

* pygump/python/gump/plugins/java: ignore python object files.

* pygump/python/gump/config.py: get rid of print statement.

Added:
    gump/branches/Gump3/pygump/python/gump/engine/loader.py
      - copied, changed from r165511, gump/branches/Gump3/pygump/python/gump/engine/modeller.py
    gump/branches/Gump3/pygump/python/gump/engine/normalizer.py
      - copied, changed from r165511, gump/branches/Gump3/pygump/python/gump/engine/modeller.py
    gump/branches/Gump3/pygump/python/gump/engine/objectifier.py
      - copied, changed from r165511, gump/branches/Gump3/pygump/python/gump/engine/modeller.py
    gump/branches/Gump3/pygump/python/gump/engine/verifier.py
      - copied, changed from r165511, gump/branches/Gump3/pygump/python/gump/engine/modeller.py
Modified:
    gump/branches/Gump3/pygump/python/gump/config.py
    gump/branches/Gump3/pygump/python/gump/engine/__init__.py
    gump/branches/Gump3/pygump/python/gump/engine/modeller.py
    gump/branches/Gump3/pygump/python/gump/plugins/java/   (props changed)
    gump/branches/Gump3/pygump/python/gump/test/testModeller.py

Modified: gump/branches/Gump3/pygump/python/gump/config.py
URL: http://svn.apache.org/viewcvs/gump/branches/Gump3/pygump/python/gump/config.py?rev=165531&r1=165530&r2=165531&view=diff
==============================================================================
--- gump/branches/Gump3/pygump/python/gump/config.py (original)
+++ gump/branches/Gump3/pygump/python/gump/config.py Sun May  1 10:55:39 2005
@@ -213,7 +213,6 @@
     """Provide a logging implementation for the given level and name."""
     logging.basicConfig()
     log = logging.getLogger(name)
-    print 'Got Logger %s for %s' % (log,name)
     # Let the file decide ... log.setLevel(config.log_level)
     return log
 
@@ -248,25 +247,25 @@
 
 def get_modeller_loader(log, vfs=None):
     """Provide a Loader implementation."""
-    from gump.engine.modeller import Loader
+    from gump.engine.loader import Loader
     return Loader(log, vfs)
 
 
 def get_modeller_normalizer(log):
     """Provide a Normalizer implementation."""
-    from gump.engine.modeller import Normalizer
+    from gump.engine.normalizer import Normalizer
     return Normalizer(log)
 
 
 def get_modeller_objectifier(log):
     """Provide a Objectifier implementation."""
-    from gump.engine.modeller import Objectifier
+    from gump.engine.objectifier import Objectifier
     return Objectifier(log)
 
 
 def get_modeller_verifier(walker):
     """Provide a Verifier implementation."""
-    from gump.engine.modeller import Verifier
+    from gump.engine.verifier import Verifier
     return Verifier(walker)
 
 

Modified: gump/branches/Gump3/pygump/python/gump/engine/__init__.py
URL: http://svn.apache.org/viewcvs/gump/branches/Gump3/pygump/python/gump/engine/__init__.py?rev=165531&r1=165530&r2=165531&view=diff
==============================================================================
--- gump/branches/Gump3/pygump/python/gump/engine/__init__.py (original)
+++ gump/branches/Gump3/pygump/python/gump/engine/__init__.py Sun May  1 10:55:39 2005
@@ -126,6 +126,11 @@
     print
 
 
+class EngineError(Exception):
+    """Generic error thrown for all internal Engine module exceptions."""
+    pass
+
+
 class _Engine:
     """This is the core of the core of the pygump application. It interacts
     with the other parts of the gump.engine package to transform the model

Copied: gump/branches/Gump3/pygump/python/gump/engine/loader.py (from r165511, gump/branches/Gump3/pygump/python/gump/engine/modeller.py)
URL: http://svn.apache.org/viewcvs/gump/branches/Gump3/pygump/python/gump/engine/loader.py?p2=gump/branches/Gump3/pygump/python/gump/engine/loader.py&p1=gump/branches/Gump3/pygump/python/gump/engine/modeller.py&r1=165511&r2=165531&rev=165531&view=diff
==============================================================================
--- gump/branches/Gump3/pygump/python/gump/engine/modeller.py (original)
+++ gump/branches/Gump3/pygump/python/gump/engine/loader.py Sun May  1 10:55:39 2005
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""This module reads, merges and converts gump xml metadata."""
+"""This module reads gump xml metadata."""
 
 __copyright__ = "Copyright (c) 2004-2005 The Apache Software Foundation"
 __license__   = "http://www.apache.org/licenses/LICENSE-2.0"
@@ -25,133 +25,88 @@
 from xml import dom
 from xml.dom import minidom
 
-from gump.model import *
+from gump.engine import EngineError
+from gump.engine.modeller import _import_node
+from gump.engine.modeller import _find_project_containing_node
+from gump.engine.modeller import _find_document_containing_node
+from gump.engine.modeller import _find_module_containing_node
+from gump.engine.modeller import _do_drop
 
 ###
-### Utility methods shared between classes
+### Helper methods
 ###
-def _find_element_text(parent, element_name):
-    """Retrieves the text contents of an element like <blah>text</blah>."""
-    elem = parent.getElementsByTagName(element_name).item(0).firstChild
-    if elem:
-        return elem.data
-    else:
-        return ""
-
-
-def _do_drop(to_remove, dropped_nodes=None):
-    """Remove node from its parent and add to dropped_nodes list."""
+def _resolve_hrefs_in_children(element, dropped_nodes, found_hrefs, download_func, error_func):
+    """Recursively resolve all hrefs in all the children for a DOM node.
     
-    node_to_remove_element_from = to_remove.parentNode
-    node_to_remove_element_from.removeChild(to_remove)
-    
-    if dropped_nodes != None:
-        dropped_nodes.append(to_remove)
-
-
-def _find_ancestor_by_tag(node, tagName):
-    """Walk up the DOM hierarchy to locate an element of the specified tag."""
-    parent = node
-    while parent.nodeType == dom.Node.ELEMENT_NODE:
-        if parent.tagName == tagName:
-            return parent
-        parent = parent.parentNode
-        if not parent:
-            return None
-
-
-def _find_document_containing_node(node):
-    """Walk up the DOM hierarchy to locate a Document node."""
-    parent = node
-    while not parent.nodeType == dom.Node.DOCUMENT_NODE:
-        parent = parent.parentNode
-        if not parent: # this indicates a bug in the DOM implementation as
-                       # its illegal
-            raise ModellerError, "Cannot find document containing this node!"
-    
-    return parent
-
-
-def _find_project_containing_node(node):
-    """Walk up the DOM hierarchy to locate a <project> Element."""
-    return _find_ancestor_by_tag(node, "project")
-
-
-def _find_module_containing_node(node):
-    """Walk up the DOM hierarchy to locate a <module> Element."""
-    return _find_ancestor_by_tag(node, "module")
-
-
-def _find_repository_containing_node(node):
-    """Walk up the DOM hierarchy to locate a <repository> Element."""
-    return _find_ancestor_by_tag(node, "repository")
-
-
-def _import_node(target_node, new_node):
-    """Combines two DOM trees together.
-
-    The second argument is merged into the first argument, which is then
-    returned.
+    The resolution is done in a resolve-then-recurse manner, so the end
+    result is a dom tree without hrefs. Note that this method expects to
+    be passed in a dom Element or something else which actually has children;
+    passing in an Attr for example makes no sense and results in problems.
+    
+    The dropped_nodes argument should be a list that will be populated with
+    nodes for which href resolution fails.
+    
+    The found_hrefs argument should be a list that will be populated with all
+    the hrefs that have been resolved by the method that resolved hrefs for
+    the parent. An error will be thrown when recursion is detected.
     """
-    _import_attributes(target_node, new_node)
-    _import_children(target_node, new_node)
-
+    for child in element.childNodes:
+        # only resolve hrefs for elements
+        if not child.nodeType == dom.Node.ELEMENT_NODE: continue
+        # there's a <url href=""/> element which is descriptive and should
+        # not be resolved
+        if child.tagName == 'url': continue
+        if child.hasAttribute('href'):
+            # yep, this is one to load...
+            _resolve_href(child, dropped_nodes, download_func, error_func)
+
+        # now recurse to resolve any hrefs within this child
+        # note that we duplicate the found_hrefs array. This means that the
+        # same file can be imported multiple times, as long as it is imported
+        # by siblings, rather than as part of some parent<->child relation.
+        _resolve_hrefs_in_children(child, dropped_nodes, found_hrefs[:], download_func, error_func)
+
+def _resolve_href(node, dropped_nodes, found_hrefs, download_func, error_func):
+    """Resolve a href for the provided node.
+
+    We merge in the referenced xml document into the provided node.
+    The href attribute on the provided node will be removed if the merge
+    succeeds. If the merge fails the provided node will be removed from
+    its parent and appended to the dropped_nodes list.
+    """
+    href = node.getAttribute('href')
+    if href in found_hrefs:
+        raise EngineError, \
+              """Recursive inclusion because files refer to each other. This href leads to
+              a cycle: %s.""" % href
+    found_hrefs.append(href)
+    
+    try:
+        stream = download_func(href)
+    except Exception, details:
+        # swallow this in interest of log readability
+        #_drop_module_or_project(node, dropped_nodes)
+        error_func(node, dropped_nodes)
+        return # make sure to stop processing...
+    
+    new_dom = minidom.parse(stream)
+    new_dom.normalize()
+    stream.close() # close file immediately, we're done!
+    new_root = new_dom.documentElement
+    
+    # we succeeded loading the new document, get rid of the href, save it
+    # as "resolved"
+    node.removeAttribute('href')
+    node.setAttribute('resolved-from-href', href)
     
-def _import_attributes(target_node, new_node):
-    """Copy all attributes from the new node to the target node."""
-    new_attributes = new_node.attributes
-    if new_attributes:
-        i = 0
-        while i < new_attributes.length: # for loops gives a KeyError,
-            att = new_attributes.item(i) #   seems like a minidom bug!
-            i = i + 1
-            if not att: continue
-
-            name = att.name.__str__()
-            value = new_node.getAttribute(name).__str__()
-            target_node.setAttribute(name, value)
-
-
-def _import_children(target_node, new_node, filter=None):
-    """Copy all children from the new node to the target node."""
-    new_elements = new_node.childNodes
-    if new_elements and new_elements.length > 0:
-        for child in new_elements:
-            if filter:
-                if filter.exclude(child):
-                    continue # skip this one
-            clone = child.cloneNode(True)
-            target_node.appendChild(clone)
+    _import_node(node, new_root)
+    
+    # we're done with the file now, allow GC
+    new_root.unlink()
 
 ###
 ### Classes
 ###
-
-class _TagNameFilter:
-    """Filter for use with _import_children().
-
-    This filter can be configured to filter out certain
-    elements based on the element name."""
-    def __init__(self, excludedTags):
-        """Create a new instance. The excludeTags argument
-        should be an array of strings specifying element
-        names to exclude."""
-        self.excludedTags = excludedTags
-
-    def exclude(self, node):
-        if not node.nodeType == dom.Node.ELEMENT_NODE:
-            return False
-        if node.tagName in self.excludedTags:
-            return True
-        
-        return False
-
-
-class ModellerError(Exception):
-    """Generic error thrown for all internal Modeller module exceptions."""
-    pass
-
-
 class Loader:
     """Parses and resolves Gump XML metadata.
 
@@ -226,89 +181,17 @@
         # resolve all hrefs in all elements, for example
         # <project href="blah.xml"/>; replaces that element with the
         # contents of blah.xml
-        self._resolve_hrefs_in_workspace(ws, dropped_nodes)
+        _resolve_hrefs_in_children(ws, dropped_nodes, [], self.get_as_stream, self.handle_error)
         
         # the combined results
         return (wsdom, dropped_nodes)
     
-    def _resolve_hrefs_in_workspace(self, ws, dropped_nodes):
-        """Redirects to _resolve_hrefs_in_children."""
-        found_hrefs=[]
-        self._resolve_hrefs_in_children(ws, dropped_nodes, found_hrefs)
-    
-    def _resolve_hrefs_in_children(self, element, dropped_nodes, found_hrefs):
-        """Recursively resolve all hrefs in all the children for a DOM node.
-        
-        The resolution is done in a resolve-then-recurse manner, so the end
-        result is a dom tree without hrefs. Note that this method expects to
-        be passed in a dom Element or something else which actually has children;
-        passing in an Attr for example makes no sense and results in problems.
-        
-        The dropped_nodes argument should be a list that will be populated with
-        nodes for which href resolution fails.
-        
-        The found_hrefs argument should be al ist that will be populated with all
-        the hrefs that have been resolved by the method that resolved hrefs for
-        the parent. An error will be thrown when recursion is detected.
-        """
-        for child in element.childNodes:
-            # only resolve hrefs for elements
-            if not child.nodeType == dom.Node.ELEMENT_NODE: continue
-            # there's a <url href=""/> element which is descriptive and should
-            # not be resolved
-            if child.tagName == 'url': continue
-            if child.hasAttribute('href'):
-                # yep, this is one to load...
-                self._resolve_href(child, dropped_nodes)
-
-            # now recurse to resolve any hrefs within this child
-            # note that we duplicate the found_hrefs array. This means that the
-            # same file can be imported multiple times, as long as it is imported
-            # by siblings, rather than as part of some parent<->child relation.
-            self._resolve_hrefs_in_children(child, dropped_nodes, found_hrefs[:])
-        
-        # we're now done with resolution
-        #return node
-    
-    def _resolve_href(self, node, dropped_nodes, found_hrefs):
-        """Resolve a href for the provided node.
-
-        We merge in the referenced xml document into the provided node.
-        The href attribute on the provided node will be removed if the merge
-        succeeds. If the merge fails the provided node will be removed from
-        its parent and appended to the dropped_nodes list.
-        """
-        href = node.getAttribute('href')
-        if href in found_hrefs:
-            raise ModellerError, \
-"""Recursive inclusion because files refer to each other. This href leads to
-a cycle: %s.""" % href
+    # callback interface for _resolve_hrefs_in_workspace
+    def get_as_stream(self, href):
         self.log.debug( "Resolving HREF: %s" % href )
-        found_hrefs.append(href)
-        
-        try:
-            stream = self.vfs.get_as_stream(href)
-        except Exception, details:
-            # swallow this in interest of log readability
-            self._drop_module_or_project(node, dropped_nodes)
-            return # make sure to stop processing...
-        
-        new_dom = minidom.parse(stream)
-        new_dom.normalize()
-        stream.close() # close file immediately, we're done!
-        new_root = new_dom.documentElement
-        
-        # we succeeded loading the new document, get rid of the href, save it
-        # as "resolved"
-        node.removeAttribute('href')
-        node.setAttribute('resolved-from-href', href)
-        
-        _import_node(node, new_root)
-        
-        # we're done with the file now, allow GC
-        new_root.unlink()
-    
-    def _drop_module_or_project(self, node, dropped_nodes):
+        return self.vfs.get_as_stream(href)
+
+    def handle_error(self, href):
         """Finds the project associated with this node and removes it.
         
         If there is no associated project, the associated module is removed
@@ -324,7 +207,7 @@
             project.appendChild(comment)
             name = project.getAttribute("name")
             self.log.warning("Dropping project '%s' from module '%s' because of href resolution error!" % (name , modulename))
-
+    
             _do_drop(project, dropped_nodes)
         else:
             module = _find_module_containing_node(node)
@@ -332,803 +215,10 @@
                 doc = _find_document_containing_node(module)
                 name = module.getAttribute("name")
                 self.log.warning("Dropping module '%s' because of href resolution error!" % name)
-
+    
                 _do_drop(project, dropped_nodes)
             else:
-                raise ModellerError, \
+                raise EngineError, \
                       "Problematic node has no parent <project/> or " + \
                       "<module/>, unable to recover! Node:\n%s" \
                       % node.toprettyxml()
-
-
-class Normalizer:
-    """Turns a messy gump DOM workspace into a simplified and normalized form.
-    
-    The normalized form is as follows:
-        
-        <workspace name="1">
-          <other-stuff .../>
-          
-          <repositories>
-            <repository name="1">
-              <full-repo-definition-without-modules-or-projects/>
-            </repository>
-            <repository name="2" ...>
-            <repository name="..." ...>
-            <repository name="N" ...>
-          </repositories>
-          
-          <modules>
-            <module name="1">
-              <repository name=""/><!-- not "url"... -->
-              <full-module-definition-without-repositories-or-projects/>
-            </module>
-            <module name="2" ...>
-            <module name="..." ...>
-            <module name="N" ...>
-          </modules>
-          
-          <projects>
-            <project name="1">
-              <module name=""/>
-              <full-project-definition-without-repositories-or-modules/>
-              
-              <depend name="1" id="one-id-only"/>
-              <depend name="1" id="one-id-only-per-tag"/>
-              <depend name="2" optional="true"/>
-              <depend name="..." ...>
-              <depend name="N" ...>
-            </project>
-            <project name="2" ...>
-            <project name="..." ...>
-            <project name="N" ...>
-          </projects>
-          
-        </workspace>
-    
-    Note that the introduction of the Notifier makes pygump more flexible than
-    the current gump model is specced in several ways:
-
-        * All named elements (repositories, modules, projects) can appear
-        anywhere within the graph. This allows, for example, repositories defined
-        within projects or modules.
-        
-        * Relationships between named elements can be established "by inverse".
-        The "child" (module to repository, project to module, ...) element can
-        appear outside the parent, then reference its parent by name. For example,
-        
-          <project name="bootstrap-ant">
-            <module name="ant"/>
-          </project>
-        
-        is valid. Of course, the reverse
-        
-          <module name="ant>
-            <project name="bootstrap-ant"/>
-          </module>
-        
-        is also still valid.
-    
-    TODO: support for 0.4 model and earlier...
-    """
-    def __init__(self, log):
-        self.log = log
-    
-    def normalize(self, olddoc):
-        """Turns a messy gump DOM workspace into a simplified and normalized form."""
-        self.olddoc = olddoc
-        self.oldroot = olddoc.documentElement
-        self.impl = dom.getDOMImplementation()
-        self.newdoc = self.impl.createDocument(None, "workspace", None)
-        self.newroot = self.newdoc.documentElement
-        
-        self._copy_workspace_root_stuff()
-        self._populate_newroot()
-        self._parse_maven_projects()
-
-        self._normalize_repositories()
-        self._normalize_modules()
-        self._normalize_projects()
-        
-        self._normalize_dependencies()
-        
-        self._pretty_xml()
-        
-        doc = self.newdoc
-        # allow GC
-        self.repositories = None
-        self.modules = None
-        self.projects = None
-        self.olddoc.unlink()
-        self.olddoc = None
-        self.oldroot = None
-        self.newroot = None
-        self.newdoc = None
-        
-        return doc
-    
-    def _copy_workspace_root_stuff(self):
-        """Copies over the unnamed config bits and properties."""
-        # copy all ws attributes
-        _import_attributes(self.newroot, self.oldroot)
-        
-        # these elements are to be filtered out completely
-        # at all levels
-        exclude = ["repositories",
-                   "repository",
-                   "modules",
-                   "module",
-                   "projects",
-                   "project"]
-        
-        # try to avoid cloning most of them
-        filter = _TagNameFilter(exclude)
-        _import_children(self.newroot, self.oldroot, filter)
-        
-        # now get rid of the excluded tags that were lower down the tree
-        # (for example in a <profile/> or somethin')
-        self._clean_out_by_tag( self.newroot, exclude )
-        
-    def _populate_newroot(self):
-        """Creates the main containers like <repositories/>."""
-        self.repositories = self.newdoc.createElement("repositories")
-        self.newroot.appendChild(self.repositories)
-
-        self.modules = self.newdoc.createElement("modules")
-        self.newroot.appendChild(self.modules)
-        
-        self.projects = self.newdoc.createElement("projects")
-        self.newroot.appendChild(self.projects)
-    
-    def _parse_maven_projects(self):
-        """Looks for <project type="maven"> and converts those."""
-        
-        projects = self.projects.getElementsByTagName("project")
-        for project in projects:
-            if not project.getAttribute("type") == "maven":
-                continue
-            
-            self._parse_maven_project(project)
-    
-    def _parse_maven_project(self, project):
-        #TODO: implement this!
-        if True: return
-
-        self._resolve_maven_imports(project)
-        
-        id = _find_element_text(project, "id")
-        groupid = _find_element_text(project, "groupId")
-        name = "%s-%s" % (groupid,id)
-
-        title = _find_element_text(project, "title")
-        url = _find_element_text(project, "url")
-        cvsweb = _find_element_text(project.getElementsByTagName("repository").item(0), "url")
-        description = _find_element_text(project, "description")
-        
-        repository = _find_element_text(project, "gumpRepositoryId")
-        if not repository:
-            # create repository and module
-            connection = _find_element_text(project.getElementsByTagName("repository").item(0), "connection")
-            connection = connection[4:] # get rid of "scm:"
-            provider = connection[:connection.index(':')] # "cvs" or "svn" or "starteam"
-            if provider.upper() == "cvs".upper():
-                repository = connection[connection.index(':')+1:]
-                parts = repository.split(':')
-                user = parts[1][:connection.index('@')]
-                host = parts[1][connection.index('@')+1:]
-                path = parts[2]
-                module = parts[3]
-
-                            
-        #new_project = self.newdoc.createElement("project")
-        #new_project.setAttribute("name", name)
-        #new_command = self.newdoc.createElement("maven")
-        #new_project.appendChild(new_command)
-    
-    def _resolve_maven_imports(self, project):
-        pass #TODO: implement this!
-    
-    def _normalize_repositories(self):
-        repos = self._get_list_merged_by_name("repository")
-        exclude = ["project", "module", "repository"];
-        for repo in repos:
-            clone = repo.cloneNode(True)
-            self._clean_out_by_tag(clone, exclude)
-            self.repositories.appendChild(clone)
-    
-    def _normalize_modules(self):
-        modules = self._get_list_merged_by_name("module")
-        
-        exclude = ["project", "module", "repository"];
-        for module in modules:
-            repository = self._find_repository_for_module(module)
-            if not repository:
-                name = module.getAttribute("name")
-                self.log.warn("Dropping module '%s' because no corresponding repository could be found!" % name)
-                continue
-            
-            clone = module.cloneNode(True)
-            self._clean_out_by_tag( clone, exclude )
-            reporef = self.newdoc.createElement("repository")
-            reporef.setAttribute("name", repository.getAttribute("name") )
-            clone.insertBefore(reporef, clone.firstChild)
-            
-            self.modules.appendChild(clone)
-
-    def _find_repository_for_module(self, module):
-        repo = None
-
-        # look within module
-        repos = module.getElementsByTagName("repository")
-        if repos.length > 0:
-            return repos.item(0)
-        
-        # look upward
-        return _find_repository_containing_node(module)
-
-    def _normalize_projects(self):
-        projects = self._get_list_merged_by_name("project")
-        exclude = ["project", "module", "repository"];
-        for project in projects:
-            module = self._find_module_for_project(project)
-            if not module:
-                name = project.getAttribute("name")
-                self.log.warn("Dropping project '%s' because no corresponding module could be found!" % name)
-                continue
-            
-            clone = project.cloneNode(True)
-            self._clean_out_by_tag( clone, exclude )
-            moduleref = self.newdoc.createElement("module")
-            moduleref.setAttribute("name", module.getAttribute("name") )
-            clone.insertBefore(moduleref, clone.firstChild)
-            
-            self.projects.appendChild(clone)
-    
-    def _find_module_for_project(self, project):
-        repo = None
-
-        # look within module
-        modules = project.getElementsByTagName("module")
-        if modules.length > 0:
-            return modules.item(0)
-        
-        # look upward
-        return _find_module_containing_node(project)
-
-    def _normalize_dependencies(self):
-        """Converts <depend/> and <option/> elements into normalized form."""
-        for project in self.projects.getElementsByTagName("project"):
-            self._normalize_optional_depend(project)
-            dependencies = project.getElementsByTagName("depend")
-            if dependencies.length > 0:
-                self._normalize_depend_inside_ant(project, dependencies)
-                self._normalize_depend_on_multiple_ids(project, dependencies)
-
-    def _normalize_optional_depend(self, project):
-        """Replace an <option/> with a <depend optional=""/>."""
-        options = project.getElementsByTagName("option")
-        if options.length > 0:
-            for option in options:
-                depend = self.newdoc.createElement("depend")
-                _import_attributes(depend, option)
-                depend.setAttribute("optional", "true")
-                project.appendChild(depend)
-                _do_drop(option)
-
-    def _normalize_depend_inside_ant(self, project, dependencies):
-        """Split <depend/> inside <ant/> out into a <depend/> and a <property/>."""
-        for dependency in dependencies:
-            if dependency.parentNode.tagName in ["ant","maven"]:
-                new_dependency = dependency.cloneNode(True)
-                new_dependency.removeAttribute("property")
-                project.appendChild(new_dependency)
-                
-                new_property = self.newdoc.createElement("property")
-                new_property.setAttribute("name", dependency.getAttribute("property"))
-                new_property.setAttribute("project", dependency.getAttribute("project"))
-                new_property.setAttribute("reference", "jarpath")
-                if dependency.getAttribute("id"):
-                    new_property.setAttribute("id", dependency.getAttribute("id"))
-                
-                dependency.parentNode.appendChild(new_property)
-                _do_drop(dependency)
-
-    def _normalize_depend_on_multiple_ids(self, project, dependencies):
-        """Split one <depend/> out into multiple, one for each id."""
-        #TODO: reverse that!
-        for dependency in dependencies:
-            ids = dependency.getAttribute("name")
-            if not ids: continue
-            if ids.find(",") == -1: continue
-            
-            project.removeChild(dependency)
-            list = ids.split(",")
-            for id in list:
-                new_dependency = dependency.cloneNode(True)
-                new_dependency.setAttribute("ids",id)
-                project.appendChild(new_dependency)
-
-    def _clean_out_by_tag(self, root, exclude):
-        for tagname in exclude:
-            elems_to_remove = root.getElementsByTagName(tagname)
-            if elems_to_remove.length > 0:
-                for to_remove in elems_to_remove:
-                    _do_drop(to_remove)
-        
-    def _get_list_merged_by_name(self, tagName):
-        list = self.oldroot.getElementsByTagName(tagName)
-        newlist = {}
-        for elem in list:
-            name = elem.getAttribute('name')
-            if not name:
-                self.log.warning( "Dropping a %s because it has no name!" % tagName )
-                continue
-            
-            if newlist.has_key(name):
-                _import_node(newlist[name], elem)
-            else:
-                clone = elem.cloneNode(True)
-                newlist[name] = clone
-        
-        return newlist.values()
-    
-    def _pretty_xml(self):
-        """Adds in some newlines."""
-        
-        self.newroot.insertBefore(self.newdoc.createTextNode("\n"), self.modules)
-        self.newroot.insertBefore(self.newdoc.createTextNode("\n"), self.modules)
-        self.newroot.insertBefore(self.newdoc.createTextNode("\n"), self.modules)
-        self.newroot.insertBefore(self.newdoc.createTextNode("\n"), self.projects)
-        self.newroot.insertBefore(self.newdoc.createTextNode("\n"), self.projects)
-        self.newroot.insertBefore(self.newdoc.createTextNode("\n"), self.projects)
-
-        self.repositories.insertBefore(self.newdoc.createTextNode("\n"), self.repositories.firstChild)
-        self.repositories.appendChild(self.newdoc.createTextNode("\n"))
-
-        self.modules.insertBefore(self.newdoc.createTextNode("\n"), self.modules.firstChild)
-        self.modules.appendChild(self.newdoc.createTextNode("\n"))
-
-        self.projects.insertBefore(self.newdoc.createTextNode("\n"), self.projects.firstChild)
-        self.projects.appendChild(self.newdoc.createTextNode("\n"))
-
-class Objectifier:
-    """Turns a *normalized* gump DOM workspace into a pythonified workspace.
-
-    The input for the objectifier is a (potentially rather big) DOM tree that
-    contains normalized gump project definitions. From this tree, it starts
-    building a python object model graph consisting of instances of the
-    classes found in the gump.model package.
-
-    Also note that the Objectifier is purely single-threaded, since it stores
-    intermediate results during parsing as properties for convenience.
-    """
-    
-    def __init__(self, log):
-        """Store all settings and dependencies as properties."""
-        self.log = log
-
-    def get_workspace(self, domtree):
-        """Transforms a workspace xml document into object form."""
-        
-        self.root = domtree.documentElement
-
-        self._find_repository_definitions()
-        self._find_module_definitions()
-        self._find_project_definitions()
-        
-        self._create_workspace()
-        self._create_repositories()
-        self._create_modules()
-        self._create_projects()
-
-        workspace = self.workspace
-        self.workspace = None
-        self.root = None
-        return workspace
-    
-    def _create_workspace(self):
-        self.workspace = Workspace(self.root.getAttribute('name'))
-    
-    ###
-    ### Searching
-    ###
-    def _find_repository_definitions(self):
-        """Retrieves a list of <repository/> elements."""
-        children = self.root.childNodes
-        for child in children:
-            if not child.nodeType == dom.Node.ELEMENT_NODE: continue
-            if child.tagName == "repositories":
-                self.repository_definitions = child.childNodes
-                break
-
-    def _find_module_definitions(self):
-        """Retrieve a list of <module/> elements."""
-        children = self.root.childNodes
-        for child in children:
-            if not child.nodeType == dom.Node.ELEMENT_NODE: continue
-            if child.tagName == "modules":
-                self.module_definitions = child.childNodes
-                break
-
-    def _find_project_definitions(self):
-        """Retrieve a list of <project/> elements."""
-        children = self.root.childNodes
-        for child in children:
-            if not child.nodeType == dom.Node.ELEMENT_NODE: continue
-            if child.tagName == "projects":
-                self.project_definitions = child.childNodes
-                break
-
-    ###
-    ### Repository parsing
-    ###
-    def _create_repositories(self):
-        """Creates all repositories and adds them to the workspace."""
-
-        for repository_definition in self.repository_definitions:
-            if not repository_definition.nodeType == dom.Node.ELEMENT_NODE: continue
-            repository = self._create_repository(repository_definition)
-            self.workspace.repositories[repository.name] = repository
-    
-    def _create_repository(self, repository_definition):
-        name = repository_definition.getAttribute("name")
-        self.log.debug("Converting repository definition '%s' into object form." % name)
-        
-        # parse the attributes and elements common to all repositories
-        title = None
-        try: title = self._find_element_text(repository_definition, "title")
-        except: pass
-        
-        home_page = None
-        try: home_page = self._find_element_text(repository_definition, "home-page")
-        except: pass
-        
-        cvsweb = None
-        try: cvsweb = self._find_element_text(repository_definition, "cvsweb")
-        except:
-            try: cvsweb = self._find_element_text(repository_definition, "web")
-            except: pass
-        
-        redistributable = False
-        if repository_definition.getElementsByTagName("redistributable").length > 0:
-            redistributable = True
-            
-        # now delegate to _create methods for specific repositories to do the rest
-        repository = None
-        type = repository_definition.getAttribute("type").upper()
-        if type == "CVS":
-            repository = self._create_cvs_repository(name, title, home_page, cvsweb, redistributable, repository_definition)
-        elif type == "SVN":
-            repository = self._create_svn_repository(name, title, home_page, cvsweb, redistributable, repository_definition)
-        else:
-            raise ModellerError, "Unknown repository type '%s' for repository '%s'" % (type, name)
-        #TODO perforce support
-        
-        return repository
-    
-    def _create_cvs_repository(self, name, title, home_page, cvsweb, redistributable, repository_definition):
-        hostname = _find_element_text(repository_definition, "hostname")
-        path = _find_element_text(repository_definition, "path")
-
-        method = CVS_METHOD_PSERVER
-        try: method = _find_element_text(repository_definition, "method")
-        except: pass
-        
-        user = None
-        try: user = _find_element_text(repository_definition, "user")
-        except: pass
-
-        password = None
-        try: password = _find_element_text(repository_definition, "password")
-        except: pass
-        
-        repository = CvsRepository(self.workspace,
-                                   name,
-                                   hostname,
-                                   path,
-                                   title = title,
-                                   home_page = home_page,
-                                   cvsweb = cvsweb,
-                                   redistributable = False,
-                                   method = CVS_METHOD_PSERVER,
-                                   user = user,
-                                   password = password)
-        return repository
-
-    def _create_svn_repository(self, name, title, home_page, cvsweb, redistributable, repository_definition):
-        url = _find_element_text(repository_definition, "url")
-
-        user = None
-        try: user = _find_element_text(repository_definition, "user")
-        except: pass
-
-        password = None
-        try: password = _find_element_text(repository_definition, "password")
-        except: pass
-        
-        repository = SvnRepository(self.workspace,
-                                   name,
-                                   url,
-                                   title = title,
-                                   home_page = home_page,
-                                   cvsweb = cvsweb,
-                                   redistributable = False,
-                                   user = user,
-                                   password = password)
-        return repository
-    
-    ###
-    ### Module parsing
-    ###
-
-    def _find_repository_for_module(self, module_definition):
-        name = module_definition.getAttribute("name")
-        repo_name = module_definition.getElementsByTagName("repository").item(0).getAttribute("name")
-        repo = self.workspace.repositories[repo_name]
-        return repo
-
-    def _create_modules(self):
-        for module_definition in self.module_definitions:
-            if not module_definition.nodeType == dom.Node.ELEMENT_NODE: continue
-            module = self._create_module(module_definition)
-            module.repository.modules[module.name] = module
-            self.workspace.modules[module.name] = module
-        
-    def _create_module(self, module_definition):
-        name = module_definition.getAttribute("name")
-        self.log.debug("Converting module definition '%s' into object form." % name)
-        repository = self._find_repository_for_module(module_definition)
-        
-        # parse the attributes and elements common to all modules
-        url = None
-        try: url = _find_element_text(module_definition, "url")
-        except: pass
-        
-        description = None
-        try: description = _find_element_text(module_definition, "description")
-        except: pass
-        
-        # now delegate to _create methods for specific modules to do the rest
-        module = None
-        if isinstance(repository, CvsRepository):
-            module = self._create_cvs_module(repository, name, url, description, module_definition)
-        elif isinstance(repository, SvnRepository):
-            module = self._create_svn_module(repository, name, url, description, module_definition)
-        else:
-            raise ModellerError, "Unknown repository type '%s' referenced by module '%s'" % (repository.__class__,name)
-        #TODO perforce support
-        return module
-    
-    def _create_cvs_module(self, repository, name, url, description, module_definition):
-        tag = module_definition.getAttribute("tag")
-        return CvsModule(repository, name, tag, url, description)
-    
-    def _create_svn_module(self, repository, name, url, description, module_definition):
-        path = module_definition.getAttribute("path")
-        return SvnModule(repository, name, path, url, description)
-    
-    ###
-    ### Project parsing
-    ###
-    
-    def _find_module_for_project(self, project_definition):
-        name = project_definition.getAttribute("name")
-        module_name = project_definition.getElementsByTagName("module").item(0).getAttribute("name")
-        module = self.workspace.modules[module_name]
-        return module
-
-    def _create_projects(self):
-        for project_definition in self.project_definitions:
-            if not project_definition.nodeType == dom.Node.ELEMENT_NODE: continue
-            project = self._create_project(project_definition)
-            project.module.projects[project.name] = project
-            self.workspace.projects[project.name] = project
-
-            self._create_commands(project,project_definition)
-            self._create_outputs(project,project_definition)
-
-        # wire up dependencies only after projects have been created
-        for project_definition in self.project_definitions:
-            if not project_definition.nodeType == dom.Node.ELEMENT_NODE: continue
-            self._create_dependencies(project_definition)
-        
-    def _create_project(self, project_definition):
-        name = project_definition.getAttribute("name")
-        self.log.debug("Converting project definition '%s' into object form." % name)
-        module = self._find_module_for_project(project_definition)
-        
-        project = Project(module, name)
-        return project
-
-    def _create_commands(self, project, project_definition):
-        rmdirs = project_definition.getElementsByTagName("delete")
-        for cmd in rmdirs:
-            dir = cmd.getAttribute("dir")
-            project.add_command(Rmdir(project, dir))
-            
-        mkdirs = project_definition.getElementsByTagName("mkdir")
-        for cmd in mkdirs:
-            dir = cmd.getAttribute("dir")
-            project.add_command(Mkdir(project, dir))
-        
-        scripts = project_definition.getElementsByTagName("script")
-        for cmd in scripts:
-            name = cmd.getAttribute("name")
-            args = []
-            for arg in cmd.getElementsByTagName("arg"):
-                name = arg.getAttribute("name")
-                value = arg.getAttribute("value")
-                args.append((name, value))
-                
-            project.add_command(Script(project, name, args))
-            
-        ants = project_definition.getElementsByTagName("ant")
-        for cmd in ants:
-            name = cmd.getAttribute("name")
-            buildfile = cmd.getAttribute("target")
-            target = cmd.getAttribute("target")
-                
-            project.add_command(Ant(project, name, target, buildfile))
-            
-            #TODO 
-        
-        #TODO more commands
-    
-    def _create_outputs(self, project, project_definition):
-        homes = project_definition.getElementsByTagName("home")
-        if homes.length > 0:
-            home = homes.item(0).getAttribute("directory")
-            project.add_output(Homedir(project,home))
-        
-        jars = project_definition.getElementsByTagName("jar")
-        for jar in jars:
-            name = jar.getAttribute("name")
-            id = jar.getAttribute("id")
-            add_to_bootclass_path = jar.getAttribute("type") == "boot"
-            project.add_output(Jar(project,name,id,add_to_bootclass_path))
-            
-        #TODO more outputs
-    
-    def _create_dependencies(self, project_definition):
-        name = project_definition.getAttribute("name")
-        project = self.workspace.projects[name]
-            
-        dependencies = project_definition.getElementsByTagName("depend")
-        for dependency in dependencies:
-            self._add_dependency(project, dependency)
-    
-    def _add_dependency(self, project, dependency):
-        dependency_name = dependency.getAttribute("project")
-        runtime = dependency.getAttribute("runtime") == "true"
-        inherit = dependency.getAttribute("inherit")
-        optional = dependency.getAttribute("optional") == "true"
-        
-        dependency_project = None
-        try:
-            dependency_project = self.workspace.projects[dependency_name]
-        except KeyError:
-            # we store the name instead. a Verifier should be used later to
-            # fix this error.
-            #TODO get_dependency_on_project (below) asserts this is a
-            # Project not a string, causing a big KABOOM!...
-            dependency_project = dependency_name
-        
-        idList = dependency.getAttribute("ids")
-        if idList:
-            ids=idList.split(' ')        
-        else:
-            ids=[]
-        
-        relationship = project.get_dependency_on_project(dependency_project)
-        relationship.add_dependency_info(DependencyInfo(relationship,optional,runtime,inherit,ids))
-
-
-class VerificationError(ModellerError):
-    """Error raised by the verifier if the model is not valid."""
-    pass
-
-class CyclicDependencyError(VerificationError):
-    """Error raised by the verifier if the model contains one or more cyclic
-    dependencies. The cycles property will contain an array of cycles, where
-    a cycle again is an array of projects that together make up a cycle."""
-    #TODO think about error hierarchies in gump and decide if this is the way
-    #     we want to implement them
-    def __init__(self, cycles):
-        self.cycles = cycles
-
-class AbstractErrorHandler:
-    """Base class for supporting configurable error recovery. Instead of
-    raising exceptions, supportive classes will pass the error to an instance
-    of this class. This allows clients to recover from errors more gracefully.
-    This default implementation tries to call a handleError() method on
-    itself, and raises the error if that is not possible.
-    
-    Subclasses should implement a handleError(error) method, where the
-    provided error argument is normally an instance of Exception.
-    
-    This setup is similar to that used by the SAX libraries."""
-    #TODO maybe move this elsewhere?
-    def _handleError(self,error):
-        if not hasattr(self,'handleError'): raise error
-        if not callable(self.handleError): raise error
-        self.handleError(error)
-
-class Verifier:
-    """Determines whether a finished gump model conforms to certain contracts.
-    
-    Those contracts are not currently completely specified, but it is somewhat
-    possible to digest them from the model documentation. However, the
-    verifier itself together with its unit tests is probably the only "hard"
-    specification of those contracts."""
-    def __init__(self, walker):
-        assert hasattr(walker, "walk")
-        assert callable(walker.walk)
-        
-        self.walker = walker
-        
-    def verify(self, workspace, errorHandler=AbstractErrorHandler()):
-        """Sends VerificationError objects to the errorHandler argument if the
-        provided model contains errors. If no errorHandler is provided, the
-        exceptions are 'raise'd."""
-        # TODO: get rid of this return statement
-        return
-        # TODO: Test this code!!!
-        from gump.plugins import AbstractPlugin
-        visitor = AbstractPlugin(None)
-        (visited_repositories, visited_modules, visited_projects) = self.walker.walk(workspace, visitor)
-        
-        if len(visited_projects) != len(workspace.projects):
-            # some projects weren't visited! Those indicate cycles...
-            unvisited = []
-            
-            for p in workspace.projects.values():
-                if not p in visited_projects:
-                    unvisited.append(p)
-            
-            cycles = self._find_cycles(unvisited[:])
-            
-            errorHandler._handleError(CyclicDependencyError(cycles))
-    
-    def _find_cycles(self,projects):
-        """Brute-force find all cycles.
-        
-        1) depth-first traverse all paths extending from each project
-           (the "needle")
-        2) use a stack for documenting the current path traversal
-        2) look for cycles in those paths involving the needle
-           2.a) avoid traversing cycles not involving the needle
-                by coloring nodes on visit, making sure to visit
-                them only once
-           2.b) store a cycle when its found
-        3) return an array containing an array of cycles"""
-        #TODO: test this
-        cycles = []
-        for project in projects:
-            needle = project
-            visited = []
-            stack = [project]
-            self._visit(project,visited,stack,needle,cycles)
-        
-        return cycles
-    
-    def _visit(self,project,visited,stack,needle,cycles):
-        #TODO: test this
-        visited.append(project)
-        for relationship in project.dependencies:
-            dependency = relationship.dependency
-            stack.append(dependency)
-            if dependency == needle: # cycle involving needle!
-                cycles.append(stack)
-                visited.append(dependency)
-                stack = stack[:-1] # pop this dependency off the stack,
-                      # we'll look at the next dependency in the next
-                      # for loop iteration
-            else:
-                if not dependency in visited:
-                    self._visit(dependency,visited,stack,needle,cycles)
-                # else we have a cycle not involving the needle,
-                # we'll find it later (or we've found it already)
-        
-            stack = stack[:-1] # pop this dependency off the stack,
-              # we'll look at the next dependency in the next
-              # for loop iteration