Index: main/trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java	(revision 24811)
+++ main/trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java	(revision 24824)
@@ -44,444 +44,530 @@
 import org.w3c.dom.*;
 
-/** This class provides access to an xml-type view of the collect.cfg file. This is useful as it allows the manipulation and free form editing of a collect.cfg file while still allowing the various CDM data managers to base themselves directly on this model (whereas they used to be independant ListModels which clobbered the ordering of unparsed commands).
+/**
+ * This class provides access to an xml-type view of the collect.cfg file. This
+ * is useful as it allows the manipulation and free form editing of a
+ * collect.cfg file while still allowing the various CDM data managers to base
+ * themselves directly on this model (whereas they used to be independant
+ * ListModels which clobbered the ordering of unparsed commands).
+ * 
  * @author John Thompson, Greenstone Digital Library, University of Waikato
  */
-public class CollectionConfiguration {
-  static final public String ENCODING = "UTF-8";
-  static final public String NEWLINE_ELEMENT = "NewLine";
-    
-  static private Document document;
-  static private String saved_config_file_string = null;
-    
-  // may be collec.cfg (GS2) or collectionConfig.xml (GS3)
-  private File collect_config_file;
-  private String collect_config_filename;
-  
-  // This method is initilised in CollectionDesignManager.java constructor
-  public CollectionConfiguration (File collect_config_file) {
-    this.collect_config_file = collect_config_file;
-    this.collect_config_filename = collect_config_file.getName();
-    // parse the XML template
-    document = XMLTools.parseXMLFile ("xml/CollectionConfig.xml", true);
-    String filename = collect_config_filename.toLowerCase ();
-        
-    if (filename.endsWith (".cfg")) {
-      saved_config_file_string = CollectCfgReadWrite.parse (collect_config_file, document);
-    }
-    else if (filename.endsWith (".xml")) {
-      CollectionConfigXMLReadWrite.parse (collect_config_file, document);
-    }
-        
-    //XMLTools.printXMLNode(document.getDocumentElement());
-  }
-
-  static public Element createElement (String element_name) {
-    return document.createElement (element_name);
-  }
-    
-  /** Gives the preferred ordering of commands */
-  static final public String[] COMMAND_ORDER =
-  {StaticStrings.COLLECTIONMETADATA_CREATOR_ELEMENT, StaticStrings.COLLECTIONMETADATA_MAINTAINER_ELEMENT, StaticStrings.COLLECTIONMETADATA_PUBLIC_ELEMENT, StaticStrings.BUILDTYPE_ELEMENT, StaticStrings.PLUGIN_ELEMENT, StaticStrings.INDEXES_ELEMENT, StaticStrings.INDEX_DEFAULT_ELEMENT, StaticStrings.INDEXOPTIONS_ELEMENT, StaticStrings.INDEXOPTION_DEFAULT_ELEMENT, StaticStrings.LANGUAGES_ELEMENT, StaticStrings.LANGUAGE_DEFAULT_ELEMENT, StaticStrings.LANGUAGE_METADATA_ELEMENT, StaticStrings.SUBCOLLECTION_ELEMENT, StaticStrings.SUBCOLLECTION_INDEXES_ELEMENT, StaticStrings.SUBCOLLECTION_DEFAULT_INDEX_ELEMENT, StaticStrings.SUPERCOLLECTION_ELEMENT, StaticStrings.CLASSIFY_ELEMENT, StaticStrings.FORMAT_ELEMENT, StaticStrings.COLLECTIONMETADATA_ELEMENT};
-     
-  /** Find the best insertion position for the given DOM Element. This should try to match command tag, and if found should then try to group by name or type (eg CollectionMeta), or append to end is no such grouping exists (eg Plugins). Failing a command match it will check against the command order for the best insertion location.
-   * @param target_element the command Element to be inserted
-   * @return the Element which the given command should be inserted before, or null to append to end of list
-   */
-  static public Node findInsertionPoint (Element target_element) {
-    ///ystem.err.println("Find insertion point: " + target_element.getNodeName());
-    String target_element_name = target_element.getNodeName ();
-    Element document_element = document.getDocumentElement ();
-    // Try to find commands with the same tag.
-    NodeList matching_elements = document_element.getElementsByTagName (target_element_name);
-    // If we found matching elements, then we have our most likely insertion location, so check within for groupings
-    if(matching_elements.getLength () != 0) {
-      ///ystem.err.println("Found matching elements.");
-      // Only CollectionMeta are grouped.
-      if(target_element_name.equals (StaticStrings.COLLECTIONMETADATA_ELEMENT)) {
-	///ystem.err.println("Dealing with collection metadata");
-	// Special case: CollectionMeta can be added at either the start or end of a collection configuration file. However the start position is reserved for special metadata, so if no non-special metadata can be found we must append to the end.
-	// So if the command to be added is special add it immediately after any other special command
-	if(target_element.getAttribute (StaticStrings.SPECIAL_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) {
-	  int index = 0;
-	  Element matched_element = (Element) matching_elements.item (index);
-	  Element sibling_element = (Element) matched_element.getNextSibling ();
-	  while(sibling_element.getAttribute (StaticStrings.SPECIAL_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) {
-	    index++;
-	    matched_element = (Element) matching_elements.item (index);
-	    sibling_element = (Element) matched_element.getNextSibling ();
-	  }
-	  if(sibling_element.getNodeName ().equals (NEWLINE_ELEMENT)) {
-	    Element newline_element = document.createElement (NEWLINE_ELEMENT);
-	    document_element.insertBefore (newline_element, sibling_element);
-	  }
-	  return sibling_element;
-	}
-	// Otherwise try to find a matching 'name' and add after the last one in that group.
-	else {
-	  int index = 0;
-	  target_element_name = target_element.getAttribute (StaticStrings.NAME_ATTRIBUTE);
-	  boolean found = false;
-	  // Skip all of the special metadata
-	  Element matched_element = (Element) matching_elements.item (index);
-	  while(matched_element.getAttribute (StaticStrings.SPECIAL_ATTRIBUTE).equals (StaticStrings.TRUE_STR)) {
-	    index++;
-	    matched_element = (Element) matching_elements.item (index);
-	  }
-	  // Begin search
-	  while(!found && matched_element != null) {
-	    if(matched_element.getAttribute (StaticStrings.NAME_ATTRIBUTE).equals (target_element_name)) {
-	      found = true;
-	    }
-	    else {
-	      index++;
-	      matched_element = (Element) matching_elements.item (index);
-	    }
-	  }
-	  // If we found a match, we need to continue checking until we find the last name match.
-	  if(found) {
-	    index++;
-	    Element previous_sibling = matched_element;
-	    Element sibling_element = (Element) matching_elements.item (index);
-	    while(sibling_element != null && sibling_element.getAttribute (StaticStrings.NAME_ATTRIBUTE).equals (target_element_name)) {
-	      previous_sibling = sibling_element;
-	      index++;
-	      sibling_element = (Element) matching_elements.item (index);
-	    }
-	    // Previous sibling now holds the command immediately before where we want to add, so find its next sibling and add to that. In this one case we can ignore new lines!
-	    return previous_sibling.getNextSibling ();
-	  }
-	  // If not found we just add after last metadata element
-	  else {
-	    Element last_element = (Element) matching_elements.item (matching_elements.getLength () - 1);
-	    return last_element.getNextSibling ();
-	  }
-	}
-                
-      }
-      else {
-	///ystem.err.println("Not dealing with collection meta.");
-	Element matched_element = (Element) matching_elements.item (matching_elements.getLength () - 1);
-	// One final quick test. If the matched element is immediately followed by a NewLine command, then we insert another NewLine after the matched command, then return the NewLine instead (thus the about to be inserted command will be placed between the two NewLines)
-	Node sibling_element = matched_element.getNextSibling ();
-	if(sibling_element != null && sibling_element.getNodeName ().equals (NEWLINE_ELEMENT)) {
-	  Element newline_element = document.createElement (NEWLINE_ELEMENT);
-	  document_element.insertBefore (newline_element, sibling_element);
-	}
-	return sibling_element; // Note that this may be null
-      }
-    }
-    ///ystem.err.println("No matching elements found.");
-    // Locate where this command is in the ordering
-    int command_index = -1;
-    for(int i = 0; command_index == -1 && i < COMMAND_ORDER.length; i++) {
-      if(COMMAND_ORDER[i].equals (target_element_name)) {
-	command_index = i;
-      }
-    }
-    ///ystem.err.println("Command index is: " + command_index);
-    // Now move forward, checking for existing elements in each of the preceeding command orders.
-    int preceeding_index = command_index - 1;
-    ///ystem.err.println("Searching before the target command.");
-    while(preceeding_index >= 0) {
-      matching_elements = document_element.getElementsByTagName (COMMAND_ORDER[preceeding_index]);
-      // If we've found a match
-      if(matching_elements.getLength () > 0) {
-	// We add after the last element
-	Element matched_element = (Element) matching_elements.item (matching_elements.getLength () - 1);
-	// One final quick test. If the matched element is immediately followed by a NewLine command, then we insert another NewLine after the matched command, then return the NewLine instead (thus the about to be inserted command will be placed between the two NewLines)
-	Node sibling_element = matched_element.getNextSibling ();
-	if(sibling_element != null && sibling_element.getNodeName ().equals (NEWLINE_ELEMENT)) {
-	  Element newline_element = document.createElement (NEWLINE_ELEMENT);
-	  document_element.insertBefore (newline_element, sibling_element);
-	}
-	return sibling_element; // Note that this may be null
-      }
-      preceeding_index--;
-    }
-    // If all that fails, we now move backwards through the commands
-    int susceeding_index = command_index + 1;
-    ///ystem.err.println("Searching after the target command.");
-    while(susceeding_index < COMMAND_ORDER.length) {
-      matching_elements = document_element.getElementsByTagName (COMMAND_ORDER[susceeding_index]);
-      // If we've found a match
-      if(matching_elements.getLength () > 0) {
-	// We add before the first element
-	Element matched_element = (Element) matching_elements.item (0);
-	// One final quick test. If the matched element is immediately preceeded by a NewLine command, then we insert another NewLine before the matched command, then return this new NewLine instead (thus the about to be inserted command will be placed between the two NewLines)
-	Node sibling_element = matched_element.getPreviousSibling ();
-	if(sibling_element != null && sibling_element.getNodeName ().equals (NEWLINE_ELEMENT)) {
-	  Element newline_element = document.createElement (NEWLINE_ELEMENT);
-	  document_element.insertBefore (newline_element, sibling_element);
-	}
-	return sibling_element; // Note that this may be null
-      }
-      susceeding_index++;
-    }
-    // Well. Apparently there are no other commands in this collection configuration. So append away...
-    return null;
-  }
-    
-    
-  static public NodeList getElementsByTagName (String element_name) {
-    return document.getDocumentElement ().getElementsByTagName (element_name);
-  }
-       
-  public Element getDocumentElement () {
-    return document.getDocumentElement ();
-  }
-    
-  /** This debug facility shows the currently loaded collect.cfg or CollectConfig.xml file as a DOM tree. */
-  public void display () {
-    JDialog dialog = new JDialog (Gatherer.g_man, "Collection Configuration", false);
-    dialog.setSize (400,400);
-    JPanel content_pane = (JPanel) dialog.getContentPane ();
-    final DOMTree tree = new DOMTree (document);
-    JButton refresh_button = new GLIButton ("Refresh Tree");
-    refresh_button.addActionListener (new ActionListener () {
-	public void actionPerformed (ActionEvent event) {
-	  tree.setDocument (document);
-	}
-      });
-    content_pane.setBorder (BorderFactory.createEmptyBorder (5,5,5,5));
-    content_pane.setLayout (new BorderLayout ());
-    content_pane.add (new JScrollPane (tree), BorderLayout.CENTER);
-    content_pane.add (refresh_button, BorderLayout.SOUTH);
-    dialog.setVisible (true);
-  }
-    
-        
-    
-  public File getFile () {
-    return collect_config_file;
-  }
-    
-  public Element getCreator () {
-    Element element = getOrCreateElementByTagName (StaticStrings.COLLECTIONMETADATA_CREATOR_ELEMENT, null, null);
-    element.setAttribute (StaticStrings.NAME_ATTRIBUTE, StaticStrings.COLLECTIONMETADATA_CREATOR_STR);
-    element.setAttribute (StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
-    return element;
-  }
-
-  public Element getMaintainer () {
-    Element element = getOrCreateElementByTagName (StaticStrings.COLLECTIONMETADATA_MAINTAINER_ELEMENT, null, null);
-    element.setAttribute (StaticStrings.NAME_ATTRIBUTE, StaticStrings.COLLECTIONMETADATA_MAINTAINER_STR);
-    element.setAttribute (StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
-    return element;
-  }
-
-  /** Retrieve or create the languages Element. */
-  public Element getLanguages () {
-    return getOrCreateElementByTagName (StaticStrings.LANGUAGES_ELEMENT, null, null);
-  }
-    
-  public Element getLanguageMetadata () {
-    return getOrCreateElementByTagName (StaticStrings.LANGUAGE_METADATA_ELEMENT, null, null);
-  }
-    
-  public Element getLevels () {
-    return getOrCreateElementByTagName (StaticStrings.INDEXOPTIONS_ELEMENT, StaticStrings.NAME_ATTRIBUTE, StaticStrings.LEVELS_STR);
-  }
-    
-  public Element getLevelDefault () {
-    return getOrCreateElementByTagName (StaticStrings.INDEXOPTION_DEFAULT_ELEMENT, StaticStrings.NAME_ATTRIBUTE, StaticStrings.LEVEL_DEFAULT_STR);
-  }
-    
-  public Element getIndexOptions () {
-    return getOrCreateElementByTagName (StaticStrings.INDEXOPTIONS_ELEMENT, StaticStrings.NAME_ATTRIBUTE, StaticStrings.INDEXOPTIONS_STR);
-  }
-    
-       
-  /** Retrieve or create the indexes Element. Note that this method behaves differently from the other getBlah methods, in that it also has to keep in mind that indexes come in two flavours, MG and MGPP. */
-  public Element getMGIndexes () {
-    return getOrCreateElementByTagName (StaticStrings.INDEXES_ELEMENT, StaticStrings.MGPP_ATTRIBUTE, StaticStrings.FALSE_STR);
-  }
-    
-  public Element getMGPPIndexes () {
-    return getOrCreateElementByTagName (StaticStrings.INDEXES_ELEMENT, StaticStrings.MGPP_ATTRIBUTE, StaticStrings.TRUE_STR);
-  }
-    
-  public Element getPublic () {
-    Element element = getOrCreateElementByTagName (StaticStrings.COLLECTIONMETADATA_PUBLIC_ELEMENT, null, null);
-    element.setAttribute (StaticStrings.NAME_ATTRIBUTE, StaticStrings.COLLECTIONMETADATA_PUBLIC_STR);
-    element.setAttribute (StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
-    return element;
-  }
-    
-  public Element getBuildType () {
-    Element element = getOrCreateElementByTagName (StaticStrings.BUILDTYPE_ELEMENT, null, null);
-    element.setAttribute (StaticStrings.NAME_ATTRIBUTE, StaticStrings.BUILDTYPE_STR);
-    element.setAttribute (StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
-    return element;
-        
-  }  
-
-public Element getDatabaseType () {
-    Element element = getOrCreateElementByTagName (StaticStrings.DATABASETYPE_ELEMENT, null, null);
-    element.setAttribute (StaticStrings.NAME_ATTRIBUTE, StaticStrings.DATABASETYPE_STR);
-    element.setAttribute (StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
-    return element;
-        
-  }
-    
-  /** Retrieve or create the subindexes Element. */
-  public Element getSubIndexes () {
-    return getOrCreateElementByTagName (StaticStrings.SUBCOLLECTION_INDEXES_ELEMENT, null, null);
-  }
-    
-  /** Retrieve or create the supercollections Element. */
-  public Element getSuperCollection () {
-    return getOrCreateElementByTagName (StaticStrings.SUPERCOLLECTION_ELEMENT, null, null);
-  }
-    
-  public boolean ready () {
-    return document != null;
-  }
-    
-    
-    
-    
-  /** ************************** Private Methods ***************************/
-    
-  
-  /** Retrieve or create the indexes Element. */
-  static private Element getOrCreateElementByTagName (String name, String conditional_attribute, String required_value) {
-    Element document_element = document.getDocumentElement ();
-    NodeList elements = document_element.getElementsByTagName (name);
-    int elements_length = elements.getLength ();
-    if(elements_length > 0) {
-      if(conditional_attribute == null) {
-	document_element = null;
-	return (Element) elements.item (0);
-      }
-      else {
-	for(int i = 0; i < elements_length; i++) {
-	  Element element = (Element) elements.item (i);
-	  if(element.getAttribute (conditional_attribute).equals (required_value)) {
-	    document_element = null;
-	    return element;
-	  }
-	  element = null;
-	}
-      }
-    }
-    // Create the element
-    Element element = document.createElement (name);
-    // If there was a property set it
-    if(conditional_attribute != null) {
-      element.setAttribute (conditional_attribute, required_value);
-    }
-    Node target_node = findInsertionPoint (element);
-    if(target_node != null) {
-      document_element.insertBefore (element, target_node);
-    }
-    else {
-      document_element.appendChild (element);
-    }
-    document_element = null;
-    return element;
-  }
-    
-
-
- 
-
-  /** Write the text to the buffer. This is used so we don't have to worry about storing intermediate String values just so we can calaulate length and offset.
-   * @param writer the BufferedWriter to which the str will be written
-   * @param str the String to be written
-   */
-  private void write (BufferedWriter writer, String str)
-    throws IOException {
-    writer.write (str, 0, str.length ());
-  }
-    
-
-  public void saveIfNecessary () {
-    
-    // Generate a string version of internal document
-    String config_file_string = null;
-    if (Gatherer.GS3) {
-      config_file_string = CollectionConfigXMLReadWrite.generateStringVersion(document);
-    } else {
-      config_file_string = CollectCfgReadWrite.generateStringVersion(document);
-    }
-    // compare to saved version
-    if (saved_config_file_string != null) {
-      if (saved_config_file_string.equals(config_file_string)) {
-	DebugStream.println (collect_config_filename +" file hasn't changed so no save necessary...");
-	return;
-      }
-    }
-    
-    // We need to save...
-    DebugStream.println (collect_config_filename +" file has changed, saving now...");
-
-       
-    // If we're using the Local Library we must release the collection before writing to the collect.cfg file
-    String collection_name = CollectionManager.getLoadedCollectionName (true); // url style slash
-    boolean collection_released = false;
-    if (Gatherer.c_man.built () && LocalLibraryServer.isRunning () == true) {
-      // Release the collection
-      LocalLibraryServer.releaseCollection (collection_name);
-      collection_released = true;
-    }
-        
-    // Make a backup of the existing config file
-    if (collect_config_file.exists ()) {
-      String config_filename;
-      String backup_filename;
-      if (Gatherer.GS3) {
-	config_filename = Utility.COLLECTION_CONFIG_XML;
-	backup_filename = Utility.COLLECTION_CONFIG_BAK;
-      } else {
-	config_filename = StaticStrings.COLLECT_CFG;
-	backup_filename = Utility.COLLECT_BAK;
-      }	
-      File original_file = new File (collect_config_file.getParentFile (), config_filename);
-      File backup_file = new File (collect_config_file.getParentFile (), backup_filename);
-      if (backup_file.exists ()) {
-	backup_file.delete ();
-      }
-      if (!original_file.renameTo (backup_file)) {
-	System.err.println ("Warning: can't rename "+config_filename + " to "+ backup_filename);
-      }
-    }
-
-    // now save the file
-    if (Gatherer.GS3) {
-      CollectionConfigXMLReadWrite.save(collect_config_file, document);
-    } else {
-      // we have already converted to string, so save here
-      try {
-	OutputStream ostream = new FileOutputStream (collect_config_file);
-	Writer file_writer = new OutputStreamWriter (ostream, ENCODING);
-	BufferedWriter buffered_writer = new BufferedWriter (file_writer);
-	buffered_writer.write (config_file_string);
-	buffered_writer.close ();
-      }
-      catch (Exception exception) {
-	DebugStream.println ("Error in CollectionConfiguration.save(): " + exception);
-	DebugStream.printStackTrace (exception);
-      }
-      
-    }
-
-    // save the string version
-    saved_config_file_string = config_file_string;
-    
-    // If we're using a remote Greenstone server, upload the new collect.cfg file
-    if (Gatherer.isGsdlRemote) {
-      Gatherer.remoteGreenstoneServer.uploadCollectionFile (collection_name, collect_config_file);
-    }
-  
-        
-    // Now re-add the collection to the Local Library server
-    if (collection_released) {
-      LocalLibraryServer.addCollection (collection_name);
-    }
-  }
-
- 
-    
+public class CollectionConfiguration
+{
+	static final public String ENCODING = "UTF-8";
+	static final public String NEWLINE_ELEMENT = "NewLine";
+
+	static private Document document;
+	static private String saved_config_file_string = null;
+
+	// may be collec.cfg (GS2) or collectionConfig.xml (GS3)
+	private File collect_config_file;
+	private String collect_config_filename;
+
+	// This method is initilised in CollectionDesignManager.java constructor
+	public CollectionConfiguration(File collect_config_file)
+	{
+		this.collect_config_file = collect_config_file;
+		this.collect_config_filename = collect_config_file.getName();
+		// parse the XML template
+		document = XMLTools.parseXMLFile("xml/CollectionConfig.xml", true);
+		String filename = collect_config_filename.toLowerCase();
+
+		if (filename.endsWith(".cfg"))
+		{
+			saved_config_file_string = CollectCfgReadWrite.parse(collect_config_file, document);
+		}
+		else if (filename.endsWith(".xml"))
+		{
+			CollectionConfigXMLReadWrite.parse(collect_config_file, document);
+		}
+
+		//XMLTools.printXMLNode(document.getDocumentElement());
+	}
+
+	static public Element createElement(String element_name)
+	{
+		return document.createElement(element_name);
+	}
+
+	/** Gives the preferred ordering of commands */
+	static final public String[] COMMAND_ORDER = { StaticStrings.COLLECTIONMETADATA_CREATOR_ELEMENT, StaticStrings.COLLECTIONMETADATA_MAINTAINER_ELEMENT, StaticStrings.COLLECTIONMETADATA_PUBLIC_ELEMENT, StaticStrings.BUILDTYPE_ELEMENT, StaticStrings.PLUGIN_ELEMENT, StaticStrings.INDEXES_ELEMENT, StaticStrings.INDEX_DEFAULT_ELEMENT, StaticStrings.INDEXOPTIONS_ELEMENT, StaticStrings.INDEXOPTION_DEFAULT_ELEMENT, StaticStrings.LANGUAGES_ELEMENT, StaticStrings.LANGUAGE_DEFAULT_ELEMENT, StaticStrings.LANGUAGE_METADATA_ELEMENT, StaticStrings.SUBCOLLECTION_ELEMENT, StaticStrings.SUBCOLLECTION_INDEXES_ELEMENT, StaticStrings.SUBCOLLECTION_DEFAULT_INDEX_ELEMENT, StaticStrings.SUPERCOLLECTION_ELEMENT, StaticStrings.CLASSIFY_ELEMENT, StaticStrings.FORMAT_ELEMENT, StaticStrings.COLLECTIONMETADATA_ELEMENT };
+
+	/**
+	 * Find the best insertion position for the given DOM Element. This should
+	 * try to match command tag, and if found should then try to group by name
+	 * or type (eg CollectionMeta), or append to end is no such grouping exists
+	 * (eg Plugins). Failing a command match it will check against the command
+	 * order for the best insertion location.
+	 * 
+	 * @param target_element
+	 *            the command Element to be inserted
+	 * @return the Element which the given command should be inserted before, or
+	 *         null to append to end of list
+	 */
+	static public Node findInsertionPoint(Element target_element)
+	{
+		///ystem.err.println("Find insertion point: " + target_element.getNodeName());
+		String target_element_name = target_element.getNodeName();
+		Element document_element = document.getDocumentElement();
+		// Try to find commands with the same tag.
+		NodeList matching_elements = document_element.getElementsByTagName(target_element_name);
+		// If we found matching elements, then we have our most likely insertion location, so check within for groupings
+		if (matching_elements.getLength() != 0)
+		{
+			///ystem.err.println("Found matching elements.");
+			// Only CollectionMeta are grouped.
+			if (target_element_name.equals(StaticStrings.COLLECTIONMETADATA_ELEMENT))
+			{
+				///ystem.err.println("Dealing with collection metadata");
+				// Special case: CollectionMeta can be added at either the start or end of a collection configuration file. However the start position is reserved for special metadata, so if no non-special metadata can be found we must append to the end.
+				// So if the command to be added is special add it immediately after any other special command
+				if (target_element.getAttribute(StaticStrings.SPECIAL_ATTRIBUTE).equals(StaticStrings.TRUE_STR))
+				{
+					int index = 0;
+					Element matched_element = (Element) matching_elements.item(index);
+					Element sibling_element = (Element) matched_element.getNextSibling();
+					while (sibling_element.getAttribute(StaticStrings.SPECIAL_ATTRIBUTE).equals(StaticStrings.TRUE_STR))
+					{
+						index++;
+						matched_element = (Element) matching_elements.item(index);
+						sibling_element = (Element) matched_element.getNextSibling();
+					}
+					if (sibling_element.getNodeName().equals(NEWLINE_ELEMENT))
+					{
+						Element newline_element = document.createElement(NEWLINE_ELEMENT);
+						document_element.insertBefore(newline_element, sibling_element);
+					}
+					return sibling_element;
+				}
+				// Otherwise try to find a matching 'name' and add after the last one in that group.
+				else
+				{
+					int index = 0;
+					target_element_name = target_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
+					boolean found = false;
+					// Skip all of the special metadata
+					Element matched_element = (Element) matching_elements.item(index);
+					while (matched_element.getAttribute(StaticStrings.SPECIAL_ATTRIBUTE).equals(StaticStrings.TRUE_STR))
+					{
+						index++;
+						matched_element = (Element) matching_elements.item(index);
+					}
+					// Begin search
+					while (!found && matched_element != null)
+					{
+						if (matched_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equals(target_element_name))
+						{
+							found = true;
+						}
+						else
+						{
+							index++;
+							matched_element = (Element) matching_elements.item(index);
+						}
+					}
+					// If we found a match, we need to continue checking until we find the last name match.
+					if (found)
+					{
+						index++;
+						Element previous_sibling = matched_element;
+						Element sibling_element = (Element) matching_elements.item(index);
+						while (sibling_element != null && sibling_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equals(target_element_name))
+						{
+							previous_sibling = sibling_element;
+							index++;
+							sibling_element = (Element) matching_elements.item(index);
+						}
+						// Previous sibling now holds the command immediately before where we want to add, so find its next sibling and add to that. In this one case we can ignore new lines!
+						return previous_sibling.getNextSibling();
+					}
+					// If not found we just add after last metadata element
+					else
+					{
+						Element last_element = (Element) matching_elements.item(matching_elements.getLength() - 1);
+						return last_element.getNextSibling();
+					}
+				}
+
+			}
+			else
+			{
+				///ystem.err.println("Not dealing with collection meta.");
+				Element matched_element = (Element) matching_elements.item(matching_elements.getLength() - 1);
+				// One final quick test. If the matched element is immediately followed by a NewLine command, then we insert another NewLine after the matched command, then return the NewLine instead (thus the about to be inserted command will be placed between the two NewLines)
+				Node sibling_element = matched_element.getNextSibling();
+				if (sibling_element != null && sibling_element.getNodeName().equals(NEWLINE_ELEMENT))
+				{
+					Element newline_element = document.createElement(NEWLINE_ELEMENT);
+					document_element.insertBefore(newline_element, sibling_element);
+				}
+				return sibling_element; // Note that this may be null
+			}
+		}
+		///ystem.err.println("No matching elements found.");
+		// Locate where this command is in the ordering
+		int command_index = -1;
+		for (int i = 0; command_index == -1 && i < COMMAND_ORDER.length; i++)
+		{
+			if (COMMAND_ORDER[i].equals(target_element_name))
+			{
+				command_index = i;
+			}
+		}
+		///ystem.err.println("Command index is: " + command_index);
+		// Now move forward, checking for existing elements in each of the preceeding command orders.
+		int preceeding_index = command_index - 1;
+		///ystem.err.println("Searching before the target command.");
+		while (preceeding_index >= 0)
+		{
+			matching_elements = document_element.getElementsByTagName(COMMAND_ORDER[preceeding_index]);
+			// If we've found a match
+			if (matching_elements.getLength() > 0)
+			{
+				// We add after the last element
+				Element matched_element = (Element) matching_elements.item(matching_elements.getLength() - 1);
+				// One final quick test. If the matched element is immediately followed by a NewLine command, then we insert another NewLine after the matched command, then return the NewLine instead (thus the about to be inserted command will be placed between the two NewLines)
+				Node sibling_element = matched_element.getNextSibling();
+				if (sibling_element != null && sibling_element.getNodeName().equals(NEWLINE_ELEMENT))
+				{
+					Element newline_element = document.createElement(NEWLINE_ELEMENT);
+					document_element.insertBefore(newline_element, sibling_element);
+				}
+				return sibling_element; // Note that this may be null
+			}
+			preceeding_index--;
+		}
+		// If all that fails, we now move backwards through the commands
+		int susceeding_index = command_index + 1;
+		///ystem.err.println("Searching after the target command.");
+		while (susceeding_index < COMMAND_ORDER.length)
+		{
+			matching_elements = document_element.getElementsByTagName(COMMAND_ORDER[susceeding_index]);
+			// If we've found a match
+			if (matching_elements.getLength() > 0)
+			{
+				// We add before the first element
+				Element matched_element = (Element) matching_elements.item(0);
+				// One final quick test. If the matched element is immediately preceeded by a NewLine command, then we insert another NewLine before the matched command, then return this new NewLine instead (thus the about to be inserted command will be placed between the two NewLines)
+				Node sibling_element = matched_element.getPreviousSibling();
+				if (sibling_element != null && sibling_element.getNodeName().equals(NEWLINE_ELEMENT))
+				{
+					Element newline_element = document.createElement(NEWLINE_ELEMENT);
+					document_element.insertBefore(newline_element, sibling_element);
+				}
+				return sibling_element; // Note that this may be null
+			}
+			susceeding_index++;
+		}
+		// Well. Apparently there are no other commands in this collection configuration. So append away...
+		return null;
+	}
+
+	static public NodeList getElementsByTagName(String element_name)
+	{
+		return document.getDocumentElement().getElementsByTagName(element_name);
+	}
+
+	public Element getDocumentElement()
+	{
+		return document.getDocumentElement();
+	}
+
+	/**
+	 * This debug facility shows the currently loaded collect.cfg or
+	 * CollectConfig.xml file as a DOM tree.
+	 */
+	public void display()
+	{
+		JDialog dialog = new JDialog(Gatherer.g_man, "Collection Configuration", false);
+		dialog.setSize(400, 400);
+		JPanel content_pane = (JPanel) dialog.getContentPane();
+		final DOMTree tree = new DOMTree(document);
+		JButton refresh_button = new GLIButton("Refresh Tree");
+		refresh_button.addActionListener(new ActionListener()
+		{
+			public void actionPerformed(ActionEvent event)
+			{
+				tree.setDocument(document);
+			}
+		});
+		content_pane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+		content_pane.setLayout(new BorderLayout());
+		content_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
+		content_pane.add(refresh_button, BorderLayout.SOUTH);
+		dialog.setVisible(true);
+	}
+
+	public File getFile()
+	{
+		return collect_config_file;
+	}
+
+	public Element getCreator()
+	{
+		Element element = getOrCreateElementByTagName(StaticStrings.COLLECTIONMETADATA_CREATOR_ELEMENT, null, null);
+		element.setAttribute(StaticStrings.NAME_ATTRIBUTE, StaticStrings.COLLECTIONMETADATA_CREATOR_STR);
+		element.setAttribute(StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
+		return element;
+	}
+
+	public Element getMaintainer()
+	{
+		Element element = getOrCreateElementByTagName(StaticStrings.COLLECTIONMETADATA_MAINTAINER_ELEMENT, null, null);
+		element.setAttribute(StaticStrings.NAME_ATTRIBUTE, StaticStrings.COLLECTIONMETADATA_MAINTAINER_STR);
+		element.setAttribute(StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
+		return element;
+	}
+
+	/** Retrieve or create the languages Element. */
+	public Element getLanguages()
+	{
+		return getOrCreateElementByTagName(StaticStrings.LANGUAGES_ELEMENT, null, null);
+	}
+
+	public Element getLanguageMetadata()
+	{
+		return getOrCreateElementByTagName(StaticStrings.LANGUAGE_METADATA_ELEMENT, null, null);
+	}
+
+	public Element getLevels()
+	{
+		return getOrCreateElementByTagName(StaticStrings.INDEXOPTIONS_ELEMENT, StaticStrings.NAME_ATTRIBUTE, StaticStrings.LEVELS_STR);
+	}
+
+	public Element getLevelDefault()
+	{
+		return getOrCreateElementByTagName(StaticStrings.INDEXOPTION_DEFAULT_ELEMENT, StaticStrings.NAME_ATTRIBUTE, StaticStrings.LEVEL_DEFAULT_STR);
+	}
+
+	public Element getIndexOptions()
+	{
+		return getOrCreateElementByTagName(StaticStrings.INDEXOPTIONS_ELEMENT, StaticStrings.NAME_ATTRIBUTE, StaticStrings.INDEXOPTIONS_STR);
+	}
+
+	/**
+	 * Retrieve or create the indexes Element. Note that this method behaves
+	 * differently from the other getBlah methods, in that it also has to keep
+	 * in mind that indexes come in two flavours, MG and MGPP.
+	 */
+	public Element getMGIndexes()
+	{
+		return getOrCreateElementByTagName(StaticStrings.INDEXES_ELEMENT, StaticStrings.MGPP_ATTRIBUTE, StaticStrings.FALSE_STR);
+	}
+
+	public Element getMGPPIndexes()
+	{
+		return getOrCreateElementByTagName(StaticStrings.INDEXES_ELEMENT, StaticStrings.MGPP_ATTRIBUTE, StaticStrings.TRUE_STR);
+	}
+
+	public Element getPublic()
+	{
+		Element element = getOrCreateElementByTagName(StaticStrings.COLLECTIONMETADATA_PUBLIC_ELEMENT, null, null);
+		element.setAttribute(StaticStrings.NAME_ATTRIBUTE, StaticStrings.COLLECTIONMETADATA_PUBLIC_STR);
+		element.setAttribute(StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
+		return element;
+	}
+
+	public Element getBuildType()
+	{
+		Element element = getOrCreateElementByTagName(StaticStrings.BUILDTYPE_ELEMENT, null, null);
+		element.setAttribute(StaticStrings.NAME_ATTRIBUTE, StaticStrings.BUILDTYPE_STR);
+		element.setAttribute(StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
+		return element;
+
+	}
+
+	public Element getDatabaseType()
+	{
+		Element element = getOrCreateElementByTagName(StaticStrings.DATABASETYPE_ELEMENT, null, null);
+		element.setAttribute(StaticStrings.NAME_ATTRIBUTE, StaticStrings.DATABASETYPE_STR);
+		element.setAttribute(StaticStrings.SPECIAL_ATTRIBUTE, StaticStrings.TRUE_STR);
+		return element;
+
+	}
+
+	/** Retrieve or create the subindexes Element. */
+	public Element getSubIndexes()
+	{
+		return getOrCreateElementByTagName(StaticStrings.SUBCOLLECTION_INDEXES_ELEMENT, null, null);
+	}
+
+	/** Retrieve or create the supercollections Element. */
+	public Element getSuperCollection()
+	{
+		return getOrCreateElementByTagName(StaticStrings.SUPERCOLLECTION_ELEMENT, null, null);
+	}
+
+	public boolean ready()
+	{
+		return document != null;
+	}
+
+	/** ************************** Private Methods ***************************/
+
+	/** Retrieve or create the indexes Element. */
+	static private Element getOrCreateElementByTagName(String name, String conditional_attribute, String required_value)
+	{
+		Element document_element = document.getDocumentElement();
+		NodeList elements = document_element.getElementsByTagName(name);
+		int elements_length = elements.getLength();
+		if (elements_length > 0)
+		{
+			if (conditional_attribute == null)
+			{
+				document_element = null;
+				return (Element) elements.item(0);
+			}
+			else
+			{
+				for (int i = 0; i < elements_length; i++)
+				{
+					Element element = (Element) elements.item(i);
+					if (element.getAttribute(conditional_attribute).equals(required_value))
+					{
+						document_element = null;
+						return element;
+					}
+					element = null;
+				}
+			}
+		}
+		// Create the element
+		Element element = document.createElement(name);
+		// If there was a property set it
+		if (conditional_attribute != null)
+		{
+			element.setAttribute(conditional_attribute, required_value);
+		}
+		Node target_node = findInsertionPoint(element);
+		if (target_node != null)
+		{
+			document_element.insertBefore(element, target_node);
+		}
+		else
+		{
+			document_element.appendChild(element);
+		}
+		document_element = null;
+		return element;
+	}
+
+	/**
+	 * Write the text to the buffer. This is used so we don't have to worry
+	 * about storing intermediate String values just so we can calaulate length
+	 * and offset.
+	 * 
+	 * @param writer
+	 *            the BufferedWriter to which the str will be written
+	 * @param str
+	 *            the String to be written
+	 */
+	private void write(BufferedWriter writer, String str) throws IOException
+	{
+		writer.write(str, 0, str.length());
+	}
+
+	public void saveIfNecessary()
+	{
+
+		// Generate a string version of internal document
+		String config_file_string = null;
+		if (Gatherer.GS3)
+		{
+			config_file_string = CollectionConfigXMLReadWrite.generateStringVersion(document);
+		}
+		else
+		{
+			config_file_string = CollectCfgReadWrite.generateStringVersion(document);
+		}
+		// compare to saved version
+		if (saved_config_file_string != null)
+		{
+			if (saved_config_file_string.equals(config_file_string))
+			{
+				DebugStream.println(collect_config_filename + " file hasn't changed so no save necessary...");
+				return;
+			}
+		}
+
+		// We need to save...
+		DebugStream.println(collect_config_filename + " file has changed, saving now...");
+
+		// If we're using the Local Library we must release the collection before writing to the collect.cfg file
+		String collection_name = CollectionManager.getLoadedCollectionName(true); // url style slash
+		boolean collection_released = false;
+		if (Gatherer.c_man.built() && LocalLibraryServer.isRunning() == true)
+		{
+			// Release the collection
+			LocalLibraryServer.releaseCollection(collection_name);
+			collection_released = true;
+		}
+
+		// Make a backup of the existing config file
+		if (collect_config_file.exists())
+		{
+			String config_filename;
+			String backup_filename;
+			if (Gatherer.GS3)
+			{
+				config_filename = Utility.COLLECTION_CONFIG_XML;
+				backup_filename = Utility.COLLECTION_CONFIG_BAK;
+			}
+			else
+			{
+				config_filename = StaticStrings.COLLECT_CFG;
+				backup_filename = Utility.COLLECT_BAK;
+			}
+			File original_file = new File(collect_config_file.getParentFile(), config_filename);
+			File backup_file = new File(collect_config_file.getParentFile(), backup_filename);
+			if (backup_file.exists())
+			{
+				backup_file.delete();
+			}
+			if (!original_file.renameTo(backup_file))
+			{
+				System.err.println("Warning: can't rename " + config_filename + " to " + backup_filename);
+			}
+		}
+
+		// now save the file
+		if (Gatherer.GS3)
+		{
+			CollectionConfigXMLReadWrite.save(collect_config_file, document);
+		}
+		else
+		{
+			// we have already converted to string, so save here
+			try
+			{
+				OutputStream ostream = new FileOutputStream(collect_config_file);
+				Writer file_writer = new OutputStreamWriter(ostream, ENCODING);
+				BufferedWriter buffered_writer = new BufferedWriter(file_writer);
+				buffered_writer.write(config_file_string);
+				buffered_writer.close();
+			}
+			catch (Exception exception)
+			{
+				DebugStream.println("Error in CollectionConfiguration.save(): " + exception);
+				DebugStream.printStackTrace(exception);
+			}
+
+		}
+
+		// save the string version
+		saved_config_file_string = config_file_string;
+
+		// If we're using a remote Greenstone server, upload the new collect.cfg file
+		if (Gatherer.isGsdlRemote)
+		{
+			Gatherer.remoteGreenstoneServer.uploadCollectionFile(collection_name, collect_config_file);
+		}
+
+		// Now re-add the collection to the Local Library server
+		if (collection_released)
+		{
+			LocalLibraryServer.addCollection(collection_name);
+		}
+	}
+
 }
-
-
