You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by ma...@apache.org on 2010/05/07 22:00:50 UTC

svn commit: r942195 [2/3] - in /ant/ivy/core/trunk: ./ doc/ doc/ivyfile/ doc/use/ src/java/org/apache/ivy/ant/ src/java/org/apache/ivy/core/deliver/ src/java/org/apache/ivy/core/module/descriptor/ src/java/org/apache/ivy/core/publish/ src/java/org/apac...

Modified: ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorUpdater.java
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorUpdater.java?rev=942195&r1=942194&r2=942195&view=diff
==============================================================================
--- ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorUpdater.java (original)
+++ ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorUpdater.java Fri May  7 20:00:48 2010
@@ -27,6 +27,7 @@ import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -34,6 +35,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
@@ -42,6 +44,12 @@ import java.util.StringTokenizer;
 import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ExtendsDescriptor;
+import org.apache.ivy.core.module.descriptor.InheritableItem;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.namespace.NameSpaceHelper;
@@ -153,6 +161,23 @@ public final class XmlModuleDescriptorUp
 
     private static class UpdaterHandler extends DefaultHandler implements LexicalHandler {
 
+        /** standard attributes of ivy-module/info */
+        private static final Collection stdAtts = Arrays.asList(new String[] {"organisation", "module", "branch",
+                "revision", "status", "publication", "namespace"});
+
+        /** elements that may appear inside ivy-module, in expected order */
+        private static final List moduleElements = Arrays.asList(new String[] {
+                "info", "configurations", "publications", "dependencies", "conflicts"
+        });
+        /** element position of "configurations" inside "ivy-module" */
+        private static final int CONFIGURATIONS_POSITION = moduleElements.indexOf("configurations");
+        /** element position of "dependencies" inside "ivy-module" */
+        private static final int DEPENDENCIES_POSITION = moduleElements.indexOf("dependencies");
+
+        /** elements that may appear inside of ivy-module/info */
+        private static final Collection infoElements = Arrays.asList(new String[]{
+                "extends", "ivyauthor", "license", "repository", "description" });
+
         private final ParserSettings settings;
 
         private final PrintWriter out;
@@ -213,6 +238,21 @@ public final class XmlModuleDescriptorUp
         // with /> instead of ></qName>
         private String justOpen = null;
         
+        //track the size of the left indent, so that inserted elements are formatted
+        //like nearby elements.
+
+        //true when we're reading indent whitespace
+        private boolean indenting;
+        private StringBuffer currentIndent = new StringBuffer();
+        private ArrayList indentLevels = new ArrayList(); // ArrayList<String>
+
+        //true if an ivy-module/info/description element has been found in the published descriptor
+        private boolean hasDescription = false;
+        //true if merged configurations have been written
+        private boolean mergedConfigurations = false;
+        //true if merged deps have been written
+        private boolean mergedDependencies = false;
+
         // the new value of the defaultconf attribute on the publications tag
         private String newDefaultConf = null;
         
@@ -225,10 +265,15 @@ public final class XmlModuleDescriptorUp
         public void startElement(String uri, String localName, String qName, Attributes attributes)
                 throws SAXException {
             inHeader = false;
+            endIndent();
             if (justOpen != null) {
                 write(">");
             }
+
+            flushMergedElementsBefore(qName);
             context.push(qName);
+
+            String path = getContext();
             if ("info".equals(qName)) {
                 infoStarted(attributes);
             } else if (replaceInclude && "include".equals(qName)
@@ -236,15 +281,17 @@ public final class XmlModuleDescriptorUp
                 //TODO, in the case of !replaceInclude, we should still replace the relative path
                 //by an absolute path. 
                 includeStarted(attributes);
-            } else if ("ivy-module/dependencies/dependency".equals(getContext())) {
+            } else if ("ivy-module/info/extends".equals(path)) {
+                startExtends(attributes);
+            } else if ("ivy-module/dependencies/dependency".equals(path)) {
                 startElementInDependency(attributes);
             } else if ("dependencies".equals(qName)) {
                 startDependencies(attributes);
-            } else if ("ivy-module/configurations/conf".equals(getContext())) {
+            } else if ("ivy-module/configurations/conf".equals(path)) {
                 startElementInConfigurationsConf(qName, attributes);
-            } else if ("ivy-module/publications/artifact/conf".equals(getContext())
-                    || "ivy-module/dependencies/dependency/conf".equals(getContext())
-                    || "ivy-module/dependencies/dependency/artifact/conf".equals(getContext())) {
+            } else if ("ivy-module/publications/artifact/conf".equals(path)
+                    || "ivy-module/dependencies/dependency/conf".equals(path)
+                    || "ivy-module/dependencies/dependency/artifact/conf".equals(path)) {
                 buffers.push(new ExtendedBuffer(getContext()));
                 ((ExtendedBuffer) confAttributeBuffers.peek()).setDefaultPrint(false);
                 String confName = substitute(settings, attributes.getValue("name"));
@@ -257,7 +304,7 @@ public final class XmlModuleDescriptorUp
                                 + substitute(settings, attributes.getValue(i)) + "\"");
                     }
                 }
-            } else if ("ivy-module/publications/artifact".equals(getContext())) {
+            } else if ("ivy-module/publications/artifact".equals(path)) {
                 ExtendedBuffer buffer = new ExtendedBuffer(getContext());
                 buffers.push(buffer);
                 confAttributeBuffers.push(buffer);
@@ -278,7 +325,7 @@ public final class XmlModuleDescriptorUp
                                 + substitute(settings, attributes.getValue(i)) + "\"");
                     }
                 }
-            } else if ("ivy-module/dependencies/dependency/artifact".equals(getContext())) {
+            } else if ("ivy-module/dependencies/dependency/artifact".equals(path)) {
                 ExtendedBuffer buffer = new ExtendedBuffer(getContext());
                 buffers.push(buffer);
                 confAttributeBuffers.push(buffer);
@@ -298,9 +345,22 @@ public final class XmlModuleDescriptorUp
                                 + substitute(settings, attributes.getValue(i)) + "\"");
                     }
                 }
-            } else if ("ivy-module/publications".equals(getContext())) {
+            } else if ("ivy-module/publications".equals(path)) {
                 startPublications(attributes);
             } else {
+                if (options.isMerge() && path.startsWith("ivy-module/info")) {
+                    ModuleDescriptor merged = options.getMergedDescriptor();
+                    if (path.equals("ivy-module/info/description")) {
+                        //if the descriptor already contains a description, don't bother printing
+                        //the merged version.
+                        hasDescription = true;
+                    } else if (!infoElements.contains(qName)) {
+                        //according to the XSD, we should write description after all of the other
+                        //standard <info> elements but before any extended elements.
+                        writeInheritedDescription(merged);
+                    }
+                }
+
                 // copy
                 write("<" + qName);
                 for (int i = 0; i < attributes.getLength(); i++) {
@@ -312,6 +372,47 @@ public final class XmlModuleDescriptorUp
             // indent.append("\t");
         }
 
+        private void startExtends(Attributes attributes) {
+            // in merge mode, comment out extends element
+            if (options.isMerge()) {
+                write("<!-- ");
+            }
+            write("<extends");
+
+            String org = substitute(settings, attributes.getValue("organisation"));
+            String module = substitute(settings, attributes.getValue("module"));
+            ModuleId parentId = new ModuleId(org, module);
+
+            for (int i = 0; i < attributes.getLength(); i++) {
+                String name = attributes.getQName(i);
+                String value = null;
+
+                if ("revision".equals(name)) {
+                    //replace inline revision with resolved parent revision
+                    ModuleDescriptor merged = options.getMergedDescriptor();
+                    if (merged != null) {
+                        ExtendsDescriptor[] parents = merged.getInheritedDescriptors();
+                        for (int j = 0; value == null && j < parents.length; ++j) {
+                            ModuleRevisionId resolvedId = parents[j].getResolvedParentRevisionId();
+                            if (parentId.equals(resolvedId.getModuleId())) {
+                                value = resolvedId.getRevision();
+                            }
+                        }
+                    }
+                    if (value == null) {
+                        value = substitute(settings, attributes.getValue(i));
+                    }
+                } else if ("organisation".equals(name)) {
+                    value = org;
+                } else if ("module".equals(name)) {
+                    value = module;
+                } else {
+                    value = substitute(settings, attributes.getValue(i));
+                }
+                write(" " + name + "=\"" + value + "\"");
+            }
+        }
+
         private void startElementInConfigurationsConf(String qName, Attributes attributes) {
             buffers.push(new ExtendedBuffer(getContext()));
             String confName = substitute(settings, attributes.getValue("name"));
@@ -556,18 +657,66 @@ public final class XmlModuleDescriptorUp
                 throw new SAXException(e);
             }
         }
