changeset 89:15f4da8bdbd3

add ability to control order of attributes, whitespace. more cleanup up of dependencies and exclusions
author smith@nwoca.org
date Fri, 17 Feb 2012 15:09:35 +0000
parents 9f2ab59a5333
children c207cdcaf13e
files fix-ivy.groovy fixivy.groovy
diffstat 2 files changed, 572 insertions(+), 489 deletions(-) [+]
line wrap: on
line diff
--- a/fix-ivy.groovy	Wed Feb 15 17:11:10 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,489 +0,0 @@
-
-import groovy.xml.QName;
-import org.codehaus.groovy.runtime.InvokerHelper;
-
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import groovy.xml.*
-
-println args
-
-if (args.any { it.toUpperCase() == '-R'} ) {
-    
-
-} else {
-    processIvy(new File('ivy.xml')
-} 
-
-def processIvy(file) {
-
-    def xml = new XmlSlurper()
-    def ivy = xml.parse( file)
-
-    def cfgs =  ivy.configurations
-    
-    // default mapping for most dependencies:
-    cfgs.@defaultconfmapping = 'compile->default'
-    
-    // Add default config, if missing:
-    if (!cfgs.children().find { it.@name == 'default' } ) {
-        println "adding default config"
-        cfgs.appendNode {
-            conf(name:"default")
-        }
-    }
-    
-    
-    ivy.dependencies.children().each { dep ->
-       if ( dep.@conf.text()?.contains('test') ) {
-           dep.@conf = 'compile-test->default' 
-       } else {
-           dep.attributes().remove('conf')
-       }
-    }
-    
-    // find groovy dependencies (or null)
-    groovy = ivy.dependencies.children().find { it.@org == 'org.codehaus.groovy' && it.@name.text().startsWith('groovy') }
-    
-    if (groovy) {
-        // Add a groovy conig for groovy projects:
-        if (!cfgs.children().any { it.@name == 'groovy' } ) {
-            println "added private groovy conf"
-            cfgs.appendNode { 
-                conf(name:'groovy',visibility:'private',transitive: true)
-            }
-        }
-        // Adjust 'compile' to exend from 'groovy' conf
-        cfgs.children().find { it.@name == 'compile' }.@extends = 'groovy'
-        cfgs.children().find { it.@name == 'groovy' }.@transitive = 'true'
-        groovy.@conf = "groovy->default"
-        println "Groovy ${groovy.@rev} conf changed to ${groovy.@conf}"    
-        // Switch project to use groovy-all instead of groovy + deps
-        if (groovy.@name == 'groovy') {
-            println "changed groovy to groovy-all"
-            groovy.@name = 'groovy-all'
-        }
-        // remove exclude for groovy-all
-        ivy.dependencies.children().find { it.name() == 'exclude' && it.@module.text() == 'groovy-all' }.replaceNode {}
-    }
-    
-    //def sameConfMappings = ivy.dependencies.children().findAll { it.@conf in ['*->@','*->*'] }
-    // 
-    //sameConfMappings.each { 
-    //    it.@conf = null  // "default->compile;%->@"
-    //    println "changing conf for ${it.@name} to ${it.@conf}" 
-    //}
-    
-    
-    cfgs.children().each { it.@visibility = 'private' }
-    cfgs.children().find {it.@name == 'runtime' }.@extends = 'compile'
-    cfgs.children().find {it.@name == 'compile-test' }.@extends = 'compile'
-    cfgs.children().find {it.@name == 'runtime-test' }.@extends = 'runtime,compile-test'
-    cfgs.children().find {it.@name == 'default' }.@visibility = 'public'
-    cfgs.children().find {it.@name == 'default' }.@extends = 'runtime'
-    
-    if (!cfgs.children().any { it.@name == 'archives' } ) {
-        println "added public 'archives' conf"
-        cfgs.appendNode { 
-           conf(name:'archives',visibility:'public')
-        }
-    }
-    cfgs.children().find {it.@name == 'archives' }.@visibility = 'public'
-    
-    
-    def outputBuilder = new StreamingMarkupBuilder()
-    //new File('ivy.xml').write(  XmlUtil.serialize( outputBuilder.bind { mkp.yield ivy }).replaceAll('>','>'))
-    
-    def stringWriter = new StringWriter()
-    
-    
-    def node = new XmlParser().parseText( XmlUtil.serialize( outputBuilder.bind { mkp.yield ivy }))
-    new MyNodePrinter(new IndentPrinter( new PrintWriter(stringWriter))).print(node)
-    file.write(stringWriter.toString())
-    
-}
-/*
-The class below is a copy/paste of groovy's XmlNodePrinter customized to order Ivy.xml attributes in
-the desired order and to add some whitespace.
-*/
-class MyNodePrinter  {
-
-    protected void printNameAttributes(Map attributes,  ctx) {
-     
-       if (attributes == null || attributes.isEmpty()) {
-           return;
-       }
-      def writer = new StringBuffer()
-      attributes.entrySet().sort{
-           switch (it.key ) {
-               case 'org': 
-                   1
-                   break
-               case 'name': 2
-                   break
-               case 'rev': 3
-                   break
-               case 'extends': 5
-                   break
-               default: 99
-           }
-       }.each { p ->
-          def tmp = new StringBuffer()
-           Map.Entry entry = (Map.Entry) p;
-        
-           tmp << " "
-           tmp << getName(entry.getKey())
-           tmp << "="
-           Object value = entry.getValue();
-           tmp << quote
-           tmp << value         
-//           if (value instanceof String) {
-//               printEscaped((String) value);
-//           } else {
-//               printEscaped(InvokerHelper.toString(value));
-//           }
-           tmp << quote
-           writer << String.format(" %-35s",tmp.toString())
-//           printNamespace(entry.getKey(), ctx);
-       }
-       out.print(" " + writer.toString().trim())
-   }
-
-    protected final IndentPrinter out;
-    private String quote;
-    private boolean namespaceAware = true;
-    private boolean preserveWhitespace = false;
-
-    public MyNodePrinter(PrintWriter out) {
-        this(out, "  ");
-    }
-
-    public MyNodePrinter(PrintWriter out, String indent) {
-        this(out, indent, "\"");
-    }
-
-    public MyNodePrinter(PrintWriter out, String indent, String quote) {
-        this(new IndentPrinter(out, indent), quote);
-    }
-
-    public MyNodePrinter(IndentPrinter out) {
-        this(out, "\"");
-    }
-
-    public MyNodePrinter(IndentPrinter out, String quote) {
-        if (out == null) {
-            throw new IllegalArgumentException("Argument 'IndentPrinter out' must not be null!");
-        }
-        this.out = out;
-        this.quote = quote;
-    }
-
-    public MyNodePrinter() {
-        this(new PrintWriter(new OutputStreamWriter(System.out)));
-    }
-
-    public void print(Node node) {
-        print(node, new NamespaceContext());
-    }
-
-    /**
-     * Check if namespace handling is enabled.
-     * Defaults to <code>true</code>.
-     *
-     * @return true if namespace handling is enabled
-     */
-    public boolean isNamespaceAware() {
-        return namespaceAware;
-    }
-
-    /**
-     * Enable and/or disable namespace handling.
-     *
-     * @param namespaceAware the new desired value
-     */
-    public void setNamespaceAware(boolean namespaceAware) {
-        this.namespaceAware = namespaceAware;
-    }
-
-    /**
-     * Check if whitespace preservation is enabled.
-     * Defaults to <code>false</code>.
-     *
-     * @return true if whitespaces are honoured when printing simple text nodes
-     */
-    public boolean isPreserveWhitespace() {
-        return preserveWhitespace;
-    }
-
-    /**
-     * Enable and/or disable preservation of whitespace.
-     *
-     * @param preserveWhitespace the new desired value
-     */
-    public void setPreserveWhitespace(boolean preserveWhitespace) {
-        this.preserveWhitespace = preserveWhitespace;
-    }
-
-    /**
-     * Get Quote to use when printing attributes.
-     *
-     * @return the quote character
-     */
-    public String getQuote() {
-        return quote;
-    }
-
-    /**
-     * Set Quote to use when printing attributes.
-     *
-     * @param quote the quote character
-     */
-    public void setQuote(String quote) {
-        this.quote = quote;
-    }
-
-    protected void print(Node node, NamespaceContext ctx) {
-        /*
-         * Handle empty elements like '<br/>', '<img/> or '<hr noshade="noshade"/>.
-         */
-        if (isEmptyElement(node)) {
-            printLineBegin();
-            out.print("<");
-            out.print(getName(node));
-            if (ctx != null) {
-                printNamespace(node, ctx);
-            }
-            printNameAttributes(node.attributes(), ctx);
-            out.print("/>");
-            printLineEnd();
-            out.flush();
-            return;
-        }
-
-        /*
-         * Hook for extra processing, e.g. GSP tag element!
-         */
-        if (printSpecialNode(node)) {
-            out.flush();
-            return;
-        }
-
-        /*
-         * Handle normal element like <html> ... </html>.
-         */
-        Object value = node.value();
-        if (value instanceof List) {
-            printName(node, ctx, true, isListOfSimple((List) value));
-            printList((List) value, ctx);
-            printName(node, ctx, false, isListOfSimple((List) value));
-            out.flush();
-            return;
-        }
-
-        // treat as simple type - probably a String
-        printName(node, ctx, true, preserveWhitespace);
-        printSimpleItemWithIndent(value);
-        printName(node, ctx, false, preserveWhitespace);
-        out.flush();
-    }
-
-    private boolean isListOfSimple(List value) {
-        for (Object p : value) {
-            if (p instanceof Node) return false;
-        }
-        return preserveWhitespace;
-    }
-
-    protected void printLineBegin() {
-        out.printIndent();
-    }
-
-    protected void printLineEnd() {
-        printLineEnd(null);
-    }
-
-    protected void printLineEnd(String comment) {
-        if (comment != null) {
-            out.print(" <!-- ");
-            out.print(comment);
-            out.print(" -->");
-        }
-        out.println();
-        out.flush();
-    }
-
-    protected void printList(List list, NamespaceContext ctx) {
-        out.incrementIndent();
-        for (Object value : list) {
-            NamespaceContext context = new NamespaceContext(ctx);
-            /*
-             * If the current value is a node, recurse into that node.
-             */
-            if (value instanceof Node) {
-                print((Node) value, context);
-                continue;
-            }
-            printSimpleItem(value);
-        }
-        out.decrementIndent();
-    }
-
-    protected void printSimpleItem(Object value) {
-        if (!preserveWhitespace) printLineBegin();
-        printEscaped(InvokerHelper.toString(value));
-        if (!preserveWhitespace) printLineEnd();
-    }
-
-    protected void printName(Node node, NamespaceContext ctx, boolean begin, boolean preserve) {
-        if (node == null) {
-            throw new NullPointerException("Node must not be null.");
-        }
-        Object name = node.name();
-        if (name == null) {
-            throw new NullPointerException("Name must not be null.");
-        }
-        if (!preserve || begin) printLineBegin();
-        out.print("<");
-        if (!begin) {
-            out.print("/");
-        }
-        out.print(getName(node));
-        if (ctx != null) {
-            printNamespace(node, ctx);
-        }
-        if (begin) {
-            printNameAttributes(node.attributes(), ctx);
-        }
-        out.print(">");
-        if (!preserve || !begin) printLineEnd();
-    }
-
-    protected boolean printSpecialNode(Node node) {
-        return false;
-    }
-
-    protected void printNamespace(Object object, NamespaceContext ctx) {
-        if (namespaceAware) {
-            if (object instanceof Node) {
-                printNamespace(((Node) object).name(), ctx);
-            } else if (object instanceof QName) {
-                QName qname = (QName) object;
-                String namespaceUri = qname.getNamespaceURI();
-                if (namespaceUri != null) {
-                    String prefix = qname.getPrefix();
-                    if (!ctx.isPrefixRegistered(prefix, namespaceUri)) {
-                        ctx.registerNamespacePrefix(prefix, namespaceUri);
-                        out.print(" ");
-                        out.print("xmlns");
-                        if (prefix.length() > 0) {
-                            out.print(":");
-                            out.print(prefix);
-                        }
-                        out.print("=" + quote);
-                        out.print(namespaceUri);
-                        out.print(quote);
-                    }
-                }
-            }
-        }
-    }
-
-  
-
-    private boolean isEmptyElement(Node node) {
-        if (node == null) {
-            throw new IllegalArgumentException("Node must not be null!");
-        }
-        if (!node.children().isEmpty()) {
-            return false;
-        }
-        return node.text().length() == 0;
-    }
-
-    private String getName(Object object) {
-        if (object instanceof String) {
-            return (String) object;
-        } else if (object instanceof QName) {
-            QName qname = (QName) object;
-            if (!namespaceAware) {
-                return qname.getLocalPart();
-            }
-            return qname.getQualifiedName();
-        } else if (object instanceof Node) {
-            Object name = ((Node) object).name();
-            return getName(name);
-        }
-        return object.toString();
-    }
-
-    private void printSimpleItemWithIndent(Object value) {
-        if (!preserveWhitespace) out.incrementIndent();
-        printSimpleItem(value);
-        if (!preserveWhitespace) out.decrementIndent();
-    }
-
-    // For ' and " we only escape if needed. As far as XML is concerned,
-    // we could always escape if we wanted to.
-    private void printEscaped(String s) {
-        for (int i = 0; i < s.length(); i++) {
-            char c = s.charAt(i);
-            switch (c) {
-                case '<':
-                    out.print("&lt;");
-                    break;
-                case '>':
-                    out.print("&gt;");
-                    break;
-                case '&':
-                    out.print("&amp;");
-                    break;
-                case '\'':
-                    if (quote.equals("'"))
-                        out.print("&apos;");
-                    else
-                        out.print(c);
-                    break;
-                case '"':
-                    if (quote.equals("\""))
-                        out.print("&quot;");
-                    else
-                        out.print(c);
-                    break;
-                default:
-                    out.print(c);
-            }
-        }
-    }
-
-    protected class NamespaceContext {
-        private final Map<String, String> namespaceMap;
-
-        public NamespaceContext() {
-            namespaceMap = new HashMap<String, String>();
-        }
-
-        public NamespaceContext(NamespaceContext context) {
-            this();
-            namespaceMap.putAll(context.namespaceMap);
-        }
-
-        public boolean isPrefixRegistered(String prefix, String uri) {
-            return namespaceMap.containsKey(prefix) && namespaceMap.get(prefix).equals(uri);
-        }
-
-        public void registerNamespacePrefix(String prefix, String uri) {
-            if (!isPrefixRegistered(prefix, uri)) {
-                namespaceMap.put(prefix, uri);
-            }
-        }
-
-        public String getNamespace(String prefix) {
-            Object uri = namespaceMap.get(prefix);
-            return (uri == null) ? null : uri.toString();
-        }
-    }
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fixivy.groovy	Fri Feb 17 15:09:35 2012 +0000
@@ -0,0 +1,572 @@
+
+import groovy.xml.QName;
+import org.codehaus.groovy.runtime.InvokerHelper;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import groovy.xml.*
+
+//args = [ '-r']
+println args
+
+ant = new AntBuilder()
+
+
+if (args.any { it.toUpperCase() == '-R'} ) {
+    
+    new File('.').traverse ([ type: groovy.io.FileType.DIRECTORIES, maxDepth: 3 ]) { project -> 
+        if (new File(project,'ivy.xml').exists() ) {
+            processIvy(new File(project,'ivy.xml'))
+        }
+    }
+
+} else {
+    processIvy(new File('ivy.xml'))
+} 
+
+def processIvy(file) {
+    println "-" * 60
+    println "processing: $file"
+    def xml = new XmlSlurper()
+    def ivy = xml.parse(file)
+
+    def cfgs =  ivy.configurations
+    
+    // default mapping for most dependencies:
+    cfgs.@defaultconfmapping = 'compile->default(*)'
+    
+    // Add default config, if missing:
+    if (!cfgs.children().find { it.@name == 'default' } ) {
+        println "adding default config"
+        cfgs.appendNode {
+            conf(name:"default",extends:'compile',visibility:'public')
+        }
+    }    
+    
+    if (ivy.dependencies.children().find { it.@name == 'org.springframework.web.servlet' } &&
+        !ivy.dependencies.children().find { it.@name == 'servlet-api' } ) {
+        ivy.dependencies.children().findAll { it.name() == 'dependency' }.list().last() + {
+            dependency(name:'servlet-api', org: 'javax.servlet', rev: '2.5')
+            dependency(name:'jsp-api', org: 'javax.servlet', rev: '2.0')
+        }
+        
+    }
+    
+    ivy.dependencies.children().each { dep ->
+       if ( dep.@conf.text()?.contains('test') ) {
+           dep.@conf = 'compile-test->default(*)' 
+       } else if ( dep.@conf.text().contains('castor-ant') ) {
+            dep.@conf = 'castor-ant->default(*)'
+       }
+       else {
+           dep.attributes().remove('conf')
+       }
+    }
+    
+    // find groovy dependencies (or null)
+    groovy = ivy.dependencies.children().find { it.@org == 'org.codehaus.groovy' && it.@name.text().startsWith('groovy') }
+    
+    if (groovy) {
+        // Add a groovy conig for groovy projects:
+        if (!cfgs.children().any { it.@name == 'groovy' } ) {
+            println "added private groovy conf"
+            cfgs.appendNode { 
+                conf(name:'groovy',visibility:'private',transitive: true)
+            }
+        }
+        // Adjust 'compile' to exend from 'groovy' conf
+        cfgs.children().find { it.@name == 'compile' }.@extends = 'groovy'
+        cfgs.children().find { it.@name == 'groovy' }.@transitive = 'true'
+        groovy.@conf = "groovy->default"
+        println "Groovy ${groovy.@rev} conf changed to ${groovy.@conf}"    
+        // Switch project to use groovy-all instead of groovy + deps
+        if (groovy.@name == 'groovy') {
+            println "changed groovy to groovy-all"
+            groovy.@name = 'groovy-all'
+        }
+        
+        def template = new File(file.parent ?: ".",'template.mf')
+        if (template.exists()) {
+            println "updating groovy in $template"
+            ant.replace(file: template, token: 'org.codehaus.groovy-groovy:major',value:'org.codehaus.groovy-groovy-all:major')
+        }
+        
+    }
+    
+    
+    ensure(ivy.dependencies, 'hibernate-validator', ['org.slf4j:slf4j-api:1.5.6','org.slf4j:slf4j-log4j12:1.5.6'])
+    
+    ensure(ivy.dependencies, 'org.springframework.test', ['junit:junit:4.8.2'])
+    
+    // remove exclude for groovy-all
+    ivy.dependencies.children().find { it.name() == 'exclude' && it.@module.text() == 'groovy-all' }.replaceNode {}
+   
+   // Add exclude for regular groovy:
+    ensureExcluded(ivy.dependencies,"org.codehaus.groovy:groovy")
+    
+    // ensure modules covered by springsource are exluded;
+    ensureSpringSourceExcludes(ivy.dependencies)
+    
+    ensureConfiguration(ivy.dependencies,'org.springframework.test','compile-test')
+
+    //remove bad junit dependencies and excludes
+    ivy.dependencies.children().find { it.@org == 'org.junit' && it.@module.text() == 'org.junit' }.replaceNode {}
+    ivy.dependencies.children().find { it.@org == 'org.junit' && it.@name.text() == 'org.junit' }.replaceNode {}
+
+    
+    cfgs.children().each { it.@visibility = 'public' }  // Should be 'private' after initial conversions.
+        
+    cfgs.children().find {it.@name == 'runtime' }.@extends = 'compile'
+    cfgs.children().find {it.@name == 'compile-test' }.@extends = 'compile'
+    cfgs.children().find {it.@name == 'runtime-test' }.@extends = 'runtime,compile-test'
+    cfgs.children().find {it.@name == 'default' }.@visibility = 'public'
+    cfgs.children().find {it.@name == 'default' }.@extends = 'runtime'
+    
+    if (!cfgs.children().any { it.@name == 'archives' } ) {
+        println "added public 'archives' conf"
+        cfgs.appendNode { 
+           conf(name:'archives',visibility:'public')
+        }
+    }
+    
+    cfgs.children().find {it.@name == 'archives' }.@visibility = 'public'
+    
+    
+    def outputBuilder = new StreamingMarkupBuilder()
+    //new File('ivy.xml').write(  XmlUtil.serialize( outputBuilder.bind { mkp.yield ivy }).replaceAll('&gt;','>'))
+    
+    def stringWriter = new StringWriter()
+    
+    
+    def node = new XmlParser().parseText( XmlUtil.serialize( outputBuilder.bind { mkp.yield ivy }))
+    new MyNodePrinter(new IndentPrinter( new PrintWriter(stringWriter))).print(node)
+    file.write(stringWriter.toString())
+    
+}
+
+def ensureConfiguration(depends,name,cfg) {
+    depends.children().findAll { it.name() == 'dependency' && it.@name == name}.each {
+        it.@conf = "$cfg->default(*)"
+    }
+}
+
+def ensureExcluded(depends,excludeModule) {
+    def (o,m) = excludeModule.split(':')
+    if (!depends.children().any { it.name() == 'exclude' && it.@module.text() == m}) {
+        depends.appendNode {
+            println "excluding $o:$m"
+            exclude(org: o, module: m)
+        }
+    }
+}
+
+def ensure(depends,exists,requires) {
+
+    if (depends.children().any { it.name() == 'dependency' && it.@name == exists } ) {
+        requires.each {
+            def (org,name,rev) = it.split(":")
+            if (!depends.children().any { it.@name == name } ) {   
+                println "  adding requirement $org:$name for $exists"
+                depends.children().findAll { it.name() == 'dependency' }.list().last() + {
+                    dependency(name: name, org: org, rev: rev)
+                }           
+            }
+        }        
+    }
+}
+
+def ensureSpringSourceExcludes(depends) {
+    def exclusions = []
+    depends.children().findAll { it.name() == 'dependency' && it.@name.text().startsWith('com.springsource.') }.each {
+        exclusions << "${it.@org}:${it.@name.text() - 'com.springsource.'}"        
+    }   
+    exclusions.each {  ensureExcluded(depends,it) }        
+
+}
+
+/*
+The class below is a copy/paste of groovy's XmlNodePrinter customized to order Ivy.xml attributes in
+the desired order and to add some whitespace.
+*/
+class MyNodePrinter  {
+
+    protected void printNameAttributes(Map attributes,  ctx) {
+     
+       if (attributes == null || attributes.isEmpty()) {
+           return;
+       }
+      def writer = new StringBuffer()
+      attributes.entrySet().sort{
+           switch (it.key ) {
+               case 'org': 
+                   1
+                   break
+               case 'name': 2
+                   break
+               case 'rev': 3
+                   break
+               case 'extends': 5
+                   break
+               default: 99
+           }
+       }.each { p ->
+          def tmp = new StringBuffer()
+           Map.Entry entry = (Map.Entry) p;
+        
+           tmp << " "
+           tmp << getName(entry.getKey())
+           tmp << "="
+           Object value = entry.getValue();
+           tmp << quote
+           tmp << value         
+//           if (value instanceof String) {
+//               printEscaped((String) value);
+//           } else {
+//               printEscaped(InvokerHelper.toString(value));
+//           }
+           tmp << quote
+           def size = entry.getKey() in ['org','name'] ? 35 : 22
+           writer << String.format(" %-${size}s",tmp.toString())
+//           printNamespace(entry.getKey(), ctx);
+       }
+       out.print(" " + writer.toString().trim())
+   }
+
+    protected final IndentPrinter out;
+    private String quote;
+    private boolean namespaceAware = true;
+    private boolean preserveWhitespace = false;
+
+    public MyNodePrinter(PrintWriter out) {
+        this(out, "  ");
+    }
+
+    public MyNodePrinter(PrintWriter out, String indent) {
+        this(out, indent, "\"");
+    }
+
+    public MyNodePrinter(PrintWriter out, String indent, String quote) {
+        this(new IndentPrinter(out, indent), quote);
+    }
+
+    public MyNodePrinter(IndentPrinter out) {
+        this(out, "\"");
+    }
+
+    public MyNodePrinter(IndentPrinter out, String quote) {
+        if (out == null) {
+            throw new IllegalArgumentException("Argument 'IndentPrinter out' must not be null!");
+        }
+        this.out = out;
+        this.quote = quote;
+    }
+
+    public MyNodePrinter() {
+        this(new PrintWriter(new OutputStreamWriter(System.out)));
+    }
+
+    public void print(Node node) {
+        print(node, new NamespaceContext());
+    }
+
+    /**
+     * Check if namespace handling is enabled.
+     * Defaults to <code>true</code>.
+     *
+     * @return true if namespace handling is enabled
+     */
+    public boolean isNamespaceAware() {
+        return namespaceAware;
+    }
+
+    /**
+     * Enable and/or disable namespace handling.
+     *
+     * @param namespaceAware the new desired value
+     */
+    public void setNamespaceAware(boolean namespaceAware) {
+        this.namespaceAware = namespaceAware;
+    }
+
+    /**
+     * Check if whitespace preservation is enabled.
+     * Defaults to <code>false</code>.
+     *
+     * @return true if whitespaces are honoured when printing simple text nodes
+     */
+    public boolean isPreserveWhitespace() {
+        return preserveWhitespace;
+    }
+
+    /**
+     * Enable and/or disable preservation of whitespace.
+     *
+     * @param preserveWhitespace the new desired value
+     */
+    public void setPreserveWhitespace(boolean preserveWhitespace) {
+        this.preserveWhitespace = preserveWhitespace;
+    }
+
+    /**
+     * Get Quote to use when printing attributes.
+     *
+     * @return the quote character
+     */
+    public String getQuote() {
+        return quote;
+    }
+
+    /**
+     * Set Quote to use when printing attributes.
+     *
+     * @param quote the quote character
+     */
+    public void setQuote(String quote) {
+        this.quote = quote;
+    }
+
+    protected void print(Node node, NamespaceContext ctx) {
+        /*
+         * Handle empty elements like '<br/>', '<img/> or '<hr noshade="noshade"/>.
+         */
+        if (isEmptyElement(node)) {
+            printLineBegin();
+            out.print("<");
+            out.print(getName(node));
+            if (ctx != null) {
+                printNamespace(node, ctx);
+            }
+            printNameAttributes(node.attributes(), ctx);
+            out.print("/>");
+            printLineEnd();
+            out.flush();
+            return;
+        }
+
+        /*
+         * Hook for extra processing, e.g. GSP tag element!
+         */
+        if (printSpecialNode(node)) {
+            out.flush();
+            return;
+        }
+
+        /*
+         * Handle normal element like <html> ... </html>.
+         */
+        Object value = node.value();
+        if (value instanceof List) {
+            printName(node, ctx, true, isListOfSimple((List) value));
+            printList((List) value, ctx);
+            printName(node, ctx, false, isListOfSimple((List) value));
+            out.flush();
+            return;
+        }
+
+        // treat as simple type - probably a String
+        printName(node, ctx, true, preserveWhitespace);
+        printSimpleItemWithIndent(value);
+        printName(node, ctx, false, preserveWhitespace);
+        out.flush();
+    }
+
+    private boolean isListOfSimple(List value) {
+        for (Object p : value) {
+            if (p instanceof Node) return false;
+        }
+        return preserveWhitespace;
+    }
+
+    protected void printLineBegin() {
+        out.printIndent();
+    }
+
+    protected void printLineEnd() {
+        printLineEnd(null);
+    }
+
+    protected void printLineEnd(String comment) {
+        if (comment != null) {
+            out.print(" <!-- ");
+            out.print(comment);
+            out.print(" -->");
+        }
+        out.println();
+        out.flush();
+    }
+
+    protected void printList(List list, NamespaceContext ctx) {
+        out.incrementIndent();
+        for (Object value : list) {
+            NamespaceContext context = new NamespaceContext(ctx);
+            /*
+             * If the current value is a node, recurse into that node.
+             */
+            if (value instanceof Node) {
+                print((Node) value, context);
+                continue;
+            }
+            printSimpleItem(value);
+        }
+        out.decrementIndent();
+    }
+
+    protected void printSimpleItem(Object value) {
+        if (!preserveWhitespace) printLineBegin();
+        printEscaped(InvokerHelper.toString(value));
+        if (!preserveWhitespace) printLineEnd();
+    }
+
+    protected void printName(Node node, NamespaceContext ctx, boolean begin, boolean preserve) {
+        if (node == null) {
+            throw new NullPointerException("Node must not be null.");
+        }
+        Object name = node.name();
+        if (name == null) {
+            throw new NullPointerException("Name must not be null.");
+        }
+        if (!preserve || begin) printLineBegin();
+        out.print("<");
+        if (!begin) {
+            out.print("/");
+        }
+        out.print(getName(node));
+        if (ctx != null) {
+            printNamespace(node, ctx);
+        }
+        if (begin) {
+            printNameAttributes(node.attributes(), ctx);
+        }
+        out.print(">");
+        if (!preserve || !begin) printLineEnd();
+    }
+
+    protected boolean printSpecialNode(Node node) {
+        return false;
+    }
+
+    protected void printNamespace(Object object, NamespaceContext ctx) {
+        if (namespaceAware) {
+            if (object instanceof Node) {
+                printNamespace(((Node) object).name(), ctx);
+            } else if (object instanceof QName) {
+                QName qname = (QName) object;
+                String namespaceUri = qname.getNamespaceURI();
+                if (namespaceUri != null) {
+                    String prefix = qname.getPrefix();
+                    if (!ctx.isPrefixRegistered(prefix, namespaceUri)) {
+                        ctx.registerNamespacePrefix(prefix, namespaceUri);
+                        out.print(" ");
+                        out.print("xmlns");
+                        if (prefix.length() > 0) {
+                            out.print(":");
+                            out.print(prefix);
+                        }
+                        out.print("=" + quote);
+                        out.print(namespaceUri);
+                        out.print(quote);
+                    }
+                }
+            }
+        }
+    }
+
+  
+
+    private boolean isEmptyElement(Node node) {
+        if (node == null) {
+            throw new IllegalArgumentException("Node must not be null!");
+        }
+        if (!node.children().isEmpty()) {
+            return false;
+        }
+        return node.text().length() == 0;
+    }
+
+    private String getName(Object object) {
+        if (object instanceof String) {
+            return (String) object;
+        } else if (object instanceof QName) {
+            QName qname = (QName) object;
+            if (!namespaceAware) {
+                return qname.getLocalPart();
+            }
+            return qname.getQualifiedName();
+        } else if (object instanceof Node) {
+            Object name = ((Node) object).name();
+            return getName(name);
+        }
+        return object.toString();
+    }
+
+    private void printSimpleItemWithIndent(Object value) {
+        if (!preserveWhitespace) out.incrementIndent();
+        printSimpleItem(value);
+        if (!preserveWhitespace) out.decrementIndent();
+    }
+
+    // For ' and " we only escape if needed. As far as XML is concerned,
+    // we could always escape if we wanted to.
+    private void printEscaped(String s) {
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            switch (c) {
+                case '<':
+                    out.print("&lt;");
+                    break;
+                case '>':
+                    out.print("&gt;");
+                    break;
+                case '&':
+                    out.print("&amp;");
+                    break;
+                case '\'':
+                    if (quote.equals("'"))
+                        out.print("&apos;");
+                    else
+                        out.print(c);
+                    break;
+                case '"':
+                    if (quote.equals("\""))
+                        out.print("&quot;");
+                    else
+                        out.print(c);
+                    break;
+                default:
+                    out.print(c);
+            }
+        }
+    }
+
+    protected class NamespaceContext {
+        private final Map<String, String> namespaceMap;
+
+        public NamespaceContext() {
+            namespaceMap = new HashMap<String, String>();
+        }
+
+        public NamespaceContext(NamespaceContext context) {
+            this();
+            namespaceMap.putAll(context.namespaceMap);
+        }
+
+        public boolean isPrefixRegistered(String prefix, String uri) {
+            return namespaceMap.containsKey(prefix) && namespaceMap.get(prefix).equals(uri);
+        }
+
+        public void registerNamespacePrefix(String prefix, String uri) {
+            if (!isPrefixRegistered(prefix, uri)) {
+                namespaceMap.put(prefix, uri);
+            }
+        }
+
+        public String getNamespace(String prefix) {
+            Object uri = namespaceMap.get(prefix);
+            return (uri == null) ? null : uri.toString();
+        }
+    }
+}
\ No newline at end of file