Index: src/main/java/com/xpn/xwiki/objects/BaseCollection.java =================================================================== --- src/main/java/com/xpn/xwiki/objects/BaseCollection.java (revision 4055) +++ src/main/java/com/xpn/xwiki/objects/BaseCollection.java (working copy) @@ -352,6 +352,30 @@ return array; } + /** + * Return an iterator that will operate on a collection of values (as would be returned + * by getProperties or getFieldList) sorted by their name (ElementInterface.getName()). + */ + public Iterator getSortedIterator() { + Iterator it = null; + try { + // Use getProperties to get the values in list form (rather than as generic collection) + List propList = Arrays.asList(getProperties()); + + // Use the element comparator to sort the properties by name (based on ElementInterface) + Collections.sort(propList, new ElementComparator()); + + // Iterate over the sorted property list + it = propList.iterator(); + } catch (ClassCastException ccex ) { + // If sorting by the comparator resulted in a ClassCastException (possible), + // iterate over the generic collection of values. + it = getFieldList().iterator(); + } + + return it; + } + public boolean equals(Object coll) { if (!super.equals(coll)) return false; Index: src/main/java/com/xpn/xwiki/objects/BaseObject.java =================================================================== --- src/main/java/com/xpn/xwiki/objects/BaseObject.java (revision 4055) +++ src/main/java/com/xpn/xwiki/objects/BaseObject.java (working copy) @@ -131,6 +131,7 @@ // Add Class if (bclass != null) { + // If the class has fields, add field information to XML Collection fields = bclass.getFieldList(); if (fields.size() > 0) { oel.add(bclass.toXML()); @@ -149,7 +150,9 @@ el.addText(getClassName()); oel.add(el); - Iterator it = getFieldList().iterator(); + // Iterate over values/properties sorted by field name so that the values are + // exported to XML in a consistent order. + Iterator it = getSortedIterator(); while (it.hasNext()) { Element pel = new DOMElement("property"); PropertyInterface bprop = (PropertyInterface) it.next(); Index: src/main/java/com/xpn/xwiki/objects/ElementComparator.java =================================================================== --- src/main/java/com/xpn/xwiki/objects/ElementComparator.java (revision 0) +++ src/main/java/com/xpn/xwiki/objects/ElementComparator.java (revision 0) @@ -0,0 +1,53 @@ +/* + * Copyright 2006-2007, XpertNet SARL, and individual contributors as indicated + * by the contributors.txt. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.objects; + +import java.util.Comparator; + +import org.apache.commons.collections.ComparatorUtils; + +/** + * Compare and sort instances of ElementInterface by name. + * + */ +public class ElementComparator implements Comparator +{ + /** + * Compares two objects (that implement ElementInterface) by name according + * to the rules for the compare method. + * + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(Object o1, Object o2) + { + // Get a null comparator that is backed by a static "natural" comparator + Comparator c = ComparatorUtils.nullLowComparator(null); + + // convert o1 and o2 into the string names when not null + Object no1 = ( o1 == null ) ? null : ((ElementInterface) o1).getName(); + Object no2 = ( o2 == null ) ? null : ((ElementInterface) o2).getName(); + + // let the null comparator handle possible null values, where null < non-null string + return c.compare(no1, no2); + } + +} Property changes on: src/main/java/com/xpn/xwiki/objects/ElementComparator.java ___________________________________________________________________ Name: svn:eol-style + native Index: src/main/java/com/xpn/xwiki/objects/classes/BaseClass.java =================================================================== --- src/main/java/com/xpn/xwiki/objects/classes/BaseClass.java (revision 4055) +++ src/main/java/com/xpn/xwiki/objects/classes/BaseClass.java (working copy) @@ -24,6 +24,7 @@ import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -44,10 +45,11 @@ import com.xpn.xwiki.objects.BaseCollection; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.BaseProperty; +import com.xpn.xwiki.objects.ElementComparator; import com.xpn.xwiki.objects.PropertyInterface; +import com.xpn.xwiki.plugin.query.OrderClause; import com.xpn.xwiki.plugin.query.XWikiCriteria; import com.xpn.xwiki.plugin.query.XWikiQuery; -import com.xpn.xwiki.plugin.query.OrderClause; import com.xpn.xwiki.validation.XWikiValidationInterface; import com.xpn.xwiki.validation.XWikiValidationStatus; @@ -219,8 +221,9 @@ el.addText((getValidationScript()==null) ? "" : getValidationScript()); cel.add(el); - - Iterator it = getFieldList().iterator(); + // Iterate over values sorted by field name so that the values are + // exported to XML in a consistent order. + Iterator it = getSortedIterator(); while (it.hasNext()) { PropertyClass bprop = (PropertyClass)it.next(); cel.add(bprop.toXML()); Index: src/main/java/com/xpn/xwiki/objects/classes/PropertyClass.java =================================================================== --- src/main/java/com/xpn/xwiki/objects/classes/PropertyClass.java (revision 4055) +++ src/main/java/com/xpn/xwiki/objects/classes/PropertyClass.java (working copy) @@ -381,7 +381,10 @@ public Element toXML() { Element pel = new DOMElement(getName()); - Iterator it = getFieldList().iterator(); + + // Iterate over values sorted by field name so that the values are + // exported to XML in a consistent order. + Iterator it = getSortedIterator(); while (it.hasNext()) { BaseProperty bprop = (BaseProperty) it.next(); pel.add(bprop.toXML()); Index: src/main/java/com/xpn/xwiki/doc/XWikiDocument.java =================================================================== --- src/main/java/com/xpn/xwiki/doc/XWikiDocument.java (revision 4055) +++ src/main/java/com/xpn/xwiki/doc/XWikiDocument.java (working copy) @@ -19,20 +19,69 @@ */ package com.xpn.xwiki.doc; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.ref.SoftReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ecs.filter.CharacterFilter; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.tools.VelocityFormatter; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.dom.DOMDocument; +import org.dom4j.dom.DOMElement; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; +import org.suigeneris.jrcs.diff.Diff; +import org.suigeneris.jrcs.diff.DifferentiationFailedException; +import org.suigeneris.jrcs.diff.Revision; +import org.suigeneris.jrcs.rcs.Version; +import org.suigeneris.jrcs.util.ToString; + import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiConstant; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; -import com.xpn.xwiki.content.parsers.LinkParser; +import com.xpn.xwiki.api.DocumentSection; +import com.xpn.xwiki.content.Link; import com.xpn.xwiki.content.parsers.DocumentParser; -import com.xpn.xwiki.content.parsers.ReplacementResultCollection; +import com.xpn.xwiki.content.parsers.LinkParser; import com.xpn.xwiki.content.parsers.RenamePageReplaceLinkHandler; -import com.xpn.xwiki.content.Link; -import com.xpn.xwiki.api.DocumentSection; +import com.xpn.xwiki.content.parsers.ReplacementResultCollection; import com.xpn.xwiki.notify.XWikiNotificationRule; import com.xpn.xwiki.objects.BaseCollection; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.BaseProperty; +import com.xpn.xwiki.objects.ElementComparator; import com.xpn.xwiki.objects.ListProperty; import com.xpn.xwiki.objects.classes.BaseClass; import com.xpn.xwiki.objects.classes.ListClass; @@ -50,50 +99,6 @@ import com.xpn.xwiki.web.ObjectAddForm; import com.xpn.xwiki.web.XWikiMessageTool; import com.xpn.xwiki.web.XWikiRequest; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.ecs.filter.CharacterFilter; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.tools.VelocityFormatter; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.Element; -import org.dom4j.dom.DOMDocument; -import org.dom4j.dom.DOMElement; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.SAXReader; -import org.dom4j.io.XMLWriter; -import org.suigeneris.jrcs.diff.Diff; -import org.suigeneris.jrcs.diff.DifferentiationFailedException; -import org.suigeneris.jrcs.diff.Revision; -import org.suigeneris.jrcs.rcs.Version; -import org.suigeneris.jrcs.util.ToString; - -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.io.StringWriter; -import java.lang.ref.SoftReference; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Vector; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; public class XWikiDocument { @@ -176,7 +181,12 @@ private String xWikiClassXML; - private Map xWikiObjects = new HashMap(); + /** + * Map holding document objects grouped by classname (className -> Vector of objects). + * The map is not synchronized, and uses a TreeMap implementation to preserve className + * ordering (consistent sorted order for output to XML, rendering in velocity, etc.) + */ + private Map xWikiObjects = new TreeMap(); private List attachmentList; @@ -927,6 +937,8 @@ public List getxWikiClasses(XWikiContext context) { List list = new ArrayList(); + + // xWikiObjects is a TreeMap, with elements sorted by className for (Iterator it = getxWikiObjects().keySet().iterator(); it.hasNext();) { String classname = (String) it.next(); BaseClass bclass = null; @@ -1659,28 +1671,6 @@ readObjectsFromForm(eform, context); } - /* - public void readFromTemplate(EditForm eform, XWikiContext context) throws XWikiException { - // Get the class from the template - String template = eform.getTemplate(); - if ((template!=null)&&(!template.equals(""))) { - if (template.indexOf('.')==-1) { - template = getSpace() + "." + template; - } - XWiki xwiki = context.getWiki(); - XWikiDocument templatedoc = xwiki.getDocument(template, context); - if (templatedoc.isNew()) { - Object[] args = { template, getFullName() }; - throw new XWikiException( XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_APP_TEMPLATE_DOES_NOT_EXIST, - "Template document {0} does not exist when adding to document {1}", null, args); - } else { - setTemplate(template); - mergexWikiObjects(templatedoc); - } - } - } - */ - public void readFromTemplate(EditForm eform, XWikiContext context) throws XWikiException { String template = eform.getTemplate(); @@ -1721,9 +1711,9 @@ if (isNew()) { // We might have received the object from the cache - // and the templace objects might have been copied already + // and the template objects might have been copied already // we need to remove them - setxWikiObjects(new HashMap()); + setxWikiObjects(new TreeMap()); } // Merge the external objects // Currently the choice is not to merge the base class and object because it is not @@ -2168,13 +2158,24 @@ // Add Class BaseClass bclass = getxWikiClass(); if (bclass.getFieldList().size() > 0) { + // If the class has fields, add class definition and field information to XML docel.add(bclass.toXML(null)); } // Add Objects + // Objects are in a vector, held in map by className: className -> vector(objects) + // To generate consistent XML, we want to work through the classes in order, + // and then the objects for each class type in order. + // xWikiObjects should be a TreeMap (and is, provided it was created in this class, + // and not set externally using setxWikiObjects), so the keys should already be sorted by className Iterator it = getxWikiObjects().values().iterator(); while (it.hasNext()) { Vector objects = (Vector) it.next(); + + // Use the ElementComparator to sort BaseObjects contained within + // the vector by name + Collections.sort(objects, new ElementComparator()); + for (int i = 0; i < objects.size(); i++) { BaseObject obj = (BaseObject) objects.get(i); if (obj != null) {