-
+        
         private void infoStarted(Attributes attributes) {
-            organisation = substitute(settings, attributes.getValue("organisation"));
+
             String module = substitute(settings, attributes.getValue("module"));
-            String rev = revision;
-            if (rev == null) {
+            String rev = null;
+            String branch = null;
+            String status = null;
+            String namespace = null;
+            Map/*<String,String>*/ extraAttributes = null;
+
+            if (options.isMerge()) {
+                //get attributes from merged descriptor, ignoring raw XML
+                ModuleDescriptor merged = options.getMergedDescriptor();
+                ModuleRevisionId mergedMrid = merged.getModuleRevisionId();
+                organisation = mergedMrid.getOrganisation();
+                branch = mergedMrid.getBranch();
+                rev = mergedMrid.getRevision();
+                status = merged.getStatus();
+
+                //TODO: should namespace be added to ModuleDescriptor interface, so we don't
+                // have to do this kind of check?
+                if (merged instanceof DefaultModuleDescriptor) {
+                    Namespace ns = ((DefaultModuleDescriptor)merged).getNamespace();
+                    if (ns != null) {
+                        namespace = ns.getName();
+                    }
+                }
+                if (namespace == null) {
+                    namespace = attributes.getValue("namespace");
+                }
+
+                extraAttributes = merged.getQualifiedExtraAttributes();
+            } else {
+                //get attributes from raw XML, performing property substitution
+                organisation = substitute(settings, attributes.getValue("organisation"));
                 rev = substitute(settings, attributes.getValue("revision"));
-            }
-            String branch = options.getBranch();
-            if (branch == null) {
                 branch = substitute(settings, attributes.getValue("branch"));
+                status = substitute(settings, attributes.getValue("status"));
+                namespace = substitute(settings, attributes.getValue("namespace"));
+                extraAttributes = new LinkedHashMap(attributes.getLength());
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    String qname = attributes.getQName(i);
+                    if (!stdAtts.contains(qname)) {
+                        extraAttributes.put(qname, substitute(settings, attributes.getValue(i)));
+                    }
+                }
+            }
+            
+            //apply override values provided in options
+            if (revision != null) {
+                rev = revision;
+            }
+            if (options.getBranch() != null) {
+                branch = options.getBranch();
             }
+            if (this.status != null) {
+                status = this.status;
+            }
+            
+            //if necessary translate mrid using optional namespace argument
             ModuleRevisionId localMid = ModuleRevisionId.newInstance(organisation, module, branch,
                 rev, ExtendableItemHelper.getExtraAttributes(settings, attributes,
                     new String[] {"organisation", "module", "revision", "status", "publication",
@@ -575,46 +724,40 @@ public final class XmlModuleDescriptorUp
             ModuleRevisionId systemMid = ns == null ? localMid : ns.getToSystemTransformer()
                     .transform(localMid);
 
-            write("<info organisation=\"" + XMLHelper.escape(systemMid.getOrganisation()) 
-                + "\" module=\"" + XMLHelper.escape(systemMid.getName()) + "\"");
+            write("<info");
+            if (organisation != null) {
+                write(" organisation=\"" + XMLHelper.escape(systemMid.getOrganisation()) + "\"");
+            }
+            write(" module=\"" + XMLHelper.escape(systemMid.getName()) + "\"");
             if (branch != null) {
                 write(" branch=\"" + XMLHelper.escape(systemMid.getBranch()) + "\"");
             }
             if (systemMid.getRevision() != null) {
                 write(" revision=\"" + XMLHelper.escape(systemMid.getRevision()) + "\"");
             }
-            if (status != null) {
-                write(" status=\"" + XMLHelper.escape(status) + "\"");
-            } else {
-                write(" status=\"" + substitute(settings, attributes.getValue("status")) + "\"");
-            }
+            write(" status=\"" + XMLHelper.escape(status) + "\"");
             if (pubdate != null) {
                 write(" publication=\"" + Ivy.DATE_FORMAT.format(pubdate) + "\"");
             } else if (attributes.getValue("publication") != null) {
                 write(" publication=\""
                         + substitute(settings, attributes.getValue("publication")) + "\"");
             }
-            Collection stdAtts = Arrays.asList(new String[] {"organisation", "module", "branch",
-                    "revision", "status", "publication", "namespace"});
-            if (attributes.getValue("namespace") != null) {
-                write(" namespace=\"" + substitute(settings, attributes.getValue("namespace"))
-                        + "\"");
+            if (namespace != null) {
+                write(" namespace=\"" + namespace + "\"");
             }
-            for (int i = 0; i < attributes.getLength(); i++) {
-                if (!stdAtts.contains(attributes.getQName(i))) {
-                    write(" " + attributes.getQName(i) + "=\""
-                            + substitute(settings, attributes.getValue(i)) + "\"");
-                }
+
+            for (Iterator extras = extraAttributes.entrySet().iterator(); extras.hasNext(); ) {
+                Map.Entry extra = (Map.Entry)extras.next();
+                write(" " + extra.getKey() + "=\"" + extra.getValue() +  "\"");
             }
         }
 
         private void write(String content) {
-            if (buffers.isEmpty()) {
-                out.print(content);
-            } else {
-                ExtendedBuffer buffer = (ExtendedBuffer) buffers.peek();
-                buffer.getBuffer().append(content);
-            }
+            getWriter().print(content);
+        }
+        
+        private PrintWriter getWriter() {
+            return buffers.isEmpty() ? out : ((ExtendedBuffer) buffers.peek()).getWriter();
         }
 
         private String getContext() {
@@ -684,15 +827,278 @@ public final class XmlModuleDescriptorUp
             return newList.toString();
         }
 
+        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+            characters(ch, start, length);
+        }
+
         public void characters(char[] ch, int start, int length) throws SAXException {
             if (justOpen != null) {
                 write(">");
                 justOpen = null;
             }
             write(String.valueOf(ch, start, length));
+            
+            //examine characters for current indent level, keeping in mind
+            //that our indent might be split across multiple calls to characters()
+            for (int i = start, end = start + length; i < end; ++i) {
+                char c = ch[i];
+                if (c == '\r' || c == '\n') {
+                    //newline resets the indent level
+                    currentIndent.setLength(0);
+                    indenting = true;
+                } else if (indenting) {
+                    //indent continues until first non-whitespace character
+                    if (Character.isWhitespace(c)) {
+                        currentIndent.append(c);
+                    } else {
+                        endIndent();
+                    }
+                }
+            }
+        }
+
+        /** record the current indent level for future elements that appear at the same depth */
+        private void endIndent() {
+            if (indenting) {
+                //record the indent at this level.  if we insert any elements at
+                //this level, we'll use the same indent.
+                setIndent(context.size() - 1, currentIndent.toString());
+                indenting = false;
+            }
+        }
+
+        /**
+         * Set the indent for the given depth.  Indents less than the provided depth
+         * will be calculated automatically, if they have not already been defined.
+         */
+        private void setIndent(int level, String indent) {
+            fillIndents(level);
+            indentLevels.set(level, indent);
+        }
+
+        /**
+         * Guarantee that indent levels have been calculated up to and including the
+         * given depth (starting at 0).
+         */
+        private void fillIndents(int level) {
+            if (indentLevels.isEmpty()) {
+                //add a default single-level indent until we see indents in the document
+                indentLevels.add("    "); 
+            }
+            String oneLevel = (String) indentLevels.get(0);
+            for (int fill = indentLevels.size(); fill <= level; ++fill) {
+                indentLevels.add(indentLevels.get(fill - 1) + oneLevel);
+            }
+        }
+
+        /** get the whitespace that should precede new elements at the current depth in the document */
+        private String getIndent() {
+            int level = context.size() - 1;
+            fillIndents(level);
+            return (String) indentLevels.get(level);
+        }
+
+        /**
+         * Write XML elements that do not appear in the source descriptor, but have been copied in
+         * from a parent module descriptor via &lt;extends&gt; declaration.
+         * @param merged child descriptor containing the merged data
+         * @param items the list of inherited items to print
+         * @param printer a printer that knows how to write the given type of item
+         * @param itemName the name of the container element, e.g. "configurations"
+         * @param includeContainer if true, include an enclosing element named
+         *   <code>itemName</code>. Otherwise just write the inherited items inline,
+         *   with a comment indicating where they came from.
+         */
+        private void writeInheritedItems(ModuleDescriptor merged, 
+                InheritableItem[] items, ItemPrinter printer, String itemName,
+                boolean includeContainer) {
+            //first categorize inherited items by their source module, so that 
+            //we can add some useful comments
+            PrintWriter out = getWriter();
+
+            Map inheritedItems = collateInheritedItems(merged, items);
+            boolean hasItems = !inheritedItems.isEmpty();
+
+            if (hasItems && includeContainer) {
+                if (currentIndent.length() == 0) {
+                    out.print(getIndent());
+                }
+                out.print("<" + itemName + ">");
+                context.push(itemName);
+                justOpen = null;
+            }
+
+            for (Iterator parents = inheritedItems.entrySet().iterator(); parents.hasNext();) {
+                Map.Entry entry = (Map.Entry) parents.next();
+                ModuleRevisionId parent = (ModuleRevisionId) entry.getKey();
+                List list = (List) entry.getValue();
+
+                if (justOpen != null) {
+                    out.println(">");
+                    justOpen = null; //helps endElement() decide how to write close tags
+                }
+                writeInheritanceComment(itemName, parent);
+                for (int c = 0; c < list.size(); ++c) {
+                    InheritableItem item = (InheritableItem) list.get(c);
+                    out.print(getIndent());
+                    printer.print(merged, item, out);
+                }
+            }
+
+            if (hasItems) {
+                if (includeContainer) {
+                    context.pop();
+                    out.println(getIndent() + "</" + itemName + ">");
+                    out.println();
+                }
+                //restore the prior indent
+                out.print(currentIndent);
+            }
+        }
+
+
+        private void writeInheritanceComment(String itemDescription, Object parentInfo) {
+            PrintWriter out = getWriter();
+            out.println();
+            out.println(getIndent() + "<!-- " + itemDescription + " inherited from "
+                + parentInfo + " -->");
+        }
+
+        /**
+         * Collect the given list of inherited descriptor items into lists keyed by parent Id.
+         * Thus all of the items inherited from parent A can be written together, then all of
+         * the items from parent B, and so on.
+         * @param merged the merged child descriptor
+         * @param items the inherited items to collate
+         * @return maps parent ModuleRevisionId to a List of InheritedItems imported from that parent
+         */
+        private Map/*<ModuleRevisionId,List>*/ collateInheritedItems(ModuleDescriptor merged, 
+                InheritableItem[] items) {
+            LinkedHashMap/*<ModuleRevisionId,List>*/ inheritedItems = new LinkedHashMap();
+            for (int i = 0; i < items.length; ++i) {
+                ModuleRevisionId source = items[i].getSourceModule();
+                //ignore items that are defined directly in the child descriptor
+                if (source != null 
+                    && !source.getModuleId().equals(merged.getModuleRevisionId().getModuleId())) {
+                    List accum = (List) inheritedItems.get(source);
+                    if (accum == null) {
+                        accum = new ArrayList();
+                        inheritedItems.put(source, accum);
+                    }
+                    accum.add(items[i]);
+                }
+            }
+            return inheritedItems;
+        }
+
+        /**
+         * If no info/description element has yet been written, write the description inherited from
+         * the parent descriptor, if any.  Calling this method more than once has no affect.
+         */
+        private void writeInheritedDescription(ModuleDescriptor merged) {
+            if (!hasDescription) {
+                hasDescription = true;
+                String description = merged.getDescription();
+                if (description != null) {
+                    PrintWriter writer = getWriter();
+                    if (justOpen != null) {
+                        writer.println(">");
+                    }
+                    writeInheritanceComment("description", "parent");
+                    writer.println(getIndent() + "<description>" + XMLHelper.escape(description) + "</description>");
+                    //restore the indent that existed before we wrote the extra elements
+                    writer.print(currentIndent);
+                    justOpen = null;
+                }
+            }
+        }
+
+        private void writeInheritedConfigurations(ModuleDescriptor merged) {
+            if (!mergedConfigurations) {
+                mergedConfigurations = true;
+                writeInheritedItems(merged, merged.getConfigurations(),
+                    ConfigurationPrinter.INSTANCE, "configurations", false);
+            }
+        }
+
+        private void writeInheritedDependencies(ModuleDescriptor merged) {
+            if (!mergedDependencies) {
+                mergedDependencies = true;
+                writeInheritedItems(merged, merged.getDependencies(),
+                    DependencyPrinter.INSTANCE, "dependencies", false);
+            }
+        }
+
+        /**
+         * <p>If publishing in merge mode, guarantee that any merged elements appearing
+         * before <code>moduleElement</code> have been written.  This method should
+         * be called <i>before</i> we write the start tag of <code>moduleElement</code>.
+         * This covers cases where merged elements like "configurations" and "dependencies" appear
+         * in the parent descriptor, but are completely missing in the child descriptor.</p>
+         *
+         * <p>For example, if "moduleElement" is "dependencies", guarantees that "configurations"
+         * has been written.  If <code>moduleElement</code> is <code>null</code>, then all
+         * missing merged elements will be flushed.</p>
+         *
+         * @param moduleElement a descriptor element name, for example "configurations" or "info"
+         */
+        private void flushMergedElementsBefore(String moduleElement) {
+            if (options.isMerge() && context.size() == 1 && "ivy-module".equals(context.peek())
+                && !(mergedConfigurations && mergedDependencies)) {
+
+                //calculate the position of the element in ivy-module
+                int position = moduleElement == null ? moduleElements.size()
+                        : moduleElements.indexOf(moduleElement);
+
+                ModuleDescriptor merged = options.getMergedDescriptor();
+
+                //see if we should write <configurations>
+                if (!mergedConfigurations && position > CONFIGURATIONS_POSITION
+                        && merged.getConfigurations().length > 0) {
+
+                    mergedConfigurations = true;
+                    writeInheritedItems(merged, merged.getConfigurations(),
+                            ConfigurationPrinter.INSTANCE, "configurations", true);
+
+                }
+                //see if we should write <dependencies>
+                if (!mergedDependencies && position > DEPENDENCIES_POSITION
+                    && merged.getDependencies().length > 0) {
+
+                    mergedDependencies = true;
+                    writeInheritedItems(merged, merged.getDependencies(),
+                            DependencyPrinter.INSTANCE, "dependencies", true);
+
+                }
+            }
+        }
+        private void flushAllMergedElements() {
+            flushMergedElementsBefore(null);
         }
 
         public void endElement(String uri, String localName, String qName) throws SAXException {
+            
+            String path = getContext();
+            if (options.isMerge()) {
+                ModuleDescriptor merged = options.getMergedDescriptor();
+
+                if ("ivy-module/info".equals(path)) {
+                    //guarantee that inherited description has been written before
+                    //info element closes.
+                    writeInheritedDescription(merged);
+                } else if ("ivy-module/configurations".equals(path)) {
+                    //write inherited configurations after all child configurations
+                    writeInheritedConfigurations(merged);
+                } else if ("ivy-module/dependencies".equals(path)) {
+                    //write inherited dependencies after all child dependencies
+                    writeInheritedDependencies(merged);
+                } else if ("ivy-module".equals(path)) {
+                    //write any remaining inherited data before we close the
+                    //descriptor.
+                    flushAllMergedElements();
+                }
+            }
+            
             if (qName.equals(justOpen)) {
                 write("/>");
             } else {
@@ -701,21 +1107,26 @@ public final class XmlModuleDescriptorUp
 
             if (!buffers.isEmpty()) {
                 ExtendedBuffer buffer = (ExtendedBuffer) buffers.peek();
-                if (buffer.getContext().equals(getContext())) {
+                if (buffer.getContext().equals(path)) {
                     buffers.pop();
                     if (buffer.isPrint()) {
-                        write(buffer.getBuffer().toString());
+                        write(buffer.toString());
                     }
                 }
             }
 
             if (!confAttributeBuffers.isEmpty()) {
                 ExtendedBuffer buffer = (ExtendedBuffer) confAttributeBuffers.peek();
-                if (buffer.getContext().equals(getContext())) {
+                if (buffer.getContext().equals(path)) {
                     confAttributeBuffers.pop();
                 }
             }
 
+            //<extends> element is commented out when in merge mode.
+            if (options.isMerge() && "ivy-module/info/extends".equals(path)) {
+                write(" -->");
+            }
+
             justOpen = null;
             context.pop();
         }
@@ -834,7 +1245,9 @@ public final class XmlModuleDescriptorUp
 
         private boolean defaultPrint = false;
 
-        private StringBuffer buffer = new StringBuffer();
+        private StringWriter buffer = new StringWriter();
+        
+        private PrintWriter writer = new PrintWriter(buffer);
 
         ExtendedBuffer(String context) {
             this.context = context;
@@ -855,12 +1268,48 @@ public final class XmlModuleDescriptorUp
             this.defaultPrint = print;
         }
 
-        StringBuffer getBuffer() {
-            return buffer;
+        PrintWriter getWriter() {
+            return writer;
         }
 
         String getContext() {
             return context;
         }
+        
+        public String toString() {
+            writer.flush();
+            return buffer.toString();
+        }
+    }
+    
+    /**
+     * Prints a descriptor item's XML representation
+     */
+    protected static interface ItemPrinter {
+        /**
+         * Print an XML representation of <code>item</code> to <code>out</code>.
+         * @param parent the module descriptor containing <code>item</code>
+         * @param item subcomponent of the descriptor, for example a {@link DependencyDescriptor} 
+         *   or {@link Configuration}
+         */
+        public void print(ModuleDescriptor parent, Object item, PrintWriter out);
+    }
+    
+    protected static class DependencyPrinter implements ItemPrinter {
+        
+        public static final DependencyPrinter INSTANCE = new DependencyPrinter();
+        
+        public void print(ModuleDescriptor parent, Object item, PrintWriter out) {
+            XmlModuleDescriptorWriter.printDependency(parent, (DependencyDescriptor) item, out);
+        }
+    }
+    
+    protected static class ConfigurationPrinter implements ItemPrinter {
+        
+        public static final ConfigurationPrinter INSTANCE = new ConfigurationPrinter();
+        
+        public void print(ModuleDescriptor parent, Object item, PrintWriter out) {
+            XmlModuleDescriptorWriter.printConfiguration((Configuration) item, out);
+        }
     }
 }

Modified: ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorWriter.java
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorWriter.java?rev=942195&r1=942194&r2=942195&view=diff
==============================================================================
--- ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorWriter.java (original)
+++ ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorWriter.java Fri May  7 20:00:48 2010
@@ -29,19 +29,11 @@ import java.util.Map.Entry;
 
 import org.apache.ivy.Ivy;
 import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.Configuration;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptorMediator;
-import org.apache.ivy.core.module.descriptor.ExcludeRule;
-import org.apache.ivy.core.module.descriptor.IncludeRule;
-import org.apache.ivy.core.module.descriptor.License;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.OverrideDependencyDescriptorMediator;
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.matcher.MapMatcher;
 import org.apache.ivy.util.Message;
+import org.apache.ivy.util.StringUtils;
 import org.apache.ivy.util.XMLHelper;
 import org.apache.ivy.util.extendable.ExtendableItem;
 
@@ -93,82 +85,89 @@ public final class XmlModuleDescriptorWr
         if (dds.length > 0) {
             out.println("\t<dependencies>");
             for (int i = 0; i < dds.length; i++) {
-                out.print("\t\t<dependency");
-                out.print(" org=\"" 
-                    + XMLHelper.escape(dds[i].getDependencyRevisionId().getOrganisation()) + "\"");
-                out.print(" name=\"" 
-                    + XMLHelper.escape(dds[i].getDependencyRevisionId().getName()) + "\"");
-                if (dds[i].getDependencyRevisionId().getBranch() != null) {
-                    out.print(" branch=\"" 
-                        + XMLHelper.escape(dds[i].getDependencyRevisionId().getBranch()) + "\"");
-                }
-                out.print(" rev=\"" 
-                    + XMLHelper.escape(dds[i].getDependencyRevisionId().getRevision()) + "\"");
-                if (!dds[i].getDynamicConstraintDependencyRevisionId()
-                        .equals(dds[i].getDependencyRevisionId())) {
-                    if (dds[i].getDynamicConstraintDependencyRevisionId().getBranch() != null) {
-                        out.print(" branchConstraint=\"" + XMLHelper.escape(
-                             dds[i].getDynamicConstraintDependencyRevisionId().getBranch()) + "\"");
-                    }
-                    out.print(" revConstraint=\"" + XMLHelper.escape(
-                        dds[i].getDynamicConstraintDependencyRevisionId().getRevision()) + "\"");
-                }
-                if (dds[i].isForce()) {
-                    out.print(" force=\"" + dds[i].isForce() + "\"");
-                }
-                if (dds[i].isChanging()) {
-                    out.print(" changing=\"" + dds[i].isChanging() + "\"");
-                }
-                if (!dds[i].isTransitive()) {
-                    out.print(" transitive=\"" + dds[i].isTransitive() + "\"");
-                }
-                out.print(" conf=\"");
-                String[] modConfs = dds[i].getModuleConfigurations();
-                for (int j = 0; j < modConfs.length; j++) {
-                    String[] depConfs = dds[i].getDependencyConfigurations(modConfs[j]);
-                    out.print(XMLHelper.escape(modConfs[j]) + "->");
-                    for (int k = 0; k < depConfs.length; k++) {
-                        out.print(XMLHelper.escape(depConfs[k]));
-                        if (k + 1 < depConfs.length) {
-                            out.print(",");
-                        }
-                    }
-                    if (j + 1 < modConfs.length) {
-                        out.print(";");
-                    }
-                }
-                out.print("\"");
-                
-                printExtraAttributes(dds[i], out, " ");
-                
-                DependencyArtifactDescriptor[] depArtifacts = dds[i].getAllDependencyArtifacts();
-                if (depArtifacts.length > 0) {
-                    out.println(">");
-                }
-                printDependencyArtefacts(md, out, depArtifacts);
-                
-                IncludeRule[] includes = dds[i].getAllIncludeRules();
-                if (includes.length > 0 && depArtifacts.length == 0) {
-                        out.println(">");
-                    }
-                printDependencyIncludeRules(md, out, includes);
-                
-                ExcludeRule[] excludes = dds[i].getAllExcludeRules();
-                if (excludes.length > 0 && includes.length == 0 && depArtifacts.length == 0) {
-                     out.println(">");
-                }
-                printDependencyExcludeRules(md, out, excludes);
-                if (includes.length + excludes.length + depArtifacts.length == 0) {
-                    out.println("/>");
-                } else {
-                    out.println("\t\t</dependency>");
-                }
+                DependencyDescriptor dep = dds[i];
+                out.print("\t\t");
+                printDependency(md, dep, out);
             }
             printAllExcludes(md, out);
             printAllMediators(md, out);
             out.println("\t</dependencies>");
         }
     }
+    
+    protected static void printDependency(ModuleDescriptor md, DependencyDescriptor dep, 
+            PrintWriter out) {
+        out.print("<dependency");
+        out.print(" org=\"" 
+            + XMLHelper.escape(dep.getDependencyRevisionId().getOrganisation()) + "\"");
+        out.print(" name=\"" 
+            + XMLHelper.escape(dep.getDependencyRevisionId().getName()) + "\"");
+        if (dep.getDependencyRevisionId().getBranch() != null) {
+            out.print(" branch=\"" 
+                + XMLHelper.escape(dep.getDependencyRevisionId().getBranch()) + "\"");
+        }
+        out.print(" rev=\"" 
+            + XMLHelper.escape(dep.getDependencyRevisionId().getRevision()) + "\"");
+        if (!dep.getDynamicConstraintDependencyRevisionId()
+                .equals(dep.getDependencyRevisionId())) {
+            if (dep.getDynamicConstraintDependencyRevisionId().getBranch() != null) {
+                out.print(" branchConstraint=\"" + XMLHelper.escape(
+                     dep.getDynamicConstraintDependencyRevisionId().getBranch()) + "\"");
+            }
+            out.print(" revConstraint=\"" + XMLHelper.escape(
+                dep.getDynamicConstraintDependencyRevisionId().getRevision()) + "\"");
+        }
+        if (dep.isForce()) {
+            out.print(" force=\"" + dep.isForce() + "\"");
+        }
+        if (dep.isChanging()) {
+            out.print(" changing=\"" + dep.isChanging() + "\"");
+        }
+        if (!dep.isTransitive()) {
+            out.print(" transitive=\"" + dep.isTransitive() + "\"");
+        }
+        out.print(" conf=\"");
+        String[] modConfs = dep.getModuleConfigurations();
+        for (int j = 0; j < modConfs.length; j++) {
+            String[] depConfs = dep.getDependencyConfigurations(modConfs[j]);
+            out.print(XMLHelper.escape(modConfs[j]) + "->");
+            for (int k = 0; k < depConfs.length; k++) {
+                out.print(XMLHelper.escape(depConfs[k]));
+                if (k + 1 < depConfs.length) {
+                    out.print(",");
+                }
+            }
+            if (j + 1 < modConfs.length) {
+                out.print(";");
+            }
+        }
+        out.print("\"");
+        
+        printExtraAttributes(dep, out, " ");
+        
+        DependencyArtifactDescriptor[] depArtifacts = dep.getAllDependencyArtifacts();
+        if (depArtifacts.length > 0) {
+            out.println(">");
+        }
+        printDependencyArtefacts(md, out, depArtifacts);
+        
+        IncludeRule[] includes = dep.getAllIncludeRules();
+        if (includes.length > 0 && depArtifacts.length == 0) {
+                out.println(">");
+            }
+        printDependencyIncludeRules(md, out, includes);
+        
+        ExcludeRule[] excludes = dep.getAllExcludeRules();
+        if (excludes.length > 0 && includes.length == 0 && depArtifacts.length == 0) {
+             out.println(">");
+        }
+        printDependencyExcludeRules(md, out, excludes);
+        if (includes.length + excludes.length + depArtifacts.length == 0) {
+            out.println("/>");
+        } else {
+            out.println("\t\t</dependency>");
+        }
+    }
 
     private static void printAllMediators(ModuleDescriptor md, PrintWriter out) {
         Map/*<MapMatcher, DependencyDescriptorMediator>*/ mediators 
@@ -381,34 +380,40 @@ public final class XmlModuleDescriptorWr
         if (confs.length > 0) {
             out.println("\t<configurations>");
             for (int i = 0; i < confs.length; i++) {
-                out.print("\t\t<conf");
-                out.print(" name=\"" + XMLHelper.escape(confs[i].getName()) + "\"");
-                out.print(" visibility=\"" 
-                    + XMLHelper.escape(confs[i].getVisibility().toString()) + "\"");
-                if (confs[i].getDescription() != null) {
-                    out.print(" description=\"" 
-                        + XMLHelper.escape(confs[i].getDescription()) + "\"");
-                }
-                String[] exts = confs[i].getExtends();
-                if (exts.length > 0) {
-                    out.print(" extends=\"");
-                    for (int j = 0; j < exts.length; j++) {
-                        out.print(XMLHelper.escape(exts[j]));
-                        if (j + 1 < exts.length) {
-                            out.print(",");
-                        }
-                    }
-                    out.print("\"");
-                }
-                if (confs[i].getDeprecated() != null) {
-                    out.print(" deprecated=\"" + XMLHelper.escape(confs[i].getDeprecated()) + "\"");
-                }
-                printExtraAttributes(confs[i], out, " ");
-                out.println("/>");
+                Configuration conf = confs[i];
+                out.print("\t\t");
+                printConfiguration(conf, out);
             }
             out.println("\t</configurations>");
         }
     }
+    
+    protected static void printConfiguration(Configuration conf, PrintWriter out) {
+        out.print("<conf");
+        out.print(" name=\"" + XMLHelper.escape(conf.getName()) + "\"");
+        out.print(" visibility=\"" 
+            + XMLHelper.escape(conf.getVisibility().toString()) + "\"");
+        if (conf.getDescription() != null) {
+            out.print(" description=\"" 
+                + XMLHelper.escape(conf.getDescription()) + "\"");
+        }
+        String[] exts = conf.getExtends();
+        if (exts.length > 0) {
+            out.print(" extends=\"");
+            for (int j = 0; j < exts.length; j++) {
+                out.print(XMLHelper.escape(exts[j]));
+                if (j + 1 < exts.length) {
+                    out.print(",");
+                }
+            }
+            out.print("\"");
+        }
+        if (conf.getDeprecated() != null) {
+            out.print(" deprecated=\"" + XMLHelper.escape(conf.getDeprecated()) + "\"");
+        }
+        printExtraAttributes(conf, out, " ");
+        out.println("/>");
+    }
 
     private static void printInfoTag(ModuleDescriptor md, PrintWriter out) {
         out.println("\t<info organisation=\"" 
@@ -442,6 +447,21 @@ public final class XmlModuleDescriptorWr
         }
         if (requireInnerInfoElement(md)) {
             out.println("\t>");
+            ExtendsDescriptor[] parents = md.getInheritedDescriptors();
+            for (int i = 0; i < parents.length; i++) {
+                ExtendsDescriptor parent = parents[i];
+                ModuleRevisionId mrid = parent.getParentRevisionId();
+                out.print("\t\t<extends organisation=\"" + XMLHelper.escape(mrid.getOrganisation()) + "\""
+                        + " module=\"" + XMLHelper.escape(mrid.getName()) + "\""
+                        + " revision=\"" + XMLHelper.escape(mrid.getRevision()) + "\"");
+
+                String location = parent.getLocation();
+                if (location != null) {
+                    out.print(" location=\"" + XMLHelper.escape(location) + "\"");
+                }
+                out.print(" extendTypes=\"" + StringUtils.join(parent.getExtendsTypes(), ",") + "\"");
+                out.println("/>");
+            }
             License[] licenses = md.getLicenses();
             for (int i = 0; i < licenses.length; i++) {
                 License license = licenses[i];
@@ -492,7 +512,8 @@ public final class XmlModuleDescriptorWr
         return md.getExtraInfo().size() > 0 
                 || md.getHomePage() != null 
                 || (md.getDescription() != null && md.getDescription().trim().length() > 0) 
-                || md.getLicenses().length > 0;
+                || md.getLicenses().length > 0
+                || md.getInheritedDescriptors().length > 0;
     }
 
     private static String getConfs(ModuleDescriptor md, Artifact artifact) {

Modified: ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd?rev=942195&r1=942194&r2=942195&view=diff
==============================================================================
--- ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd (original)
+++ ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/parser/xml/ivy.xsd Fri May  7 20:00:48 2010
@@ -60,6 +60,15 @@
   		    <xs:element name="info">
             	<xs:complexType>
 			        <xs:sequence>
+			      		<xs:element name="extends" minOccurs="0" maxOccurs="unbounded">
+			      		<xs:complexType>
+			      			<xs:attribute name="organisation" type="xs:string" use="required"/>
+			      			<xs:attribute name="module" type="xs:string" use="required"/>
+			      			<xs:attribute name="revision" type="xs:string" use="required"/>
+			      			<xs:attribute name="location" type="xs:string" />
+			      			<xs:attribute name="extendType" type="xs:string" />
+			              	</xs:complexType>
+			      		</xs:element>
 			      		<xs:element name="license" minOccurs="0" maxOccurs="unbounded">
 			            	<xs:complexType>
 					            <xs:attribute name="name" type="xs:string" use="required"/>
@@ -91,7 +100,7 @@
 			      		</xs:element>
 			      		<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
 			        </xs:sequence>
-		            <xs:attribute name="organisation" type="xs:string" use="required"/>
+		            <xs:attribute name="organisation" type="xs:string"/>
 		            <xs:attribute name="module" type="xs:string" use="required"/>
 		            <xs:attribute name="branch" type="xs:string"/>
 		            <xs:attribute name="revision" type="xs:string"/>

Modified: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyDeliverTest.java
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyDeliverTest.java?rev=942195&r1=942194&r2=942195&view=diff
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyDeliverTest.java (original)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyDeliverTest.java Fri May  7 20:00:48 2010
@@ -20,6 +20,9 @@ package org.apache.ivy.ant;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.ParseException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -87,6 +90,64 @@ public class IvyDeliverTest extends Test
         del.execute();
     }
 
+    public void testMergeParent() throws IOException, ParseException {
+        //publish the parent descriptor first, so that it can be found while
+        //we are reading the child descriptor.
+        project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-multiconf.xml");
+        IvyResolve res = new IvyResolve();
+        res.setProject(project);
+        res.execute();
+
+        IvyPublish pubParent = new IvyPublish();
+        pubParent.setProject(project);
+        pubParent.setResolver("1");
+        pubParent.setPubrevision("1.0");
+        File art = new File("build/test/deliver/resolve-simple-1.0.jar");
+        FileUtil.copy(new File("test/repositories/1/org1/mod1.1/jars/mod1.1-1.0.jar"), art, null);
+        pubParent.execute();
+        
+        //resolve and deliver the child descriptor
+        project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml");
+        res = new IvyResolve();
+        res.setProject(project);
+        res.execute();
+        
+        deliver.setPubrevision("1.2");
+        deliver.setDeliverpattern("build/test/deliver/merge/ivy-[revision].xml");
+        deliver.execute();
+
+        // should have delivered the file to the specified destination
+        File delivered = new File("build/test/deliver/merge/ivy-1.2.xml");
+        assertTrue(delivered.exists());
+
+        // do a text compare, since we want to test comments as well as structure.
+        // we could do a better job of this with xmlunit
+        int lineNo = 1;
+
+        BufferedReader merged = new BufferedReader(new FileReader(delivered));
+        BufferedReader expected = new BufferedReader(new InputStreamReader(getClass()
+            .getResourceAsStream("ivy-extends-merged.xml")));
+        try {
+            for (String mergeLine = merged.readLine(),
+                        expectedLine = expected.readLine(); 
+                 mergeLine != null && expectedLine != null; 
+                 mergeLine = merged.readLine(),
+                 expectedLine = expected.readLine()) {
+    
+                mergeLine = mergeLine.trim();
+                expectedLine = expectedLine.trim();
+    
+                if (!mergeLine.startsWith("<info"))
+                    assertEquals("published descriptor matches at line[" + lineNo + "]", expectedLine.trim(), mergeLine.trim());
+    
+                ++lineNo;
+            }
+        } finally {
+            merged.close();
+            expected.close();
+        }
+    }
+    
     public void testSimple() throws Exception {
         project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-latest.xml");
         IvyResolve res = new IvyResolve();

Modified: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyPublishTest.java
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyPublishTest.java?rev=942195&r1=942194&r2=942195&view=diff
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyPublishTest.java (original)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyPublishTest.java Fri May  7 20:00:48 2010
@@ -20,7 +20,9 @@ package org.apache.ivy.ant;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
+import java.io.IOException;
 import java.io.InputStreamReader;
+import java.text.ParseException;
 
 import junit.framework.TestCase;
 
@@ -85,6 +87,124 @@ public class IvyPublishTest extends Test
         del.execute();
     }
 
+    public void testMergeParent() throws IOException, ParseException {
+        //publish the parent descriptor first, so that it can be found while
+        //we are reading the child descriptor.
+        project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-multiconf.xml");
+        IvyResolve res = new IvyResolve();
+        res.setProject(project);
+        res.execute();
+
+        IvyPublish pubParent = new IvyPublish();
+        pubParent.setProject(project);
+        pubParent.setResolver("1");
+        pubParent.setPubrevision("1.0");
+        File art = new File("build/test/publish/resolve-simple-1.0.jar");
+        FileUtil.copy(new File("test/repositories/1/org1/mod1.1/jars/mod1.1-1.0.jar"), art, null);
+        pubParent.execute();
+        
+        //update=true implies merge=true
+        project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml");
+        publish.setResolver("1");
+        publish.setUpdate(true);
+        publish.setOrganisation("apache");
+        publish.setModule("resolve-extends");
+        publish.setRevision("1.0");
+        publish.setPubrevision("1.2");
+        publish.setStatus("release");
+        publish.addArtifactspattern("test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml");
+        publish.execute();
+
+        // should have published the files with "1" resolver
+        File published = new File("test/repositories/1/apache/resolve-extends/ivys/ivy-1.2.xml");
+        assertTrue(published.exists());
+
+        // do a text compare, since we want to test comments as well as structure.
+        // we could do a better job of this with xmlunit
+
+        int lineNo = 1;
+
+        BufferedReader merged = new BufferedReader(new FileReader(published));
+        BufferedReader expected = new BufferedReader(new InputStreamReader(getClass()
+            .getResourceAsStream("ivy-extends-merged.xml")));
+        for (String mergeLine = merged.readLine(),
+                    expectedLine = expected.readLine(); 
+            mergeLine != null && expectedLine != null; 
+            mergeLine = merged.readLine(),
+            expectedLine = expected.readLine()) {
+
+            //strip timestamps for the comparison
+            if (mergeLine.indexOf("<info") >= 0) {
+                mergeLine = mergeLine.replaceFirst("\\s?publication=\"\\d+\"", "");
+            }
+            //discard whitespace-only lines
+            if (!(mergeLine.trim().equals("") && expectedLine.trim().equals(""))) {
+                assertEquals("published descriptor matches at line[" + lineNo + "]", expectedLine, mergeLine);
+            }
+
+            ++lineNo;
+        }
+    }
+    
+    public void testMinimalMerge() throws IOException, ParseException {
+        //publish the parent descriptor first, so that it can be found while
+        //we are reading the child descriptor.
+        project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-multiconf.xml");
+        IvyResolve res = new IvyResolve();
+        res.setProject(project);
+        res.execute();
+
+        IvyPublish pubParent = new IvyPublish();
+        pubParent.setProject(project);
+        pubParent.setResolver("1");
+        pubParent.setPubrevision("1.0");
+        File art = new File("build/test/publish/resolve-simple-1.0.jar");
+        FileUtil.copy(new File("test/repositories/1/org1/mod1.1/jars/mod1.1-1.0.jar"), art, null);
+        pubParent.execute();
+
+        //update=true implies merge=true
+        project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-extends-minimal.xml");
+        publish.setResolver("1");
+        publish.setUpdate(true);
+        publish.setOrganisation("apache");
+        publish.setModule("resolve-extends");
+        publish.setRevision("1.0");
+        publish.setPubrevision("1.2");
+        publish.setStatus("release");
+        publish.addArtifactspattern("test/java/org/apache/ivy/ant/ivy-extends-minimal.xml");
+        publish.execute();
+
+        // should have published the files with "1" resolver
+        File published = new File("test/repositories/1/apache/resolve-minimal/ivys/ivy-1.2.xml");
+        assertTrue(published.exists());
+
+        // do a text compare, since we want to test comments as well as structure.
+        // we could do a better job of this with xmlunit
+
+        int lineNo = 1;
+
+        BufferedReader merged = new BufferedReader(new FileReader(published));
+        BufferedReader expected = new BufferedReader(new InputStreamReader(getClass()
+            .getResourceAsStream("ivy-extends-minimal-merged.xml")));
+        for (String mergeLine = merged.readLine(),
+                    expectedLine = expected.readLine();
+            mergeLine != null && expectedLine != null;
+            mergeLine = merged.readLine(),
+            expectedLine = expected.readLine()) {
+
+            //strip timestamps for the comparison
+            if (mergeLine.indexOf("<info") >= 0) {
+                mergeLine = mergeLine.replaceFirst("\\s?publication=\"\\d+\"", "");
+            }
+            //discard whitespace-only lines
+            if (!(mergeLine.trim().equals("") && expectedLine.trim().equals(""))) {
+                assertEquals("published descriptor matches at line[" + lineNo + "]", expectedLine, mergeLine);
+            }
+
+            ++lineNo;
+        }
+    }
+
     public void testSimple() throws Exception {
         project.setProperty("ivy.dep.file", "test/java/org/apache/ivy/ant/ivy-multiconf.xml");
         IvyResolve res = new IvyResolve();

Added: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-merged.xml
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-merged.xml?rev=942195&view=auto
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-merged.xml (added)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-merged.xml Fri May  7 20:00:48 2010
@@ -0,0 +1,51 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="2.0"> 
+
+	<info organisation="apache" module="resolve-extends" revision="1.2" status="release">
+		<!-- <extends organisation="apache" module="resolve-simple" revision="1.0" location="./ivy-multiconf.xml"/> -->
+        
+		<!-- description inherited from parent -->
+		<description>Demonstrates configuration-specific dependencies</description>
+	</info>
+
+	<configurations>
+		<!-- conf not in parent -->
+		<conf name="extra"/>
+		<!-- overrides conf in parent -->
+		<conf name="default" extends="compile" description="overrides the conf definition in parent descriptor"/>
+
+		<!-- configurations inherited from apache#resolve-simple;1.0 -->
+		<conf name="compile" visibility="public"/>
+	</configurations>
+
+	<publications/>
+  
+	<dependencies>
+		<!-- not in parent -->
+		<dependency org="org2" name="mod2.1" rev="0.3" conf="default"/>
+		<!-- overrides parent -->
+		<dependency org="org1" name="mod1.1" rev="1.1" conf="default,compile->default" transitive="false"/>
+
+		<!-- dependencies inherited from apache#resolve-simple;1.0 -->
+		<dependency org="org1" name="mod1.2" rev="2.0" conf="default->default"/>
+		<dependency org="org1" name="mod1.1" rev="2.0" transitive="false" conf="compile->default"/>
+	</dependencies>
+
+</ivy-module>
\ No newline at end of file

Propchange: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-merged.xml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal-merged.xml
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal-merged.xml?rev=942195&view=auto
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal-merged.xml (added)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal-merged.xml Fri May  7 20:00:48 2010
@@ -0,0 +1,42 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="2.0"> 
+
+	<info organisation="apache" module="resolve-minimal" revision="1.2" status="release">
+		<!-- <extends organisation="apache" module="resolve-simple" revision="1.0" location="./ivy-multiconf.xml"/> -->
+        
+		<!-- description inherited from parent -->
+		<description>Demonstrates configuration-specific dependencies</description>
+	</info>
+
+	<configurations>
+		<!-- configurations inherited from apache#resolve-simple;1.0 -->
+		<conf name="default" visibility="public"/>
+		<conf name="compile" visibility="public"/>
+	</configurations>
+
+	<publications/>
+  
+	<dependencies>
+		<!-- dependencies inherited from apache#resolve-simple;1.0 -->
+		<dependency org="org1" name="mod1.2" rev="2.0" conf="default->default"/>
+		<dependency org="org1" name="mod1.1" rev="2.0" transitive="false" conf="compile->default"/>
+	</dependencies>
+
+</ivy-module>
\ No newline at end of file

Propchange: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal-merged.xml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal.xml
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal.xml?rev=942195&view=auto
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal.xml (added)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal.xml Fri May  7 20:00:48 2010
@@ -0,0 +1,27 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="2.0"> 
+
+	<info module="resolve-minimal">
+		<extends organisation="apache" module="resolve-simple" revision="latest.integration" location="./ivy-multiconf.xml"/>
+	</info>
+
+	<publications/>
+
+</ivy-module>
\ No newline at end of file

Propchange: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-minimal.xml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml?rev=942195&view=auto
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml (added)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml Fri May  7 20:00:48 2010
@@ -0,0 +1,41 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="2.0"> 
+
+	<info module="resolve-extends">
+		<extends organisation="apache" module="resolve-simple" revision="latest.integration" location="./ivy-multiconf.xml"/>
+	</info>
+
+	<configurations>
+		<!-- conf not in parent -->
+		<conf name="extra" />
+		<!-- overrides conf in parent -->
+		<conf name="default" extends="compile" description="overrides the conf definition in parent descriptor"/>
+	</configurations>
+
+	<publications/>
+
+	<dependencies>
+		<!-- not in parent -->
+		<dependency org="org2" name="mod2.1" rev="0.3" conf="default" />
+		<!-- overrides parent -->
+		<dependency org="org1" name="mod1.1" rev="1.1" conf="default,compile->default" transitive="false" />
+	</dependencies>
+
+</ivy-module>
\ No newline at end of file

Propchange: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-extends-multiconf.xml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-multiconf.xml
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-multiconf.xml?rev=942195&r1=942194&r2=942195&view=diff
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-multiconf.xml (original)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/ivy-multiconf.xml Fri May  7 20:00:48 2010
@@ -21,7 +21,9 @@
 	       module="resolve-simple"
 	       revision="1.0"
 	       status="release"
-	/>
+	>
+        <description>Demonstrates configuration-specific dependencies</description>
+    </info>
 	<configurations>
 		<conf name="default" />
 		<conf name="compile" />

Modified: ant/ivy/core/trunk/test/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorParserTest.java
URL: http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorParserTest.java?rev=942195&r1=942194&r2=942195&view=diff
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorParserTest.java (original)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/plugins/parser/xml/XmlModuleDescriptorParserTest.java Fri May  7 20:00:48 2010
@@ -31,7 +31,6 @@ import org.apache.ivy.core.module.descri
 import org.apache.ivy.core.module.descriptor.Configuration;
 import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptorMediator;
 import org.apache.ivy.core.module.descriptor.ExcludeRule;
 import org.apache.ivy.core.module.descriptor.License;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
@@ -46,6 +45,8 @@ import org.apache.ivy.plugins.matcher.Ex
 import org.apache.ivy.plugins.matcher.GlobPatternMatcher;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.apache.ivy.plugins.parser.AbstractModuleDescriptorParserTester;
+import org.apache.ivy.plugins.resolver.FileSystemResolver;
+import org.apache.ivy.util.FileUtil;
 import org.apache.ivy.util.XMLHelper;
 
 public class XmlModuleDescriptorParserTest extends AbstractModuleDescriptorParserTester {
@@ -55,6 +56,8 @@ public class XmlModuleDescriptorParserTe
         super.setUp();
         
         this.settings = new IvySettings();
+        //prevent test from polluting local cache
+        settings.setDefaultCache(new File("build/cache"));
     }
 
     public void testSimple() throws Exception {
@@ -78,7 +81,7 @@ public class XmlModuleDescriptorParserTe
         assertNotNull(md.getDependencies());
         assertEquals(0, md.getDependencies().length);
     }
-
+    
     public void testNamespaces() throws Exception {
         ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
             getClass().getResource("test-namespaces.xml"), true);
@@ -1018,4 +1021,333 @@ public class XmlModuleDescriptorParserTe
             // expected
         }
     }
+    
+    public void testExtendsAll() throws Exception {
+        //default extends type is 'all' when no extendsType attribute is specified.
+        ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            getClass().getResource("test-extends-all.xml"), true);
+        assertNotNull(md);
+        
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation());
+        assertEquals("mymodule", md.getModuleRevisionId().getName());
+        assertEquals(Ivy.getWorkingRevision(), md.getModuleRevisionId().getRevision());
+        assertEquals("integration", md.getStatus());
+
+        //verify that the parent description was merged.
+        assertEquals("Parent module description.", md.getDescription());
+        
+        //verify that the parent and child configurations were merged together.
+        final Configuration[] expectedConfs = { new Configuration("default"), 
+                new Configuration("conf1"), new Configuration("conf2") };
+        assertNotNull(md.getConfigurations());
+        assertEquals(Arrays.asList(expectedConfs), Arrays
+                .asList(md.getConfigurations()));
+
+        //verify parent and child dependencies were merged together.
+        DependencyDescriptor[] deps = md.getDependencies();
+        assertNotNull(deps);
+        assertEquals(2, deps.length);
+
+        assertEquals(Arrays.asList(new String[]{ "default" }), 
+            Arrays.asList(deps[0].getModuleConfigurations()));
+        ModuleRevisionId dep = deps[0].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule1", dep.getModuleId().getName());
+        assertEquals("1.0", dep.getRevision());
+
+        assertEquals(Arrays.asList(new String[]{ "conf1", "conf2" }), 
+            Arrays.asList(deps[1].getModuleConfigurations()));
+        dep = deps[1].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule2", dep.getModuleId().getName());
+        assertEquals("2.0", dep.getRevision());
+        
+        //verify only child publications are present
+        Artifact[] artifacts = md.getAllArtifacts();
+        assertNotNull(artifacts);
+        assertEquals(1, artifacts.length);
+        assertEquals("mymodule", artifacts[0].getName());
+        assertEquals("jar", artifacts[0].getType());
+    }
+    
+    public void testExtendsDependencies() throws Exception {
+        //descriptor specifies that only parent dependencies should be included
+        ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            getClass().getResource("test-extends-dependencies.xml"), true);
+        assertNotNull(md);
+        
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation());
+        assertEquals("mymodule", md.getModuleRevisionId().getName());
+        assertEquals(Ivy.getWorkingRevision(), md.getModuleRevisionId().getRevision());
+        assertEquals("integration", md.getStatus());
+
+        //verify that the parent description was ignored.
+        assertEquals("", md.getDescription());
+        
+        //verify that the parent configurations were ignored.
+        final Configuration[] expectedConfs = { new Configuration("default") };
+        assertNotNull(md.getConfigurations());
+        assertEquals(Arrays.asList(expectedConfs), Arrays
+                .asList(md.getConfigurations()));
+
+        //verify parent dependencies were merged.
+        DependencyDescriptor[] deps = md.getDependencies();
+        assertNotNull(deps);
+        assertEquals(2, deps.length);
+
+        assertEquals(Arrays.asList(new String[]{ "default" }), 
+            Arrays.asList(deps[0].getModuleConfigurations()));
+        ModuleRevisionId dep = deps[0].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule1", dep.getModuleId().getName());
+        assertEquals("1.0", dep.getRevision());
+        
+        assertEquals(Arrays.asList(new String[]{ "default" }), 
+            Arrays.asList(deps[1].getModuleConfigurations()));
+        dep = deps[1].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule2", dep.getModuleId().getName());
+        assertEquals("2.0", dep.getRevision());
+        
+        //verify only child publications are present
+        Artifact[] artifacts = md.getAllArtifacts();
+        assertNotNull(artifacts);
+        assertEquals(1, artifacts.length);
+        assertEquals("mymodule", artifacts[0].getName());
+        assertEquals("jar", artifacts[0].getType());
+    }
+    
+    public void testExtendsConfigurations() throws Exception {
+        //descriptor specifies that only parent configurations should be included
+        ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            getClass().getResource("test-extends-configurations.xml"), true);
+        assertNotNull(md);
+        
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation());
+        assertEquals("mymodule", md.getModuleRevisionId().getName());
+        assertEquals(Ivy.getWorkingRevision(), md.getModuleRevisionId().getRevision());
+        assertEquals("integration", md.getStatus());
+
+        //verify that the parent description was ignored.
+        assertEquals("", md.getDescription());
+        
+        //verify that the parent and child configurations were merged together.
+        final Configuration[] expectedConfs = { new Configuration("default"), 
+                new Configuration("conf1"), new Configuration("conf2") };
+        assertNotNull(md.getConfigurations());
+        assertEquals(Arrays.asList(expectedConfs), Arrays
+                .asList(md.getConfigurations()));
+
+        //verify parent dependencies were ignored.
+        DependencyDescriptor[] deps = md.getDependencies();
+        assertNotNull(deps);
+        assertEquals(1, deps.length);
+
+        assertEquals(Arrays.asList(new String[]{ "conf1", "conf2" }), 
+            Arrays.asList(deps[0].getModuleConfigurations()));
+        ModuleRevisionId dep = deps[0].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule2", dep.getModuleId().getName());
+        assertEquals("2.0", dep.getRevision());
+        
+        //verify only child publications are present
+        Artifact[] artifacts = md.getAllArtifacts();
+        assertNotNull(artifacts);
+        assertEquals(1, artifacts.length);
+        assertEquals("mymodule", artifacts[0].getName());
+        assertEquals("jar", artifacts[0].getType());
+    }
+    
+    public void testExtendsDescription() throws Exception {
+        //descriptor specifies that only parent description should be included
+        ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            getClass().getResource("test-extends-description.xml"), true);
+        assertNotNull(md);
+        
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation());
+        assertEquals("mymodule", md.getModuleRevisionId().getName());
+        assertEquals(Ivy.getWorkingRevision(), md.getModuleRevisionId().getRevision());
+        assertEquals("integration", md.getStatus());
+
+        //verify that the parent description was merged.
+        assertEquals("Parent module description.", md.getDescription());
+        
+        //verify that the parent configurations were ignored.
+        final Configuration[] expectedConfs = { new Configuration("default") };
+        assertNotNull(md.getConfigurations());
+        assertEquals(Arrays.asList(expectedConfs), Arrays
+                .asList(md.getConfigurations()));
+
+        //verify parent dependencies were ignored.
+        DependencyDescriptor[] deps = md.getDependencies();
+        assertNotNull(deps);
+        assertEquals(1, deps.length);
+
+        assertEquals(Arrays.asList(new String[]{ "default" }), 
+            Arrays.asList(deps[0].getModuleConfigurations()));
+        ModuleRevisionId dep = deps[0].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule2", dep.getModuleId().getName());
+        assertEquals("2.0", dep.getRevision());
+        
+        //verify only child publications are present
+        Artifact[] artifacts = md.getAllArtifacts();
+        assertNotNull(artifacts);
+        assertEquals(1, artifacts.length);
+        assertEquals("mymodule", artifacts[0].getName());
+        assertEquals("jar", artifacts[0].getType());
+    }
+
+    public void testExtendsDescriptionWithOverride() throws Exception {
+        //descriptor specifies that only parent description should be included
+        ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            getClass().getResource("test-extends-description-override.xml"), true);
+        assertNotNull(md);
+        
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation());
+        assertEquals("mymodule", md.getModuleRevisionId().getName());
+        assertEquals(Ivy.getWorkingRevision(), md.getModuleRevisionId().getRevision());
+        assertEquals("integration", md.getStatus());
+
+        //child description should always be preferred, even if extendType="description"
+        assertEquals("Child description overrides parent.", md.getDescription());
+        
+        //verify that the parent configurations were ignored.
+        final Configuration[] expectedConfs = { new Configuration("default") };
+        assertNotNull(md.getConfigurations());
+        assertEquals(Arrays.asList(expectedConfs), Arrays
+                .asList(md.getConfigurations()));
+
+        //verify parent dependencies were ignored.
+        DependencyDescriptor[] deps = md.getDependencies();
+        assertNotNull(deps);
+        assertEquals(1, deps.length);
+
+        assertEquals(Arrays.asList(new String[]{ "default" }), 
+            Arrays.asList(deps[0].getModuleConfigurations()));
+        ModuleRevisionId dep = deps[0].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule2", dep.getModuleId().getName());
+        assertEquals("2.0", dep.getRevision());
+        
+        //verify only child publications are present
+        Artifact[] artifacts = md.getAllArtifacts();
+        assertNotNull(artifacts);
+        assertEquals(1, artifacts.length);
+        assertEquals("mymodule", artifacts[0].getName());
+        assertEquals("jar", artifacts[0].getType());
+    }
+    
+    public void testExtendsMixed() throws Exception {
+        //descriptor specifies that parent configurations and dependencies should be included
+        ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            getClass().getResource("test-extends-mixed.xml"), true);
+        assertNotNull(md);
+        
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation());
+        assertEquals("mymodule", md.getModuleRevisionId().getName());
+        assertEquals(Ivy.getWorkingRevision(), md.getModuleRevisionId().getRevision());
+        assertEquals("integration", md.getStatus());
+
+        //verify that the parent description was ignored.
+        assertEquals("", md.getDescription());
+        
+        //verify that the parent and child configurations were merged together.
+        final Configuration[] expectedConfs = { new Configuration("default"), 
+                new Configuration("conf1"), new Configuration("conf2") };
+        assertNotNull(md.getConfigurations());
+        assertEquals(Arrays.asList(expectedConfs), Arrays
+                .asList(md.getConfigurations()));
+
+        //verify parent and child dependencies were merged together.
+        DependencyDescriptor[] deps = md.getDependencies();
+        assertNotNull(deps);
+        assertEquals(2, deps.length);
+
+        assertEquals(Arrays.asList(new String[]{ "default" }), 
+            Arrays.asList(deps[0].getModuleConfigurations()));
+        ModuleRevisionId dep = deps[0].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule1", dep.getModuleId().getName());
+        assertEquals("1.0", dep.getRevision());
+
+        assertEquals(Arrays.asList(new String[]{ "conf1", "conf2" }), 
+            Arrays.asList(deps[1].getModuleConfigurations()));
+        dep = deps[1].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule2", dep.getModuleId().getName());
+        assertEquals("2.0", dep.getRevision());
+        
+        //verify only child publications are present
+        Artifact[] artifacts = md.getAllArtifacts();
+        assertNotNull(artifacts);
+        assertEquals(1, artifacts.length);
+        assertEquals("mymodule", artifacts[0].getName());
+        assertEquals("jar", artifacts[0].getType());
+    }
+    
+    public void testExtendsCached() throws Exception {
+        //configure a resolver to serve the parent descriptor, so that parse succeeds.
+        File resolveRoot = new File("build/tmp/xmlModuleDescriptorTest");
+        assertTrue(resolveRoot.exists() || resolveRoot.mkdirs());
+        
+        FileUtil.copy(getClass().getResource("test-extends-parent.xml"), 
+            new File(resolveRoot, "myorg/myparent/ivy.xml"), null);
+        
+        FileSystemResolver resolver = new FileSystemResolver();
+        resolver.setSettings(settings);
+        resolver.setName("testExtendsCached");
+        resolver.addIvyPattern(resolveRoot.getAbsolutePath() 
+            + "/[organisation]/[module]/[artifact].[ext]");
+
+        settings.addResolver(resolver);
+        settings.setDefaultResolver("testExtendsCached");
+        
+        //descriptor extends a module without a location="..." attribute, so resolver lookup
+        //must be performed.
+        ModuleDescriptor md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings,
+            getClass().getResource("test-extends-cached.xml"), true);
+        assertNotNull(md);
+        
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation());
+        assertEquals("mymodule", md.getModuleRevisionId().getName());
+        assertEquals(Ivy.getWorkingRevision(), md.getModuleRevisionId().getRevision());
+        assertEquals("integration", md.getStatus());
+
+        //verify that the parent description was merged.
+        assertEquals("Parent module description.", md.getDescription());
+        
+        //verify that the parent and child configurations were merged together.
+        final Configuration[] expectedConfs = { new Configuration("default"), 
+                new Configuration("conf1"), new Configuration("conf2") };
+        assertNotNull(md.getConfigurations());
+        assertEquals(Arrays.asList(expectedConfs), Arrays
+                .asList(md.getConfigurations()));
+
+        //verify parent and child dependencies were merged together.
+        DependencyDescriptor[] deps = md.getDependencies();
+        assertNotNull(deps);
+        assertEquals(2, deps.length);
+
+        assertEquals(Arrays.asList(new String[]{ "default" }), 
+            Arrays.asList(deps[0].getModuleConfigurations()));
+        ModuleRevisionId dep = deps[0].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule1", dep.getModuleId().getName());
+        assertEquals("1.0", dep.getRevision());
+
+        assertEquals(Arrays.asList(new String[]{ "conf1", "conf2" }), 
+            Arrays.asList(deps[1].getModuleConfigurations()));
+        dep = deps[1].getDependencyRevisionId();
+        assertEquals("myorg", dep.getModuleId().getOrganisation());
+        assertEquals("mymodule2", dep.getModuleId().getName());
+        assertEquals("2.0", dep.getRevision());
+        
+        //verify only child publications are present
+        Artifact[] artifacts = md.getAllArtifacts();
+        assertNotNull(artifacts);
+        assertEquals(1, artifacts.length);
+        assertEquals("mymodule", artifacts[0].getName());
+        assertEquals("jar", artifacts[0].getType());
+    }
 }