Index: /trunk/gli/src/org/greenstone/gatherer/cdm/Argument.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/Argument.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/Argument.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,50 +25,46 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
-/**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Company:      The University of Waikato
- * Written:      01/05/02
- * Revised:      16/08/02 Optimized and Commented.
- **************************************************************************************/
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.HashMap;
+
+import java.io.*;
+import java.util.*;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.collection.Collection;
+import org.greenstone.gatherer.collection.CollectionManager;
 import org.greenstone.gatherer.msm.ElementWrapper;
+import org.greenstone.gatherer.msm.MetadataSetManager;
 import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.util.StaticStrings;
 import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
+
 /** This class contains all the details about a single argument that can be passed to this plugin, including option lists if the parameters are restricted.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
  * @version 2.3
  */
-
-// ####################################################################################
-// Optimization                          Saving
-// ####################################################################################
-// Vector -> ArrayList                   + Memory, + Processor (pos. - Processor)
-// Hashtable -> HashMap                  + Processor
-// int -> byte (x7)                      + Memory (168b)
-// External method calls                 + Memory, Processor
-// ####################################################################################
-
 public class Argument
     implements Comparable, Serializable {
-    /** If this argument has multiple values, then they are stored here. This was originally a Hashtable, but since the synchronization overhead is unwarrented it was changed to a HashMap. */
-    private ArrayList values = null;
+    /** An element of the argument type enumeration. */
+    static final public byte ENUM = 0;
+    /** An element of the argument type enumeration. */
+    static final public byte FLAG = 1;
+    /** An element of the argument type enumeration. */
+    static final public byte HIERARCHY = 2;
+    /** An element of the argument type enumeration. */
+    static final public byte INTEGER = 3;
+    /** An element of the argument type enumeration. */
+    static final public byte LANGUAGE = 4;
+    /** An element of the argument type enumeration. */
+    static final public byte METADATA = 5;
+    /** An element of the argument type enumeration. */
+    static final public byte METADATUM = 6;
+    /** An element of the argument type enumeration. */
+    static final public byte STRING = 7;
     /** <i>true</i> if this argument is required for the applicable script to work properly, <i>false</i> otherwise. */
     private boolean required = false;
     /** The type of this argument. Initially an int, but bytes are cheaper. */
-    private byte type = FLAG;
-
-    private ElementWrapper element = null;
+    private byte type = STRING;
+    private Element element;
     /** If the argument is of type ENUM then this map holds all the various options. Each entry is an &lt;option value&gt; -&gt; &lt;description&gt; mapping. */
     private HashMap list = null;
@@ -86,52 +72,22 @@
     private String default_value = null;
     /** The text description of this argument parsed from the pluginfo output. */
-    private String desc = null;
+    private String description = null;
     /** The argument flag as it appears in the command. Also used as the unique identifier of an argument. */
     private String name = null;
     /** The plugin that owns this argument, for the purposes of visualising inheritance. */
     private String owner = null;
-    /** If this argument has been assigned, what is its value. Must be non-null for the argument to be printed. */
-    private String value = null;
-    /** An element of the argument type enumeration. */
-    static final public byte ENUM = 0;
-    /** An element of the argument type enumeration. */
-    static final public byte FLAG = 1;
-    /** An element of the argument type enumeration. */
-    static final public byte HIERARCHY = 2;
-    /** An element of the argument type enumeration. */
-    static final public byte INTEGER = 3;
-    /** An element of the argument type enumeration. */
-    static final public byte LANGUAGE = 4;
-    /** An element of the argument type enumeration. */
-    static final public byte METADATA = 5;
-    /** An element of the argument type enumeration. */
-    static final public byte METADATUM = 6;
-    /** An element of the argument type enumeration. */
-    static final public byte STRING = 7;
+
     /** Default Constructor. */
     public Argument() {
     }
-    /** Normal Constructor, based on data parsed from an information script.
-	  * @param name The argument flag as it appears in the command, as a <strong>String</strong>. Also used as the unique identifier of an argument.
-	  * @param desc The text description of this argument parsed from the output as a <strong>String</strong>.
-	  * @param type The type of this argument as a <i>byte</i>.
-	  * @param default_value The default value for a parameter type argument, which may include a Perl type regular expression, as a <strong>String</strong>.
-	  */
-    public Argument(String name, String desc, byte type, String default_value) {
-	this.default_value = default_value;
-	this.desc = desc;
-	this.name = name;
-	this.type = type;
-	if(type == ENUM) {
-	    this.list = new HashMap();
-	}
-	if(type == METADATUM) {
-	    values = new ArrayList();
-	}
-    }
+
+    public Argument(Element element) {
+	this.element = element;
+    }
+
     /** Method to add an element to the option list.
-	  * @param name The name value of the option as a <strong>String</strong>.
-	  * @param desc The description of this options as a <strong>String</strong>.
-	  */
+     * @param name The name value of the option as a <strong>String</strong>.
+     * @param desc The description of this options as a <strong>String</strong>.
+     */
     public void addOption(String name, String desc) {
 	if(type == ENUM && name != null) {
@@ -139,64 +95,65 @@
 		desc = "";
 	    }
-	    if(!list.containsKey(name)) {
-		list.put(name, desc);
-	    }
-	}
-    }
+	    if(list == null) {
+		list = new HashMap();
+	    }
+	    list.put(name, desc);
+	}
+    }
+
     /** Method to compare two arguments for ordering.
-	  * @param object The argument we are comparing to, as an <strong>Object</strong>.
-	  * @return An <i>int</i> specifying the argument order, using values as set out in String.
-	  * @see java.lang.String#compareTo
-	  * @see org.greenstone.gatherer.cdm.Argument
-	  */
+     * @param object The argument we are comparing to, as an <strong>Object</strong>.
+     * @return An <i>int</i> specifying the argument order, using values as set out in String.
+     * @see java.lang.String#compareTo
+     * @see org.greenstone.gatherer.cdm.Argument
+     */
     public int compareTo(Object object) {
-	Argument argument = (Argument)object;
-	return getName().compareTo(argument.getName());
-    }
-    /** Method to deep copy this argument.
-	  * @return A newly created <strong>Argument</strong> which has the same details as this one.
-	  */
+	if(object instanceof Argument) {
+	    return getName().compareTo(((Argument)object).getName());
+	}
+	else {
+	    return toString().compareTo(object.toString());
+	}
+    }
+
     public Argument copy() {
-	Argument copy = new Argument(name, desc, type, default_value);
+	Argument copy = new Argument();
+	copy.setDefaultValue(default_value);
+	copy.setDescription(description);
+	copy.setOptions(list);
+	copy.setOwner(owner);
+	copy.setName(name);
 	copy.setRequired(required);
-	if(values != null) {
-	    copy.setValues(values);
-	}
-	if(list != null) {
-	    HashMap list_deep_copy = new HashMap();
-	    Iterator it = list.keySet().iterator();
-	    while(it.hasNext()) {
-		String key = (String) it.next();
-		String desc = (String) list.get(key);
-		list_deep_copy.put(new String(key), new String(desc));
-	    }
-	    copy.setOptions(list_deep_copy);
-	}
+	copy.setType(type);
 	return copy;
     }
+
     /** Method to determine if two arguments are equal.
-	  * @param object The argument to test against, as an <strong>Object</strong>.
-	  * @return <i>true</i> if the arguments names match, <i>false</i> otherwise.
-	  */
+     * @param object The argument to test against, as an <strong>Object</strong>.
+     * @return <i>true</i> if the arguments names match, <i>false</i> otherwise.
+     */
     public boolean equals(Object object) {
 	return (compareTo(object) == 0);
     }
+
     /** Method to retrieve the value of default_value.
-	  * @return A <strong>String</strong> containing the default value.
-	  */
+     * @return A <strong>String</strong> containing the default value.
+     */
     public String getDefaultValue() {
 	return default_value;
     }
+
     /** Method to retrieve this arguments description.
-	  * @return A <strong>String</strong> containing the description.
-	  */
-    public String getDesc() {
-	return desc;
-    }
+     * @return A <strong>String</strong> containing the description.
+     */
+    public String getDescription() {
+	return description;
+    }
+
     /** Method to retrieve the description of a certain list option value.
-	  * @param key The <strong>String</strong> whose description we are searching for.
-	  * @return The description of the desired key as a <strong>String</strong> which may be empty if no such key exists.
-	  */
-    public String getDesc(String key) {
+     * @param key The <strong>String</strong> whose description we are searching for.
+     * @return The description of the desired key as a <strong>String</strong> which may be empty if no such key exists.
+     */
+    public String getDescription(String key) {
 	if(list.containsKey(key)) {
 	    return (String)list.get(key);
@@ -204,256 +161,275 @@
 	return "";
     }
+
     /** Method to retrieve the option list for this argument.
-	  * @return A <strong>HashMap</strong> containing &lt;option value&gt; -&gt; &lt;description&gt; entries.
-	  */
-    public HashMap getList() {
+     * @return A <strong>HashMap</strong> containing &lt;option value&gt; -&gt; &lt;description&gt; entries.
+     */
+    public HashMap getOptions() {
 	return list;
     }
+
     /** Method to retrieve the value of name.
-	  * @return A <strong>String</strong> containing the argument name.
-	  */
+     * @return A <strong>String</strong> containing the argument name.
+     */
     public String getName() {
+	if(name == null && element != null) {
+	    name = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
+	}
 	return name;
     }
+
     /** Retrieve the name of the owner of this argument.
-	  * @return The owners name as a <strong>String</strong>.
-	  */
+     * @return The owners name as a <strong>String</strong>.
+     */
     public String getOwner() {
 	return owner;
     }
+    
     /** Method to determine the type of this argument.
-	  * @return An <i>byte</i> specifying the type.
-	  */
+     * @return An <i>byte</i> specifying the type.
+     */
     public byte getType() {
 	return type;
     }
+
     /** Method to retrieve the value of value.
-	  * @return The value of value as a <strong>String</strong>.
-	  */
+     * @return The value of value as a <strong>String</strong>.
+     */
     public String getValue() {
-	if(element != null) {
-	    return element.toString();
+	String value = null;
+	// Only assigned arguments have values. 
+	if(element != null) {
+	    value = MSMUtils.getValue(element);
+	    // We may have to retrieve the language dependant string for a MSM Element
+	    if(type == METADATUM) {
+		ElementWrapper element_wrapper = Gatherer.c_man.getCollection().msm.getElement(value);
+		if(element_wrapper != null) {
+		    value = element_wrapper.toString();
+		}
+	    }
 	}
 	return value;
     }
+
     /** Retrieve the vector of values.
-	  * @return An <strong>ArrayList</strong> of values.
-	  */
+     * @return An <strong>ArrayList</strong> of values.
+     */
     public ArrayList getValues() {
+	ArrayList values = new ArrayList();
+	// Only assigned arguments have values. 
+	if(element != null) {
+	    String value = MSMUtils.getValue(element);
+	    StringTokenizer tokenizer = new StringTokenizer(value, ",");
+	    while(tokenizer.hasMoreTokens()) {
+		String token = tokenizer.nextToken();
+		if(type == METADATA) {
+		    ElementWrapper element_wrapper = Gatherer.c_man.getCollection().msm.getElement(token);
+		    if(element_wrapper != null) {
+			token = element_wrapper.toString();
+		    }
+		}
+		values.add(token);
+	    }
+	}
 	return values;
     }
+
     /** Method to determine if this argument has been assigned.
-	  * @return <i>true</i> if it has, <i>false</i> otherwise.
-	  */
+     * @return <i>true</i> if it has, <i>false</i> otherwise.
+     */
     public boolean isAssigned() {
-	return (required || value != null || (values != null && values.size() > 0));
-    }
+	return (element != null && element.getAttribute(StaticStrings.ASSIGNED_ATTRIBUTE).equals(StaticStrings.TRUE_STR));
+    }
+
+    public boolean isCustomArgument() {
+	return (element != null && element.getAttribute(StaticStrings.CUSTOM_ATTRIBUTE).equals(StaticStrings.TRUE_STR));
+    }
+
     /** Method to determine of this argument is required for the associated script to work.
-	  * @return <i>true</i> if this argument is required, <i>false</i> otherwise.
-	  */
+     * @return <i>true</i> if this argument is required, <i>false</i> otherwise.
+     */
     public boolean isRequired() {
 	return required;
     }
+
     /** Method to allow for the activation of arguments that might never have their setValue() method called.
-	  * @param new_state The required state as a <i>boolean</i>.
-	  */
-    public void setAssigned(boolean new_state) {
-	if(new_state && (value == null || values == null)) {
-	    value = "";
-	}
-	else {
-	    value = null;
-	}
-    }
+     * @param assigned the desired state as a boolean
+     */
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, (assigned ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
+	}
+    }
+
+    public void setCustomArgument(boolean custom) {
+	if(element != null) {
+	    element.setAttribute(StaticStrings.CUSTOM_ATTRIBUTE, (custom ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
+	}
+    }
+
     /** Sets the value of default_value.
-	  * @param new_default_value The new value for default_value as a <strong>String</strong>.
-	  */
-    public void setDefault(String new_default_value) {
-	default_value = new_default_value;
-    }
+     * @param default_value The new value for default_value as a <strong>String</strong>.
+     */
+    public void setDefaultValue(String default_value) {
+	this.default_value = default_value;
+    }
+
     /** Set the value of desc.
-	  * @param new_desc The new value of desc as a <strong>String</strong>.
-	  */
-    public void setDesc(String new_desc) {
-	desc = new_desc;
-    }
-
-    public void setElementValue(ElementWrapper element) {
+     * @param description The new value of desc as a <strong>String</strong>.
+     */
+    public void setDescription(String description) {
+	this.description = description;
+    }
+
+    public void setElement(Element element) {
 	this.element = element;
     }
 
     /** Set the value of name.
-	  * @param new_name The new value of name as a <strong>String</strong>.
-	  */
-    public void setName(String new_name) {
-	name = new_name;
-    }
+     * @param name The new value of name as a <strong>String</strong>.
+     */
+    public void setName(String name) {
+	this.name = name;
+    }
+
     /** Sets the value of the options list.
-	  * @param new_list The new options list as a <strong>HashMap</strong>.
-	  */
-    public void setOptions(HashMap new_list) {
-	list = new_list;
-    }
+     * @param list The new options list as a <strong>HashMap</strong>.
+     */
+    public void setOptions(HashMap list) {
+	this.list = list;
+    }
+
     /** Set the owner of this argument.
-	  * @param owner The name of the owner of this argument as a <strong>String</strong>.
-	  */
+     * @param owner The name of the owner of this argument as a <strong>String</strong>.
+     */
     public void setOwner(String owner) {
 	this.owner = owner;
     }
+
     /** Set the value of required.
-	  * @param new_required The new value of required as a <i>boolean</i>.
-	  */
-    public void setRequired(boolean new_required) {
-	required = new_required;
-    }
+     * @param required The new value of required as a <i>boolean</i>.
+     */
+    public void setRequired(boolean required) {
+	this.required = required;
+    }
+
     /** Set the value of type.
-	  * @param new_type The new value of type as an <i>byte</i>.
-	  */
-    public void setType(byte new_type) {
-	type = new_type;
-	if(type == ENUM) {
-	    list = new HashMap();
-	}
-	if(type == METADATUM && values == null) {
-	    values = new ArrayList();
-	}
-    }
+     * @param type The new value of type as an <i>byte</i>.
+     */
+    public void setType(byte type) {
+	this.type = type;
+    }
+
     /** Set the value of type, by matching a type to the given string.
-	  * @param new_type A <strong>String</strong> which contains the name of a certain argument type.
-	  */
+     * @param new_type A <strong>String</strong> which contains the name of a certain argument type.
+     */
     public void setType(String new_type) {
-	if(new_type.equalsIgnoreCase("enum")) {
+	if(new_type.equalsIgnoreCase(StaticStrings.ENUM_STR)) {
 	    this.type = ENUM;
 	    list = new HashMap();
 	}
-	else if(new_type.equalsIgnoreCase("flag")) {
+	else if(new_type.equalsIgnoreCase(StaticStrings.FLAG_STR)) {
 	    this.type = FLAG;
 	}
-	else if(new_type.equalsIgnoreCase("hierarchy")) {
+	else if(new_type.equalsIgnoreCase(StaticStrings.HIERARCHY_STR)) {
 	    this.type = HIERARCHY;
 	}
-	else if(new_type.equalsIgnoreCase("int")) {
+	else if(new_type.equalsIgnoreCase(StaticStrings.INT_STR)) {
 	    this.type = INTEGER;
 	}
-	else if(new_type.equalsIgnoreCase("language")) {
+	else if(new_type.equalsIgnoreCase(StaticStrings.LANGUAGE_STR)) {
 	    this.type = LANGUAGE;
 	}
-	else if(new_type.equalsIgnoreCase("metadata")) {
+	else if(new_type.equalsIgnoreCase(StaticStrings.METADATA_TYPE_STR)) {
 	    this.type = METADATA;
 	}
-	else if(new_type.equalsIgnoreCase("metadatum")) {
+	else if(new_type.equalsIgnoreCase(StaticStrings.METADATUM_TYPE_STR)) {
 	    this.type = METADATUM;
-	    if(values == null) {
-		values = new ArrayList();
-	    }
-	}
-	else if(new_type.equalsIgnoreCase("string")) {
+	}
+	else {
 	    this.type = STRING;
 	}
-    }
-
-    /** Method to set the value of value.
-	  * @param new_value The new value of value as a <strong>String</strong>.
-	  */
-    public void setValue(String new_value) {
-	this.value = new_value;
-    }
+	
+    }
+    /** Method to set the value of this argument.
+     * @param value
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.msm.MSMUtils 
+     */
+    public void setValue(String value) {
+	if(element != null) {
+	    MSMUtils.setValue(element, value);
+	}
+	else {
+	    Gatherer.println("Argument.setValue(" + value + ") called on a base Argument.");
+	}
+    }
+
     /** Set the values vector to the given values. Currently I just assign the new values, whereas I may later want to implement a deep clone.
-	  * @param new_values An <strong>ArrayList</strong> of values.
-	  */
-    public void setValues(ArrayList new_values) {
-	values = new_values;
-    }
+     * @param values an ArrayList of values
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.msm.MSMUtils   
+     */
+    public void setValues(ArrayList values) {
+	if(element != null) {
+	    StringBuffer value = new StringBuffer();
+	    int value_length = values.size();
+	    for(int i = 0; i < value_length; i++) {
+		value.append(values.get(i));
+		value.append(StaticStrings.COMMA_CHARACTER);
+	    }
+	    value.deleteCharAt(value.length() - 1); // Remove last ','
+	    MSMUtils.setValue(element, value.toString());
+	}
+	else {
+	    Gatherer.println("Argument.setValues([" + values.size() + " items]) called on a base Argument.");
+	}
+    }
+
     /** Method for translating the data of this class into a string.
-	  * @return A <strong>String</strong> containing a fragment of the total arguments string.
-	  */
+     * @return a String containing a fragment of the total arguments string
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.Collection
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     * @see org.greenstone.gatherer.msm.MetadataSetManager
+     * @see org.greenstone.gatherer.msm.MSMUtils
+     * @see org.greenstone.gatherer.util.StaticStrings
+     */
     public String toString() {
-	switch(type) {
-	case FLAG:
-	    return "-" + name;
-	case METADATA:
-	    StringBuffer metadata_text = new StringBuffer("-");
-	    metadata_text.append(name);
-	    metadata_text.append(" ");
-	    if(element != null) {
-		String element_name = element.toString();
-		if(element_name.indexOf(" ") != -1) {
-		    metadata_text.append("\"");
-		    metadata_text.append(element_name);
-		    metadata_text.append("\"");
+	StringBuffer text = new StringBuffer("-");
+	if(element != null) {
+	    if(name == null) {
+		name = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
+	    }
+	    text.append(name);
+	    String value = MSMUtils.getValue(element);
+	    if(value.length() > 0) {
+		text.append(StaticStrings.SPACE_CHARACTER);
+		// Tokenize the string
+		StringTokenizer tokenizer = new StringTokenizer(value, ",");
+		while(tokenizer.hasMoreTokens()) {
+		    String token = tokenizer.nextToken();
+		    if(type == METADATA || type == METADATUM) {
+			ElementWrapper element_wrapper = Gatherer.c_man.getCollection().msm.getElement(token);
+			if(element_wrapper != null) {
+			    text.append(element_wrapper.toString());
+			    element_wrapper = null;
+			}
+			else {
+			    text.append(token);
+			}
+		    }
+		    else {
+			text.append(token);
+		    }
+		    token = null;
+		    text.append(StaticStrings.COMMA_CHARACTER);
 		}
-		else {
-		    metadata_text.append(element_name);
-		}
-		element_name = null;
-	    }
-	    else {
-		metadata_text.append(value);
-	    }
-	    return metadata_text.toString();
-	case METADATUM:
-	    StringBuffer metadatum_text = new StringBuffer("-");
-	    metadatum_text.append(name);
-	    metadatum_text.append(" ");
-	    for(int i = 0; i < values.size(); i++) {
-		metadatum_text.append(values.get(i).toString());
-		if(i < values.size() - 1) {
-		    metadatum_text.append(",");
-		}
-	    }
-	    return metadatum_text.toString();
-	case STRING:
-	    // we need to put quotes if they are not there
-	    if (value != null && value.indexOf(' ') != -1 && value.indexOf('"')==-1) {
-		// if there is a space and we haven't got any quotes
-		return "-" + name + " \"" + value + "\"";
-	    } // otherwise it will stuff up but I cant be bothered doing this properly
-	default:
-	    return "-" + name + " " + value;
-	}
-    }
-
-    public String toStringConfig() {
-	switch(type) {
-	case METADATA:
-	    StringBuffer metadata_text = new StringBuffer("-");
-	    metadata_text.append(name);
-	    if(element != null) {
-		metadata_text.append(" ");
-		String element_name = element.getName();
-		if (element_name.startsWith(Utility.EXTRACTED_METADATA_NAMESPACE+MSMUtils.NS_SEP)) {
-		    
-		    element_name = element_name.substring(element_name.indexOf(MSMUtils.NS_SEP)+1);
-		}
-		metadata_text.append(element_name);
-		element_name = null;
-	    }
-	    else if(value != null) {
-		metadata_text.append(" ");
-		metadata_text.append(value);
-	    }
-	    return metadata_text.toString();
-	case METADATUM:
-	    StringBuffer metadatum_text = new StringBuffer("-");
-	    metadatum_text.append(name);
-	    metadatum_text.append(" ");
-	    for(int i = 0; i < values.size(); i++) {
-		String temp_value = values.get(i).toString();
-		if (temp_value.startsWith(Utility.EXTRACTED_METADATA_NAMESPACE+MSMUtils.NS_SEP)) {
-		    
-		    temp_value = temp_value.substring(temp_value.indexOf(MSMUtils.NS_SEP)+1);
-		}
-		metadatum_text.append(temp_value);
-		if(i < values.size() - 1) {
-		    metadatum_text.append(",");
-		}
-	    }
-	    return metadatum_text.toString();
-	default:
-	    return toString();
-	}
-    }
-    
+		tokenizer = null;
+		text.deleteCharAt(text.length() - 1);
+	    }
+	    value = null;
+	}
+	return text.toString();
+    }   
 }
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/ArgumentConfiguration.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/ArgumentConfiguration.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/ArgumentConfiguration.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,48 +25,10 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
-/**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Company:      The University of Waikato
- * Written:      01/05/02
- * Revised:      16/08/02 Optimized and Commented. Added destructor.
- **************************************************************************************/
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.Toolkit;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.awt.event.KeyEvent;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.DefaultListModel;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
-import javax.swing.JComponent;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextField;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
 import org.greenstone.gatherer.Gatherer;
 import org.greenstone.gatherer.cdm.Argument;
@@ -97,12 +49,4 @@
  * @see org.greenstone.gatherer.cdm.PlugIn
  */
-// ####################################################################################
-// Optimization                          Saving
-// ####################################################################################
-// Vector -> ArrayList                   + Processor
-// Hashtable -> HashMap                  + Processor
-// Remove several global references      + Memory (1Kb+)
-// ####################################################################################
-
 public class ArgumentConfiguration
     extends ModalDialog
@@ -114,8 +58,4 @@
     /** Whether we have successfully edited the arguments associated with the ArgumentContainer or if we have failed to enter required arguments and have instead cancelled (which would cause argument additions to roll back). */
     private boolean success = false;
-    /** A reference to the main CollectionDesignManager class for access to other managers. */
-    private CollectionDesignManager manager = null;
-    /** A reference to the Gatherer. */
-    private Gatherer gatherer = null;
     /** A button to cancel this dialog. */
     private JButton cancel = null;
@@ -137,14 +77,10 @@
     static final private Dimension SIZE = new Dimension(800, 425);
     /** Constructor.
-     * @param gatherer A reference to the <strong>Gatherer</strong>.
-     * @param manager The <strong>CollectionDesignManager</strong> for access to other configuration managers.
      * @param data The plugin or classifier whose arguments we are configuring, in the form of its supported <strong>ArgumentContainer</strong> interface.
      * @see org.greenstone.gatherer.Configuration
      */
-    public ArgumentConfiguration(Gatherer gatherer, CollectionDesignManager manager, ArgumentContainer data) {
-	super(gatherer.g_man);
+    public ArgumentConfiguration(ArgumentContainer data) {
+	super(Gatherer.g_man);
 	this.data = data;
-	this.gatherer = gatherer;
-	this.manager = manager;
 	this.self = this;
 	String custom_str = data.getCustom();
@@ -210,5 +146,5 @@
 
 	// Display on screen.
-	Dimension screen_size = gatherer.config.screen_size;
+	Dimension screen_size = Gatherer.config.screen_size;
 	setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
 	screen_size = null;
@@ -223,7 +159,5 @@
 	if(event.getSource() == ok) {
 	    // Update the details stored in the data objects arguments.
-	    if(custom.getText().length() > 0) {
-		data.setCustom(custom.getText());
-	    }
+	    data.setCustom(custom.getText());
 	    // Loop through each of the controls in the central pane, updating the matching argument as necessary.
 	    for(int i = 0; i < central_pane.getComponentCount(); i++) {
@@ -248,6 +182,4 @@
 	custom = null;
 	data = null;
-	gatherer = null;
-	manager = null;
 	ok = null;
 	self = null;
@@ -285,5 +217,5 @@
 	  */
     private void generateControls() {
-	ArrayList arguments = data.getArguments();
+	ArrayList arguments = data.getArguments(true, false);
 	int total_height = 250;
 	int size = arguments.size();
@@ -317,5 +249,5 @@
 	    key = "CDM.ArgumentConfiguration." + key;
 	}
-	return gatherer.dictionary.get(key, args);
+	return Gatherer.dictionary.get(key, args);
     }
     /** This class encapsulates all the technical difficulty of creating a specific control based on an Argument. */
@@ -324,4 +256,7 @@
 	/** The Argument this control will be based on. */
 	private Argument argument = null;
+
+	private Color colour_one = Gatherer.config.getColor("coloring.collection_heading_background", false);
+	private Color colour_two = Gatherer.config.getColor("coloring.collection_tree_background", false);
 	/** One of a possible two buttons available for adding to this control. */
 	private JButton one = null;
@@ -345,10 +280,10 @@
 	public ArgumentControl(Argument argument) {
 	    this.argument = argument;
-	    String tip = "<html>" + argument.getDesc() + "</html>";
+	    String tip = "<html>" + argument.getDescription() + "</html>";
 	    tip = Utility.formatHTMLWidth(tip, 60);
 	    // If this is the first control, there is no history.
 	    if(previous_owner == null) {
 		previous_owner = argument.getOwner();
-		addHeader(previous_owner, Color.white);
+		addHeader(previous_owner, colour_one);
 	    }
 	    // Otherwise if the owner of the control has changed since the last argument, toggle the colouring of the control.
@@ -356,12 +291,12 @@
 		coloured = !coloured;
 		previous_owner = argument.getOwner();
-		addHeader(previous_owner, (coloured ? Gatherer.config.getColor("coloring.collection_tree_background", false) : Color.white));
+		addHeader(previous_owner, (coloured ? colour_two : colour_one));
 	    }
 	    // Create
 	    if(coloured) {
-		setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+		setBackground(colour_one);
 	    }
 	    else {
-		setBackground(Color.white);
+		setBackground(colour_two);
 	    }
 	    JLabel owner_label = new JLabel(argument.getOwner());
@@ -382,5 +317,5 @@
 	    case Argument.ENUM:
 		// Build an option model, wrapping each entry of the list table.
-		HashMap arg_list = argument.getList();
+		HashMap arg_list = argument.getOptions();
 		ArrayList options_model = new ArrayList();
 		Iterator it = arg_list.keySet().iterator();
@@ -405,5 +340,5 @@
 		break;
 	    case Argument.HIERARCHY:
-		value = new JComboBox(gatherer.c_man.msm.getAssignedElements(true));
+		value = new JComboBox(Gatherer.c_man.getCollection().msm.getAssignedElements(true));
 		/** @TODO - figure out a smarter way of allowing Greenstone extracted metadata to be selected. */
 		((JComboBox)value).setEditable(true);
@@ -450,19 +385,19 @@
 		break;
 	    case Argument.LANGUAGE:
-		value = new JComboBox(manager.languages.getLanguageCodes().toArray());
+		value = new JComboBox(CollectionDesignManager.language_manager.getLanguageCodes().toArray());
 		// Now ensure we have the existing value or default value selected if either exist.
 		Language selected = null;
 		if(existing_value != null) {
-		    selected = manager.languages.getLanguage(existing_value, false);
+		    selected = CollectionDesignManager.language_manager.getLanguage(existing_value);
 		}
 		else if(default_value != null) {
-		    selected = manager.languages.getLanguage(default_value, false);
+		    selected = CollectionDesignManager.language_manager.getLanguage(default_value);
 		}
 		if(selected != null) {
-		    ((JComboBox)value).setSelectedItem(selected);
+		    ((JComboBox)value).setSelectedItem(selected.getCode());
 		}
 		break;
-	    case Argument.METADATA:
-		value = new JComboBox(gatherer.c_man.msm.getAssignedElements());
+	    case Argument.METADATUM:
+		value = new JComboBox(Gatherer.c_man.getCollection().msm.getAssignedElements());
 		/** @TODO - figure out a smarter way of allowing Greenstone extracted metadata to be selected. */
 		((JComboBox)value).setEditable(true);
@@ -475,8 +410,8 @@
 		}
 		break;
-	    case Argument.METADATUM:
+	    case Argument.METADATA:
 		// Comma separated metadata values.
 		ArrayList values = argument.getValues();
-		value = new JComboBox(gatherer.c_man.msm.getAssignedElements());
+		value = new JComboBox(Gatherer.c_man.getCollection().msm.getAssignedElements());
 		DefaultListModel model = new DefaultListModel();
 		list = new JList(model);
@@ -566,4 +501,5 @@
 	    add(inner_pane, BorderLayout.CENTER);
 	}
+
 	public Object getValue() {
 	    if(value instanceof JComboBox) {
@@ -650,13 +586,10 @@
 		    // Kinda lucked out here. Its impossible not to choose an entry from these comboboxes as they are restricted.
 		    return true;
-		case Argument.METADATA:
-		    // I feel like a cast-away
-		    argument.setElementValue((ElementWrapper)((JComboBox)value).getSelectedItem());
-		    return true;
+		case Argument.METADATUM:
 		case Argument.HIERARCHY:
 		    argument.setValue(((JComboBox)value).getSelectedItem().toString());
 		    // Kinda lucked out here. Its impossible not to choose an entry from these comboboxes as they are restricted.
 		    return true;
-		case Argument.METADATUM:
+		case Argument.METADATA:
 		    DefaultListModel model = (DefaultListModel)list.getModel();
 		    ArrayList values = new ArrayList();
@@ -743,11 +676,11 @@
 		this.target = target;
 	    }
-	    /** When the add button is clicked, we attempt to add the selected metadata from the source into the target.
-	     * @param event An <strong>ActionEvent</strong> containing information about the event.
-	     * @see org.greenstone.gatherer.msm.ElementWrapper
-	     */
+				/** When the add button is clicked, we attempt to add the selected metadata from the source into the target.
+				 * @param event An <strong>ActionEvent</strong> containing information about the event.
+				 * @see org.greenstone.gatherer.msm.ElementWrapper
+				 */
 	    public void actionPerformed(ActionEvent event) {
 		ElementWrapper element = (ElementWrapper) source.getSelectedItem();
-		String name = element.getName();
+		String name = element.toString();
 		if(!model.contains(name)) {
 		    boolean found = false;
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/ArgumentContainer.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/ArgumentContainer.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/ArgumentContainer.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,18 +25,8 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Company:      The University of Waikato
  * Written:      20/05/02
- * Revised:
+ * Revised:      03/07/03
  **************************************************************************************/
 import java.util.ArrayList;
@@ -57,17 +37,19 @@
 public interface ArgumentContainer {
     /** Method to retrieve the list of arguments from this container. Note that this method returns both the containers arguments plus its 'supers' arguments if any, and alphabetically orders them.
-     * @return The arguments within a <strong>ArrayList</strong>.
+     * @param include_normal true to filter so normal arguments are returned
+     * @param include_custom true to filter so custom arguments are returned
+     * @return the arguments within a ArrayList
      */
-    public ArrayList getArguments();
+    public ArrayList getArguments(boolean include_normal, boolean include_custom);
     /** Method to retrieve this containers custom argument string.
-     * @return The custom arguments as a <strong>String</strong>.
+     * @return the custom arguments as a String
      */
     public String getCustom();
     /** Method to retrieve the name associated with this argument container.
-     * @return The name as a <strong>String</strong>.
+     * @return the name as a String
      */
     public String getName();
     /** Method to set the custom arguments string.
-     * @param custom The new custom argument <strong>String</strong>.
+     * @param custom the new custom argument String
      */
     public void setCustom(String custom);
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/Classifier.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/Classifier.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/Classifier.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,55 +25,104 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Company:      The University of Waikato
  * Written:      01/05/02
  * Revised:      16/08/02 Optimized and Commented.
+ *               11/07/03 DOM support
  **************************************************************************************/
-import java.io.Serializable;
-import java.util.ArrayList;
+import java.io.*;
+import java.util.*;
 import org.greenstone.gatherer.cdm.Argument;
 import org.greenstone.gatherer.cdm.ArgumentContainer;
-import org.greenstone.gatherer.cdm.CustomClassifier;
-import org.greenstone.gatherer.cdm.Format;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
 /** This class is responsible for storing information from a parsed classinfo.pl call in such a way that it allows easy access to parsed details for the purposes of user design and specification of classifiers.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
  * @version 2.3
  */
-// ####################################################################################
-// Optimization                          Saving
-// ####################################################################################
-// Vector -> ArrayList (x8)              + Processor
-// ####################################################################################
 public class Classifier
     extends ArrayList
-    implements ArgumentContainer, Comparable, Serializable {
+    implements ArgumentContainer, Comparable, DOMProxyListEntry, Serializable {
+
+    static final public String CLASSIFIER_PREFIX = "CL";
+
     /** A reference to the classifier that this one inherits from. */
     private Classifier super_classifier = null;
-    /** A reference to the manager of this classifier. Only available once the classifiers been assigned, always <i>null</i> for classifiers in reserve. */
-    private ClassifierManager manager = null;
-    /** Custom arguments (ie those unparsable from classinfo.pl) provided to this class. */
-    private String custom = null;
+    /** The element this classifier is based upon. */
+    private Element element;
     /** A description of this classifier. */
-    private String desc = null;
+    private String description = null;
     /** The name of the classifier as it would appear in the collect.cfg file. */
     private String name = null;
-    /** A list of format commands that are dependant on this classifier. */
-    private ArrayList dependant_formats = null;
-    /** Default Constructor.
+    /** This string is filled out the first time this classifier is created, and remains unchanged there-after. It is used to match up with Format commands that may not yet have been instantiated (and thus only have offline references along the lines of 'CL1' to figure out what Classifier they want.) */
+    private String old_position_string = null;
+
+    /** Constructor used only in DOMProxyListModel initializations.
      */
     public Classifier() {
+    }
+
+    public Classifier(Element element, Classifier base_classifier) {
 	super();
-	dependant_formats = new ArrayList();
-    }
+	this.element = element;
+	this.name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	///atherer.println("Establishing Classifier: " + name);
+	// Parse in any argument options for this classifier, keeping a list of the ones found
+	HashMap known_arguments = new HashMap();
+	NodeList option_elements = element.getElementsByTagName(StaticStrings.OPTION_ELEMENT);
+	int option_elements_length = option_elements.getLength();
+	for(int i = 0; i < option_elements_length; i++) {
+	    Element option_element = (Element) option_elements.item(i);
+	    Argument argument = new Argument(option_element);
+	    ///atherer.println("Rebuilding existing argument: " + argument.getName());
+	    argument.setOwner(name);
+	    add(argument);
+	    known_arguments.put(argument.getName(), argument);
+	}
+	// If a base classifier was given
+	if(base_classifier != null) {
+	    // Copy the details, and add a reference to whatever base_classifiers super classifier is.
+	    description = base_classifier.getDescription();
+	    // Now search through the 'dummy' arguments belonging to the base classifier. For each found, if it is already assigned, fill out further details such as type. If any are found that are not already assigned for this classifier, copy them and add them, but without a value.
+	    ArrayList all_arguments = base_classifier.getArguments(true, true);
+	    int argument_count = all_arguments.size();
+	    for(int j = 0; j < argument_count; j++) {
+		Argument base_argument = (Argument) all_arguments.get(j);
+		String base_argument_name = base_argument.getName();
+		///atherer.println("Library indicates this classifier should have an argument: " + base_argument_name);
+		Argument existing_argument = (Argument) known_arguments.get(base_argument_name);
+		// Found an existing argument. Complete its details
+		if(existing_argument != null) {
+		    ///atherer.println("Found existing argument. Filling out details.");
+		    existing_argument.setCustomArgument(false);
+		    existing_argument.setDefaultValue(base_argument.getDefaultValue());
+		    existing_argument.setDescription(base_argument.getDescription());
+		    existing_argument.setOptions(base_argument.getOptions());
+		    existing_argument.setRequired(base_argument.isRequired());
+		    existing_argument.setType(base_argument.getType());
+		}
+		// No existing argument. Copy base_argument and add it, but do not set its assigned flag. That should be set the first time its changed by the user.
+		else {
+		    ///atherer.println("No such argument. Adding new, unassigned, argument.");
+		    // The trick thing is that we have to create a new element in the DOM as well.
+		    Argument new_argument = base_argument.copy();
+		    Element argument_element = CollectionDesignManager.collect_config.document.createElement(StaticStrings.OPTION_ELEMENT);
+		    argument_element.setAttribute(StaticStrings.NAME_ATTRIBUTE, base_argument_name);
+		    argument_element.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.FALSE_STR);
+		    argument_element.setAttribute(StaticStrings.CUSTOM_ATTRIBUTE, StaticStrings.FALSE_STR);
+		    new_argument.setElement(argument_element);
+		    // All done. Add it.
+		    element.appendChild(argument_element);
+		    add(new_argument);
+		}
+	    }
+	}
+	old_position_string = getPositionString();
+    }
+
     /** Constructor.
 	  * @param name The name of this classifier as a <strong>String</strong>.
@@ -91,25 +130,21 @@
 	  * @param super_classifier The super class of this classifier, as a <strong>Classifier</strong>.
 	  */
-    public Classifier(String name, String desc, Classifier super_classifier) {
-	this();
-	this.desc = desc;
+    public Classifier(String name, String description, Classifier super_classifier) {
+	super();
+	this.description = description;
 	this.name = name;
 	this.super_classifier = super_classifier;
     }
+
     /** Method to add an argument to this classifier. Only adds the argument if it isn't already present.
 	  * @param argument The <strong>Argument</strong> to add.
 	  */
     public void addArgument(Argument argument) {
-	if(!contains(argument)) {
+	if(element == null && !contains(argument)) {
 	    add(argument);
 	    argument.setOwner(name);
 	}
     }
-    /** Method to register a dependant format with this classifier. If the classifier changes, this format also needs to be updated.
-	  * @param format A <strong>Format</strong> which is dependant on this classifier.
-	  */
-    public void addDependantFormat(Format format) {
-	dependant_formats.add(format);
-    }
+
     /** Method to compare two classifiers for ordering.
 	  * @param object The classifier we are comparing to, as an <strong>Object</strong>.
@@ -118,45 +153,37 @@
 	  */
     public int compareTo(Object object) {
-	if(object instanceof Classifier) {
-	    Classifier classifier = (Classifier) object;
-	    return name.compareTo(classifier.getName());
-	}
-	return name.compareTo(object.toString());
-    }
-    /** This method produces a deep copy of this classifier. Note that this also creates a new copy of each of the super classes of classifiers as well. This is the way it should be, as each assigned classifier may have different values for the higher classifiers (such as BasPlug).
-	  * @return A newly created <strong>Classifier</strong> with the same details and Arguments as this one.
-	  * @see org.greenstone.gatherer.cdm.Argument
-	  */
-    public Classifier copy() {
-	Classifier copy = null;
-	if(super_classifier == null) {
-	    copy = new Classifier(name, desc, null);
-	}
-	else {
-	    copy = new Classifier(name, desc, super_classifier.copy());
-	}
-	for(int i = 0; i < size(); i++) {
-	    copy.addArgument(((Argument)get(i)).copy());
-	}
-	return copy;
-    }
+	if(object == null) {
+	    return -1;
+	}
+	return toString().compareTo(object.toString());
+    }
+
+    /** The assigned classifier constructor.
+     * @param element the DOM Element this classifier is based upon
+     * @param base_classifier the Classifier from the stored library showing details about this classifier, may be null
+     */
+    public DOMProxyListEntry create(Element element) {
+	String classifier_name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	// Determine the base classifier from the classifier name
+	Classifier base_classifier = CollectionDesignManager.classifier_manager.getBaseClassifier(classifier_name);
+	Classifier classifier = new Classifier(element, base_classifier);
+	base_classifier = null;
+	classifier_name = null;
+	return classifier;
+    }
+
     /** Method to determine if two classifiers are equal.
-	  * @param object The classifier to test against, as an <strong>Object</strong>.
-	  * @return <i>true</i> if the classifier names match, <i>false</i> otherwise.
-	  * @see org.greenstone.gatherer.cdm.CustomClassifier
-	  */
+     * @param object The classifier to test against, as an <strong>Object</strong>.
+     * @return <i>true</i> if the classifier names match, <i>false</i> otherwise.
+     * @see org.greenstone.gatherer.cdm.CustomClassifier
+     */
     public boolean equals(Object object) {
-	if(object instanceof CustomClassifier) {
-	    CustomClassifier classifier = (CustomClassifier) object;
-	    return (toString().equalsIgnoreCase(classifier.getCommand()));
-	}
-	else {
-	    return(toString().equalsIgnoreCase(object.toString()));
-	}
-    }
+	return (compareTo(object) == 0);
+    }
+
     /** Method to retrieve an argument by its name.
-	  * @param name The name of the argument as a <strong>String</strong>.
-	  * @return The <strong>Argument</strong> requested, or <i>null</i> if no such argument.
-	  */
+     * @param name The name of the argument as a <strong>String</strong>.
+     * @return The <strong>Argument</strong> requested, or <i>null</i> if no such argument.
+     */
     public Argument getArgument(String name) {
 	// The name given may still include the '-'
@@ -164,5 +191,5 @@
 	    name = name.substring(1);
 	}
-	ArrayList arguments = getArguments();
+	ArrayList arguments = getArguments(true, true);
 	for(int i = 0; i < arguments.size(); i++) {
 	    Argument argument = (Argument)arguments.get(i);
@@ -173,113 +200,205 @@
 	return null;
     }
-    /** Method to retrieve all of the arguments available to a classifier, including both specific and general ones.
-	  * @return A <strong>Hashtable</strong> of arguments, with &lt;name&gt; -&gt; &lt;argument&gt; entries.
-	  */
-    public ArrayList getArguments() {
-	ArrayList all_arguments = new ArrayList(this);
+
+     /** Retrieve all of the arguments available to this base classifier, including its super classifiers arguments. Some complexity is added by allowing the caller to choose whether they want normal arguments, custom arguments, or both.
+     * @return an ArrayList of all of the arguments, starting with those for this classifier and ending with the arguments for basplug or similiar root classifier
+     */
+    public ArrayList getArguments(boolean include_normal, boolean include_custom) {
+	ArrayList arguments = new ArrayList();
+	if(include_normal && include_custom) {
+	    arguments.addAll(this);
+	}
+	else {
+	    int size = size();
+	    for(int i = 0; i < size; i++) {
+		Argument argument = (Argument) get(i);
+		if(argument.isCustomArgument()) {
+		    if(include_custom && !arguments.contains(argument)) {
+			arguments.add(argument);
+		    }
+		}
+		else {
+		    if(include_normal && !arguments.contains(argument)) {
+			arguments.add(argument);
+		    }
+		}
+		argument = null;
+	    }
+	}
 	if(super_classifier != null) {
-	    ArrayList super_arguments = super_classifier.getArguments();
-	    for(int i = 0; i < super_arguments.size(); i++) {
-		Object argument = super_arguments.get(i);
-		if(!all_arguments.contains(argument)) {
-		    all_arguments.add(argument);
-		}
-	    }
-	}
-	return all_arguments;
-    }
-    /** Method to retrieve a classifiers custom argument information.
-	  * @return The custom arguments as a <strong>String</strong>.
-	  */
+	    ArrayList remainder = super_classifier.getArguments(include_normal, include_custom);
+	    remainder.removeAll(arguments);
+	    arguments.addAll(remainder);
+	}
+	return arguments;
+    }
+
+    /** Method to retrieve a classifiers custom argument information. Custom arguments are defined to be those that have not got matching arguments in the base reference classifier from the library. Of course if there is no base classifier then all arguments are considered to be custom.
+     * @return the custom arguments as a String
+     */
     public String getCustom() {
-	return custom;
-    }
+	StringBuffer custom_text = new StringBuffer();
+	// Retrieve all of the arguments, and append any that are custom into one long string
+	ArrayList arguments = getArguments(false, true);
+	int arguments_size = arguments.size();
+	boolean first = true;
+	for(int i = 0; i < arguments_size; i++) {
+	    Argument argument = (Argument) arguments.get(i);
+	    if(argument.isAssigned()) {
+		if(!first) {
+		    custom_text.append(" ");
+		}
+		custom_text.append(argument.toString());
+		first = false;
+	    }
+	}
+	return custom_text.toString();
+    }
+
+    public String getDescription() {
+	return description;
+    }
+
+    public Element getElement() {
+	return element;
+    }
+
     /** Method to retrieve a classifiers name.
-	  * @return A <strong>String</strong> containing the classifiers name.
-	  */
+     * @return A <strong>String</strong> containing the classifiers name.
+     */
     public String getName() {
+	if(name == null && element != null) {
+	    name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	}
 	return name;
     }
-    /** Method to retrieve the position of this classifier as a special keyword or the form "CL#" where # is the classifiers order in the set of classifiers. Note that if this is called for a Classifier that has never been assigned a position of 'Search' is returned, as this is the only case where we will try to write a Classifier that does not have a manager.
-	  * @return A <strong>String</strong> containing the special position keyword.
-	  * @see org.greenstone.gatherer.cdm.ClassifierManager
-	  */
+
+    public String getOldPositionString() {
+	return old_position_string;
+    }
+
+    /** Generate the string showing this classifiers position. */
     public String getPositionString() {
-	if(manager == null) {
-	    return "Search";
-	}
-	return "CL" + (manager.indexOf(this) + 1);
-    }
-    /** Method to set the value of custom.
-	  * @param custom The new value of custom as a <strong>String</strong>.
-	  */
-    public void setCustom(String custom) {
-	this.custom = custom;
-    }
-    /** Method to set the value of desc.
-	  * @param desc The new value of desc as a <strong>String</strong>.
-	  */
-    public void setDesc(String desc) {
-	this.desc = desc;
-    }
-    /** Method to set the value of manager.
-	  * @param manager The new manager as a <strong>ClassifierManager</strong>.
-	  */
-    public void setManager(ClassifierManager manager) {
-	this.manager = manager;
-    }
-    /** Method to set the value of name.
-	  * @param name The new value of name as a <strong>String</strong>.
-	  */
+	String position_string = CLASSIFIER_PREFIX;
+	if(element != null) {
+	    // Determine our place in the collect.cfg file
+	    int position_int = CollectionDesignManager.classifier_manager.indexOf(this) + 1;
+	    if(position_int != -1) {
+		position_string = position_string + position_int;
+	    }
+	}
+	return position_string;
+    }
+
+    public boolean isAssigned() {
+	return (element != null && !element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.FALSE_STR));
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    /** Set the custom arguments. This turns out to be quite tricky. We must parse in the string, searching for arguments (for that we use a handy method in CollectionConfiguration). Next, for each argument, we check if we already know about it. If so we update its value, otherwise we create a new argument and assign it (must assign!). 
+     * @param custom_str the custom arguments all splodged together in one String
+     */
+    public void setCustom(String custom_str) {
+	HashMap raw_arguments = CollectionConfiguration.parseArguments(new CommandTokenizer(custom_str));
+	ArrayList custom_arguments = getArguments(false, true);
+	int size = custom_arguments.size();
+	for(int i = 0; i < size; i++) {
+	    Argument argument = (Argument) custom_arguments.get(i);
+	    String original_argument_name = StaticStrings.MINUS_CHARACTER + argument.getName();
+	    if(raw_arguments.containsKey(original_argument_name)) {
+		// Set as assigned
+		argument.setAssigned(true);
+		String argument_value = (String)raw_arguments.remove(original_argument_name);
+		if(argument_value != null) {
+		    argument.setValue(argument_value);
+		    argument_value = null;
+		}
+	    }
+	    // We've removed it from our custom statement, so unassign
+	    else {
+		argument.setAssigned(false);
+	    }
+	    argument = null;
+	}
+	// Any left over, add to the classifier
+	Iterator argument_names = raw_arguments.keySet().iterator();
+	while(argument_names.hasNext()) {
+	    String argument_name = (String) argument_names.next();
+	    String argument_value = (String) raw_arguments.get(argument_name);
+	    // The tricky thing is that we have to create a new element in the DOM as well.
+	    Element argument_element = CollectionDesignManager.collect_config.document.createElement(StaticStrings.OPTION_ELEMENT);
+	    argument_element.setAttribute(StaticStrings.NAME_ATTRIBUTE, argument_name.substring(1));
+	    argument_element.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
+	    argument_element.setAttribute(StaticStrings.CUSTOM_ATTRIBUTE, StaticStrings.TRUE_STR);
+	    Argument argument = new Argument(argument_element);
+	    argument_name = null;
+	    if(argument_value != null) {
+		argument.setValue(argument_value);
+		argument_value = null;
+	    }
+	    // All done. Add it.
+	    element.appendChild(argument_element);
+	    add(argument);
+	    argument_element = null;
+	}
+	raw_arguments = null;
+    }
+
+    /** Method to set the value of desc. 
+     * @param desc The new value of desc as a <strong>String</strong>.
+     */
+    public void setDescription(String desc) {
+	this.description = description;
+    }
+
+    public void setElement(Element element) {
+	this.element = element;
+    }
+
+    /** Method to set the value of name. 
+     * @param name The new value of name as a <strong>String</strong>.
+     */
     public void setName(String name) {
 	this.name = name;
     }
+
     /** Method to set the value of the super_classifier.
-	  * @param super_classifier The new value of super_classifier as a <strong>Classifier</strong>, or <i>null</i> if this class has no inheritance.
-	  */
+     * @param super_classifier The new value of super_classifier as a <strong>Classifier</strong>, or <i>null</i> if this class has no inheritance. 
+     */
     public void setSuper(Classifier super_classifier) {
 	this.super_classifier = super_classifier;
     }
+
     /** Method to print out this classifier as it would appear to the user in the interface
 	  * @return A <strong>String</strong> containing a single classifier command.
 	  */
     public String toString() {
-	StringBuffer text = new StringBuffer("classify ");
-	text.append(name);
-	text.append(" ");
-	ArrayList arguments = getArguments();
-	for(int i = 0; i < arguments.size(); i++) {
-	    Argument argument = (Argument)arguments.get(i);
-	    if(argument.isAssigned()) {
-		text.append(argument.toString());
-		text.append(" ");
-	    }
-	}
-	if(custom != null) {
-	    text.append(custom);
-	}
-	return text.toString();
-    }
-    /** Method to print out this classifier as it would appear as a command within the collection configuration file.
-	  * @return A <strong>String</strong> containing a single classifier command.
-	  */
-    public String toStringConfig() {
-	StringBuffer text = new StringBuffer("classify ");
-	text.append(name);
-	text.append(" ");
-	ArrayList arguments = getArguments();
-	for(int i = 0; i < arguments.size(); i++) {
-	    Argument argument = (Argument)arguments.get(i);
-	    if(argument.isAssigned()) {
-		text.append(argument.toStringConfig());
-		text.append(" ");
-	    }
-	}
-	if(custom != null) {
-	    text.append(custom);
-	}
-	return text.toString();
+	if(element != null) {
+	    if(name == null) {
+		name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	    }
+	    StringBuffer text = new StringBuffer(StaticStrings.CLASSIFY_STR);
+	    text.append(" ");
+	    text.append(name);
+	    text.append(" ");
+	    ArrayList arguments = getArguments(true, true);
+	    int arguments_size = arguments.size();
+	    for(int i = 0; i < arguments_size; i++) {
+		Argument argument = (Argument)arguments.get(i);
+		if(argument.isAssigned()) {
+		    text.append(argument.toString());
+		    text.append(" ");
+		}
+	    }
+	    return text.substring(0, text.length() - 1);
+	}
+	else {
+	    return name;
+	}
     }
 }
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/ClassifierManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/ClassifierManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/ClassifierManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -36,86 +26,52 @@
  */
 package org.greenstone.gatherer.cdm;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.StringReader;
-import java.lang.Exception;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.jar.JarFile;
-import javax.swing.BorderFactory;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-import javax.swing.ListSelectionModel;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import org.apache.xerces.parsers.DOMParser;
+/**************************************************************************************
+ * Written:      01/05/02
+ * Revised:      16/08/02 Optimized and Commented.
+ *               11/07/03 DOM support
+ **************************************************************************************/
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import org.apache.xerces.parsers.*;
 import org.greenstone.gatherer.Gatherer;
 import org.greenstone.gatherer.cdm.Argument;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
 import org.greenstone.gatherer.cdm.CollectionDesignManager;
 import org.greenstone.gatherer.cdm.CommandTokenizer;
+import org.greenstone.gatherer.cdm.Control;
 import org.greenstone.gatherer.cdm.Classifier;
 import org.greenstone.gatherer.cdm.CustomClassifier;
-import org.greenstone.gatherer.cdm.DynamicListModel;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
 import org.greenstone.gatherer.file.FileNode;
-import org.greenstone.gatherer.msm.ElementWrapper;
+import org.greenstone.gatherer.gui.GComboBox;
 import org.greenstone.gatherer.msm.MSMEvent;
 import org.greenstone.gatherer.msm.MSMListener;
 import org.greenstone.gatherer.msm.MSMUtils;
 import org.greenstone.gatherer.util.Utility;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.xml.sax.InputSource;
+import org.w3c.dom.*;
+import org.xml.sax.*;
 /** This class is responsible for keeping track of all the classifiers assigned to this collection, and providing methods for adding and removing them.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
  * @version 2.3
  */
-// ####################################################################################
-// Optimization                          Saving
-// ####################################################################################
-// Vector -> ArrayList                   + Memory, + Processor, (pos. - Processor)
-// Unnecessary global references         + Memory (5Kb+)
-// ####################################################################################
 public class ClassifierManager
-    implements MSMListener {
-    /** An interface to the Gatherer, the creator of this cdm module, for access to the Greenstone installation directory. */
-    private Gatherer gatherer = null;
-    /** A reference to the CollectionDesignManager for access to other configuration managers. */
-    private CollectionDesignManager manager = null;
+    extends DOMProxyListModel {
+
+    /** The default size for a label. */
+    static final private Dimension LABEL_SIZE = new Dimension(140, 20);
+
+    /** A list of known, but currently unassigned, classifiers. */
+    private ArrayList library = null;
     /** The controls for editing the contents of this manager. */
     private Control controls = null;
-    /** A list of assigned classifiers. */
-    private DynamicListModel assigned = null;
-    /** A list of known, but currently unassigned, classifiers. */
-    private DynamicListModel reserve = null;
-    /** We may have somehow recieved a classifier command that are, in fact, custom classifiers which can refer to classifiers that haven't been parsed yet, so this holds a list of failed commands which are retried after the loading is complete. */
-    private ArrayList unresolved_commands = null;
+
+    private DOMProxyListModel model;
+
     /** Constructor.
-     * @param gatherer A reference to the <strong>Gatherer</strong> for access to the Dictionary.
-     * @param manager A reference to the <strong>CollectionDesignManager</strong> itself.
-     * @see org.greenstone.gatherer.Gatherer
      * @see org.greenstone.gatherer.cdm.DynamicListModel
      * @see org.greenstone.gatherer.collection.CollectionManager
@@ -123,146 +79,216 @@
      * @see org.greenstone.gatherer.msm.MSMListener
      */
-    public ClassifierManager(Gatherer gatherer, CollectionDesignManager manager) {
-	this.assigned = new DynamicListModel();
-	this.gatherer = gatherer;
-	this.manager = manager;
-	this.unresolved_commands = new ArrayList();
+    public ClassifierManager() {
+	super(CollectionDesignManager.collect_config.getDocumentElement(), CollectionConfiguration.CLASSIFY_ELEMENT, new Classifier());
+	this.model = this;
+	Gatherer.println("ClassifierManager: " + getSize() + " classifiers parsed.");
+	// Reload/Create the library
 	loadClassifiers();
 	saveClassifiers();
-	// Register as a MSMListener.
-	Gatherer.c_man.getCollection().msm.addMSMListener(this);
-    }
-    /** Method to add a new classifier to reserve.
-	  * @param classifier The new <strong>Classifier</strong>.
+    }
+
+    /** Method to add a new classifier to library.
+     * @param classifier The new <strong>Classifier</strong>.
+     * @see org.greenstone.gatherer.cdm.DynamicListModel
+     */
+    public void addClassifier(Classifier classifier) {
+	if(!library.contains(classifier)) {
+	    library.add(classifier);
+	}
+    }
+
+    /** Method to assign a classifier.
+     * @param classifier The base <strong>Classifier</strong> to assign.
+     * @see org.greenstone.gatherer.cdm.DynamicListModel
+     */
+    public void assignClassifier(Classifier classifier) {
+	if(!contains(classifier)) {
+	    Element element = classifier.getElement();
+	    // Locate where we should insert this new classifier. 
+	    Node target_node = CollectionConfiguration.findInsertionPoint(element);
+	    add(root, classifier, target_node);
+	    Gatherer.c_man.configurationChanged();
+	}
+    }
+
+    /** Destructor.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.cdm.CollectionDesignManager
+     * @see org.greenstone.gatherer.cdm.DynamicListModel
+     */
+    public void destroy() {
+	if(controls != null) {
+	    controls.destroy();
+	    controls = null;
+	}
+	library.clear();
+	library = null;
+    }
+
+    public Classifier getBaseClassifier(String name) {
+	int library_size = library.size();
+	for(int i = 0; i < library_size; i++) {
+	    Classifier classifier = (Classifier) library.get(i);
+	    if(classifier.getName().equals(name)) {
+		return classifier;
+	    }
+	}
+	// No success.
+	return null;
+    }
+
+    /** Method to retrieve the classifier with the given index.
+     * @param index The index of the desired classifier as an <i>int</i>.
+     * @return The requested Classifier or <i>null</i> if no such classifier exists.
+     */
+    public Classifier getClassifier(int index) {
+	if(0 <= index && index < getSize()) {
+	    return (Classifier) getElementAt(index);
+	}
+	return null;
+    }
+    
+    /** Method to retrieve the control for this manager.
+     * @return the Control for editing classifiers
+     */
+    public Control getControls() {
+	if(controls == null) {
+	    // Build controls
+	    this.controls = new ClassifierControl();
+	}
+	return controls;
+    }
+
+    /** Method to move a classifier in the list order.
+     * @param classifier the Classifier you want to move.
+     * @param direction true to move the classifier up, false to move it down.
+     * @param all true to move to move all the way, false for a single step.
+     */
+    public void moveClassifier(Classifier classifier, boolean direction, boolean all) {
+	if(getSize() < 2) {
+	    Gatherer.println("Not enough classifiers to allow moving.");
+	    return;
+	}
+	if(all) {
+	    // Move to top
+	    if(direction) {
+		// Remove the moving classifier
+		remove(classifier);
+		// Retrieve the first classifier
+		Classifier first_classifier = (Classifier) getElementAt(0);
+		// Add the moving classifier before the first classifier
+		addBefore(classifier, first_classifier);
+		first_classifier = null;
+		Gatherer.c_man.configurationChanged();
+	    }
+	    else {
+		// Remove the moving classifier
+		remove(classifier);
+		// And add after last classifier
+		add(getSize(), classifier);
+	    }
+	}
+	else {
+	    // Try to move the classifier one step in the desired direction.
+	    int index = indexOf(classifier);
+	    ///ystem.err.println("Index of " + classifier + " = " + index);
+	    if(direction) {
+		index--;
+		if(index < 0) {
+		    String args[] = new String[2];
+		    args[0] = get("CDM.ClassifierManager.Classifier");
+		    args[1] = classifier.getName();
+		    JOptionPane.showMessageDialog(Gatherer.g_man, get("CDM.Move.At_Top", args), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
+		    return;
+		}
+		remove(classifier);
+		add(index, classifier);
+		Gatherer.c_man.configurationChanged(); 
+	    }
+	    else {
+		index++;
+		if(index >= getSize()) {
+		    String args[] = new String[2];
+		    args[0] = get("CDM.ClassifierManager.Classifier_Str");
+		    args[1] = classifier.getName();
+		    JOptionPane.showMessageDialog(Gatherer.g_man, get("CDM.Move.At_Bottom", args), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
+		    return;
+		}
+		remove(classifier);
+		add(index, classifier);
+		Gatherer.c_man.configurationChanged();
+	    }
+	}
+    }
+
+    /** This method removes an assigned classifier. I was tempted to call it unassign, but remove is more consistant. Note that there is no way to remove a classifier from the library.
+	  * @param classifier The Classifier to remove
 	  * @see org.greenstone.gatherer.cdm.DynamicListModel
 	  */
-    public void addClassifier(Classifier classifier) {
-	if(!reserve.contains(classifier)) {
-	    reserve.addElement(classifier);
-	}
-    }
-    /** Method to assign a classifier.
-	  * @param classifier The reserve <strong>Classifier</strong> to assign.
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
+    public void removeClassifier(Classifier classifier) {
+	remove(classifier);
+	Gatherer.c_man.configurationChanged();
+    }
+
+    /** Method to cache the current contents of library (known classifiers) to file.
+     * @see org.greenstone.gatherer.util.Utility
+     */
+    public void saveClassifiers() {
+	try {
+	    FileOutputStream file = new FileOutputStream(Utility.BASE_DIR + "classifiers.dat");
+	    ObjectOutputStream out = new ObjectOutputStream(file);
+	    out.writeObject(library);
+	    out.close();
+	}
+	catch (Exception error) {
+	}
+    }
+
+    /** Retrieve a phrase from the dictionary based on a certain key.
+	  * @param key The search <strong>String</strong>.
+	  * @return The matching phrase from the Dictionary.
 	  */
-    public void assignClassifier(Classifier classifier) {
-	if(!assigned.contains(classifier)) {
-	    assigned.addElement(classifier);
-	    classifier.setManager(this);
-	    gatherer.c_man.configurationChanged();
-	}
-    }
-    /** Method to assign a classifier.
-	  * @param classifier The <strong>CustomClassifier</strong> to assign.
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
+    private String get(String key) {
+	return get(key, (String[])null);
+    }
+    /** Retrieve a phrase from the dictionary based on a certain key and certain arguments.
+	  * @param key The search <strong>String</strong>.
+	  * @param args A <strong>String[]</strong> used to complete and format the returned phrase.
+	  * @return The matching phrase from the Dictionary.
+	  * @see org.greenstone.gatherer.Dictionary
+	  * @see org.greenstone.gatherer.Gatherer
 	  */
-    public void assignClassifier(CustomClassifier classifier) {
-	if(!assigned.contains(classifier)) {
-	    assigned.addElement(classifier);
-	    classifier.setManager(this);
-	    gatherer.c_man.configurationChanged();
-	}
-    }
-    /** Destructor.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.cdm.CollectionDesignManager
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
-	  */
-    public void destroy() {
-	// Deregister as a listener
-	if(gatherer.c_man != null && gatherer.c_man.msm != null) {
-	    gatherer.c_man.msm.removeMSMListener(this);
-	}
-	// Null globals
-	assigned = null;
-	controls = null;
-	gatherer = null;
-	manager = null;
-	reserve = null;
-	unresolved_commands = null;
-    }
-    /** Method to retrieve the classifier with the given index.
-	  * @param index The index of the desired classifier as an <i>int</i>.
-	  * @return The requested Classifier as an <strong>Object</strong> or <i>null</i> if no such classifier exists.
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
-	  */
-    public Object getClassifier(int index) {
-	if(0 <= index && index < assigned.size()) {
-	    return assigned.get(index);
-	}
-	return null;
-    }
-    /** Method to retrieve the named classifier.
-	  * @param name The name of the desired classifier as a <strong>String</strong>.
-	  * @return The requested <strong>Classifier</strong> or <i>null</i> if no such classifier exists.
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
-	  */
-    public Classifier getClassifier(String name) {
-	for(int i = 0; i < reserve.size(); i++) {
-	    Classifier classifier = (Classifier)reserve.get(i);
-	    if(classifier.getName().equals(name)) {
-		return classifier;
-	    }
-	}
-	// No success.
-	return null;
-    }
-    /** Method to retrieve the control for this manager.
-	  * @return A <strong>JPanel</strong> containing the controls.
-	  */
-    public JPanel getControls() {
-	if(controls == null) {
-	    controls = new Control();
-	}
-	return controls;
-    }
-    /** Called whenever a metadata element changes significantly.
-	  * @param event A <strong>MSMEvent</strong> choc' full of event informationy goodness.
-	  */
-    public void elementChanged(MSMEvent event) {
-	// Don't really care, as the elements dealt with here are all live references so changes like identifier change will propagate immediately.
-    }
-    /** Method to find the index of the given classifier within the assigned classifiers.
-	  * @param classifier The <strong>Classifier</strong> whose index you wish to find.
-	  * @return The index of the classifier as an <i>int</i>, which has a value of -1 if the classifier was not found.
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
-	  */
-    public int indexOf(Classifier classifier) {
-	for(int i = 0; i < assigned.size(); i++) {
-	    Object elem = assigned.get(i);
-	    if (elem instanceof Classifier) {
-		Classifier sibling = (Classifier)elem;
-		if(sibling.equals(classifier)) {
-		    return i;
-		}
-	    }
-	}
-	return -1;
-    }
-    // these two methods assume that a custome classifier can never be the same as a classifier
-    public int indexOf(CustomClassifier classifier) {
-	for(int i = 0; i < assigned.size(); i++) {
-	    Object elem = assigned.get(i);
-	    if (elem instanceof CustomClassifier) {
-		CustomClassifier sibling = (CustomClassifier) assigned.get(i);
-		if(sibling.equals(classifier)) {
-		    return i;
-		}
-	    }
-	}
-	return -1;
-    }
-    /** Method to invalidate controls after a significant change in the system state.
-	  */
-    public void invalidateControls() {
-	if(controls != null) {
-	    controls.destroy();
-	}
-	controls = null;
-    }
+    private String get(String key, String arg) {
+	String[] args = null;
+	if(arg != null) {
+	    args = new String[1];
+	    args[0] = arg;
+	}
+	return get(key, args);
+    }
+
+    private String get(String key, String[] args) {
+	if(key.indexOf(".") == -1) {
+	    key = "CDM.ClassifierManager." + key;
+	}
+	return Gatherer.dictionary.get(key, args);
+    }
+
+    /** Method to extract just the classifiers name from a file object.
+     * @param classifier The <strong>File</strong> which references a certain classifier.
+     * @return A <strong>String</strong> containing just the classifiers name, without extension.
+     */
+    private String getClassifierName(File classifier) {
+	String name = classifier.getName();
+	if(name.indexOf(".") != -1) {
+	    name = name.substring(0, name.indexOf("."));
+	}
+	return name;
+    }
+
     /** Method to load the details of a single plug-in.
-	  * @param classifier The classifier <strong>File</strong> you wish to load.
-	  */
-    public void loadClassifier(File classifier) {
+     * @param classifier The classifier <strong>File</strong> you wish to load.
+     */
+    private void loadClassifier(File classifier) {
 	///ystem.err.println("Attempting to parse " + classifier.toString());
 	Document document = null;
@@ -274,11 +300,11 @@
 	    if(Utility.isWindows()) {
 		args = new String[4];
-		if(gatherer.config.perl_path != null) {
-		    args[0] = gatherer.config.perl_path;
+		if(Gatherer.config.perl_path != null) {
+		    args[0] = Gatherer.config.perl_path;
 		}
 		else {
 		    args[0] = "Perl.exe";
 		}
-		args[1] = gatherer.config.gsdl_path + "bin" + File.separator + "script" + File.separator + "classinfo.pl";
+		args[1] = Gatherer.config.gsdl_path + "bin" + File.separator + "script" + File.separator + "classinfo.pl";
 		args[2] = "-xml";
 		args[3] = getClassifierName(classifier);
@@ -334,266 +360,11 @@
 	}
 	if(document != null) {
-	    parse(document.getDocumentElement());
-	}
-    }
-    /** Called whenever the metadata value associated to a certain record changes. */
-    public void metadataChanged(MSMEvent event) {
-	FileNode record = event.getRecord();
-	if(record != null) {
-	    for(int i = 0; i < assigned.size(); i++) {
-		Object object = assigned.get(i);
-		if(object instanceof CustomClassifier) {
-		    CustomClassifier classifier = (CustomClassifier) object;
-		    if(!classifier.isProcessing()) { // are we already in the middle of processing??
-			classifier.process(record);
-		    } 
-		}
-	    }
-	}
-    }
-    /** This method attempts to parse a classifier command from a command string taken from the collection configuration file. This process is quite complex as not only must the correct classifier be matched but also all of the parameters given must be legal. If such a command is found, the classifier is immediately assigned.
-	  * @param command The command <strong>String</strong> that may include classifier information.
-	  * @return <i>true</i> if a classifier command was parsed, <i>false</i> otherwise.
-	  * @see org.greenstone.gatherer.cdm.Argument
-	  * @see org.greenstone.gatherer.cdm.Classifier
-	  * @see org.greenstone.gatherer.cdm.CommandTokenizer
-	  */
-    public boolean parse(String command) {
-	String command_lc = command.toLowerCase();
-	if(command_lc.startsWith("classify")) {
-	    CommandTokenizer tokenizer = new CommandTokenizer(command);
-	    if(tokenizer.countTokens() >= 2) {
-		tokenizer.nextToken(); // Throw away 'classifier'
-		String name = tokenizer.nextToken();
-		// Try to locate the classifier with this name.
-		Classifier classifier = getClassifier(name);
-		// And if successful start to parse the arguments.
-		if(classifier != null) {
-		    // Take a copy.
-		    classifier = classifier.copy();
-		    String key = null;
-		    while((key = tokenizer.nextToken()) != null) {
-			// Try to retrieve a matching argument.
-			Argument argument = classifier.getArgument(key);
-			if(argument != null) {
-			    // Set as assigned.
-			    argument.setAssigned(true);
-			    // And if the argument is of a parameter type, parse a parameter.
-			    if(argument.getType() != Argument.FLAG && tokenizer.hasMoreTokens()) {
-				String value = tokenizer.nextToken();
-				ElementWrapper element = null;
-				// special check for metadata
-				if (argument.getType() == Argument.METADATA) {
-				    value = value.replace(':', MSMUtils.NS_SEP);
-				    if (value.indexOf(MSMUtils.NS_SEP)==-1){
-					value = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + value;
-				    }
-				    // Now retrieve the element this refers to, if available.
-				    element = Gatherer.c_man.getCollection().msm.getElement(value);
-				}
-				if(element != null) {
-				    argument.setElementValue(element);
-				    element = null;
-				}
-				else {
-				    argument.setValue(value);
-				}
-			    }
-			}
-			// Argument cannot be matched.
-			else {
-			    String cur_key = key;
-			    String value = tokenizer.nextToken();
-			    if(value.startsWith("-")) {
-				key = value;
-				value = null;
-			    }
-			    else {
-				key = null;
-			    }
-			    String custom = classifier.getCustom();
-			    if(custom == null) {
-				if(value == null) {
-				    classifier.setCustom(cur_key);
-				}
-				else {
-				    classifier.setCustom(cur_key + " " + value);
-				}
-			    }
-			    else {
-				if(value == null) {
-				    classifier.setCustom(custom + " " + cur_key);
-				}
-				else {
-				    classifier.setCustom(custom + " " + cur_key + " " + value);
-				}
-			    }
-			}
-		    }
-		    assignClassifier(classifier);
-		    return true;
-		}
-		else {
-		    ///ystem.err.println("Unknown classifier");
-		}
-	    }
-	}
-	else if(command_lc.startsWith("customclassifier")) {
-	    unresolved_commands.add(command);
-	    return true;
-	}
-	return false;
-    }
-    /** This method removes an assigned classifier. I was tempted to call it unassign, but remove is more consistant. Note that there is no way to remove a classifier from the reserve.
-	  * @param classifier The Classifier or CustomClassifier, as an <strong>Object</strong>, to remove.
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
-	  */
-    public void removeClassifier(Object classifier) {
-	assigned.removeElement(classifier);
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method which attempts to reparse obvious classifier commands which previously referenced unresovable Classifiers.
-	  * @see org.greenstone.gatherer.cdm.Classifier
-	  * @see org.greenstone.gatherer.cdm.CommandTokenizer
-	  * @see org.greenstone.gatherer.cdm.CustomClassifier
-	  */
-    public void reparseUnresolved() {
-	for(int i = 0; i < unresolved_commands.size(); i++) {
-	    String command = (String) unresolved_commands.get(i);
-	    CommandTokenizer tokenizer = new CommandTokenizer(command);
-	    if(tokenizer.countTokens() >= 6) {
-		tokenizer.nextToken();// Lose customclassifier
-		// Get class name.
-		String class_name = tokenizer.nextToken();
-		// Parse arguments.
-		String replaces = null;
-		String separations = null;
-		while(tokenizer.hasMoreTokens()) {
-		    String arg_name = tokenizer.nextToken();
-		    if(arg_name.equalsIgnoreCase("-replaces")) {
-			replaces = tokenizer.nextToken();
-		    }
-		    else if (arg_name.equalsIgnoreCase("-separations")){
-			separations = tokenizer.nextToken();
-		    }
-		}
-		try {
-		    replaces = replaces.substring(2);
-		    int index = Integer.parseInt(replaces);
-		    Classifier original = (Classifier)getClassifier(index);
-		    if(original != null) {
-			Class custom_classifier_class = Class.forName("org.greenstone.gatherer.cdm.custom." + class_name);
-			CustomClassifier custom_classifier = (CustomClassifier) custom_classifier_class.newInstance();
-			custom_classifier.setGatherer(gatherer);
-			custom_classifier.recreate(original, separations);
-			assigned.add(index, custom_classifier);
-			assigned.removeElement(original);
-		    }
-		    else {
-			///ystem.err.println("Missing original.");
-		    }
-		}
-		catch (Exception error) {
-		    error.printStackTrace();
-		}
-	    }
-	}
-	// Regardless of if they work, clear the commands.
-	unresolved_commands.clear();
-    }
-    /** Method to cache the current contents of reserve (known classifiers) to file.
-	  * @see org.greenstone.gatherer.util.Utility
-	  */
-    public void saveClassifiers() {
-	try {
-	    FileOutputStream file = new FileOutputStream(Utility.BASE_DIR + "classifiers.dat");
-	    ObjectOutputStream out = new ObjectOutputStream(file);
-	    out.writeObject(reserve);
-	    out.close();
-	}
-	catch (Exception error) {
-	}
-    }
-    /** Called when a metadata set changed significantly.
-	  * @param event A <strong>MSMEvent</strong> containing information about the set change.
-	  */
-    public void setChanged(MSMEvent event) {
-	// Again, we would only worry about this if we contained 'inanimate' references to elements or something, but our references are live, and controls are rebuilt everytime a pop-up is needed.
-    }
-    /** Method used to determine the number of classifiers that have been assigned.
-	  * @return An <i>int</i> which is the number of classifiers.
-	  */
-    public int size() {
-	return assigned.size();
-    }
-    /** Method to print out a block of classifier commands, much like you'd find in a collection configuration file.
-	  * @return A <strong>String</strong> containing a series of classifier commands separated by new lines.
-	  * @see org.greenstone.gatherer.cdm.Classifier
-	  * @see org.greenstone.gatherer.cdm.CustomClassifier
-	  */
-    public String toString() {
-	StringBuffer text = new StringBuffer();
-	for(int i = 0; i < assigned.size(); i++) {
-	    Object object = assigned.get(i);
-	    if(object instanceof Classifier) {
-		Classifier classifier = (Classifier) object;
-		text.append(classifier.toStringConfig());
-	    }
-	    else if(object instanceof CustomClassifier) {
-		CustomClassifier classifier = (CustomClassifier) object;
-		text.append(classifier.getCommand());
-		text.append("\n");
-		text.append(classifier.getCustomCommand(i));
-	    }
-	    text.append("\n");
-	}
-	text.append("\n");
-	return text.toString();
-    }
-    /** Called when a significant change has occured to a value tree for a certain element, however we take no further action.
-	  * @param event A <strong>MSMEvent</strong> containing information relevant to the event.
-	  */
-    public void valueChanged(MSMEvent event) {
-    }
-    /** Retrieve a phrase from the dictionary based on a certain key.
-	  * @param key The search <strong>String</strong>.
-	  * @return The matching phrase from the Dictionary.
-	  */
-    private String get(String key) {
-	return get(key, null);
-    }
-    /** Retrieve a phrase from the dictionary based on a certain key and certain arguments.
-	  * @param key The search <strong>String</strong>.
-	  * @param args A <strong>String[]</strong> used to complete and format the returned phrase.
-	  * @return The matching phrase from the Dictionary.
-	  * @see org.greenstone.gatherer.Dictionary
-	  * @see org.greenstone.gatherer.Gatherer
-	  */
-    private String get(String key, String arg) {
-	String[] args = null;
-	if(arg != null) {
-	    args = new String[1];
-	    args[0] = arg;
-	}
-	if(key.indexOf(".") == -1) {
-	    key = "CDM.ClassifierManager." + key;
-	}
-	return gatherer.dictionary.get(key, args);
-    }
-    /** Method to extract just the classifiers name from a file object.
-	  * @param classifier The <strong>File</strong> which references a certain classifier.
-	  * @return A <strong>String</strong> containing just the classifiers name, without extension.
-	  */
-    private String getClassifierName(File classifier) {
-	String name = classifier.getName();
-	if(name.indexOf(".") != -1) {
-	    name = name.substring(0, name.indexOf("."));
-	}
-	return name;
-    }
+	    parseXML(document.getDocumentElement());
+	}
+    }
+
     /** Method to initially load information from the standard plug-ins within the gsdl Perl library.
-	  * @see org.greenstone.gatherer.cdm.DynamicListModel
-	  * @see org.greenstone.gatherer.util.Utility
-	  */
+     * @see org.greenstone.gatherer.util.Utility
+     */
     private void loadClassifiers() {
 	// Attempt to restore the cached file.
@@ -601,37 +372,31 @@
 	    FileInputStream file = new FileInputStream(Utility.BASE_DIR + "classifiers.dat");
 	    ObjectInputStream input = new ObjectInputStream(file);
-	    reserve = (DynamicListModel) input.readObject();
+	    library = (ArrayList) input.readObject();
 	}
 	catch (Exception error) {
 	}
-	if(reserve == null) {
-	    reserve = new DynamicListModel();
-				// Retrieve the gsdl home directory...
-	    String directory = gatherer.config.gsdl_path;
+	if(library == null) {
+	    library = new ArrayList();
+	    // Retrieve the gsdl home directory...
+	    String directory = Gatherer.config.gsdl_path;
 	    directory = directory + "perllib" + File.separator + "classify" + File.separator;
-	    loadClassifiers(new File(directory));
-	}
-    }
-    /** Method to load plug-in information from a specified directory. Of course no plug-ins may be found at this location.
-	  * @param directory A <strong>File</strong> indicating the directory to be scanned for plug-ins.
-	  * @see org.greenstone.gatherer.cdm.ParsingProgress
-	  */
-    private void loadClassifiers(File directory) {
-	File files[] = directory.listFiles();
-	if(files != null) {
-	    // Create a progress indicator.
-	    ParsingProgress progress = new ParsingProgress(get("CDM.ClassifierManager.Parsing.Title"), get("CDM.ClassifierManager.Parsing.Message"), files.length);
-	    for(int i = 0; i < files.length; i++) {
-		// We only want to check Perl Modules.
-		if(files[i].getName().endsWith(".pm")) {
-		    loadClassifier(files[i]);
-		}
-		progress.inc();
-	    }
-	    progress.dispose();
-	    progress.destroy();
-	    progress = null;
-	}
-    }
+	    File files[] = (new File(directory)).listFiles();
+	    if(files != null) {
+		// Create a progress indicator.
+		ParsingProgress progress = new ParsingProgress(get("CDM.ClassifierManager.Parsing.Title"), get("CDM.ClassifierManager.Parsing.Message"), files.length);
+		for(int i = 0; i < files.length; i++) {
+		    // We only want to check Perl Modules.
+		    if(files[i].getName().endsWith(".pm")) {
+			loadClassifier(files[i]);
+		    }
+		    progress.inc();
+		}
+		progress.dispose();
+		progress.destroy();
+		progress = null;
+	    }
+	}
+    }
+
     /** Parses a DOM tree model turning it into a Classifier and its associated arguments.
 	  * @param root The <strong>Node</strong> at the root of the DOM model.
@@ -639,5 +404,5 @@
 	  * @see org.greenstone.gatherer.cdm.Argument
 	  */
-    private Classifier parse(Node root) {
+    private Classifier parseXML(Node root) {
 	Classifier classifier = new Classifier();
 	String node_name = null;
@@ -648,5 +413,5 @@
 		String name = MSMUtils.getValue(node);
 		// We can save ourselves some processing time if a classifier with this name already exists in our manager. If so retrieve it and return it.
-		Classifier existing = getClassifier(name);
+		Classifier existing = getBaseClassifier(name);
 		if(existing != null) {
 		    return existing;
@@ -655,5 +420,5 @@
 	    }
 	    else if(node_name.equals("Desc")) {
-		classifier.setDesc(MSMUtils.getValue(node));
+		classifier.setDescription(MSMUtils.getValue(node));
 	    }
 	    // Parse the multitude of arguments.
@@ -671,5 +436,5 @@
 			    }
 			    else if(node_name.equals("Desc")) {
-				argument.setDesc(MSMUtils.getValue(det));
+				argument.setDescription(MSMUtils.getValue(det));
 			    }
 			    else if(node_name.equals("Type")) {
@@ -677,5 +442,5 @@
 			    }
 			    else if(node_name.equals("Default")) {
-				argument.setDefault(MSMUtils.getValue(det));
+				argument.setDefaultValue(MSMUtils.getValue(det));
 			    }
 			    else if(node_name.equals("List")) {
@@ -713,5 +478,5 @@
 		    // A super classifier class.
 		    else if(node_name.equals("ClasInfo")) {
-			Classifier super_classifier = parse(arg);
+			Classifier super_classifier = parseXML(arg);
 			classifier.setSuper(super_classifier);
 		    }
@@ -726,14 +491,25 @@
     }
     /** A class which provides controls for assigned and editing classifiers. */
-    private class Control
-	extends JPanel {
+    private class ClassifierControl
+	extends JPanel 
+	implements Control {
+	/** A combobox containing all of the known classifiers, including those that may have already been assigned. */
+	private GComboBox classifier = null;
 	/** Button for adding classifiers. */
 	private JButton add = null;
 	/** Button for configuring the selected classifier. */
 	private JButton configure = null;
+
+	private JButton move_bottom_button;
+
+	private JButton move_down_button;
+
+	private JButton move_top_button;
+
+	private JButton move_up_button;
+
 	/** Button to remove the selected classifier. */
 	private JButton remove = null;
-	/** A combobox containing all of the known classifiers, including those that may have already been assigned. */
-	private JComboBox classifier = null;
+
 	/** A list of assigned classifiers. */
 	private JList classifier_list = null;
@@ -745,13 +521,6 @@
 	 * @see org.greenstone.gatherer.cdm.ClassifierManager.Control.RemoveListener
 	 */
-	public Control() {
-	    Object classifiers[] = reserve.toArray();
-	    ArrayList classifier_model = new ArrayList();
-	    for(int i = 0; i < classifiers.length; i++) {
-		classifier_model.add(((Classifier)classifiers[i]).getName());
-	    }
-	    // Now we add custom classifiers.
-	    addCustomClassifiers(classifier_model);
-	    Collections.sort(classifier_model);
+	public ClassifierControl() {
+	    Collections.sort(library);
 	    // Create
 	    add = new JButton(get("Add"));
@@ -766,8 +535,15 @@
 	    instructions.setRows(5);
 	    instructions.setWrapStyleWord(true);
-	    classifier = new JComboBox(classifier_model.toArray());
-	    classifier.setEditable(false);
+
+	    classifier = new GComboBox(library.toArray());
+	    classifier.setBackgroundNonSelectionColor(Gatherer.config.getColor("coloring.editable", false));
+	    classifier.setBackgroundSelectionColor(Gatherer.config.getColor("coloring.collection_selection_background", false));
+	    classifier.setEditable(true);
+	    classifier.setSelectedIndex(0);
+	    classifier.setTextNonSelectionColor(Gatherer.config.getColor("coloring.workspace_tree_foreground", false));
+	    classifier.setTextSelectionColor(Gatherer.config.getColor("coloring.collection_selection_foreground", false));
+
 	    JLabel classifier_label = new JLabel(get("Classifier"));
-	    classifier_list = new JList(assigned);
+	    classifier_list = new JList(model);
 	    classifier_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 	    JLabel classifier_list_label = new JLabel(get("Assigned"));
@@ -782,4 +558,51 @@
 	    title.setOpaque(true);
 	    JPanel temp = new JPanel(new BorderLayout());
+
+	    JPanel move_button_pane = new JPanel();
+
+	    move_bottom_button = new JButton();
+	    JLabel move_bottom_label = new JLabel(get("CDM.Move.Move_Bottom"));
+	    move_bottom_label.setHorizontalAlignment(JLabel.CENTER);
+	    move_bottom_label.setPreferredSize(LABEL_SIZE);
+	    ImageIcon move_bottom_icon = Utility.getImage("arrow-bottom.gif");
+	    move_bottom_button.setLayout(new BorderLayout());
+	    move_bottom_button.add(new JLabel(move_bottom_icon), BorderLayout.WEST);
+	    move_bottom_button.add(move_bottom_label, BorderLayout.CENTER);
+	    move_bottom_button.add(new JLabel(move_bottom_icon), BorderLayout.EAST);
+	    move_bottom_button.setMnemonic(KeyEvent.VK_B);
+
+	    move_down_button = new JButton();
+	    JLabel move_down_label = new JLabel(get("CDM.Move.Move_Down"));
+	    move_down_label.setHorizontalAlignment(JLabel.CENTER);
+	    move_down_label.setPreferredSize(LABEL_SIZE);
+	    ImageIcon move_down_icon = Utility.getImage("arrow-down.gif");
+	    move_down_button.setLayout(new BorderLayout());
+	    move_down_button.add(new JLabel(move_down_icon), BorderLayout.WEST);
+	    move_down_button.add(move_down_label, BorderLayout.CENTER);
+	    move_down_button.add(new JLabel(move_down_icon), BorderLayout.EAST);
+	    move_down_button.setMnemonic(KeyEvent.VK_D);
+
+	    move_top_button = new JButton();
+	    JLabel move_top_label = new JLabel(get("CDM.Move.Move_Top"));
+	    move_top_label.setHorizontalAlignment(JLabel.CENTER);
+	    move_top_label.setPreferredSize(LABEL_SIZE);
+	    ImageIcon move_top_icon = Utility.getImage("arrow-top.gif");
+	    move_top_button.setLayout(new BorderLayout());
+	    move_top_button.add(new JLabel(move_top_icon), BorderLayout.WEST);
+	    move_top_button.add(move_top_label, BorderLayout.CENTER);
+	    move_top_button.add(new JLabel(move_top_icon), BorderLayout.EAST);
+	    move_top_button.setMnemonic(KeyEvent.VK_T);
+
+	    move_up_button = new JButton();
+	    JLabel move_up_label = new JLabel(get("CDM.Move.Move_Up"));
+	    move_up_label.setHorizontalAlignment(JLabel.CENTER);
+	    move_up_label.setPreferredSize(LABEL_SIZE);
+	    ImageIcon move_up_icon = Utility.getImage("arrow-up.gif");
+	    move_up_button.setLayout(new BorderLayout());
+	    move_up_button.add(new JLabel(move_up_icon), BorderLayout.WEST);
+	    move_up_button.add(move_up_label, BorderLayout.CENTER);
+	    move_up_button.add(new JLabel(move_up_icon), BorderLayout.EAST);
+	    move_up_button.setMnemonic(KeyEvent.VK_U);
+
 	    // Listeners
 	    add.addActionListener(new AddListener());
@@ -788,30 +611,53 @@
 	    classifier_list.addMouseListener(new ClickListener());
 	    classifier_list.addListSelectionListener(new ListListener());
+
+	    MoveListener ml = new MoveListener();
+	    move_bottom_button.addActionListener(ml);
+	    move_down_button.addActionListener(ml);
+	    move_top_button.addActionListener(ml);
+	    move_up_button.addActionListener(ml);
 	    // Layout
 	    title.setBorder(BorderFactory.createEmptyBorder(0,0,2,0));
+
 	    instructions.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
 	    header_pane.setLayout(new BorderLayout());
 	    header_pane.add(title, BorderLayout.NORTH);
 	    header_pane.add(new JScrollPane(instructions), BorderLayout.CENTER);
+
+	    move_button_pane.setLayout(new GridLayout(4,1));
+	    move_button_pane.add(move_top_button);
+	    move_button_pane.add(move_up_button);
+	    move_button_pane.add(move_down_button);
+	    move_button_pane.add(move_bottom_button);
+
 	    classifier_list_label.setBorder(BorderFactory.createEmptyBorder(0,2,0,2));
+
 	    classifier_list_pane.setLayout(new BorderLayout());
 	    classifier_list_pane.add(classifier_list_label, BorderLayout.NORTH);
 	    classifier_list_pane.add(new JScrollPane(classifier_list), BorderLayout.CENTER);
+	    classifier_list_pane.add(move_button_pane, BorderLayout.EAST);
+
 	    classifier_label.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
+
 	    classifier_pane.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
 	    classifier_pane.setLayout(new GridLayout(1,2));
 	    classifier_pane.add(classifier_label);
 	    classifier_pane.add(classifier);
+
 	    button_pane.setLayout(new GridLayout(3,1));
 	    button_pane.add(add);
 	    button_pane.add(configure);
 	    button_pane.add(remove);
+
 	    // Scope these mad bordering skillz.
 	    temp.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5,0,5,0), BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(get("Controls")), BorderFactory.createEmptyBorder(2,2,2,2))));
 	    temp.add(classifier_pane, BorderLayout.NORTH);
 	    temp.add(button_pane, BorderLayout.SOUTH);
+
 	    central_pane.setLayout(new BorderLayout());
 	    central_pane.add(classifier_list_pane, BorderLayout.CENTER);
 	    central_pane.add(temp, BorderLayout.SOUTH);
+
 	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
 	    setLayout(new BorderLayout());
@@ -831,55 +677,15 @@
 	/** This method is overridden to ensure the instructions are scrolled to top, before the super classes updateUI() is called.
 			*/
-	public void updateUI() {
+	public void gainFocus() {
 	    if(instructions != null) {
 		instructions.setCaretPosition(0);
 	    }
-	    super.updateUI();
-	}
-	/** Searches and adds a list of dynamically located CustomClassifiers. Note that the classes must be located under org.greenstone.gatherer.cdm.custom and have accompaning properties files which are used as dictionaries.
-			* @param classifier_model An <strong>ArrayList</strong> which will be used as the model for the combobox listing all known Classifiers.
-			*/
-	private void addCustomClassifiers(ArrayList classifier_model) {
-	    //classifier_model.add("CustomAZList");
-	    // Search for classifiers under the org.greenstone.gatherer.cdm.custom directory.
-	    File custom_directory = new File(Utility.BASE_DIR + "classes" + File.separator + "org" + File.separator + "greenstone" + File.separator + "gatherer" + File.separator + "cdm" + File.separator + "custom");
-	    if(custom_directory.exists()) {
-		File children[] = custom_directory.listFiles();
-		for(int i = 0; i < children.length; i++) {
-		    String temp = children[i].getName().toLowerCase();
-		    // There are a whole bunch of conditions about what files are custom classifier main classes.
-		    if(temp.endsWith(".class") && temp.indexOf("$") == -1) {
-			// Determine the name of this custom classifier.
-			String name = children[i].getName();
-			name = name.substring(0, name.indexOf("."));
-			classifier_model.add(name);
-		    }
-		}
-	    }
-	    // Search for any other CustomClassifiers within the jar file (if present)
-	    File jar_file = new File(Utility.GLI_ARCHIVE);
-	    if(jar_file.exists()) {
-		try {
-		    JarFile jar = new JarFile(jar_file);
-		    for(Enumeration entries = jar.entries(); entries.hasMoreElements(); ) {
-			String name = entries.nextElement().toString();
-			if(name.startsWith("org/greenstone/gatherer/cdm/custom/") && name.endsWith(".class") && name.indexOf("$") == -1) {
-			    name = name.substring(35, name.length() - 6);
-			    if(!classifier_model.contains(name)) {
-				classifier_model.add(name);
-			    }
-			}
-			name = null;
-		    }
-		    jar = null;
-		}
-		catch (Exception error) {
-		    error.printStackTrace();
-		}
-	    }
-	    jar_file = null;
-	}
+	}
+
+	public void loseFocus() {
+	}
+
 	/** This class listens for actions upon the add button in the controls, and if detected calls the assignClassifier() method.
-			*/
+	 */
 	private class AddListener
 	    implements ActionListener {
@@ -892,42 +698,38 @@
 	     */
 	    public void actionPerformed(ActionEvent event) {
-		String name = (String)classifier.getSelectedItem();
-		Classifier target = getClassifier(name);
-		Classifier classifier = null;
-		CustomClassifier custom_classifier = null;
-		if(target != null) {
-		    classifier = target.copy();
-		}
-		else {
-		    // Try to retrieve custom classifier for name.
-		    try {
-			Class custom_class = Class.forName("org.greenstone.gatherer.cdm.custom." + name);
-			custom_classifier = (CustomClassifier)custom_class.newInstance();
-			custom_classifier.setGatherer(gatherer);
-		    }
-		    catch (Exception error) {
-			Gatherer.println("Error in ClassifierManager.AddListener.actionPerformed(): " + error);
-			Gatherer.printStackTrace(error);
-			
-		    }
-		    // And if all else fails create a new classifier.
-		    if(classifier == null && custom_classifier == null) {
-			classifier = new Classifier(name, "", null);
-		    }
-		}
-		if(classifier != null) {
+		Object selected_object = classifier.getSelectedItem();
+		// If there is something in the combobox, but we haven't registered a selection, then add the object and select it!
+		if(selected_object != null && classifier.getSelectedIndex() == -1) {
+		    classifier.insertItemAt(selected_object, classifier.getItemCount());
+		}
+		if(selected_object != null) {
+		    // Create a new element in the DOM
+		    Element element = CollectionDesignManager.collect_config.document.createElement(CollectionConfiguration.CLASSIFY_ELEMENT);
+		    Classifier new_classifier = null;
+		    if(selected_object instanceof Classifier) {
+			Classifier base_classifier = (Classifier) selected_object;
+			element.setAttribute(CollectionConfiguration.TYPE_ATTRIBUTE, base_classifier.getName());
+			new_classifier = new Classifier(element, base_classifier);
+			base_classifier = null;
+		    }
+		    else {
+			element.setAttribute(CollectionConfiguration.TYPE_ATTRIBUTE, selected_object.toString());
+			new_classifier = new Classifier(element, null);
+		    }
+		    element = null;
 		    // Automatically chain to configuration. This ensures required arguments are filled out.
-		    ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, classifier);
+		    ArgumentConfiguration ac = new ArgumentConfiguration(new_classifier);
 		    if(ac.display()) {
-			assignClassifier(classifier);
-		    }
-		    ac.destroy();
+			if(!model.contains(new_classifier)) {
+			    assignClassifier(new_classifier);
+			    classifier_list.setSelectedValue(new_classifier, true);
+			}
+			else {
+			    JOptionPane.showMessageDialog(Gatherer.g_man, get("Classifier_Exists"), get("General.Error"), JOptionPane.ERROR_MESSAGE);
+			}
+		    }
 		    ac = null;
-		}
-		// Custom classifier
-		else {
-		    // Spawn a new thread to handle this, as custom_classifiers can be processor heavy.
-		    CustomClassifierTask task = new CustomClassifierTask(custom_classifier);
-		    task.start();
+		    new_classifier = null;
+		    classifier.setSelectedIndex(0);
 		}
 	    }
@@ -936,27 +738,17 @@
 	private class ClickListener
 	    extends MouseAdapter {
-				/** Called whenever the mouse is clicked over a registered component, we use this to chain through to the configure prompt.
-				 * @param event A <strong>MouseEvent</strong> containing information about the mouse click.
-				 */
+	    /** Called whenever the mouse is clicked over a registered component, we use this to chain through to the configure prompt.
+	     * @param event A <strong>MouseEvent</strong> containing information about the mouse click.
+	     */
 	    public void mouseClicked(MouseEvent event) {
 		if(event.getClickCount() == 2 ) {
 		    if(!classifier_list.isSelectionEmpty()) {
-			Object object = classifier_list.getSelectedValue();
-			if(object instanceof Classifier) {
-			    ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, (Classifier)object);
-			    if(ac.display()) {
-				assigned.refresh();
-			    }
-			    ac.destroy();
-			    ac = null;
+			Classifier classifier = (Classifier) classifier_list.getSelectedValue();
+			ArgumentConfiguration ac = new ArgumentConfiguration(classifier);
+			if(ac.display()) {
+			    refresh(classifier);
 			}
-			else if(object instanceof CustomClassifier) {
-			    CustomClassifier cc = (CustomClassifier)object;
-			    if(cc.display(true)) {
-				assigned.refresh();
-			    }
-			    cc.hide(); // Remove gui prompt or else.
-			    cc = null;
-			}
+			ac.destroy();
+			ac = null;
 		    }
 		}
@@ -975,40 +767,12 @@
 	    public void actionPerformed(ActionEvent event) {
 		if(!classifier_list.isSelectionEmpty()) {
-		    Object object = classifier_list.getSelectedValue();
-		    if(object instanceof Classifier) {
-			ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, (Classifier)object);
-			if(ac.display()) {
-			    assigned.refresh();
-			}
-			ac.destroy();
-			ac = null;
-		    }
-		    else if(object instanceof CustomClassifier) {
-			CustomClassifier cc = (CustomClassifier)object;
-			if(cc.display(true)) {
-			    assigned.refresh();
-			}
-			cc.hide(); // Remove gui prompt or else.
-			cc = null;
-		    }
-		}
-	    }
-	}
-
-	private class CustomClassifierTask
-	    extends Thread {
-
-	    private CustomClassifier custom_classifier;
-
-	    CustomClassifierTask(CustomClassifier custom_classifier) {
-		this.custom_classifier = custom_classifier;
-	    }
-
-	    public void run() {
-		if(custom_classifier.display(true)) {
-		    assignClassifier(custom_classifier);
-		}
-		custom_classifier.hide(); // Remove gui prompt or else.
-		custom_classifier = null;
+		   Classifier classifier = (Classifier) classifier_list.getSelectedValue();
+		   ArgumentConfiguration ac = new ArgumentConfiguration(classifier);
+		   if(ac.display()) {
+		       refresh(classifier);
+		   }
+		   ac.destroy();
+		   ac = null; 
+		}
 	    }
 	}
@@ -1030,4 +794,34 @@
 	    }
 	}
+
+	/** Listens for actions apon the move buttons in the manager controls, and if detected calls the <i>moveClassifier()</i> method of the manager with the appropriate details. */
+	private class MoveListener
+	    implements ActionListener {
+	    /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
+	     * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
+	     */
+	    public void actionPerformed(ActionEvent event) {
+		if(!classifier_list.isSelectionEmpty()) {
+		    Object object = classifier_list.getSelectedValue();
+		    if(object instanceof Classifier) {
+			Classifier classifier = (Classifier) object;
+			if(event.getSource() == move_top_button) {
+			    moveClassifier(classifier, true, true);
+			}
+			else if(event.getSource() == move_up_button) {
+			    moveClassifier(classifier, true, false);
+			}
+			else if(event.getSource() == move_down_button) {
+			    moveClassifier(classifier, false, false);
+			}
+			else {
+			    moveClassifier(classifier, false, true);
+			}
+			classifier_list.setSelectedValue(classifier, true);
+		    }
+		}
+	    }
+	}
+
 	/** This class listens for actions upon the remove button in the controls, and if detected calls the removeClassifier() method.
 			*/
@@ -1041,7 +835,9 @@
 	    public void actionPerformed(ActionEvent event) {
 		if(!classifier_list.isSelectionEmpty()) {
-		    Object object = classifier_list.getSelectedValue();
-		    if(object instanceof Classifier || object instanceof CustomClassifier) {
-			removeClassifier(object);
+		    Object [] objects = classifier_list.getSelectedValues();
+		    for(int i = 0; i < objects.length; i++) {
+			if(objects[i] instanceof Classifier) {
+			    removeClassifier((Classifier)objects[i]);
+			}
 		    }
 		}
@@ -1050,8 +846,2 @@
     }
 }
-
-
-
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionConfiguration.java	(revision 4932)
@@ -0,0 +1,1310 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CommandTokenizer;
+import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.util.DOMTree;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
+/** This class provides either access to a pseudo-G3 document, or parses a collect.cfg file in such a way as to provide an xml-type view of its content. This later version is useful as it allows the manipulation and free form editing of a legacy 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
+ * @version 2.3d
+ */
+public class CollectionConfiguration 
+    extends StaticStrings {
+
+    static public Document document;
+
+    static public void main(String[] args) {
+	if(args.length >= 1) {
+	    File file = new File(args[0]);
+	    CollectionConfiguration collect_cfg = new CollectionConfiguration(file);
+	    collect_cfg.save(true);
+	    collect_cfg.save(false);
+	    collect_cfg = null;
+	}
+	else {
+	    System.out.println("Usage: CollectionConfiguration <filename>");
+	}
+    }
+
+    /** 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 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(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(SPECIAL_ATTRIBUTE).equals(TRUE_STR)) {
+		    int index = 0;
+		    Element matched_element = (Element) matching_elements.item(index);
+		    Element sibling_element = (Element) matched_element.getNextSibling();
+		    while(sibling_element.getAttribute(SPECIAL_ATTRIBUTE).equals(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(NAME_ATTRIBUTE);
+		    boolean found = false;
+		    // Skip all of the special metadata
+		    Element matched_element = (Element) matching_elements.item(index);
+		    while(matched_element.getAttribute(SPECIAL_ATTRIBUTE).equals(TRUE_STR)) {
+			index++;
+			matched_element = (Element) matching_elements.item(index);
+		    }
+		    // Begin search
+		    while(!found && matched_element != null) {
+			if(matched_element.getAttribute(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(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 String toString(Element command_element, boolean show_extracted_namespace) {
+	String command_element_name = command_element.getNodeName();
+	if(command_element_name.equals(CLASSIFY_ELEMENT)) {
+	    return self.classifyToString(command_element, show_extracted_namespace);
+	}
+	else if(command_element_name.equals(FORMAT_ELEMENT)) {
+	    return self.formatToString(command_element, show_extracted_namespace);
+	}
+	else if(command_element_name.equals(INDEXES_ELEMENT)) {
+	    return self.indexesToString(command_element, show_extracted_namespace);
+	}
+	else if(command_element_name.equals(INDEX_DEFAULT_ELEMENT)) {
+	    return self.indexDefaultToString(command_element, show_extracted_namespace);
+	}
+	else if(command_element_name.equals(LANGUAGES_ELEMENT)) {
+	    return self.languagesToString(command_element);
+	}
+	else if(command_element_name.equals(LANGUAGE_DEFAULT_ELEMENT)) {
+	    return self.languageDefaultToString(command_element);
+	}
+	else if(command_element_name.equals(COLLECTIONMETADATA_ELEMENT)) {
+	    return self.metadataToString(command_element);
+	}
+	else if(command_element_name.equals(PLUGIN_ELEMENT)) {
+	    return self.pluginToString(command_element, show_extracted_namespace);
+	}
+	//else if(command_element_name.equals(SEARCHTYPE_ELEMENT)) {
+	//    return self.searchtypeToString(command_element);
+	//}
+	else if(command_element_name.equals(SUBCOLLECTION_ELEMENT)) {
+	    return self.subcollectionToString(command_element, show_extracted_namespace);
+	}
+	else if(command_element_name.equals(SUBCOLLECTION_DEFAULT_INDEX_ELEMENT)) {
+	    return self.subcollectionDefaultIndexToString(command_element);
+	}
+	else if(command_element_name.equals(SUBCOLLECTION_INDEXES_ELEMENT)) {
+	    return self.subcollectionIndexesToString(command_element);
+	}
+	else if(command_element_name.equals(SUPERCOLLECTION_ELEMENT)) {
+	    return self.supercollectionToString(command_element);
+	}
+	else if(command_element_name.equals(UNKNOWN_ELEMENT)) {
+	    return self.unknownToString(command_element);
+	}
+	return "";
+    }
+
+    /** Parses arguments from a tokenizer and returns a HashMap of mappings. The tricky bit here is that not all entries in the HashMap are name->value pairs, as some arguments are boolean and are turned on by their presence. Arguments are denoted by a '-' prefix.
+     * @param tokenizer a CommandTokenizer based on the unconsumed portion of a command string
+     * @return a HashMap containing the arguments parsed
+     */
+    static public HashMap parseArguments(CommandTokenizer tokenizer) {
+	HashMap arguments = new HashMap();
+	String name = null;
+	String value = null;
+	while(tokenizer.hasMoreTokens() || name != null) {
+	    // First we retrieve a name if we need one.
+	    if(name == null) {
+		name = tokenizer.nextToken();
+	    }
+	    // Now we attempt to retrieve a value
+	    if(tokenizer.hasMoreTokens()) {
+		value = tokenizer.nextToken();
+		// Test if the value is actually a name, and if so add the name by itself, then put value into name so that it is parsed correctly during the next loop.
+		if(value.startsWith(StaticStrings.MINUS_CHARACTER)) {
+		    arguments.put(name, null);
+		    name = value;
+		}
+		// Otherwise we have a typical name->value pair ready to go
+		else {
+		    arguments.put(name, value);
+		    name = null;
+		}
+	    }
+	    // Otherwise its a binary flag
+	    else {
+		arguments.put(name, null);
+		name = null;
+	    } 
+	}
+	return arguments;
+    }
+
+    static private ArrayList known_metadata;   
+
+    static private CollectionConfiguration self;
+
+    static final private String EXTRACTED_PREFIX = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP;
+    /** Gives the preferred ordering of commands */
+    static final private String[] COMMAND_ORDER = {StaticStrings.COLLECTIONMETADATA_ELEMENT, StaticStrings.SEARCHTYPE_ELEMENT, StaticStrings.PLUGIN_ELEMENT, StaticStrings.INDEXES_ELEMENT, StaticStrings.INDEX_DEFAULT_ELEMENT, StaticStrings.LANGUAGES_ELEMENT, StaticStrings.LANGUAGE_DEFAULT_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};
+
+    /** ************************** Public Data Members ***************************/
+
+    /** ************************** Private Data Members ***************************/
+
+    private File collect_config_file;
+
+    /** ************************** Public Methods ***************************/
+
+    public CollectionConfiguration(File collect_config_file) {
+	this.self = this;
+	this.collect_config_file = collect_config_file;
+	// If collect_cfg is xml we can load it straight away
+	String collect_config_name = collect_config_file.getName();
+	if(collect_config_name.equals(COLLECTCONFIGURATION_XML)) {
+	    // Parse with Utility but don't use class loader
+	    document = Utility.parse(collect_config_file, false);
+	}
+	// Otherwise if this is a legacy collect.cfg file then read in the template and send to magic parser
+	else if(collect_config_name.equals(COLLECT_CFG)) {
+	    document = Utility.parse(PSEUDO_COLLECTCONFIGURATION_XML, true);
+	    parse(collect_config_file);
+	}
+    }
+
+    /** 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 JButton("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.show();
+    }
+
+    public Element getDocumentElement() {
+	return document.getDocumentElement();
+    }
+
+    public File getFile() {
+	return collect_config_file;
+    }
+
+    /** Retrieve or create the indexes Element. */
+    public Element getIndexes() {
+	return getOrCreateElementByTagName(INDEXES_ELEMENT);
+    }
+
+    /** Retrieve or create the languages Element. */
+    public Element getLanguages() {
+	return getOrCreateElementByTagName(LANGUAGES_ELEMENT);
+    }
+
+    /** Retrieve or create the subindexes Element. */
+    public Element getSubIndexes() {
+	return getOrCreateElementByTagName(SUBCOLLECTION_INDEXES_ELEMENT);
+    }
+
+    /** Retrieve or create the supercollections Element. */
+    public Element getSuperCollection() {
+	return getOrCreateElementByTagName(SUPERCOLLECTION_ELEMENT);
+    }
+
+    public void save() {
+	save(false);
+    }
+
+    public void save(boolean force_xml) {
+	if(collect_config_file.exists()) {
+	    File original_file = new File(collect_config_file.getParentFile(), COLLECT_CFG);
+	    File backup_file = new File(collect_config_file.getParentFile(), "collect.bak");
+	    if(backup_file.exists()) {
+		backup_file.delete();
+	    }
+	    if(!original_file.renameTo(backup_file)) {
+		Gatherer.println("Can't rename collect.cfg");
+	    }
+	}
+	if(force_xml || collect_config_file.getName().equals(COLLECTCONFIGURATION_XML)) {
+	    ///ystem.err.println("Writing XML");
+	    Utility.export(document, new File(collect_config_file.getParentFile(), COLLECTCONFIGURATION_XML));
+	}
+	else {
+	    ///ystem.err.println("Writing text");
+	    try {
+		FileWriter file_writer = new FileWriter(collect_config_file, false);
+		BufferedWriter buffered_writer = new BufferedWriter(file_writer);
+		// In order to write out an old style collect.cfg we have to traverse the model and do several 'cute' tricks to ensure the collect.cfg is valid (for instance while every metadata element has a language attribute, only second or subsequent metadata, for a certain name, needs a language argument - hence the known metadata array. Note that within GLI the language will always be shown, but it doesn't crash and burn like G2 does, te-he).
+		known_metadata = new ArrayList();
+		Element collect_config_element = document.getDocumentElement();
+		NodeList command_elements = collect_config_element.getChildNodes();
+		for(int i = 0; i < command_elements.getLength(); i++) {
+		    Node command_node = command_elements.item(i);
+		    if(command_node instanceof Element) {
+			Element command_element = (Element) command_node;
+			// The only thing left are NewLine elements
+			if(command_element.getNodeName().equals(NEWLINE_ELEMENT)) {
+			    buffered_writer.newLine();
+			}
+			// Anything else we write to file, but only if it has been assigned
+			else if(!command_element.getAttribute(ASSIGNED_ATTRIBUTE).equals(FALSE_STR)){
+			    String command = toString(command_element, false);
+			    if(command != null && command.length() > 0) {
+				write(buffered_writer, command);
+				buffered_writer.newLine();
+			    }
+			}
+		    }
+		}
+		buffered_writer.close();
+		known_metadata = null;
+	    }
+	    catch (Exception exception) {
+		Gatherer.println("Error in CollectionConfiguration.save(boolean): " + exception);
+		Gatherer.printStackTrace(exception);
+	    }
+	}
+    }
+
+    /** ************************** Private Methods ***************************/
+
+    private String classifyToString(Element command_element, boolean show_extracted_namespace) {
+	StringBuffer text = new StringBuffer(CLASSIFY_STR);
+	text.append(TAB_CHARACTER);
+	text.append(command_element.getAttribute(TYPE_ATTRIBUTE));
+	text.append(SPACE_CHARACTER);
+	NodeList option_elements = command_element.getElementsByTagName(OPTION_ELEMENT);
+	int option_elements_length = option_elements.getLength();
+	for(int j = 0; j < option_elements_length; j++) {
+	    Element option_element = (Element) option_elements.item(j);
+	    if(option_element.getAttribute(ASSIGNED_ATTRIBUTE).equals(TRUE_STR)) {
+		text.append(StaticStrings.MINUS_CHARACTER);
+		text.append(option_element.getAttribute(NAME_ATTRIBUTE));
+		String value_str = MSMUtils.getValue(option_element);
+		if(value_str.length() > 0) {
+		    // If the value happens to be the identifier of an extracted metadata element, then remove the namespace.
+		    if(!show_extracted_namespace && value_str.startsWith(EXTRACTED_PREFIX)) {
+			value_str = value_str.substring(EXTRACTED_PREFIX.length());
+		    }
+		    text.append(SPACE_CHARACTER);
+		    if(value_str.indexOf(SPACE_CHARACTER) == -1) {
+			text.append(value_str);
+		    }
+		    else {
+			text.append(QUOTE_CHARACTER);
+			text.append(value_str);
+			text.append(QUOTE_CHARACTER);
+		    }
+		}
+		value_str = null;
+		if(j < option_elements_length - 1) {
+		    text.append(SPACE_CHARACTER);
+		}
+	    }
+	    option_element = null;
+	}
+	option_elements = null;
+	return text.toString();
+    }
+
+    private String formatToString(Element command_element, boolean show_extracted_namespace) {
+	StringBuffer text = new StringBuffer(FORMAT_STR);
+	text.append(SPACE_CHARACTER);
+	text.append(command_element.getAttribute(NAME_ATTRIBUTE));
+	text.append(SPACE_CHARACTER);
+	String value_str = command_element.getAttribute(VALUE_ATTRIBUTE);
+	if(value_str.length() != 0) {
+	    text.append(value_str);
+	}
+	else {
+	    value_str = MSMUtils.getValue(command_element);
+	    // Remove any references to a namespace for extracted metadata
+	    if(!show_extracted_namespace) {
+		value_str.replaceAll(EXTRACTED_PREFIX, "");
+	    }
+	    text.append(QUOTE_CHARACTER);
+	    text.append(value_str);
+	    text.append(QUOTE_CHARACTER);
+	}
+	value_str = null;
+	return text.toString();
+    }
+
+    /** Retrieve or create the indexes Element. */
+    private Element getOrCreateElementByTagName(String name) {
+	///ystem.err.println("Get or create element by tag name: " + name);
+	Element document_element = document.getDocumentElement();
+	NodeList elements = document_element.getElementsByTagName(name);
+	if(elements.getLength() > 0) {
+	    ///ystem.err.println("Found element.");
+	    document_element = null;
+	    return (Element) elements.item(0);
+	}
+	else {
+	    ///ystem.err.println("Creating element.");
+	    Element element = document.createElement(name);
+	    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;
+	}
+    }    
+
+    private String indexesToString(Element command_element, boolean show_extracted_namespace) {
+	StringBuffer text = new StringBuffer(INDEX_STR);
+	text.append(TAB_CHARACTER);
+	text.append(TAB_CHARACTER);
+	NodeList index_elements = command_element.getElementsByTagName(INDEX_ELEMENT);
+	// For each index, write its level, a colon, then concatenate its child content elements into a single comma separated list
+	int index_elements_length = index_elements.getLength();
+	for(int j = 0; j < index_elements_length; j++) {
+	    Element index_element = (Element) index_elements.item(j);
+	    text.append(index_element.getAttribute(LEVEL_ATTRIBUTE));
+	    text.append(StaticStrings.COLON_CHARACTER);
+	    NodeList content_elements = index_element.getElementsByTagName(CONTENT_ELEMENT);
+	    int content_elements_length = content_elements.getLength();
+	    for(int k = 0; k < content_elements_length; k++) {
+		Element content_element = (Element) content_elements.item(k);
+		String name_str = content_element.getAttribute(NAME_ATTRIBUTE);
+		if(!show_extracted_namespace && name_str.startsWith(EXTRACTED_PREFIX)) {
+		    name_str = name_str.substring(EXTRACTED_PREFIX.length());
+		}
+		text.append(name_str);
+		name_str = null;
+		if(k < content_elements_length - 1) {
+		    text.append(StaticStrings.COMMA_CHARACTER);
+		}
+		content_element = null;
+	    }
+	    if(j < index_elements_length - 1) {
+		text.append(SPACE_CHARACTER);
+	    }
+	    content_elements = null;
+	    index_element = null;
+	}
+	index_elements = null;
+	return text.toString();
+    }
+
+    private String indexDefaultToString(Element command_element, boolean show_extracted_namespace) {
+	StringBuffer text = new StringBuffer(INDEX_DEFAULT_STR);
+	text.append(TAB_CHARACTER);
+	text.append(command_element.getAttribute(LEVEL_ATTRIBUTE));
+	text.append(StaticStrings.COLON_CHARACTER);
+	NodeList content_elements = command_element.getElementsByTagName(CONTENT_ELEMENT);
+	int content_elements_length = content_elements.getLength();
+	for(int j = 0; j < content_elements_length; j++) {
+	    Element content_element = (Element) content_elements.item(j);
+	    String name_str = content_element.getAttribute(NAME_ATTRIBUTE);
+	    if(!show_extracted_namespace && name_str.startsWith(EXTRACTED_PREFIX)) {
+		name_str = name_str.substring(EXTRACTED_PREFIX.length());
+	    }
+	    text.append(name_str);
+	    name_str = null;
+	    if(j < content_elements_length - 1) {
+		text.append(StaticStrings.COMMA_CHARACTER);
+	    }
+	    content_element = null;
+	}
+	content_elements = null;
+	return text.toString();
+    }
+
+    private String languagesToString(Element command_element) {
+	StringBuffer text = new StringBuffer(LANGUAGES_STR);
+	text.append(TAB_CHARACTER);
+	// Retrieve all the languages and write them out in a space separated list
+	NodeList language_elements = command_element.getElementsByTagName(LANGUAGE_ELEMENT);
+	int language_elements_length = language_elements.getLength();
+	if(language_elements_length == 0) {
+	    return null;
+	}
+	for(int j = 0; j < language_elements_length; j++) {
+	    Element language_element = (Element) language_elements.item(j);
+	    text.append(language_element.getAttribute(NAME_ATTRIBUTE));
+	    if(j < language_elements_length - 1) {
+		text.append(SPACE_CHARACTER);
+	    }  
+	}
+	return text.toString();
+    }
+
+    private String languageDefaultToString(Element command_element) {
+	StringBuffer text = new StringBuffer(LANGUAGE_DEFAULT_STR);
+	text.append(TAB_CHARACTER);
+	text.append(command_element.getAttribute(NAME_ATTRIBUTE));
+	return text.toString();
+    }
+
+    static public String metadataToString(Element command_element) {
+	// If there is no value attribute, then we don't write anything
+	String value_str = MSMUtils.getValue(command_element);
+	if(value_str == null || value_str.length() == 0) {
+	    return "";
+	}
+	else {
+	    StringBuffer text = new StringBuffer("");
+	    String name_str = command_element.getAttribute(NAME_ATTRIBUTE);
+	    // If the name is one of the special four, we don't write the collectionmeta first. Note the maintainer collectionmeta is singled out for 'prittying' reasons.
+	    if(name_str.equals(COLLECTIONMETADATA_MAINTAINER_STR)) {
+		text.append(name_str);
+		text.append(TAB_CHARACTER);
+	    }
+	    else if(name_str.equals(COLLECTIONMETADATA_BETA_STR) || name_str.equals(COLLECTIONMETADATA_CREATOR_STR) || name_str.equals(COLLECTIONMETADATA_PUBLIC_STR)) {
+		text.append(name_str);
+		text.append(TAB_CHARACTER);
+		text.append(TAB_CHARACTER);
+	    }
+	    else {
+		text.append(COLLECTIONMETADATA_STR);
+		text.append(TAB_CHARACTER);
+		text.append(name_str);
+	    }
+	    String language_str = command_element.getAttribute(LANGUAGE_ATTRIBUTE);
+	    text.append(SPACE_CHARACTER);
+	    // If this is element is in english, and it is the first one found, we don't need to write the language argument.
+	    if(!language_str.equals(ENGLISH_LANGUAGE_STR) || known_metadata == null || known_metadata.contains(name_str)) {
+		text.append(LBRACKET_CHARACTER);
+		text.append(LANGUAGE_ARGUMENT);
+		text.append(language_str);
+		text.append(RBRACKET_CHARACTER);
+		text.append(SPACE_CHARACTER);
+	    }
+	    if(known_metadata != null) {
+		known_metadata.add(name_str);
+	    }
+	    language_str = null;
+	    name_str = null;
+	    // We don't wrap the email addresses in quotes, nor any string without spaces
+	    if(value_str.indexOf(SPACE_CHARACTER) == -1) {
+		text.append(value_str);
+	    }
+	    else {
+		text.append(QUOTE_CHARACTER);
+		text.append(value_str);
+		text.append(QUOTE_CHARACTER);
+	    }
+	    value_str = null;
+	    return text.toString();
+	}
+    }
+
+    /** Parse a collect.cfg into a DOM model representation. */
+    private void parse(File collect_config_file) {
+	try {
+	    Element collect_cfg_element = document.getDocumentElement();
+	    // Read in the file command at a time.
+	    FileReader in_reader = new FileReader(collect_config_file);
+	    BufferedReader in = new BufferedReader(in_reader);
+	    String command_str = null;
+	    while((command_str = in.readLine()) != null) {
+		Element command_element = null;
+		// A command may be broken over several lines.
+		command_str = command_str.trim();
+		boolean eof = false;
+		while(!eof && command_str.endsWith(NEWLINE_CHARACTER)) {
+		    String next_line = in.readLine();
+		    if(next_line != null) {
+			next_line = next_line.trim();
+			if(next_line.length() > 0) {
+			    // Remove the new line character
+			    command_str = command_str.substring(0, command_str.lastIndexOf(NEWLINE_CHARACTER));
+			    // And append the next line, which due to the test above must be non-zero length
+			    command_str = command_str + next_line;
+			}
+			next_line = null;
+		    }
+		    // If we've reached the end of the file theres nothing more we can do
+		    else {
+			eof = true;
+		    }
+		}
+		// If there is still a new line character, then we remove it and hope for the best
+		if(command_str.endsWith(NEWLINE_CHARACTER)) {
+		    command_str = command_str.substring(0, command_str.lastIndexOf(NEWLINE_CHARACTER));
+		}
+		// Now we've either got a command to parse...
+		if(command_str.length() != 0) {
+		    // Start trying to figure out what it is
+		    StringTokenizer tokenizer = new StringTokenizer(command_str);
+		    String command_type = tokenizer.nextToken().toLowerCase();
+		    tokenizer = null;
+		    // Why can't you switch on strings eh? We pass it to the various subparsers who each have a bash at parsing the command. If none can parse the command, an unknown element is created
+		    if(command_element == null && command_type.equals(CLASSIFY_STR)) {
+			command_element = parseClassify(command_str);
+		    }
+		    if(command_element == null && command_type.equals(FORMAT_STR)) {
+			command_element = parseFormat(command_str);
+		    }
+		    if(command_element == null && command_type.equals(INDEX_STR)) {
+			command_element = parseIndex(command_str);
+		    }
+		    if(command_element == null && command_type.equals(INDEX_DEFAULT_STR)) {
+			command_element = parseIndexDefault(command_str);
+		    }
+		    if(command_element == null && command_type.equals(LANGUAGES_STR)) {
+			command_element = parseLanguage(command_str);
+		    }
+		    if(command_element == null && command_type.equals(LANGUAGE_DEFAULT_STR)) {
+			command_element = parseLanguageDefault(command_str);
+		    }
+		    if(command_element == null && command_type.equals(COLLECTIONMETADATA_STR)) {
+			command_element = parseMetadata(command_str);
+		    }
+		    if(command_element == null && (command_type.equals(COLLECTIONMETADATA_BETA_STR) || command_type.equals(COLLECTIONMETADATA_PUBLIC_STR) || command_type.equals(COLLECTIONMETADATA_CREATOR_STR) || command_type.equals(COLLECTIONMETADATA_MAINTAINER_STR))) {
+			command_element = parseMetadataSpecial(command_str);
+		    }
+		    if(command_element == null && command_type.equals(PLUGIN_STR)) {
+			command_element = parsePlugIn(command_str);
+		    }
+		    if(command_element == null && command_type.equals(SUBCOLLECTION_STR)) {
+			command_element = parseSubCollection(command_str);
+		    }
+		    if(command_element == null && command_type.equals(SUBCOLLECTION_DEFAULT_INDEX_STR)) {
+			command_element = parseSubCollectionDefaultIndex(command_str);
+		    }
+		    if(command_element == null && command_type.equals(SUBCOLLECTION_INDEX_STR)) {
+			command_element = parseSubCollectionIndex(command_str);
+		    }
+		    if(command_element == null && (command_type.equals(SUPERCOLLECTION_STR) || command_type.equals(CCS_STR))) {
+			command_element = parseSuperCollection(command_str);
+		    }
+		    // Doesn't match any known type
+		    command_type = null;
+		    if(command_element == null) {
+			// No-one knows what to do with this command, so we create an Unknown command element
+			command_element = document.createElement(UNKNOWN_ELEMENT);
+			MSMUtils.setValue(command_element, command_str);
+		    }
+		}
+		// Or an empty line to remember for later
+		else {
+		    command_element = document.createElement(NEWLINE_ELEMENT);
+		}
+		// Now command element shouldn't be null so we append it to the collection config DOM
+		collect_cfg_element.appendChild(command_element);
+	    }
+	}
+	catch(Exception exception) {
+	    Gatherer.println("Error in CollectionConfiguration.parse(java.io.File): " + exception);
+	    Gatherer.printStackTrace(exception);
+	}
+    }
+
+    private Element parseClassify(String command_str) {
+	Element command_element = null;
+	try {
+	    CommandTokenizer tokenizer = new CommandTokenizer(command_str);
+	    // Check the token count. The token count from a command tokenizer isn't guarenteed to be correct, but it does give the maximum number of available tokens according to the underlying StringTokenizer (some of which may actually be append together by the CommandTokenizer as being a single argument).
+	    if(tokenizer.countTokens() >= 4) {
+		command_element = document.createElement(CLASSIFY_ELEMENT);
+		// First token is classify
+		tokenizer.nextToken();
+		// The next token is the classifier type
+		command_element.setAttribute(TYPE_ATTRIBUTE, tokenizer.nextToken());
+		// Now we parse out the remaining arguments into a hashmapping from name to value
+		HashMap arguments = parseArguments(tokenizer);
+		// Assign the arguments as Option elements, but watch out for the metadata argument as we treat that differently
+		Iterator names = arguments.keySet().iterator();
+		while(names.hasNext()) {
+		    String name = (String) names.next();
+		    String value = (String) arguments.get(name); // Can be null
+		    // The metadata argument gets added as the content attribute
+		    if(name.equals(METADATA_ARGUMENT) && value != null) {
+			// The metadata argument must be the fully qualified name of a metadata element, so if it doesn't yet have a namespace, append the extracted metadata namespace.
+			if(value.indexOf(MSMUtils.NS_SEP) == -1) {
+			    value = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + value;
+			}
+			//command_element.setAttribute(CONTENT_ATTRIBUTE, value);
+		    }
+		    // Everything else is an Option Element
+		    Element option_element = document.createElement(OPTION_ELEMENT);
+		    option_element.setAttribute(NAME_ATTRIBUTE, name.substring(1));
+		    if(value != null) {
+			MSMUtils.setValue(option_element, value);
+		    }
+		    option_element.setAttribute(ASSIGNED_ATTRIBUTE, TRUE_STR);
+		    option_element.setAttribute(CUSTOM_ATTRIBUTE, TRUE_STR);
+		    command_element.appendChild(option_element);
+		    option_element = null;
+		    name = null;
+		    value = null;
+		}
+		names = null;
+		arguments = null;
+	    }
+	    tokenizer = null;
+	}
+	catch(Exception error) {
+	}
+	return command_element;
+    }
+
+    private Element parseFormat(String command_str) {
+	Element command_element = null;
+	try {
+	    CommandTokenizer tokenizer = new CommandTokenizer(command_str);
+	    if(tokenizer.countTokens() >= 3) {
+		command_element = document.createElement(FORMAT_ELEMENT);
+		// First token is format
+		tokenizer.nextToken();
+		command_element.setAttribute(NAME_ATTRIBUTE, tokenizer.nextToken());
+		String format_value = tokenizer.nextToken();
+		// If the value is true or false we add it as an attribute
+		if(format_value.equalsIgnoreCase(TRUE_STR) || format_value.equalsIgnoreCase(FALSE_STR)) {
+		    command_element.setAttribute(VALUE_ATTRIBUTE, format_value.toLowerCase());
+		}
+		// Otherwise it gets added as a text node
+		else {
+		    // Strip any speech marks
+		    if(format_value.startsWith(QUOTE_CHARACTER) && format_value.endsWith(QUOTE_CHARACTER)) {
+			format_value = format_value.substring(1, format_value.length() - 1);
+		    }
+		    MSMUtils.setValue(command_element, format_value);
+		}
+		format_value = null;
+	    }
+	    tokenizer = null;
+	}
+	catch(Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parseIndex(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    tokenizer.nextToken();
+	    if(tokenizer.hasMoreTokens()) {
+		command_element = document.createElement(INDEXES_ELEMENT);
+	    }
+	    while(tokenizer.hasMoreTokens()) {
+		Element index_element = document.createElement(INDEX_ELEMENT);
+		String index_str = tokenizer.nextToken();
+		index_element.setAttribute(LEVEL_ATTRIBUTE, index_str.substring(0, index_str.indexOf(StaticStrings.COLON_CHARACTER)));
+		String raw_content_str = index_str.substring(index_str.indexOf(StaticStrings.COLON_CHARACTER) + 1);
+		StringTokenizer content_tokenizer = new StringTokenizer(raw_content_str, StaticStrings.COMMA_CHARACTER);
+		while(content_tokenizer.hasMoreTokens()) {
+		    Element content_element = document.createElement(CONTENT_ELEMENT);
+		    String content_str = content_tokenizer.nextToken();
+		    // Since the contents of indexes have to be certain keywords, or metadata elements, if the content isn't a keyword and doesn't yet have a namespace, append the extracted metadata namespace.
+		    if(!content_str.equals(StaticStrings.TEXT_STR) && content_str.indexOf(MSMUtils.NS_SEP) == -1) {
+			content_str = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + content_str;
+		    }
+		    content_element.setAttribute(NAME_ATTRIBUTE, content_str);
+		    index_element.appendChild(content_element);
+		    content_element = null;
+		}
+		content_tokenizer = null;
+		raw_content_str = null;
+		index_str = null;
+		command_element.appendChild(index_element);
+		index_element = null;
+	    }
+	    tokenizer = null;
+	}
+	catch (Exception exception) {
+	    exception.printStackTrace();
+	}
+	return command_element;
+    }
+
+    private Element parseIndexDefault(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    if(tokenizer.countTokens() >= 2) {
+		command_element = document.createElement(INDEX_DEFAULT_ELEMENT);
+		tokenizer.nextToken();
+		String index_str = tokenizer.nextToken();
+		command_element.setAttribute(LEVEL_ATTRIBUTE, index_str.substring(0, index_str.indexOf(StaticStrings.COLON_CHARACTER)));
+		String content_str = index_str.substring(index_str.indexOf(StaticStrings.COLON_CHARACTER) + 1);
+		StringTokenizer content_tokenizer = new StringTokenizer(content_str, StaticStrings.COMMA_CHARACTER);
+		while(content_tokenizer.hasMoreTokens()) {
+		    Element content_element = document.createElement(CONTENT_ELEMENT);
+		    content_element.setAttribute(NAME_ATTRIBUTE, content_tokenizer.nextToken());
+		    command_element.appendChild(content_element);
+		    content_element = null;
+		}
+		content_tokenizer = null;
+		content_str = null;
+		content_str = null;
+		index_str = null;
+	    }
+	    tokenizer = null;
+	}
+	catch (Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parseLanguage(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    tokenizer.nextToken();
+	    if(tokenizer.hasMoreTokens()) {
+		command_element = document.createElement(LANGUAGES_ELEMENT);
+	    }
+	    while(tokenizer.hasMoreTokens()) {
+		Element language_element = document.createElement(LANGUAGE_ELEMENT);
+		language_element.setAttribute(NAME_ATTRIBUTE, tokenizer.nextToken());
+		command_element.appendChild(language_element);
+		language_element = null;
+	    }
+	    tokenizer = null;
+	}
+	catch (Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parseLanguageDefault(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    if(tokenizer.countTokens() >= 2) {
+		command_element = document.createElement(LANGUAGE_DEFAULT_ELEMENT);
+		tokenizer.nextToken();
+		String default_language_str = tokenizer.nextToken();
+		command_element.setAttribute(NAME_ATTRIBUTE, default_language_str);
+		default_language_str = null;
+	    }
+	    tokenizer = null;
+	}
+	catch (Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parseMetadata(String command_str) {
+	Element command_element = null;
+	try {
+	    CommandTokenizer tokenizer = new CommandTokenizer(command_str);
+	    if(tokenizer.countTokens() >= 3) {
+		command_element = document.createElement(COLLECTIONMETADATA_ELEMENT);
+		// First token is command type
+		tokenizer.nextToken();
+		String name_str = tokenizer.nextToken();
+		String value_str = tokenizer.nextToken();
+		String language_str = "en"; // By default
+		// Check if the value string is actually a language string
+		if(value_str.startsWith(LBRACKET_CHARACTER) && value_str.endsWith(RBRACKET_CHARACTER)) {
+		    language_str = value_str.substring(value_str.indexOf(LANGUAGE_ARGUMENT) + 2, value_str.length() - 1);
+		    value_str = tokenizer.nextToken();
+		}
+		// Remove any speech marks from value
+		if(value_str.startsWith(QUOTE_CHARACTER) && value_str.endsWith(QUOTE_CHARACTER)) {
+		    value_str = value_str.substring(1, value_str.length() - 1);
+		}
+		if(value_str != null) {
+		    command_element.setAttribute(NAME_ATTRIBUTE, name_str);
+		    command_element.setAttribute(LANGUAGE_ATTRIBUTE, language_str);
+		    MSMUtils.setValue(command_element, value_str);
+		}
+		else {
+		    command_element = null;
+		}
+		language_str = null;
+		value_str = null;
+		name_str = null;
+	    }
+	    tokenizer = null;
+	}
+	catch (Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parseMetadataSpecial(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    if(tokenizer.countTokens() >= 2) {
+		command_element = document.createElement(COLLECTIONMETADATA_ELEMENT);
+		String name_str = tokenizer.nextToken();
+		String value_str = tokenizer.nextToken();
+		command_element.setAttribute(NAME_ATTRIBUTE, name_str);
+		command_element.setAttribute(LANGUAGE_ATTRIBUTE, ENGLISH_LANGUAGE_STR);
+		command_element.setAttribute(SPECIAL_ATTRIBUTE, TRUE_STR);
+		MSMUtils.setValue(command_element, value_str);
+		value_str = null;
+		name_str = null;
+	    }
+	    tokenizer = null;
+	}
+	catch (Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parsePlugIn(String command_str) {
+	Element command_element = null;
+	try {
+	    CommandTokenizer tokenizer = new CommandTokenizer(command_str);
+	    // Check the token count. The token count from a command tokenizer isn't guarenteed to be correct, but it does give the maximum number of available tokens according to the underlying StringTokenizer (some of which may actually be append together by the CommandTokenizer as being a single argument).
+	    if(tokenizer.countTokens() >= 2) {
+		command_element = document.createElement(PLUGIN_ELEMENT);
+		// First token is plugin
+		tokenizer.nextToken();
+		// The next token is the type
+		command_element.setAttribute(TYPE_ATTRIBUTE, tokenizer.nextToken());
+		// Now we parse out the remaining arguments into a hashmapping from name to value
+		HashMap arguments = parseArguments(tokenizer);
+		// Assign the arguments as Option elements, but watch out for the metadata argument as we treat that differently
+		Iterator names = arguments.keySet().iterator();
+		while(names.hasNext()) {
+		    String name = (String) names.next();
+		    String value = (String) arguments.get(name); // Can be null
+		    Element option_element = document.createElement(OPTION_ELEMENT);
+		    option_element.setAttribute(NAME_ATTRIBUTE, name.substring(1));
+		    option_element.setAttribute(ASSIGNED_ATTRIBUTE, TRUE_STR);
+		    option_element.setAttribute(CUSTOM_ATTRIBUTE, TRUE_STR); // All arguments are considered to be custom until matched against base plugins arguments
+		    if(value != null) {
+			if(name.equals(METADATA_ARGUMENT)) {
+			    // The metadata argument must be the fully qualified name of a metadata element, so if it doesn't yet have a namespace, append the extracted metadata namespace.
+			    if(value.indexOf(MSMUtils.NS_SEP) == -1) {
+				value = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + value;
+			    }
+			}
+			MSMUtils.setValue(option_element, value);
+		    }
+		    command_element.appendChild(option_element);
+		    option_element = null;
+		    name = null;
+		    value = null;
+		}
+		names = null;
+		arguments = null;
+	    }
+	    tokenizer = null;
+	}
+	catch(Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parseSubCollection(String command_str) {
+	Element command_element = null;
+	try {
+	    CommandTokenizer tokenizer = new CommandTokenizer(command_str);
+	    if(tokenizer.countTokens() >= 3) {
+		command_element = document.createElement(SUBCOLLECTION_ELEMENT);
+		// First token is command type
+		tokenizer.nextToken();
+		// Then subcollection identifier
+		command_element.setAttribute(NAME_ATTRIBUTE, tokenizer.nextToken());
+		// Then finally the pattern used to build the subcollection partition
+		String full_pattern_str = tokenizer.nextToken();
+		// To make life easier I'm going to parse this up now.
+		boolean exclusion = (full_pattern_str.substring(1, 2).equals(EXCLAIMATION_CHARACTER));
+		// Set inclusion/exclusion flag, remove any exclaimation mark and the speech marks
+		if(exclusion) {
+		    full_pattern_str = full_pattern_str.substring(2, full_pattern_str.length() - 1);
+		    command_element.setAttribute(TYPE_ATTRIBUTE, EXCLUDE_STR);
+		}
+		else {
+		    full_pattern_str = full_pattern_str.substring(1, full_pattern_str.length() - 1);
+		    command_element.setAttribute(TYPE_ATTRIBUTE, INCLUDE_STR);
+		}
+		StringTokenizer pattern_tokenizer = new StringTokenizer(full_pattern_str, SEPARATOR_CHARACTER);
+		if(pattern_tokenizer.countTokens() >= 2) {
+		    String content_str = pattern_tokenizer.nextToken();
+		    // Since the contents of indexes have to be certain keywords, or metadata elements, if the content isn't a keyword and doesn't yet have a namespace, append the extracted metadata namespace.
+		    if(!content_str.equals(StaticStrings.FILENAME_STR) && content_str.indexOf(MSMUtils.NS_SEP) == -1) {
+			content_str = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + content_str;
+		    }
+		    command_element.setAttribute(CONTENT_ATTRIBUTE, content_str);
+		    MSMUtils.setValue(command_element, pattern_tokenizer.nextToken());
+		    if(pattern_tokenizer.hasMoreTokens()) {
+			command_element.setAttribute(OPTIONS_ATTRIBUTE, pattern_tokenizer.nextToken());
+		    }
+		}
+		pattern_tokenizer = null;
+	    }
+	}
+	catch(Exception exception) {
+	    exception.printStackTrace();
+	}
+	return command_element;
+    }
+
+    private Element parseSubCollectionDefaultIndex(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    if(tokenizer.countTokens() == 2) {
+		command_element = document.createElement(SUBCOLLECTION_DEFAULT_INDEX_ELEMENT);
+		tokenizer.nextToken();
+		//command_element.setAttribute(CONTENT_ATTRIBUTE, tokenizer.nextToken());
+		String content_str = tokenizer.nextToken();
+		StringTokenizer content_tokenizer = new StringTokenizer(content_str, StaticStrings.COMMA_CHARACTER);
+		while(content_tokenizer.hasMoreTokens()) {
+		    Element content_element = document.createElement(CONTENT_ELEMENT);
+		    content_element.setAttribute(NAME_ATTRIBUTE, content_tokenizer.nextToken());
+		    command_element.appendChild(content_element);
+		    content_element = null;
+		}
+		content_tokenizer = null;
+		content_str = null;
+	    }
+	    tokenizer = null;
+	}
+	catch(Exception exception) {
+	}
+	return command_element;	
+    }
+
+    private Element parseSubCollectionIndex(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    tokenizer.nextToken();
+	    if(tokenizer.hasMoreTokens()) {
+		command_element = document.createElement(SUBCOLLECTION_INDEXES_ELEMENT);
+	    }
+	    while(tokenizer.hasMoreTokens()) {
+		Element subcollectionindex_element = document.createElement(INDEX_ELEMENT);
+		//command_element.setAttribute(CONTENT_ATTRIBUTE, tokenizer.nextToken());
+		String content_str = tokenizer.nextToken();
+		StringTokenizer content_tokenizer = new StringTokenizer(content_str, StaticStrings.COMMA_CHARACTER);
+		while(content_tokenizer.hasMoreTokens()) {
+		    Element content_element = document.createElement(CONTENT_ELEMENT);
+		    content_element.setAttribute(NAME_ATTRIBUTE, content_tokenizer.nextToken());
+		    subcollectionindex_element.appendChild(content_element);
+		    content_element = null;
+		}
+		content_tokenizer = null;
+		content_str = null;
+		command_element.appendChild(subcollectionindex_element);
+		subcollectionindex_element = null;
+	    }
+	    tokenizer = null;
+	}
+	catch (Exception exception) {
+	}
+	return command_element;
+    }
+
+    private Element parseSuperCollection(String command_str) {
+	Element command_element = null;
+	try {
+	    StringTokenizer tokenizer = new StringTokenizer(command_str);
+	    if(tokenizer.countTokens() >= 3) {
+		command_element = document.createElement(SUPERCOLLECTION_ELEMENT);
+		tokenizer.nextToken();
+		while(tokenizer.hasMoreTokens()) {
+		    Element collection_element = document.createElement(COLLECTION_ELEMENT);
+		    collection_element.setAttribute(NAME_ATTRIBUTE, tokenizer.nextToken());
+		    command_element.appendChild(collection_element);
+		    collection_element = null;
+		}
+	    }
+	    tokenizer = null;
+	}
+	catch(Exception exception) {
+	}
+	return command_element;	
+    }
+
+    private String pluginToString(Element command_element, boolean show_extracted_namespace) {
+	StringBuffer text = new StringBuffer();
+	if(!command_element.getAttribute(SEPARATOR_ATTRIBUTE).equals(TRUE_STR)) {
+	    text.append(PLUGIN_STR);
+	    text.append(TAB_CHARACTER);
+	    text.append(TAB_CHARACTER);
+	    text.append(command_element.getAttribute(TYPE_ATTRIBUTE));
+	    // Retrieve, and output, the arguments
+	    NodeList option_elements = command_element.getElementsByTagName(OPTION_ELEMENT);
+	    int option_elements_length = option_elements.getLength();
+	    if(option_elements_length > 0) {
+		text.append(SPACE_CHARACTER);
+		for(int j = 0; j < option_elements_length; j++) {
+		    Element option_element = (Element) option_elements.item(j);
+		    if(option_element.getAttribute(ASSIGNED_ATTRIBUTE).equals(TRUE_STR)) {
+			text.append(StaticStrings.MINUS_CHARACTER);
+			text.append(option_element.getAttribute(NAME_ATTRIBUTE));
+			String value_str = MSMUtils.getValue(option_element);
+			if(!show_extracted_namespace && value_str.startsWith(EXTRACTED_PREFIX)) {
+			    value_str = value_str.substring(EXTRACTED_PREFIX.length());
+			}
+			if(value_str.length() > 0) {
+			    text.append(SPACE_CHARACTER);
+			    if(value_str.indexOf(SPACE_CHARACTER) == -1) {
+				text.append(value_str);
+			    }
+			    else {
+				text.append(QUOTE_CHARACTER);
+				text.append(value_str);
+				text.append(QUOTE_CHARACTER);
+			    }
+			}
+			value_str = null;
+			if(j < option_elements_length - 1) {
+			    text.append(SPACE_CHARACTER);
+			}
+		    }
+		    option_element = null;
+		}
+	    }
+	    option_elements = null;
+	}
+	return text.toString();
+    }
+
+    private String subcollectionToString(Element command_element, boolean show_extracted_namespace) {
+	StringBuffer text = new StringBuffer(SUBCOLLECTION_STR);
+	text.append(SPACE_CHARACTER);
+	text.append(command_element.getAttribute(NAME_ATTRIBUTE));
+	text.append(SPACE_CHARACTER);
+	text.append(TAB_CHARACTER);
+	text.append(QUOTE_CHARACTER);
+	if(command_element.getAttribute(TYPE_ATTRIBUTE).equals(EXCLUDE_STR)) {
+	    text.append(EXCLAIMATION_CHARACTER);
+	}
+	String content_str = command_element.getAttribute(CONTENT_ATTRIBUTE);
+	if(!show_extracted_namespace && content_str.startsWith(EXTRACTED_PREFIX)) {
+	    content_str = content_str.substring(EXTRACTED_PREFIX.length());
+	}
+	text.append(content_str);
+	content_str = null;
+	text.append(SEPARATOR_CHARACTER);
+	text.append(MSMUtils.getValue(command_element));
+	String options_str = command_element.getAttribute(OPTIONS_ATTRIBUTE);
+	if(options_str.length() > 0) {
+	    text.append(SEPARATOR_CHARACTER);
+	    text.append(options_str);
+	}
+	options_str = null;
+	text.append(QUOTE_CHARACTER);
+	return text.toString();
+    }
+
+    private String subcollectionDefaultIndexToString(Element command_element) {
+	StringBuffer text = new StringBuffer(SUBCOLLECTION_DEFAULT_INDEX_STR);
+	text.append(TAB_CHARACTER);
+	NodeList content_elements = command_element.getElementsByTagName(CONTENT_ELEMENT);
+	int content_elements_length = content_elements.getLength();
+	for(int j = 0; j < content_elements_length; j++) {
+	    Element content_element = (Element) content_elements.item(j);
+	    text.append(content_element.getAttribute(NAME_ATTRIBUTE));
+	    if(j < content_elements_length - 1) {
+		text.append(StaticStrings.COMMA_CHARACTER);
+	    }
+	}
+	return text.toString();
+    }
+
+    private String subcollectionIndexesToString(Element command_element) {
+	StringBuffer text = new StringBuffer(SUBCOLLECTION_INDEX_STR);
+	text.append(TAB_CHARACTER);
+	// Retrieve all of the subcollection index partitions
+	NodeList subcollectionindex_elements = command_element.getElementsByTagName(INDEX_ELEMENT);
+	int subcollectionindex_elements_length = subcollectionindex_elements.getLength();
+	if(subcollectionindex_elements_length == 0) {
+	    return null;
+	}
+	for(int j = 0; j < subcollectionindex_elements_length; j++) {
+	    Element subcollectionindex_element = (Element) subcollectionindex_elements.item(j);
+	    NodeList content_elements = subcollectionindex_element.getElementsByTagName(CONTENT_ELEMENT);
+	    int content_elements_length = content_elements.getLength();
+	    for(int k = 0; k < content_elements_length; k++) {
+		Element content_element = (Element) content_elements.item(k);
+		text.append(content_element.getAttribute(NAME_ATTRIBUTE));
+		if(k < content_elements_length - 1) {
+		    text.append(StaticStrings.COMMA_CHARACTER);
+		}
+	    }
+	    if(j < subcollectionindex_elements_length - 1) {
+		text.append(SPACE_CHARACTER);
+	    }
+	}
+	return text.toString();
+    }
+
+    private String supercollectionToString(Element command_element) {
+	NodeList content_elements = command_element.getElementsByTagName(COLLECTION_ELEMENT);
+	int content_elements_length = content_elements.getLength();
+	if(content_elements_length > 1) {
+	    StringBuffer text = new StringBuffer(SUPERCOLLECTION_STR);
+	    text.append(TAB_CHARACTER);
+	    for(int j = 0; j < content_elements_length; j++) {
+		Element content_element = (Element) content_elements.item(j);
+		text.append(content_element.getAttribute(NAME_ATTRIBUTE));
+		if(j < content_elements_length - 1) {
+		    text.append(SPACE_CHARACTER);
+		}
+	    }
+	    return text.toString();
+	}
+	return null;
+    }
+
+    private String unknownToString(Element command_element) {
+	return MSMUtils.getValue(command_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());
+    }
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionDesignManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionDesignManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionDesignManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,185 +25,121 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
-/**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Company:      The University of Waikato
- * Written:      02/05/02
- * Revised:      16/08/02 Optimized and added Destructor.
- **************************************************************************************/
-import java.awt.BorderLayout;
-import java.awt.Rectangle;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileOutputStream;
-import java.lang.Exception;
-import java.lang.String;
-import java.lang.StringBuffer;
-import java.util.ArrayList;
-import java.util.StringTokenizer;
-import javax.swing.JPanel;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.event.*;
 import org.greenstone.gatherer.Gatherer;
 import org.greenstone.gatherer.cdm.ClassifierManager;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
 import org.greenstone.gatherer.cdm.CollectionMetaManager;
 import org.greenstone.gatherer.cdm.FormatManager;
-import org.greenstone.gatherer.cdm.GUI;
-import org.greenstone.gatherer.cdm.Index;
+import org.greenstone.gatherer.cdm.GeneralManager;
 import org.greenstone.gatherer.cdm.IndexManager;
-import org.greenstone.gatherer.cdm.MetadataSetManager;
+import org.greenstone.gatherer.cdm.MetadataSetView;
 import org.greenstone.gatherer.cdm.PlugInManager;
 import org.greenstone.gatherer.cdm.SubcollectionManager;
-import org.greenstone.gatherer.util.EmailAddress;
+import org.greenstone.gatherer.cdm.TranslationView;
 /** This manager provides access to submanagers, which in turn provide tools for the designing of Greenstone collections via the information stored in etc/collect.cfg. This class acts as a hub for the managers that handle specific parts of the configuration such as classifiers, format strings and language settings.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
- * @version 2.3
+ * @version 2.3d
  */
-// ####################################################################################
-// Optimization                          Saving
-// ####################################################################################
-// Vector -> ArrayList                   + Processor
-// String -> StringBuffer                + Memory, + Processor
-// ####################################################################################
 public class CollectionDesignManager {
-    /** Whether this collection is to be made public or not. */
-    public boolean public_col = false;
-    /** Whether this collection is a beta version or not. */
-    public boolean beta = true;
+    /** This listener listens for any event on any of the components in any of the sub-views, and marks the collection as needing saving if any change occurs. */
+    static public CDMChangeListener change_listener;
     /** A list of classifiers to use at build time. */
-    public ClassifierManager classifiers = null;
+    static public ClassifierManager classifier_manager;
+    /** The CollectionConfiguration object on which this CDM will be based. */
+    static public CollectionConfiguration collect_config;
     /** A manager of collection level metadata. */
-    public CollectionMetaManager collectionmetadatum = null;
-    /** E-mail address of the collection's creator. */
-    public EmailAddress creator = null;
-    /** E-mail address of the collection's maintainer. */
-    public EmailAddress maintainer = null;
-    /** The collection configuration file. */
-    public File in_file = null;
+    static public CollectionMetaManager collectionmeta_manager;
     /** A list of formating strings to use at build time. */
-    public FormatManager formats = null;
+    static public FormatManager format_manager;
     /** The manager in charge of displaying this manager and the controls for other managers. */
-    public GUI gui = null;
+    static public GeneralManager general_manager;
     /** List of indexes to be built, and the default index. */
-    public IndexManager indexes = null;
+    static public IndexManager index_manager;
     /** Contains instructions dealing with the collection language. */
-    public LanguageManager languages = null;
+    static public LanguageManager language_manager;
     /** A simple manager for the visual review of metadata sets. */
-    public MetadataSetManager metadatasets = null;
+    static public MetadataSetView metadataset_view;
     /** A list of plugins to use at build time. */
-    public PlugInManager plugins = null;
+    static public PlugInManager plugin_manager;
     /** Contains: A list of subcollections, (defined on metadatadata), a list of which subcollection indexes to build and the default subcollection index. */
-    public SubcollectionManager subcollections = null;
+    static public SubcollectionManager subcollection_manager;
+
+    static public SubcollectionIndexManager subcollectionindex_manager;
     /** A supercollection command allows a single search to be conducted across several collections. It is a very basic command and so avoids all the crazy model stuff that exists in most of the design managers. */
-    public SuperCollectionManager superman; // Just cause I could ;p
+    static public SuperCollectionManager supercollection_manager; // Just cause I could ;p
     /** The text translation manager. */
-    public TranslationManager transman;
-    /** A list of whatever commands could not be parsed at all. */
-    private ArrayList rest = null;
-    /** A reference to the Gatherer. */
-    private Gatherer gatherer = null;
-    /** Constructor.
-     * @param gatherer The <strong>Gatherer</strong> that created this class.
-     * @see org.greenstone.gatherer.cdm.ClassifierManager
-     * @see org.greenstone.gatherer.cdm.CollectionMetaManager
-     * @see org.greenstone.gatherer.cdm.FormatManager
-     * @see org.greenstone.gatherer.cdm.IndexManager
-     * @see org.greenstone.gatherer.cdm.LanguageManager
-     * @see org.greenstone.gatherer.cdm.MetadataSetManager
-     * @see org.greenstone.gatherer.cdm.PlugInManager
-     * @see org.greenstone.gatherer.cdm.SubcollectionManager
-     */
-    public CollectionDesignManager() {
-	this.gatherer = Gatherer.self;
-	this.classifiers = new ClassifierManager(gatherer, this);
-	this.collectionmetadatum = new CollectionMetaManager(gatherer, this);
-	this.formats = new FormatManager(gatherer, this);
-	this.indexes = new IndexManager(gatherer, this);
-	this.languages = new LanguageManager(gatherer, this);
-	this.metadatasets = new MetadataSetManager(gatherer);
-	this.plugins = new PlugInManager(gatherer, this);
-	this.rest = new ArrayList();
-	this.subcollections = new SubcollectionManager(gatherer, this);
-	this.superman = new SuperCollectionManager();
-	this.transman = new TranslationManager(this);
-    }
-    /** In order to prevent Components that have registered themselves wasting memory, this method invalidates each of the sub-managers controls, causing them to unregister listeners.
-     * @see org.greenstone.gatherer.cdm.ClassifierManager
-     * @see org.greenstone.gatherer.cdm.CollectionMetaManager
-     * @see org.greenstone.gatherer.cdm.FormatManager
-     * @see org.greenstone.gatherer.cdm.IndexManager
-     * @see org.greenstone.gatherer.cdm.LanguageManager
-     * @see org.greenstone.gatherer.cdm.MetadataSetManager
-     * @see org.greenstone.gatherer.cdm.PlugInManager
-     * @see org.greenstone.gatherer.cdm.SubcollectionManager
+    static public TranslationView translation_view;
+    /** Constructor. Loads a certain collection configuration file, which is parsed into a DOM. This model is then registered with the command information managers, each of whom knows how to, and provides controls to, alter certain commands.
+     * @param collect_config_file the File representing a collection configuration file either in its text (G2) or xml (G3) form
+     */
+    public CollectionDesignManager(File collect_config_file) {
+	Gatherer.println("Initializaing CollectionDesignModule.");
+	change_listener = new CDMChangeListener();
+	// Parse the collection configuration
+	collect_config = new CollectionConfiguration(collect_config_file);
+	if(Gatherer.debug != null) {
+	    collect_config.display();
+	}
+	// Create the command information managers, registering the config file with each as necessary
+	language_manager = new LanguageManager(collect_config.getLanguages());
+	collectionmeta_manager = new CollectionMetaManager();
+	classifier_manager = new ClassifierManager();
+	general_manager = new GeneralManager();
+	index_manager = new IndexManager(collect_config.getIndexes());
+	metadataset_view = new MetadataSetView();
+	plugin_manager = new PlugInManager();
+	plugin_manager.placeSeparator();
+	subcollection_manager = new SubcollectionManager();
+	subcollectionindex_manager = new SubcollectionIndexManager(collect_config.getSubIndexes());
+	supercollection_manager = new SuperCollectionManager(collect_config.getSuperCollection());
+	translation_view = new TranslationView();
+	format_manager = new FormatManager(); // Parse formats at the very end, given that they depend upon several other managers to appear properly.
+	Gatherer.println("CollectionDesignModule loaded.");
+    }
+    /** This method deconstructs each of the managers, causing them to dispose of their controls.
      */
     public void destroy() {
 	// Remove visual the component from its parent.
-	if(gui.getParent() != null) {
-	    gui.getParent().remove(gui);
+	if(general_manager.getParent() != null) {
+	    general_manager.getParent().remove(general_manager);
 	}
 	// Remove references from persistant listeners.
-	if(classifiers != null && formats != null && gui != null && indexes != null && languages != null && metadatasets != null && plugins != null && subcollections != null) {
-	    classifiers.invalidateControls();
-	    formats.invalidateControls();
-	    gui.invalidateControls();
-	    indexes.invalidateControls();
-	    languages.invalidateControls();
-	    metadatasets.invalidateControls();
-	    plugins.invalidateControls();
-	    subcollections.invalidateControls();
-	    superman.invalidateControls();
-	}
-	// Null globals.
-	classifiers = null;
-	collectionmetadatum = null;
-	creator = null;
-	maintainer = null;
-	in_file = null;
-	formats = null;
-	gui = null;
-	indexes = null;
-	languages = null;
-	metadatasets = null;
-	plugins = null;
-	subcollections = null;
-	superman = null;
-	rest = null;
-	gatherer = null;
+	classifier_manager.destroy();
+	classifier_manager = null;
+	format_manager.destroy();
+	format_manager = null;
+	general_manager.destroy();
+	general_manager = null;
+	index_manager.destroy();
+	index_manager = null;
+	language_manager.destroy();
+	language_manager = null;
+	metadataset_view.destroy();
+	metadataset_view = null;
+	plugin_manager.destroy();
+	plugin_manager = null;
+	subcollection_manager.destroy();
+	subcollection_manager = null;
+	supercollection_manager.destroy();
+	supercollection_manager = null;
+	translation_view.destroy();
+	translation_view = null;
     }
 
     /** Display the GUI interface for the CollectionDesignManager in the centre of the indicated panel.
-     * @param target The <strong>JPanel</strong> you wish to display the GUI on.
-     * @see org.greenstone.gatherer.cdm.GUI
+     * @param target the JPanel you wish to display the gui on
      */
     public void display(JPanel target) {
-	this.gui = new GUI(gatherer, this);
-	target.add(gui, BorderLayout.CENTER);
+	target.add(general_manager, BorderLayout.CENTER);
     }
     /** When the tab on the JTabbedPane that contains the GUI is selected, this method is called to ensure that the controls are all up to date, in terms of references to metadata etc.
-     * @see org.greenstone.gatherer.cdm.GUI
      */
     public void gainFocus() {
-	gui.updateUI();
-    }
-    /** Retrieve the current set of indexes as defined by the user configuration.
-     * @return An <strong>ArrayList</strong> of indexes.
-     * @see org.greenstone.gatherer.cdm.Index
-     * @see org.greenstone.gatherer.cdm.IndexManager
-     */
-    public ArrayList getIndexes() {
-	ArrayList result = new ArrayList();
-	int size = indexes.size();
-	for(int i = 0; i < size; i++) {
-	    result.add(indexes.getIndex(i));
-	}
-	return result;
+	general_manager.gainFocus();
     }
     /** Retrieve the name of the collection configuration file which is being used as the source of the information in this object.
@@ -221,8 +147,8 @@
      */
     public String getFilename() {
-	return in_file.getAbsolutePath();
-    }
-    /** Used to parse the given file as if it was a collection configuration file, passing any parsed commands to the relevant submanager. Currently ignores any comments, and marks any other lines as being unrecognizable if they can't be parsed.
-     * @param filename The name of the file you wish to attempt to pass as a <strong>String</strong>.
+	return collect_config.getFile().getAbsolutePath();
+    }
+
+    /** Cause the current collection configuration to be written out to disk.
      * @see org.greenstone.gatherer.cdm.ClassifierManager
      * @see org.greenstone.gatherer.cdm.CollectionMetaManager
@@ -235,212 +161,7 @@
      * @see org.greenstone.gatherer.util.EmailAddress
      */
-    private BufferedReader in;
-    /** If one of the submanagers detects that it hasn't been provided enough arguments (such as format which always requires at least three) it can call this method to request another line of text from the collect.cfg. If the collect.cfg was not well formed this will cause some problems, but that can't be helped. 
-     * @param prefix the String we have so far
-     * @return a String with a further line form the collect.cfg appended
-     */
-    public String parseMore(String prefix) {
-	///ystem.err.println("Command incomplete: " + prefix);
-	try {
-	    String suffix = in.readLine();
-	    ///ystem.err.println("Adding: " + suffix);
-	    if(suffix != null) {
-		prefix = prefix + " " + suffix;
-	    }
-	    suffix = null;
-	}
-	catch(Exception error) {
-	    error.printStackTrace();
-	}
-	return prefix;
-    }
-
-    public void parse(String filename) {
-	try {
-	    in_file = new File(filename);
-	    FileReader in_reader = new FileReader(in_file);
-	    in = new BufferedReader(in_reader);
-	    String command = null;
-	    while((command = in.readLine()) != null) {
-		if(command.length() > 0) {
-		    // Now we've finished parsing a command line, see what manager wants a piece of it.
-		    // We have to test the end of command for the special character '\'. If found, remove it and append the next line, then repeat.
-		    while(command.trim().endsWith("\\")) {
-			String next_line = (in.readLine()).trim();
-			if(next_line != null && next_line.length() > 0) {
-			    command = command.substring(0, command.lastIndexOf("\\"));
-			    command = command + next_line;
-			}
-		    }
-		    // Now we've finished parsing a command line, see what manager wants a piece of it.
-		    boolean found = false;
-		    String command_lc = command.toLowerCase();
-		    if(command_lc.startsWith("creator")) {
-			creator = new EmailAddress(gatherer, command);
-			found = true;
-		    }
-		    if(command_lc.startsWith("maintainer")) {
-			maintainer = new EmailAddress(gatherer, command);
-			found = true;
-		    }
-		    if(command_lc.startsWith("public")) {
-			if(command_lc.endsWith("true")) {
-			    public_col = true;
-			}
-			else {
-			    public_col = false;
-			}
-			found = true;
-		    }
-		    if(command_lc.startsWith("beta")) {
-			if(command_lc.endsWith("false")) {
-			    beta = false;
-			}
-			else {
-			    beta = true;
-			}
-			found = true;
-		    }
-		    if(!found) {
-			found = indexes.parse(command);
-		    }
-		    if(!found) {
-			found = subcollections.parse(command, false);
-		    }
-		    if(!found) {
-			found = languages.parse(command);
-		    }
-		    if(!found) {
-			found = plugins.parse(command);
-		    }
-		    if(!found) {
-			found = classifiers.parse(command);
-		    }
-		    if(!found) {
-			found = formats.parse(command, false);
-		    }
-		    if(!found) {
-			found = collectionmetadatum.parse(command, false);
-		    }
-		    if(!found) {
-			found = superman.parse(command);
-		    }
-		    // Metadataset commands
-		    if(command_lc.startsWith("metadataset")) {
-			// Nothing yet. Eventually used to import metadata.
-			found = true;
-		    }
-		    // Comments we ignore.
-		    if(command_lc.startsWith("# these instructions are not recognized by the gatherer.")) {
-			// Ignore
-			found = true;
-		    }
-		    // We have been unable to parse this command, add it to rest.
-		    if(!found) {
-			rest.add(command);
-		    }
-		}
-	    }
-	    in.close();
-	    // Now attempt to finalize any commands that were not immediately parsed as they were waiting for unresolved references.
-	    subcollections.reparseUnresolved();
-	    classifiers.reparseUnresolved();
-	    formats.reparseUnresolved();
-	    collectionmetadatum.reparseUnresolved();
-	}
-	catch(Exception error) {
-	    error.printStackTrace();
-	}
-    }
-    /** Cause the current definitions within the Collection Design Manager to be written back out to whatever collect.cfg it is based upon, taking care to change reference pointers into something more sensible within the text file.
-     * @see org.greenstone.gatherer.cdm.ClassifierManager
-     * @see org.greenstone.gatherer.cdm.CollectionMetaManager
-     * @see org.greenstone.gatherer.cdm.FormatManager
-     * @see org.greenstone.gatherer.cdm.IndexManager
-     * @see org.greenstone.gatherer.cdm.LanguageManager
-     * @see org.greenstone.gatherer.cdm.MetadataSetManager
-     * @see org.greenstone.gatherer.cdm.PlugInManager
-     * @see org.greenstone.gatherer.cdm.SubcollectionManager
-     * @see org.greenstone.gatherer.util.EmailAddress
-     */
     public void save() {
-	try {
-	    // If the file already exists (it should) rename it.
-	    if(in_file.exists()) {
-		String filename = in_file.getAbsolutePath();
-		File backup = new File(filename + "~");
-		backup.deleteOnExit();
-		if(!in_file.renameTo(backup)) {
-		    Gatherer.println("Error in CollectionDesignManager.parse(): FileRenamedException");
-		}
-		in_file = new File(filename); // Just in case we moved it.
-	    }
-	    FileOutputStream out = new FileOutputStream(in_file);
-
-	    StringBuffer text = new StringBuffer("");
-	    if(creator != null) {
-		text.append(creator.toString());
-		text.append("\n");
-	    }
-	    if(maintainer != null) {
-		text.append(maintainer.toString());
-		text.append("\n");
-	    }
-	    if(public_col) {
-		text.append("public true\n");
-	    }
-	    else {
-		text.append("public false\n");
-	    }
-	    if(beta) {
-		text.append("beta true\n");
-	    }
-	    else {
-		text.append("beta false\n");
-	    }
-	    if(text.length() > 0) {
-		text.append("\n");
-	    }
-	    if(plugins.size() > 0) {
-		text.append(plugins.toString());
-	    }
-	    if(indexes.size() > 0) {
-		text.append(indexes.toString());
-	    }
-	    if(languages.size() > 0) {
-		text.append(languages.toString());
-	    }
-	    if(subcollections.size() > 0) {
-		text.append(subcollections.toString());
-	    }
-	    String super_collections_command = superman.toString();
-	    if(super_collections_command != null) {
-		text.append(super_collections_command);
-	    }
-	    if(classifiers.size() > 0) {
-		text.append(classifiers.toString());
-	    }
-	    if(formats.size() > 0) {
-		text.append(formats.toString());
-	    }
-	    if(collectionmetadatum.size() > 0) {
-		text.append(collectionmetadatum.toString());
-	    }
-	    text.append(metadatasets.toString());
-	    if(rest.size() > 0) {
-		// Write out rest at the bottom.
-		text.append("# These instructions are not recognized by the Gatherer.\n");
-		for(int i = 0; i < rest.size(); i++) {
-		    text.append(rest.get(i));
-		    text.append("\n");
-		}
-	    }
-	    out.write(text.toString().getBytes());
-	    out.close();
-	    out = null;
-	}
-	catch(Exception error) {
-	    error.printStackTrace();
-	}
+	general_manager.loseFocus();
+	collect_config.save();
     }
     /** Method used during a global search and replace to highlight the appropriate record within the Collection Design Managers version of the Metadata Set Manager (view only).
@@ -451,7 +172,30 @@
     public Rectangle setSelectedElement(String element) {
 	// First ensure that the metadata set controls are visible.
-	gui.setSelectedView("CDM.GUI.MetadataSets");
+	general_manager.setSelectedView("CDM.GUI.MetadataSets");
 	// Then tell them to select the given element.
-	return metadatasets.setSelectedElement(element);
+	return metadataset_view.setSelectedElement(element);
+    }
+
+    private class CDMChangeListener
+	implements ActionListener, DocumentListener {
+	
+	public void actionPerformed(ActionEvent event) {
+	    Gatherer.c_man.getCollection().setSaved(false);
+	}
+
+	/** Gives notification that an attribute or set of attributes changed. */
+	public void changedUpdate(DocumentEvent e) {
+	    Gatherer.c_man.getCollection().setSaved(false);
+	}
+          
+	/** Gives notification that there was an insert into the document. */
+	public void insertUpdate(DocumentEvent e) {
+	    Gatherer.c_man.getCollection().setSaved(false);
+	}
+         
+	/** Gives notification that a portion of the document has been removed. */
+	public void removeUpdate(DocumentEvent e) {
+	    Gatherer.c_man.getCollection().setSaved(false);
+	}
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionMeta.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionMeta.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionMeta.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,137 +25,117 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
- * Written:        /05/02
- * Revised:      22/08/02 Revamped, Optimized and Commented.
+ * Written:     
+ * Revised:     28/06/03 - DOM support
  **************************************************************************************/
+import org.greenstone.gatherer.Configuration;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
 import org.greenstone.gatherer.cdm.Index;
-import org.greenstone.gatherer.cdm.Language;
+import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.util.StaticStrings;
 import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
 /** This class encapsulates a single collection level metadata assignment, which constitutes a name, language and value.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
- * @version 2.3
+ * @version 2.4
  */
 public class CollectionMeta
-    implements Comparable {
-    /** A reference to the collection design manager for access to the language manager. */
-    private CollectionDesignManager manager = null;
-    /** The language of this metadata. Should be a two letter code. */
-    private Language language = null;
-    /** The name of the thing this metadata is assigned to, which may also refer to an Index or a Partition. */
-    private Object name = null;
-    /** The value of this metadata. */
-    private String value = null;
+    implements DOMProxyListEntry {
+    private Element element;
+    private String text;
+
     /** Constructor.
-     * @param name The object the metadata is assigned to as an <strong>Object</strong>.
-     * @param language The language of the metadata as a <strong>Language</strong>. Should be a two letter code.
-     * @param value The value of this metadata, as a <strong>String</strong>.
-	  */
-    public CollectionMeta(CollectionDesignManager manager, Object name, Language language, String value) {
-	this.language = language;
-	this.manager = manager;
-	this.name = name;
-	this.value = value;
+     * @param element the Element from which we will determine metadata details
+     */
+    public CollectionMeta(Element element) {
+	this.element = element;
     }
+
+    /** Constructor to create a new piece of metadata given its name. */
+    public CollectionMeta(String name) {
+	element = CollectionDesignManager.collect_config.document.createElement(StaticStrings.COLLECTIONMETADATA_ELEMENT);
+	element.setAttribute(StaticStrings.NAME_ATTRIBUTE, name);
+	element.setAttribute(StaticStrings.LANGUAGE_ATTRIBUTE, Gatherer.config.interface_language);
+    }
+
+    /** Constructor to create a new piece of metadata given its name. */
+    public CollectionMeta(String name, String language) {
+	element = CollectionDesignManager.collect_config.document.createElement(StaticStrings.COLLECTIONMETADATA_ELEMENT);
+	element.setAttribute(StaticStrings.NAME_ATTRIBUTE, name);
+	element.setAttribute(StaticStrings.LANGUAGE_ATTRIBUTE, language);
+    }
+
     /** Method to compare two collection metadata objects to calculate their respective ordering.
-	  * @param object The other metadata to compare to, as an <strong>Object</strong>.
-	  * @return An <i>int</i> which is less than 0 if this object proceeds the given object, 0 if they are equal and greater than 0 otherwise.
-	  * @see org.greenstone.gatherer.cdm.Language
-	  */
+     * @param object the other metadata to compare to, as an Object
+     * @return an int which is less than 0 if this object proceeds the given object, 0 if they are equal and greater than 0 otherwise.
+     * @see org.greenstone.gatherer.cdm.Language
+     */
     public int compareTo(Object object) {
-	if(object instanceof CollectionMeta) {
-	    CollectionMeta metadata = (CollectionMeta) object;
-	    int result = name.toString().compareTo(metadata.getName().toString());
-	    if(result == 0) {
-		Language other_language = metadata.getLanguage();
-		if(language != null && other_language != null) {
-		    result = language.compareTo(metadata.getLanguage());
-		    if(result == 0) {
-			return value.compareTo(metadata.getValue());
-		    }
-		}
-		else if(language != null) {
-		    return -1;
-		}
-		else if(other_language != null) {
-		    return 1;
-		}
-	    }
-	    return result;
-	}
 	return toString().compareTo(object.toString());
     }
+
+    /** Factory constructor. */
+    public DOMProxyListEntry create(Element element) {
+	return new CollectionMeta(element);
+    }
+
     /** Method to compare two collection metadata objects for equality.
-	  * @param object The other metadata to compare to, as an <strong>Object</strong>.
-	  * @return A <i>boolean</i> value of <i>true</i> if the object are equal, <i>false</i> otherwise.
-	  */
+     * @param object The other metadata to compare to, as an <strong>Object</strong>.
+     * @return A <i>boolean</i> value of <i>true</i> if the object are equal, <i>false</i> otherwise.
+     */
     public boolean equals(Object object) {
-	if(compareTo(object) == 0) {
-	    return true;
-	}
-	return false;
+	return (compareTo(object) == 0);
     }
+
+    public Element getElement() {
+	return element;
+    }
+
     /** Method to retrieve the value of language.
-     * @return The value of language as a <strong>Language</strong>.
+     * @return The value of language as a <strong>String</strong>.
      */
-    public Language getLanguage() {
-	return language;
+    public String getLanguage() {
+	// Retrieve the language string
+	return element.getAttribute(StaticStrings.LANGUAGE_ATTRIBUTE);
     }
+
     /** Method to retrieve the value of name.
-     * @return The value of name as an <strong>Object</strong>.
+     * @return 
      */
-    public Object getName() {
-	return name;
+    public String getName() {
+	return element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
     }
     /** Method to retrieve the value of value (well great choice of name there).
-	  * @return The value of value as a <strong>String</strong>.
-	  */
+     * @return The value of value as a <strong>String</strong>.
+     */
     public String getValue() {
-	return value;
+	// We have to replace the string made of '\' 'n' with the '\n' character! Also watch for other greenstone magic
+	return Utility.decodeGreenstone(MSMUtils.getValue(element));
     }
-    /** Method to print out this class as it would appear within the collection configuration file.
-	  * @return A <strong>String</strong> containing the text value of this class.
-	  */
-    public String toString() {
-	String text = "collectionmeta ";
-	if(name instanceof Index) {
-	    text = text + ".";
-	    Index index = (Index)name;
-	    text = text + index.toStringConfig() + " ";
+
+    public boolean isAssigned() {
+	return (element != null && !element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.FALSE_STR));
+    }
+
+    /** Determine if this metadata is one of the four special pieces of metadata.
+     * @return true if this metadata is special, false otherwise.
+     */
+    public boolean isSpecial() {
+	return (element != null && element.getAttribute(StaticStrings.SPECIAL_ATTRIBUTE).equals(StaticStrings.TRUE_STR));
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
 	}
-	else if(name instanceof SubIndex) {
-	    text = text + ".";
-	    SubIndex index = (SubIndex)name;
-	    text = text + index.toString() + " ";
-	}
-	else {
-	    text = text + name.toString() + " ";
-	}
-	if(language != null && manager.languages.size() > 0 && !manager.languages.isDefaultLanguage(language)) {
-	    text = text + "[l=" + language.getCode() + "] ";
-	}
-	text = text + "\"" + Utility.encodeGreenstone(value) + "\"\n";
-	return text;
     }
-    /** Used to update the contents of this collection level metadata to the given 'new' values.
-	  * @param name The new name of the metadata, as a <strong>Object</strong>.
-	  * @param language The new <strong>Language</strong> of the metadata.
-	  * @param value And the value the metadata is assigned, as a <strong>String</strong>.
-	  */
-    public void update(Object name, Language language, String value) {
-	this.name = name;
-	this.language = language;
-	this.value = value;
+
+    public void setElement(Element element) {
+	this.element = element;
+	text = null;
     }
 
@@ -174,12 +144,16 @@
      */
     public void setValue(String value) {
-	this.value = value;
+	MSMUtils.setValue(element, Utility.encodeGreenstone(value));
+	text = null; // Reset text
     }
 
-    /** Ensure this is a valid metadata entry by checking that the value is non-null and non-zero length (after having removed whitespace).
-     * @return <i>true</i> if this metadata has a valid value and should be added to the config, <i>false</i> otherwise.
-     */
-    public boolean valid() {
-	return(value != null && value.trim().length() > 0);
+    /** Method to print out this class as it would appear within the collection configuration file.
+	  * @return A <strong>String</strong> containing the text value of this class.
+	  */
+    public String toString() {
+	if(text == null) {
+	    text = CollectionConfiguration.toString(element, true);
+	}
+	return text;
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionMetaManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionMetaManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/CollectionMetaManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,20 +25,5 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
-/**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
- * Written:        /05/02
- * Revised:      22/08/02 Revamped, Optimized and Commented.
- **************************************************************************************/
 import java.awt.*;
 import java.awt.event.*;
@@ -59,111 +34,60 @@
 import org.greenstone.gatherer.cdm.CollectionDesignManager;
 import org.greenstone.gatherer.cdm.CollectionMeta;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
 import org.greenstone.gatherer.cdm.Index;
-import org.greenstone.gatherer.cdm.Language;
 import org.greenstone.gatherer.gui.EditorDialog;
+import org.greenstone.gatherer.util.StaticStrings;
 import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
 /** This class is responsible for maintaining a list of assigned collection level metadata, and for allows manipulations on the aforementioned data.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
- * @version 2.3
+ * @version 2.3d
  */
-// ####################################################################################
-// Optimization                               Saving
-// ####################################################################################
-// Removed Vector and Hashtable               + Memory, + Processor
-// ####################################################################################
-public class CollectionMetaManager
-    extends DefaultListModel {
-    /** A reference to the cdm manager so we can access indexes and languages. */
-    private CollectionDesignManager manager = null;
-    /** A reference to ourself so that inner classes can use us as a model. */
-    private DefaultListModel model = null;
-    /** A reference to the Gatherer. */
-    private Gatherer gatherer = null;
-    /** The language the most recent metadata returned was in. */
-    private Language current_language = null;
-    /** We can't safely parse metadata commands until after all the other commands have been parsed, so we store commands here for now. */
-    private ArrayList unresolved_commands = null;
-    /** Constructor.
-     * @param gatherer A reference to the <strong>Gatherer</strong>.
-     * @param manager A reference to the <strong>CollectionDesignManager</strong> for access to other configuration managers.
-     */
-    public CollectionMetaManager(Gatherer gatherer, CollectionDesignManager manager) {
-	super();
-	this.gatherer = gatherer;
-	this.manager = manager;
-	this.model = this;
-	this.unresolved_commands = new ArrayList();
+public class CollectionMetaManager 
+    extends DOMProxyListModel {
+    
+    /** Constructor. */
+    public CollectionMetaManager() {
+	super(CollectionDesignManager.collect_config.getDocumentElement(), StaticStrings.COLLECTIONMETADATA_ELEMENT, new CollectionMeta(""));
+	Gatherer.println("CollectionMetaManager: " + getSize() + " metadata parsed.");
     }
     /** Method to add a new piece of metadata.
-	  * @param metadata The new <strong>CollectionMeta</strong>.
-	  */
-    public void addMetadata(CollectionMeta metadata) {
-	CollectionMeta existing = getMetadata(metadata.getName().toString(), metadata.getLanguage(), false);
-	if(existing != null) {
-	    removeElement(existing);
+     * @param metadata the new CollectionMeta
+     */
+    public void addMetadatum(CollectionMeta metadata) {
+	if(!contains(metadata)) {
+	    Element element = metadata.getElement();
+	    // Locate where we should insert this new subcollection. 
+	    Node target_node = CollectionConfiguration.findInsertionPoint(element);
+	    // Failing that we insert immediately after a language string
+	    add(root, metadata, target_node);
+	    Gatherer.c_man.configurationChanged();
 	}
-	addElement(metadata);
-	gatherer.c_man.configurationChanged();
+	Gatherer.c_man.configurationChanged();
     }
-    /** Retrieve the collectionextra metadata in the default language, if present.
-	  * @return A <strong>CollectionMeta</strong> containing the collectionextra in the default language if present, or else the first collectionextra of any language, otherwise <i>null</i>.
-	  * @see org.greenstone.gatherer.cdm.Language
-	  */
-    public CollectionMeta getCollectionExtra() {
-	CollectionMeta result = getMetadata("collectionextra", Gatherer.config.interface_language, true);
-	if(result == null) {
-	    result = new CollectionMeta(manager, "collectionextra", Gatherer.config.interface_language, "");
-	    addMetadata(result);
+
+    public CollectionMeta get(int i) {
+	return (CollectionMeta) getElementAt(i);
+    }
+
+    /** Retrieve all of the general metadata. */
+    public ArrayList getMetadata() {
+	ArrayList result = new ArrayList();
+	int size = getSize();
+	for(int i = 0; i < size; i++) {
+	    CollectionMeta metadata = (CollectionMeta) getElementAt(i);
+	    if(!metadata.getName().startsWith(StaticStrings.STOP_CHARACTER)) {
+		result.add(metadata);
+	    }
 	}
 	return result;
     }
-    /** Retrieve the collectionname metadata in the default language, if present.
-	  * @return A <strong>CollectionMeta</strong> containing the collectionname in the default language if present, or else the first collectionname of any language, otherwise <i>null</i>.
-	  * @see org.greenstone.gatherer.cdm.Language
-	  */
-    public CollectionMeta getCollectionName() {
-	return getMetadata("collectionname", Gatherer.config.interface_language, true);
-    }
-    /** Retrieve the iconcollection metadata in the default language, if present.
-	  * @return A <strong>CollectionMeta</strong> containing the iconcollection in the default language if present, or else the first iconcollection of any language, otherwise <i>null</i>.
-	  * @see org.greenstone.gatherer.cdm.Language
-	  */
-    public CollectionMeta getIconCollection() {
-	CollectionMeta result = getMetadata("iconcollection", Gatherer.config.interface_language, true);
-	if(result == null) {
-	    result = new CollectionMeta(manager, "iconcollection", Gatherer.config.interface_language, "");
-	    addMetadata(result);
-	}
-	return result;
-    }
-    /** Retrieve the iconcollection metadata in the default language, if present.
-	  * @return A <strong>CollectionMeta</strong> containing the iconcollectionsmall in the default language if present, or else the first iconcollectionsmall of any language, otherwise <i>null</i>.
-	  * @see org.greenstone.gatherer.cdm.Language
-	  */
-    public CollectionMeta getIconCollectionSmall() {
-	CollectionMeta result = getMetadata("iconcollectionsmall", Gatherer.config.interface_language, true);
-	if(result == null) {
-	    result = new CollectionMeta(manager, "iconcollectionsmall", Gatherer.config.interface_language, "");
-	    addMetadata(result);
-	}
-	return result;
-    }
-    /** Method to retrieve the list of metadata.
-	  * @return An <strong>ArrayList</strong> containing the metadata.
-	  */
-    public ArrayList getMetadata() {
-	ArrayList metadata = new ArrayList();
-	for(int i = 0; i < size(); i++) {
-	    metadata.add(get(i));
-	}
-	Collections.sort(metadata);
-	return metadata;
-    }
 	 
     /** Retrieve all of the metadata for the given feature, regardless of language. */
-    public ArrayList getMetadata(Object name) {
+    public ArrayList getMetadata(String name) {
 	ArrayList result = new ArrayList();
-	for(int i = 0; i < size(); i++) {
-	    CollectionMeta metadata = (CollectionMeta) get(i);
+	int size = getSize(); // Refresh DOM Model
+	for(int i = 0; i < size; i++) {
+	    CollectionMeta metadata = (CollectionMeta) getElementAt(i);
 	    if(metadata.getName().equals(name)) {
 		result.add(metadata);
@@ -172,24 +96,51 @@
 	return result;
     }
+
+    /** Retrieve the named piece of metadata, in the default language, if available. If no such metadata is available then it is created.
+     * @param name the name of the metadatum to retrieve as a String
+     * @return the dom Element containing the specified metadatum
+     */
+    public CollectionMeta getMetadatum(String name) {
+	return getMetadatum(name, true);
+    }
+
+    public CollectionMeta getMetadatum(String name, boolean add_if_not_found) {
+	///atherer.println("Get the metadata for " + name + " in the default language.");
+	int size = getSize();
+	for(int i = 0; i < size; i++) {
+	    CollectionMeta metadatum = (CollectionMeta) getElementAt(i);
+	    if(metadatum.getName().equals(name) && metadatum.getLanguage().equals(Gatherer.config.interface_language)) {
+		///atherer.println("Found '" + metadatum + "'");
+		return metadatum;
+	    }
+	    else {
+		///atherer.println("No match with: " + metadatum.getName() + " [l=" + metadatum.getLanguage() + "] \"" + metadatum.getValue() + "\"");
+	    }
+	    metadatum = null;
+	}
+	if(add_if_not_found) {
+	    CollectionMeta result = new CollectionMeta(name);
+	    addMetadatum(result);
+	    ///atherer.println("Added new metadata.");
+	    return result;
+	}
+	else {
+	    return null;
+	}
+    }
+
     /** Method to retrieve a certain piece of metadata based on its name and language.
-	  * @param name the name of the metadata as an Object (as it may actually be a refernce to an Index or SubIndex)
-	  * @param language The <strong>Language</strong> of the metadata.
-	  * @param partial <i>true</i> to return the first partial match (ie matches name but not language).
-	  * @return The <strong>CollectionMeta</strong> requested, or <i>null</i> if no such metadata.
-	  */
-    public CollectionMeta getMetadata(Object name, Language language, boolean partial) { 
+     * @param name the name of the metadata as an Object (as it may actually be a refernce to an Index or SubIndex)
+     * @param language the language of the metadata.
+     * @param partial <i>true</i> to return the first partial match (ie matches name but not language).
+     * @return The <strong>CollectionMeta</strong> requested, or <i>null</i> if no such metadata.
+     */
+    public CollectionMeta getMetadata(String name, String language, boolean partial) { 
 	CollectionMeta partial_match = null;
-	for(int i = 0; i < size(); i++) {
-	    CollectionMeta metadata = (CollectionMeta) get(i);
+	for(int i = 0; i < getSize(); i++) {
+	    CollectionMeta metadata = (CollectionMeta) getElementAt(i);
 	    Object metadata_name = metadata.getName();
 	    // We test the case of an object match (ie Index to Index)...
 	    if(metadata_name.equals(name)) {
-		if (metadata.getLanguage().equals(language)) {
-		    return metadata;
-		}
-		partial_match = metadata;
-	    }
-	    // But if that fails we also try a string match such that "section:dls.Title" == Index
-	    if(metadata_name.toString().equals(name.toString())) {
 		if (metadata.getLanguage().equals(language)) {
 		    return metadata;
@@ -203,179 +154,44 @@
 	return null;
     }
-    /** Method that attempts to parse a collection metadata command from the given text. If a command is parsed successfully it is immediately added to the the collections metadata.
-	 * @param command The command text we wish to parse, as a <strong>String</strong>.
-	 * @return A <i>boolean</i> which is <i>true</i> if a collection metadata command was successfully parsed, <i>false</i> otherwise.
-	 * @see org.greenstone.gatherer.cdm.CollectionDesignManager
-	 * @see org.greenstone.gatherer.cdm.CommandTokenizer
-	 * @see org.greenstone.gatherer.cdm.Language
-	 * @see org.greenstone.gatherer.cdm.LanguageManager
-	 * @see org.greenstone.gatherer.util.Utility
-	 */
-    public boolean parse(String command, boolean finished) {
-	String temp = command.toLowerCase();
-	if(temp.startsWith("collectionmeta")) {
-	    if(finished) {
-		CommandTokenizer ct = new CommandTokenizer(command);
-		if(ct.countTokens() >= 3) {
-		    ct.nextToken(); // Throw away collectionmeta
-		    Object key = ct.nextToken();
-		    Language language = null;
-		    String language_code = null;
-		    String value = ct.nextToken();
-		    // Arg. Remember a language token will be '[l=<code>]'
-		    if(value.startsWith("[") && value.endsWith("]")) {
-			language_code = value.substring(3, value.length() - 1);
-			///ystem.err.println("Language code = " + language_code);
-			value = ct.nextToken();
-		    }
-		    // Check if the key is an index, and if so retrieve it.
-		    if(((String)key).startsWith(".")) {
-			///ystem.err.println("Detected '.' prefix.");
-			String key_str = (String)key;
-			key = null;
-			key_str = key_str.substring(1);
-			Index ind = Index.parseIndexConfig(key_str, manager);
-			if (ind != null) {
-			    
-			    key = manager.indexes.getIndex(ind.toString(false));
-			}
-			
-			// If that didn't work, then it might be a subindex so have a bash at retrieving that instead.
-			if(key == null) {
-			    key = manager.subcollections.getSubIndex(key_str);
-			    //if(key != null) {
-				///ystem.err.println("Its a subindex.");
-			    //}
-			    //else {
-				///ystem.err.println("Error Will Robinson.");
-			    //}
-			}
-			//else {
-			    ///ystem.err.println("Its an index.");
-			//}
-		    }
-		    // An if we have a language code, retrieve its object too.
-		    if(language_code != null) {
-			language = manager.languages.getLanguage(language_code, false);
-		    }
-		    // Otherwise set language to the default language.
-		    else {
-			language = Gatherer.config.interface_language;
-		    }
-		    if(key != null) {
-			// Trim any "
-			if(value.equals("\"\"")) {
-			    value = "";
-			}
-			else {
-			    if(value.startsWith("\"")) {
-				value = value.substring(1);
-			    }
-			    if(value.endsWith("\"")) {
-				value = value.substring(0, value.length() - 1);
-			    }
-			}
-			CollectionMeta meta = new CollectionMeta(manager, key, language, Utility.decodeGreenstone(value));
-			addMetadata(meta);
-		    }
-		    return true;
+
+    /** Method to remove a piece of metadata.
+     * @param metadata metadata
+     */
+    public void removeMetadata(CollectionMeta metadata) {
+	if(metadata != null) {
+	    String name = metadata.getName();
+	    String language = metadata.getLanguage();
+	    for(int i = 0; i < getSize(); i++) {
+		CollectionMeta other = (CollectionMeta) getElementAt(i);
+		if(name.equals(other.getName()) && language.equals(other.getLanguage())) {
+		    remove(i);
+		    Gatherer.c_man.configurationChanged();
+		    return;
 		}
-	    }
-	    else {
-		unresolved_commands.add(command);
-		return true;
-	    }
+		other = null;
+	    }	
+	    language = null;
+	    name = null;
 	}
-	return false;
-    }
-    /** Ensure that the values being showed are the most up to date. */
-    public void refresh() {
-	fireContentsChanged(this, 0, size());
     }
 
-    /** Method to remove a piece of metadata.
-	  * @param metadata The <strong>CollectionMeta</strong> you wish to remove.
-	  */
-    public void removeMetadata(CollectionMeta metadata) {
-	removeElement(metadata);
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method which attempts to reparse obvious metadata commands which used unresovable references.
-	  */
-    public void reparseUnresolved() {
-	for(int i = 0; i < unresolved_commands.size(); i++) {
-	    parse((String)unresolved_commands.get(i), true);
+    /** Removes all of the metadata with a certain name, regardless of language or value. */
+    public void removeMetadata(String name) {
+	boolean removed_entry = false;
+	for(int i = getSize(); i != 0; i--) {
+	    CollectionMeta other = (CollectionMeta) getElementAt(i - 1);
+	    if(name.equals(other.getName())) {
+		remove(i - 1);
+		removed_entry = true;
+	    }
+	    other = null;
+	}	
+	if(removed_entry) {
+	    Gatherer.c_man.configurationChanged();
 	}
-	// Regardless of if they work, clear the commands.
-	unresolved_commands.clear();
-    }
-    /** Sets the value of a certain metadata.
-	  * @param name The name of the metadata as a <strong>String</strong>.
-	  * @param language The <strong>Language</strong> to use.
-	  * @param value The value of the metadata also as a <strong>String</strong>.
-	  */
-    public void setMetadata(String name, Language language, String value) {
-	addMetadata(new CollectionMeta(manager, name, language, value));
-    }
-    /** Method to produce the list of metadata in a string such as you would find in the collection configuration file.
-	  * @return A <strong>String</strong> containing the list of collection metadata.
-	  */
-    public String toString() {
-	// We sort the metadata first.
-	ArrayList metadatum = new ArrayList();
-	for(int i = 0; i < size(); i++) {
-	    metadatum.add(get(i));
-	}
-	Collections.sort(metadatum, new CollectionMetaComparator());
-	// Now we print them
-	StringBuffer text = new StringBuffer("");
-	for(int i = 0; i < metadatum.size(); i++) {
-	    CollectionMeta data = (CollectionMeta) metadatum.get(i);
-	    if(data.valid()) {
-		text.append(data.toString());
-	    }
-	}
-	metadatum = null;
-	text.append("\n");
-	return text.toString();
-    }
-    /** Overloaded to call get with both a key and an empty argument array.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  */
-    private String get(String key) {
-	return get(key, null);
-    }
-    /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
-	  * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.Dictionary
-	  */
-    private String get(String key, String args[]) {
-	if(key.indexOf('.') == -1) {
-	    key = "CDM.CollectionMetaManager." + key;
-	}
-	return gatherer.dictionary.get(key, args);
     }
 
-    private class CollectionMetaComparator
-	implements Comparator {
-	public int compare(Object o1, Object o2) {
-	    String s1 = o1.toString();
-	    String s2 = o2.toString();
-	    if(s1.startsWith(".")) {
-		return 1;
-	    }
-	    else if(s2.startsWith(".")) {
-		return -1;
-	    }
-	    return o1.toString().compareTo(o2.toString());
-	}
-	public boolean equals(Object o2) {
-	    return (compare(this, o2) == 0);
-	}
+    public int size() {
+	return getSize();
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/Control.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/Control.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/Control.java	(revision 4932)
@@ -0,0 +1,36 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+/**
+ * @author John Thompson, Greenstone Digital Library, University of Waikato
+ * @version 2.3d
+ */
+public interface Control {
+    public void destroy();
+    public void gainFocus();
+    public void loseFocus();
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/DOMProxyListEntry.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/DOMProxyListEntry.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/DOMProxyListEntry.java	(revision 4932)
@@ -0,0 +1,36 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+import org.w3c.dom.*;
+public interface DOMProxyListEntry 
+    extends Comparable {
+    public DOMProxyListEntry create(Element element);
+    public Element getElement();
+    public boolean isAssigned();
+    public void setAssigned(boolean assigned);
+    public void setElement(Element element);
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/DOMProxyListModel.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/DOMProxyListModel.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/DOMProxyListModel.java	(revision 4932)
@@ -0,0 +1,237 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+
+import java.util.*;
+import javax.swing.*;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.w3c.dom.*;
+
+/** This class provides ListModel like access to a list of nodes within a DOM model.
+ * @author John Thompson, Greenstone Digital Library, University of Waikato
+ * @version 2.3d
+ */
+public class DOMProxyListModel
+    extends AbstractListModel {
+    protected Element root;
+    private DOMProxyListEntry class_type;
+    private HashMap cache = new HashMap();
+    private NodeList children = null;
+    private String tag_name;
+
+    /** Constructor.
+     * @param root the Element at the root of the subtree to be searched for appropriate child elements
+     * @param tag_name the name of appropriate elements as a String
+     * @param class_type the type of object to wrap the elements returned in, as a DOMProxyListEntry
+     */
+    public DOMProxyListModel(Element root, String tag_name, DOMProxyListEntry class_type) {
+	this.class_type = class_type;
+	this.root = root;
+	this.tag_name = tag_name;
+    }
+
+    /** Used to add an element into the underlying dom, and fire the appropriate repaint events. This version always adds the new element at the very head of the DOM. */
+    public synchronized void add(DOMProxyListEntry entry) {
+	Element element = entry.getElement();
+	if(root.hasChildNodes()) {
+	    Node sibling = root.getFirstChild();
+	    root.insertBefore(element, sibling);
+	    sibling = null;
+	}
+	else {
+	    root.appendChild(element);
+	}
+	element = null;
+	// Regardless fire update event
+	cache.clear();
+	fireIntervalAdded(this, 0, 0);
+    }
+
+    /** Used to add an element into the underlying dom, and fire the appropriate repaint events.
+     * @param index the index where the element should be inserted (relative of the other elements in this proxy list)
+     * @param element the Element to be inserted
+     */
+    public synchronized void add(int index, DOMProxyListEntry entry) {
+	///atherer.println("Add entry at " + index + " where size = " + getSize());
+	Element element = entry.getElement();
+	// retrieve the node where we want to insert
+	if(index < children.getLength()) {
+	    Node sibling = children.item(index);
+	    // Find the parent node
+	    Node parent_node = sibling.getParentNode();
+	    parent_node.insertBefore(element, sibling);
+	    sibling = null;
+	}
+	// If the index is too large, we are adding to the end of our list of entries. However you have to remember that this list is only a viewport on the entire DOM so there might be entries following this group that we actually want to insert before (not append at the very end!)
+	else {
+	    // Retrieve the currently last entry
+	    Node sibling = children.item(children.getLength() - 1);
+	    Node parent_node = sibling.getParentNode();
+	    sibling = sibling.getNextSibling();
+	    if(sibling != null) {
+		parent_node.insertBefore(element, sibling);
+	    }
+	    // Add to the root node
+	    else {
+		root.appendChild(element);
+	    }
+	}
+	// Regardless fire update event
+	cache.clear();
+	fireIntervalAdded(this, index, index);
+    }
+
+    /** Used to add an element into the underlying dom, and fire the appropriate repaint events. This version inserts the new entry immediately -after- the given entry in the DOM.
+     * @param entry the DOMProxyListEntry to be inserted
+     * @param proceeding_entry the DOMProxyListEntry immediately before where we want the new entry
+     */
+    public synchronized void addAfter(DOMProxyListEntry entry, DOMProxyListEntry proceeding_entry) {
+	Element element = entry.getElement();
+	Element proceeding_sibling = proceeding_entry.getElement();
+	Node parent_node = proceeding_sibling.getParentNode();
+	Node following_sibling = proceeding_sibling.getNextSibling();
+	if(following_sibling != null) {
+	    parent_node.insertBefore(element, following_sibling);
+	}
+	else {
+	    parent_node.appendChild(element);
+	}
+	// Regardless fire update event
+	cache.clear();
+	int index = indexOf(proceeding_entry) + 1;
+	fireIntervalAdded(this, index, index);
+    }
+
+    /** Used to add an element into the underlying dom, and fire the appropriate repaint events. This version inserts the new entry immediately -before- the given entry in the DOM.
+     * @param entry the DOMProxyListEntry to be inserted
+     * @param following_entry the DOMProxyListEntry immediately after where we want the new entry
+     */
+    public synchronized void addBefore(DOMProxyListEntry entry, DOMProxyListEntry following_entry) {
+	Element element = entry.getElement();
+	Element following_sibling = following_entry.getElement();
+	Node parent_node = following_sibling.getParentNode();
+	parent_node.insertBefore(element, following_sibling);
+	// Regardless fire update event
+	cache.clear();
+	int index = indexOf(entry);
+	fireIntervalAdded(this, index, index);
+    }
+
+    public synchronized void add(Node parent, DOMProxyListEntry entry, Node sibling) {
+	Element child = entry.getElement();
+	if(sibling != null) {
+	    parent.insertBefore(child, sibling);
+	}
+	else {
+	    parent.appendChild(child);
+	}
+	cache.clear();
+	int index = indexOf(entry);
+	fireIntervalAdded(this, index, index);
+    }
+
+    public synchronized ArrayList children() {
+	ArrayList child_list = new ArrayList();
+	int child_count = children.getLength();
+	for(int i = 0; i < child_count; i++) {
+	    child_list.add(class_type.create((Element)children.item(i)));
+	}
+	return child_list;
+    }
+
+    public synchronized boolean contains(DOMProxyListEntry entry) {
+	boolean found = false;
+	int size = getSize();
+	for(int i = 0; !found && i < size; i++) {
+	    DOMProxyListEntry sibling = (DOMProxyListEntry) getElementAt(i);
+	    if(sibling.equals(entry)) {
+		found = true;
+	    }
+	}
+	return found;
+    }
+
+    public synchronized Object getElementAt(int index) {
+	Object object = cache.get(new Integer(index));
+	if(object == null) {
+	    // Retrieve the required element
+	    Element element = (Element) children.item(index);
+	    // Now wrap it in the object of the users choice
+	    object = class_type.create(element);
+	    cache.put(new Integer(index), object);
+	}
+	return object;
+    }
+
+    public synchronized int indexOf(DOMProxyListEntry entry) {
+	Element element = entry.getElement();
+	int children_length = children.getLength();
+	for(int i = 0; i < children_length; i++) {
+	    Node node = children.item(i);
+	    if(element == node) {
+		return i;
+	    }
+	}
+	return -1;
+    }
+
+    public synchronized int getSize() {
+	if(children == null) {
+	    children = root.getElementsByTagName(tag_name);
+	}
+	return children.getLength();
+    }
+
+    public synchronized void refresh() {
+	fireContentsChanged(this, 0, getSize());
+    }
+
+    public synchronized void refresh(DOMProxyListEntry entry) {
+	int index = indexOf(entry);
+	fireContentsChanged(this, index, index);
+    }
+
+    public synchronized void remove(DOMProxyListEntry entry) {
+	remove(indexOf(entry));
+    } 
+
+    public synchronized void remove(int index) {
+	// Retrieve the required element
+	Node node = children.item(index);
+	// Find its parent
+	Node parent_node = node.getParentNode();
+	// Now remove it
+	parent_node.removeChild(node);
+	// Refresh model
+	cache.clear();
+	fireIntervalRemoved(this, index, index);
+    }
+}
+
+
+
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/Format.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/Format.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/Format.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,43 +25,70 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Company:      The University of Waikato
  * Written:      06/05/02
  * Revised:      04/10/02 - Commented
+ *               14/07/03 - DOM Support
  **************************************************************************************/
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
 import org.greenstone.gatherer.cdm.Classifier;
-/** This class encapsulates all the information about a format command from the collection configuration. This includes both the 'general' formatting commands, and the commands targetted at specific classifiers or classification component types.
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.greenstone.gatherer.msm.MSMUtils;
+import org.w3c.dom.*;
+/** This class encapsulates all the information about a format command from the collection configuration. This includes both the 'general' formatting commands, and the commands targetted at specific classifiers or classification component types. This class is, unfortunately, the most complex in terms of DOM model support, since we have to maintain a live reference to any classifier this format applies to. We temporarily use the feature name as the format name attribute but since the order of classifiers can change, this of course gets horribly out of date. Before the CollectionDesignManager saves the CollectionConfiguration it calls a method in the FormatManager to force all of the Formats to update their name attributes.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
  * @version 2.3
  */
-public class Format {
-    /** If this format is of a true/false type, the current state. */
-    private boolean state = false;
-    /** The type of this format command, either FLAG, FORMAT or PARAM. */
-    private int type = -1;
-    /** The feature this format command affects. */
-    private Object feature = null;
-    /** If this format refers to a specific part of a feature, this indicates the part. */
-    private String part = null;
-    /** A command string, which may include a specific HTML chunk used to format a control. */
-    private String value = null;
-    /** An element of the format type enumeration. A true/false option. */
-    static final public int FLAG = 0;
-    /** An element of the format type enumeration. A string parameter. */
-    static final public int VALUE = 1;
+public class Format 
+    implements DOMProxyListEntry {
+
     /** The default features as specified by the Greenstone Developers manual. */
     static final public String DEFAULT_FEATURES[] = {"", "DocumentArrowsBottom","DocumentButtons","DocumentContents","DocumentHeading","DocumentImages","DocumentText","DocumentUseHTML","Search"};
     /** The list of known feature parts. */
     static final public String DEFAULT_PARTS[] = {"","DateList","HList","Invisible","VList"};
+
+    /** Not all features have an associated part, such as those in the default features list (apart from Search which does have parts!) */
+    static public boolean canHavePart(String name) {
+	return !(name.equalsIgnoreCase(DEFAULT_FEATURES[1]) || name.equalsIgnoreCase(DEFAULT_FEATURES[2]) || name.equalsIgnoreCase(DEFAULT_FEATURES[3]) || name.equalsIgnoreCase(DEFAULT_FEATURES[4]) || name.equalsIgnoreCase(DEFAULT_FEATURES[5]) || name.equalsIgnoreCase(DEFAULT_FEATURES[6]) || name.equalsIgnoreCase(DEFAULT_FEATURES[7]));
+    }
+
+    static public boolean isParamType(String name) {
+	return (name.equalsIgnoreCase(DEFAULT_FEATURES[1]) || name.equalsIgnoreCase(DEFAULT_FEATURES[3]) || name.equalsIgnoreCase(DEFAULT_FEATURES[5]) || name.equalsIgnoreCase(DEFAULT_FEATURES[7]));
+    }
+
+    /** If this format is altering a Classifier then this is the classifier in question. */
+    private Classifier classifier = null;
+    /** The element this format is based on. */
+    private Element element = null;
+    /** We keep a copy of the part because its slightly more computationally tricky to calculate. */
+    private String part = null;
+    /** Cached result of toString. */
+    private String text = null;
+
+    /** Constructor only to be used during DOMProxyListModel initialization. */
+    public Format() {
+    }
+
+    public Format(Element element) {
+	this.element = element;
+	// Immediately check if we are dealing with a classifier, by retrieving the feature name. If it is a classifier, then restore the live reference immediately before anything changes it.
+	Object object = getFeature();
+	if(object instanceof Classifier) {
+	    classifier = (Classifier) object;
+	}
+	else {
+	    String feature = (String) object;
+	    if(feature.toUpperCase().startsWith(Classifier.CLASSIFIER_PREFIX)) {
+		// Extract the integer index
+		int index = Integer.parseInt(feature.substring(Classifier.CLASSIFIER_PREFIX.length()));
+		// Subtract one (as java is 0-2 where G2 is 1-3)
+		index = index - 1;
+		// Retrieve the appropriate classifier
+		classifier = CollectionDesignManager.classifier_manager.getClassifier(index);
+	    }
+	}
+    }
+
     /** Constructor for a flag format command.
      * @param feature The <Strong>Object</strong> this format affects.
@@ -80,9 +97,9 @@
      */
     public Format(Object feature, String part, boolean state) {
-	this.feature = feature;
-	this.part = part;
-	this.state = state;
-	this.type = FLAG;
-    }
+	element = CollectionDesignManager.collect_config.document.createElement(CollectionConfiguration.FORMAT_ELEMENT);
+	setName(feature, part);
+	setState(state);
+    }
+
     /** Constructor for a format-string format command.
      * @param feature The <Strong>Object</strong> this format affects.
@@ -91,108 +108,206 @@
      */
     public Format(Object feature, String part, String value) {
-	this.feature = feature;
-	this.part = part;
-	this.type = VALUE;
-	this.value = value;
-    }
+	element = CollectionDesignManager.collect_config.document.createElement(CollectionConfiguration.FORMAT_ELEMENT);
+	setName(feature, part);
+	setValue(value);
+    }
+
+    public boolean canHavePart() {
+	return canHavePart(getName());
+    }
+
+    public int compareTo(Object object) {
+	if(object == null) { // It seems that occasionally compareTo is called and somehow null ends up in comparison.
+	    return -1;
+	}
+	if(object instanceof Format && element != null) {
+	    return element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE).compareTo(((Format)object).getName());
+	}
+	return toString().compareTo(object.toString());
+    }
+
+    public DOMProxyListEntry create(Element element) {
+	return new Format(element);
+    }
+
+    public boolean equals(Object object) {
+	return (compareTo(object) == 0);
+    }
+
+    public Element getElement() {
+	return element;
+    }
+
     /** Method to retrieve the value of feature, which may either be a String or a Classifier.
      * @return The value of feature as an <strong>Object</strong>.
      */
     public Object getFeature() {
-	return feature;
-    }
-    /** Method to retrieve the value of part.
+	if(classifier != null) {
+	    return classifier;
+	}
+	else if(element != null) {
+	    String name = element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE);
+	    // Remove part
+	    String part = getPart();
+	    String feature;
+	    if(part != null) {
+		feature = name.substring(0, name.length() - part.length());
+	    }
+	    else {
+		feature = name;
+	    }
+	    part = null;
+	    name = null;
+	    // If the feature now refers to a classifier, retrieve it.
+	    if(feature.toUpperCase().startsWith(Classifier.CLASSIFIER_PREFIX)) {
+		// Extract the integer index
+		int index = Integer.parseInt(feature.substring(Classifier.CLASSIFIER_PREFIX.length()));
+		// Subtract one (as java is 0-2 where G2 is 1-3)
+		index = index - 1;
+		// Retrieve the appropriate classifier
+		classifier = CollectionDesignManager.classifier_manager.getClassifier(index);
+		return classifier;
+	    }
+	    else {
+		return feature;
+	    }
+	}
+	else {
+	    return "Error";
+	}
+    }
+
+    public String getName() {
+	if(element != null) {
+	    return element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE);
+	}
+	return "Error";
+    }
+
+    /** Method to retrieve the value of part. Of course there may not be one, in which case return ""
      * @return The value of part as a <Strong>String</strong>.
      */
     public String getPart() {
+	if(part == null && element != null) {
+	    // To determine a part, we retrieve the Format name, then look for one of our recognized parts
+	    String name = element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE);
+	    // DEFAULT_PARTS[0] is an empty string to correctly enable comboboxes.
+	    name = name.toLowerCase();
+	    for(int i = 1; part == null && i < DEFAULT_PARTS.length; i++) {
+		if(name.endsWith(DEFAULT_PARTS[i].toLowerCase())) {
+		    part = DEFAULT_PARTS[i];
+		}
+	    }
+	    if(part == null) {
+		part = DEFAULT_PARTS[0];
+	    }
+	}
 	return part;
     }
-    /** Method to retrieve this formats special Greenstone position code.
-     * @return A <strong>String</strong> which distinctly indicates this classifiers position.
-     * @see org.greenstone.gatherer.cdm.Classifier
-     */
-    public String getPosition() {
-	///ystem.err.println("Retrieving the name of feature " + feature);
-	String position = null;
-	if(feature instanceof Classifier) {
-	    position = ((Classifier)feature).getPositionString();
-	}
-	else {
-	    position = feature.toString();
-	}
-	///ystem.err.println("Result: " + position + part);
-	return position + part;
-    }
+
     /** Method to retrieve the value of state.
-     * @param The value of state as a <i>boolean</i>.
+     * @return value of state as a <i>boolean</i>.
      */
     public boolean getState() {
-	return state;
-    }
-    /** Method to retrieve the value of type.
-     * @param The value of type as an <i>int</i>.
-     */
-    public int getType() {
-	return type;
-    }
+	return (element != null && element.getAttribute(CollectionConfiguration.VALUE_ATTRIBUTE).equals(CollectionConfiguration.TRUE_STR));
+    }
+
     /** Retrieve the value of value.
      * @return A <strong>String</strong> which is the value of value.
      */
     public String getValue() {
+	String value = "Error";
+	if(element != null) {
+	    value = MSMUtils.getValue(element);
+	}
 	return value;
     }
+
+    public boolean isAssigned() {
+	return (element != null && !element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.FALSE_STR));
+    }
+
+    public boolean isParamType() {
+	return (element != null && element.getAttribute(CollectionConfiguration.VALUE_ATTRIBUTE).length() > 0);
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    public void setElement(Element element) {
+	this.element = element;
+	part = null;
+	text = null;
+	// Establish the classifier reference, if necessary
+	getFeature();
+    }
+
+    public void setName(Object feature, String part) {
+	// Can't do anything unless an element exists.
+	if(element != null) {
+	    this.part = part; // Easier for later on.
+	    if(feature instanceof Classifier) {
+		classifier = (Classifier) feature;
+		element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, classifier.getPositionString() + part);
+	    }
+	    else {
+		element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, feature.toString() + part);
+	    }
+	    text = null;
+	}
+    }
+
     /** Set the value of state.
      * @param state The new value for state as a <i>boolean</i>.
      */
     public void setState(boolean state) {
-	this.state = state;
-    }
-    /** Set the value of type.
-     * @param type The new value for type as an <i>int</i>.
-     */
-    public void setType(int type) {
-	this.type = type;
-    }
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.VALUE_ATTRIBUTE, (state ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	    text = null;
+	}
+    }
+
     /** Set the value of value. Hmmm.
      * @param value The new value from value as a <strong>String</strong>.
      */
     public void setValue(String value) {
-	this.value = value;
-    }
+	if(element != null) {
+	    MSMUtils.setValue(element, value);
+	    text = null;
+	}
+    }
+
     /** Method to translate this classes information into a line of text as you would expect in the collection configuration file.
      * @return A <strong>String</strong> containing the format command.
      */
     public String toString() {
-	String text = "format ";
-	text = text + getPosition() + " ";
-	switch(type) {
-	case FLAG:
-	    if(state) {
-		text = text + "true";
+	if(text == null && element != null) {
+	    StringBuffer temp = new StringBuffer(CollectionConfiguration.FORMAT_STR);
+	    temp.append(" ");
+	    temp.append(element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE));
+	    temp.append(" ");
+	    String value = element.getAttribute(CollectionConfiguration.VALUE_ATTRIBUTE);
+	    if(value.length() > 0) {
+		temp.append(value);
 	    }
 	    else {
-		text = text + "false";
-	    }
-	    break;
-	default:
-	    text = text + "\"" + value + "\"";
+		temp.append("\"");
+		temp.append(MSMUtils.getValue(element));
+		temp.append("\"");
+	    }
+	    text = temp.toString();
+	    temp = null;
 	}
 	return text;
     }
-    /** Retrieve the type of a certain named feature.
-     * @param name The name of the feature as a <strong>String</strong>.
-     * @return An <i>int</i> indicating the type, and hence the controls needed to edit this type.
-     */
-    static public int getType(String name) {
-	int index = -1;
-	for(int i = 1; i < DEFAULT_FEATURES.length; i++) {
-	    if(name.equalsIgnoreCase(DEFAULT_FEATURES[i])) {
-		index = i;
-	    }
-	}
-	if(index == 1 || index == 3 || index == 5 || index == 7) {
-	    return FLAG;
-	}
-	return VALUE;
+
+    public void update() {
+	if(classifier != null) {
+	    element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, classifier.getPositionString() + getPart());
+	    text = null;
+	}
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/FormatManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/FormatManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/FormatManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,58 +25,27 @@
  *########################################################################
  */
-
- 
-
-
-
-
-/* GPL_HEADER */
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Company:      The University of Waikato
- * Written:        /05/02
- * Revised:      04/10/02 - Commented
+ * Written:     06/05/02
+ * Revised:     04/10/02 - Commented
+ *              14/07/03 - DOM support
  **************************************************************************************/
-import java.awt.BorderLayout;
-import java.awt.CardLayout;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Vector;
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTabbedPane;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.JToggleButton;
-import javax.swing.ListSelectionModel;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
 import org.greenstone.gatherer.Gatherer;
 import org.greenstone.gatherer.cdm.Classifier;
 import org.greenstone.gatherer.cdm.ClassifierManager;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
 import org.greenstone.gatherer.cdm.CollectionDesignManager;
 import org.greenstone.gatherer.cdm.CommandTokenizer;
-import org.greenstone.gatherer.cdm.DynamicListModel;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
 import org.greenstone.gatherer.cdm.Format;
 import org.greenstone.gatherer.msm.ElementWrapper;
 import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
 /** This class maintains a list of format statements, and allows the addition and removal of these statements.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
@@ -94,25 +53,21 @@
  */
 public class FormatManager
-    extends DynamicListModel {
-    /** The main manager so we can get to ClassifierManager which is needed to turn position reference such as "CL1" into the appropriate Classifier. */
-    private CollectionDesignManager manager = null;
+    extends DOMProxyListModel {
+
     /** The controls used to edit the format commands. */
     private Control controls = null;
     /** A reference to ourselves so inner classes can get at the model. */
-    private DynamicListModel model = null;
-    /** A reference to the Gatherer. */
-    private Gatherer gatherer = null;
-    /** We may have somehow recieved a format command which references a classifier that hasn't been parsed yet, so this variable holds a list of failed commands which are retried after the loading is complete. */
-    private Vector unresolved_commands = null;
-    /** Constructor.
-     * @param gatherer A reference to the <strong>Gatherer</strong>.
-     * @param manager A reference to the <strong>CollectionDesignManager</strong> that created this manager.
-     */
-    public FormatManager(Gatherer gatherer, CollectionDesignManager manager) {
-	super();
-	this.gatherer = gatherer;
-	this.manager = manager;
+    private DOMProxyListModel model = null;
+
+    /** Constructor. */
+    public FormatManager() {
+	super(CollectionDesignManager.collect_config.getDocumentElement(), CollectionConfiguration.FORMAT_ELEMENT, new Format());
 	this.model = this;
-	this.unresolved_commands = new Vector();
+	Gatherer.println("FormatManager: parsed " + getSize() + " format statements.");
+	// Establish all of the format objects, so that classifier indexes are initially correct (subsequent refreshes of the model will be sufficient to keep these up to date, as long as we start with a live reference to a classifier.
+	int size = getSize();
+	for(int i = 0; i < size; i++) {
+	    getElementAt(i);
+	}
     }
     /** Method to add a new format to this manager.
@@ -120,237 +75,75 @@
 	  */
     public void addFormat(Format format) {
-	if(formatExists(format) == null) {
-	    addElement(format);
-	    gatherer.c_man.configurationChanged();
-	}
-    }
-    /** Method which determines if a certain format exists, in which case it must be removed before a new format of the same component is added. For general formatting statements this compares types, for 'custom' ones it compares list and part.
-	  * @param format The <strong>Format</strong> whose uniqueness we want to check.
-	  * @return The <strong>Format</strong> that matches the one given, or <i>null</i> if no such format exists.
-	  */
-    public Format formatExists(Format format) {
-	for(int i = 0; i < size(); i++) {
-	    Format current = (Format) get(i);
-	    if(format.getPosition().equals(current.getPosition())) {
-		return current;
-	    }
-	}
-	return null;
-    }
+	if(!contains(format)) {
+	    Element element = format.getElement();
+	    // Locate where we should insert this new classifier. 
+	    Node target_node = CollectionConfiguration.findInsertionPoint(element);
+	    add(root, format, target_node);
+	    Gatherer.c_man.configurationChanged();
+	}
+    }
+
+    public void destroy() {
+	if(controls != null) {
+	    controls.destroy();
+	    controls = null;
+	}
+    }
+
     /** Gets the format indicated by the index.
-	  * @param index The location of the desired format, as an <i>int</i>.
-	  */
+     * @param index The location of the desired format, as an <i>int</i>.
+     */
     public Format getFormat(int index) {
-	return (Format)get(index);
-    }
+	Format result = null;
+	if(0 < index && index < getSize()) {
+	    result = (Format) getElementAt(index);
+	}
+	return result;
+    }
+    
     /** Method to retrieve this managers controls.
-	  * @return The <strong>Control</strong> for this collection.
-	  */
+     * @return the Control for this collection.
+     */
     public Control getControls() {
 	if(controls == null) {
-	    controls = new Control();
+	    controls = new FormatControl();
 	}
 	return controls;
     }
-    /** Method to invalidate controls when some significant change has occured within the collection. */
-    public void invalidateControls() {
-	if(controls != null) {
-	    controls.destroy();
-	}
-	controls = null;
-    }
-    /** This method attempts to parse a format command from the given string. If a format is parsed, it is immediately added to this managers list of format commands.
-	  * @param command The <strong>String</strong> we are trying to parse a command from.
-	  * @param finished A <i>boolean</i> which is <i>true</i> if we are calling this after the the input has been completely parsed (i.e all legal Classifiers are know to exist), or <i>false</i> otherwise.
-	  * @return A <i>boolean</i> which is <i>true</i> if we managed to parse a command, <i>false</i> otherwise.
-	  * @see org.greenstone.gatherer.cdm.Classifier
-	  * @see org.greenstone.gatherer.cdm.CommandTokenizer
-	  * @see org.greenstone.gatherer.cdm.Format
-	  */
-    public boolean parse(String command, boolean finished) {
-	String temp = command.toLowerCase();
-	if(temp.startsWith("format")) {
-	    if(finished) {
-		CommandTokenizer ct = new CommandTokenizer(command);
-		ct.nextToken(); // Throw away 'format'
-		String position = ct.nextToken();
-		String value = ct.nextToken();
-		// String speech marks.
-		if(value.startsWith("\"") && value.endsWith("\"")) {
-		    if(value.equals("\"\"")) {
-			value = "";
-		    }
-		    else {
-			value = value.substring(1, value.length() - 1);
-		    }
-		}
-		// Ensure we parsed a good command.
-		if(position != null && value != null) {
-		    // The trickiest bit of parsing format commands is figuring out how much of the position is feature, and how much part. Since the parts are far less likely to change or be customized in any way, we'll try to match them first (except "" of course).
-		    String feature_name = null;
-		    String part = null;
-		    // If this works, then we're all finished. Yay.
-		    for(int i = 1; i < Format.DEFAULT_PARTS.length; i++) {
-			if(position.endsWith(Format.DEFAULT_PARTS[i])) {
-			    part = Format.DEFAULT_PARTS[i];
-			    feature_name = position.substring(0, position.length() - part.length());
-			}
-		    }
-		    // Otherwise we can attempt to find the default features, but we have less chance of success.
-		    if(feature_name == null || part == null) {
-			for(int i = 1; i < Format.DEFAULT_FEATURES.length; i++) {
-			    if(position.startsWith(Format.DEFAULT_FEATURES[i])) {
-				feature_name = Format.DEFAULT_FEATURES[i];
-				if(position.length() > feature_name.length()) {
-				    part = position.substring(feature_name.length());
-				}
-				else {
-				    part = "";
-				}
-			    }
-			}
-		    }
-		    // Otherwise we can assume we are dealing with a classifier and split the position using...
-		    // position            ::= <classifier_position><part>
-		    // classifier_position ::= <alphanum>[0-9]$
-		    // part                ::= ^![0-9]<alpha>
-		    // But I don't like my chances of this working if someone does a scary like CL4Part8B. I just have
-		    // to hope no-one uses numbers, and no-one expects CustomFeatureCustomPart to parse properly.
-		    if(feature_name == null || part == null) {
-			part = "";
-			boolean found = false;
-			int index = position.length() - 1;
-			while(index >= 0 && !found) {
-			    if(Character.isDigit(position.charAt(index))) {
-				found = true;
-			    }
-			    else {
-				part = position.charAt(index) + part;
-				index--;
-			    }
-			}
-			if(found) {
-			    feature_name = position.substring(0, index + 1);
-			}
-			// We ran out of string. No digits. Arg!
-			else {
-			    part = null;
-			}
-		    }
-		    // And if all else fails, stick it all in feature.
-		    if(feature_name == null || part == null) {
-			feature_name = position;
-			part = "";
-		    }
-		    // Now try to retrieve a classifier with the feature name.
-		    Object feature = null;
-		    String feature_name_lc = feature_name.toLowerCase();
-		    if(feature_name_lc.startsWith("cl") && feature_name.length() >= 3) {
-			String raw_index = feature_name.substring(2); // Lose the 'CL'
-			int index = -1;
-			try {
-			    index = Integer.parseInt(raw_index);
-			}
-			catch(NumberFormatException nfe) {
-			    nfe.printStackTrace();
-			}
-			feature = manager.classifiers.getClassifier(index - 1);
-		    }
-		    else {
-			///ystem.err.println("name to short for classifier.");
-		    }
-		    if(feature == null) {
-			feature = feature_name;
-		    }
-		    ///ystem.err.println("Feature name = " + feature_name + "\nPart = " + part);
-		    if(feature instanceof Classifier) {
-			///ystem.err.println("Feature is a classifier.");
-		    }
-		    else {
-			///ystem.err.println("Feature is a String.");
-		    }
-		    // Now we have a quick look at value. If its true or false we create a FLAG type
-		    if(value.equalsIgnoreCase("true")) {
-			addFormat(new Format(feature, part, true));
-		    }
-		    else if(value.equalsIgnoreCase("false")) {
-			addFormat(new Format(feature, part, false));
-		    }
-		    // Otherwise add a plain old value based format
-		    else {
-			addFormat(new Format(feature, part, value));
-		    }
-		    return true;
-		}
-		// Somethings gone terribly, terribly wrong.
-		return false;
-	    }
-	    else {
-		// Ensure we have enough tokens for a format command
-		CommandTokenizer ct = new CommandTokenizer(command);
-		while(ct.countTokens() < 3) {
-		    command = manager.parseMore(command);
-		    ct = new CommandTokenizer(command);
-		}
-		unresolved_commands.add(command);
-	    }
-	    return true;
-	}
-	return false;
-    }
+
     /** Method to remove a format.
-	  * @param format The <strong>Format</strong> to remove.
-	  */
+     * @param format The <strong>Format</strong> to remove.
+     */
     public void removeFormat(Format format) {
-	removeElement(format);
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method which attempts to reparse obvious format commands which previously referenced unresovable Classifiers.
-	*/
-    public void reparseUnresolved() {
-	for(int i = 0; i < unresolved_commands.size(); i++) {
-	    if(!parse((String)unresolved_commands.get(i), true)) {
-		///ystem.err.println("*** Error: Command " + unresolved_commands.get(i));
-	    }
-	}
-	// Regardless of if they work, clear the commands.
-	unresolved_commands.clear();
-    }
-    /** Method to produce a block of text representing the format commands in this manager, ready to be used in the collection configuration file.
-	* @return A <strong>String</strong> containing a series of format commands.
-	*/
-    public String toString() {
-	StringBuffer text = new StringBuffer("");
-	for(int i = 0; i < size(); i++) {
-	    Format format = (Format) get(i);
-	    text.append(format.toString());
-	    text.append("\n");
-	}
-	text.append("\n");
-	return text.toString();
-    }
+	remove(format);
+	Gatherer.c_man.configurationChanged();
+    }
+
     /** Overloaded to call get with both a key and an empty argument array.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  */
+     * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+     * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
+     */
     private String get(String key) {
 	return get(key, null);
     }
+
     /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
-	  * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.Dictionary
-	  */
+     * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
+     * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+     * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
+     * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.Dictionary
+     */
     private String get(String key, String args[]) {
 	if(key.indexOf('.') == -1) {
 	    key = "CDM.FormatManager." + key;
 	}
-	return gatherer.dictionary.get(key, args);
-    }
-    private class Control
-	extends JPanel {
+	return Gatherer.dictionary.get(key, args);
+    }
+
+    private class FormatControl
+	extends JPanel 
+	implements Control {
 	/** Do we ignore selection changing events (mainly because we're generating them!) */
 	private boolean ignore = false;
@@ -404,15 +197,6 @@
 	private Vector part_model = null;
 	private Vector special_model = null;
-	public Control() {
-	    ArrayList feature_model = new ArrayList();
-				// Add the set options
-	    for(int i = 0; i < Format.DEFAULT_FEATURES.length; i++) {
-		feature_model.add(new Entry(Format.DEFAULT_FEATURES[i]));
-	    }
-				// Now the classifiers.
-	    for(int j = 0; j < manager.classifiers.size(); j++) {
-		feature_model.add(new Entry(manager.classifiers.getClassifier(j)));
-	    }
-	    Collections.sort(feature_model);
+	public FormatControl() {
+	    ArrayList feature_model = buildFeatureModel();
 	    part_model = new Vector();
 	    part_model.add("");//get("Custom"));
@@ -430,10 +214,10 @@
 	    special_model.add("[parent(Top):_]");
 	    special_model.add("[parent(All'_'):_]");
-	    Vector elements = gatherer.c_man.msm.getAssignedElements();
+	    Vector elements = Gatherer.c_man.getCollection().msm.getAssignedElements();
 	    for(int i = 0; i < elements.size(); i++) {
 		special_model.add("[" + ((ElementWrapper)elements.get(i)).toString() + "]");
 	    }
 	    Collections.sort(special_model);
-				// Create
+	    // Create
 	    add = new JButton(get("Add"));
 	    add.setEnabled(false);
@@ -443,8 +227,8 @@
 	    control_pane = new JPanel();
 	    editor = new JTextArea();
-	    editor.setBackground(Color.white);
+	    editor.setBackground(Gatherer.config.getColor("coloring.editable", false));
 	    editor.setCaretPosition(0);
 	    editor.setLineWrap(true);
-	    editor.setWrapStyleWord(true);
+	    editor.setWrapStyleWord(false);
 	    editor_label = new JLabel(get("Editor"));
 	    editor_label.setHorizontalAlignment(JLabel.CENTER);
@@ -496,9 +280,9 @@
 	    value_pane = new JPanel();
 	    view_pane = new JPanel();
-				// Connect
+	    // Connect
 	    add.addActionListener(new AddListener());
 	    button_group.add(on);
 	    button_group.add(off);
-	    editor.addKeyListener(new EditorListener());
+	    editor.getDocument().addDocumentListener(new EditorListener());
 	    feature.addActionListener(new FeatureListener());
 	    format_list.addListSelectionListener(new FormatListListener());
@@ -596,33 +380,53 @@
 	    ready = true;
 	}
+
 	public void destroy() {
 	}
+
 	/** Overriden to ensure that the instructions pane is scrolled to the top.
-			*/
-	public void updateUI() {
+	 */
+	public void gainFocus() {
+	    // This is only necessary if the components have been realized
 	    if(ready) {
-		// Rebuild feature model.
-		ArrayList feature_model = new ArrayList();
-		// Add the set options
-		for(int i = 0; i < Format.DEFAULT_FEATURES.length; i++) {
-		    feature_model.add(new Entry(Format.DEFAULT_FEATURES[i]));
-		}
-		// Now the classifiers.
-		for(int j = 0; j < manager.classifiers.size(); j++) {
-		    feature_model.add(new Entry(manager.classifiers.getClassifier(j)));
-		}
+		model.refresh();
+		ArrayList feature_model = buildFeatureModel();
+		// Remember the current selection
+		Object selected_object = feature.getSelectedItem();
 		feature.setModel(new DefaultComboBoxModel(feature_model.toArray()));
+		// Now resotre the selected object as best as possible
+		feature.setSelectedItem(selected_object);
 		if(instructions != null) {
 		    instructions.setCaretPosition(0);
 		}
-	    }
-	    super.updateUI();
-	}
-
-
+	    }   
+	}
+
+	public void loseFocus() {
+	    // Force all of the Formats to update their names with the correct values.
+	    int size = model.getSize();
+	    for(int i = 0; i < size; i++) {
+		Format format = (Format) model.getElementAt(i);
+		format.update();
+	    }
+	}
+
+	private ArrayList buildFeatureModel() {
+	    // Rebuild feature model.
+	    ArrayList feature_model = new ArrayList();
+	    // Add the set options
+	    for(int i = 0; i < Format.DEFAULT_FEATURES.length; i++) {
+		feature_model.add(new Entry(Format.DEFAULT_FEATURES[i]));
+	    }
+	    // Now the classifiers.
+	    for(int j = 0; j < CollectionDesignManager.classifier_manager.getSize(); j++) {
+		feature_model.add(new Entry(CollectionDesignManager.classifier_manager.getClassifier(j)));
+	    }
+	    Collections.sort(feature_model);
+	    return feature_model;
+	}
 
 	/** Formats the formatting string so that it contains safe characters and isn't enclosed in speech marks etc. (Ironic eh?)
-			* @see java.lang.StringBuffer
-			*/
+	 * @see java.lang.StringBuffer
+	 */
 	private String format(String raw) {
 	    String safe = null;
@@ -665,9 +469,10 @@
 	    return raw;
 	}
+
 	/** Listens for clicks on the add button, and if the relevant details are provided adds a new format. */
 	private class AddListener
 	    implements ActionListener {
 	    public void actionPerformed(ActionEvent event) {
-		ignore = true;
+		//ignore = true;
 		Entry entry = (Entry)feature.getSelectedItem();
 		Object f = entry.getFeature();
@@ -685,10 +490,24 @@
 		format_list.setSelectedValue(current_format, true);
 		new_entry = false;
-		ignore = false;
-	    }
-	}
+		//ignore = false;
+	    }
+	}
+
 	private class EditorListener
-	    extends KeyAdapter {
-	    public void keyReleased(KeyEvent event) {
+	    implements DocumentListener {
+ 
+	    public void changedUpdate(DocumentEvent e) {
+		update();
+	    }
+          
+	    public void insertUpdate(DocumentEvent e) {
+		update();
+	    }
+          
+	    public void removeUpdate(DocumentEvent e) {
+		update();
+	    }
+
+	    public void update() {
 		String safe = format(editor.getText());
 		if(!ignore && current_format != null) {
@@ -700,6 +519,6 @@
 			current_format.setValue("");
 		    }
-		    gatherer.c_man.configurationChanged();
-		    model.refresh();
+		    Gatherer.c_man.configurationChanged();
+		    model.refresh(current_format);
 		}
 		Entry entry = (Entry) feature.getSelectedItem();
@@ -713,4 +532,5 @@
 	    }
 	}
+
 	/** This object provides a wrapping around an entry in the format target control, which is tranparent as to whether it is backed by a String or a Classifier. */
 	private class Entry
@@ -781,4 +601,5 @@
 	    }
 	}
+
 	private class FeatureListener
 	    implements ActionListener {
@@ -788,7 +609,5 @@
 		    Entry entry = (Entry) feature.getSelectedItem();
 		    String name = entry.toString();
-		    int type = Format.getType(name);
-		    switch(type) {
-		    case Format.FLAG:
+		    if(Format.isParamType(name)) {
 			// Flags first.
 			part.setEnabled(false);
@@ -797,6 +616,6 @@
 			view_type = FLAG;
 			add.setEnabled(true); // One of the options must be selected.
-			break;
-		    default:
+		    }
+		    else {
 			part.setEnabled(true);
 			part_pane.add(part, BorderLayout.CENTER);
@@ -815,4 +634,5 @@
 	    }
 	}
+
 	private class FormatListListener
 	    implements ListSelectionListener {
@@ -825,9 +645,28 @@
 			Entry an_entry = new Entry(current_format.getFeature());
 			feature.setSelectedItem(an_entry);
-			// Try to match the part.
-			part.setSelectedItem(current_format.getPart());
+			// If we didn't match anything, add it and select it again
+			Entry result_entry = (Entry) feature.getSelectedItem();
+			if(!an_entry.equals(result_entry)) {
+			    feature.insertItemAt(an_entry, feature.getItemCount());
+			    feature.setSelectedItem(an_entry);
+			}
+			
+			if(current_format.canHavePart()) {
+			    part.setEnabled(true);
+			    // Try to match the part.
+			    String part_entry = current_format.getPart();
+			    part.setSelectedItem(part_entry);
+			    // If we didn't match anything, add it and select it again
+			    String selected_part = (String)part.getSelectedItem();
+			    if(!part_entry.equals(selected_part)) {
+				part.insertItemAt(part_entry, part.getItemCount());
+				feature.setSelectedItem(part_entry);
+			    }
+			}
+			else {
+			    part.setEnabled(false);
+			}
 			// Now use type to determine what controls are visible, and what have initial values.
-			switch(current_format.getType()) {
-			case Format.FLAG:
+			if(current_format.isParamType()) {
 			    // Flags first.
 			    part.setEnabled(false);
@@ -839,6 +678,6 @@
 			    off.setSelected(!current_format.getState());
 			    add.setEnabled(false); // Can only update
-			    break;
-			default:
+			}
+			else {
 			    part.setEnabled(true);
 			    part_pane.add(part, BorderLayout.CENTER);
@@ -907,5 +746,5 @@
 		    // We have just performed an edit. Immediately update the format and model.
 		    current_format.setState(on.isSelected());
-		    model.refresh();
+		    model.refresh(current_format);
 		}
 	    }
@@ -917,5 +756,5 @@
 		    // We have just performed an edit. Immediately update the format and model.
 		    current_format.setValue(value.getText());
-		    model.refresh();
+		    model.refresh(current_format);
 		}
 	    }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/GeneralManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/GeneralManager.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/GeneralManager.java	(revision 4932)
@@ -0,0 +1,464 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.JTextComponent;
+import javax.swing.tree.*;
+import org.greenstone.gatherer.Configuration;
+import org.greenstone.gatherer.Dictionary;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.gui.EmailField;
+import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.util.Utility;
+/** This class is responsible for generating the necessary GUI components. It does this by calling the getEditControls() method of the appropriate class (i.e. IndexManager for index related edits). It also is in charge of correctly adding (and removing) listeners, using phrases from the <strong>Dictionary</strong> rather than the text keys inserted in the component classes, and several other aspects of the design page, including the config file section tree.
+* @author John Thompson, Greenstone Digital Library, University of Waikato
+* @version 2.3d
+*/
+public class GeneralManager
+    extends JPanel {
+    /** The available subscreens. */
+    static final private String CONTENTS[] = {"General", "Plugins", "Indexes", "Subcollections", "Classifiers", "Formats", "SuperCollection", "Translation",  "MetadataSets"};
+    /** The preferred size of the collection design module screen real-estate. */
+    static final private Dimension SIZE = new Dimension(760, 500);
+    /** The preferred size of the contents tree. */
+    static final private Dimension TREE_SIZE = new Dimension(200, 500);
+    /** The preferred size of label. */
+    static final private Dimension LABEL_SIZE = new Dimension(200,25);
+    /** The controls used to modify the general options. */
+    private Control controls;
+    /** The panel apon which is rendered the currently selected section screen. */
+    private Control view = null;
+    /** A tree to serve as a 'table of contents' for this design tool. We decided on a tree rather than a list, as it allows us to break sections into subsections if they become to complicated. */
+    private DesignTree tree;
+    /** Constructor. */
+    public GeneralManager() {
+	super();
+	Gatherer.println("GeneralManager: Main GUI components created.");
+	// Assignments
+	this.controls = new GeneralControl();
+
+	// Creation
+	JPanel tree_pane = new JPanel();
+	JLabel title = new JLabel(get("Design_Topics"));
+	tree = new DesignTree();
+	view = controls;
+
+	// Connect
+	tree.addTreeSelectionListener(new TreeListener());
+
+	// Layout
+	tree_pane.setLayout(new BorderLayout());
+	tree_pane.setPreferredSize(TREE_SIZE);
+	tree_pane.add(title, BorderLayout.NORTH);
+	tree_pane.add(new JScrollPane(tree), BorderLayout.CENTER);
+
+	setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+	setLayout(new BorderLayout());
+	add(tree_pane, BorderLayout.WEST);
+	add((JPanel)view, BorderLayout.CENTER);
+    }
+
+    /** Destructor. */
+    public void destroy() {
+	controls.destroy();
+	controls = null;
+	tree = null;
+	view = null;
+    }
+
+    /** Force the display to show a certain pane of controls.
+     * @param type a String giving the name of the information manager or view we wish to display
+     */
+    public void setSelectedView(String type) {
+	tree.setSelectedView(type);
+    }
+
+    /** Refresh the values on our controls that could be stale due to changes in other components. */
+    public void gainFocus() {
+	if(view != null) {
+	    view.gainFocus();
+	}
+    }
+
+    /** Saves the state of the controls in the current view. */
+    public void loseFocus() {
+	if(view != null) {
+	    view.loseFocus();
+	}
+    }
+
+    /** Overloaded to call get with both a key and an empty argument array.
+     * @param key a String which is mapped to a key within the dictionary
+     * @return a String which has been referenced by the key and that either contains no argument fields, or has had the argument fields automatically populated with arguments provided in the get call
+     */
+    private String get(String key) {
+	return get(key, null);
+    }
+
+    /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
+     * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
+     * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+     * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
+     * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.Dictionary
+     */
+    private String get(String key, String args[]) {
+	if(key.indexOf('.') == -1) {
+	    key = "CDM.GUI." + key;
+	}
+	return Gatherer.dictionary.get(key, args);
+    }
+
+    /** This class is resposible for generating the controls for the editing of general options.
+     * @return the Control for editing the general options
+     */
+    private Control getControls() {
+	return controls;
+    }
+
+    /** This class represents the visual component of the general options stored in the CollectionDesignManager. */
+    private class GeneralControl
+	extends JPanel 
+	implements Control {
+	private boolean ready = false;
+	private CollectionMeta beta_collectionmeta;
+	private CollectionMeta collection_extra_collectionmeta;
+	private CollectionMeta collection_name_collectionmeta;
+	private CollectionMeta creator_collectionmeta;
+	private CollectionMeta icon_collection_collectionmeta;
+	private CollectionMeta icon_collection_small_collectionmeta;
+	private CollectionMeta maintainer_collectionmeta;
+	private CollectionMeta public_collectionmeta;
+	/** The creators email. */
+	private EmailField creator_emailfield;
+	/** The maintainers email. */
+	private EmailField maintainer_emailfield;
+	/** The checkbox controlling the state of the collection. */
+	private JCheckBox beta_checkbox;
+	/** The checkbox controlling public access to the collection. */
+	private JCheckBox public_checkbox;
+	private JLabel creator_label;
+	private JLabel description_label;
+	private JLabel icon_label;
+	private JLabel maintainer_label;
+	private JLabel name_label;
+	private JLabel small_icon_label;
+	private JLabel title_label;
+	/** The text field used to edit the file name of the collections icon. */
+	private JTextField icon_textfield;
+	/** The text field used to edit the collections title. */
+	private JTextField name_textfield;
+	/** The text field used to edit the file name of the collections small icon. */
+	private JTextField small_icon_textfield;
+	/** A text area used to modify the collection description. */
+	private JTextArea description_textarea;
+	private JTextArea instructions_textarea;
+	/** Constructor. */
+	public GeneralControl() {
+	    super();
+	    // Retrieve some of the model elements, those we know aren't language dependant
+	    beta_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_BETA_STR);
+	    public_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_PUBLIC_STR);
+	   
+	    // Creation
+	    JPanel instruction_panel = new JPanel();
+	    title_label = new JLabel("CDM.General.Title");
+	    title_label.setHorizontalAlignment(JLabel.CENTER);
+	    instructions_textarea = new JTextArea("CDM.General.Instructions");
+	    instructions_textarea.setCaretPosition(0);
+	    instructions_textarea.setEditable(false);
+	    instructions_textarea.setLineWrap(true);
+	    instructions_textarea.setRows(4);
+	    instructions_textarea.setWrapStyleWord(true);
+
+	    JPanel all_details_panel = new JPanel();
+	    JPanel details_panel = new JPanel();
+	    JPanel creator_panel = new JPanel();
+	    creator_label = new JLabel("CDM.General.Email.Creator");
+	    creator_label.setPreferredSize(LABEL_SIZE);
+	    creator_emailfield = new EmailField(Gatherer.config.getColor("coloring.error_background", false));
+	    JPanel maintainer_panel = new JPanel();
+	    maintainer_label = new JLabel("CDM.General.Email.Maintainer");
+	    maintainer_label.setPreferredSize(LABEL_SIZE);
+	    maintainer_emailfield = new EmailField(Gatherer.config.getColor("coloring.error_background", false));
+	    public_checkbox = new JCheckBox("CDM.General.Access", public_collectionmeta.getValue().equals(CollectionConfiguration.TRUE_STR));
+	    beta_checkbox = new JCheckBox("CDM.General.Beta", beta_collectionmeta.getValue().equals(CollectionConfiguration.TRUE_STR));
+	    JPanel name_panel = new JPanel();
+	    name_label = new JLabel("CDM.General.Collection_Name");
+	    name_label.setPreferredSize(LABEL_SIZE);
+	    name_textfield = new JTextField();
+	    JPanel icon_panel = new JPanel();
+	    icon_label = new JLabel("CDM.General.Icon_Collection");
+	    icon_label.setPreferredSize(LABEL_SIZE);
+	    icon_textfield = new JTextField();
+	    JPanel small_icon_panel = new JPanel();
+	    small_icon_label = new JLabel("CDM.General.Icon_Collection_Small");
+	    small_icon_label.setPreferredSize(LABEL_SIZE);
+	    small_icon_textfield = new JTextField();
+	    JPanel description_panel = new JPanel();
+	    description_label = new JLabel("CDM.General.Collection_Extra");
+	    description_label.setPreferredSize(LABEL_SIZE);
+	    description_textarea = new JTextArea();
+	    description_textarea.setBackground(Gatherer.config.getColor("coloring.editable", false));
+	    // Connection
+	    beta_checkbox.addActionListener(CollectionDesignManager.change_listener);
+	    public_checkbox.addActionListener(CollectionDesignManager.change_listener);
+	    creator_emailfield.getDocument().addDocumentListener(CollectionDesignManager.change_listener);
+	    description_textarea.getDocument().addDocumentListener(CollectionDesignManager.change_listener);
+	    icon_textfield.getDocument().addDocumentListener(CollectionDesignManager.change_listener);
+	    maintainer_emailfield.getDocument().addDocumentListener(CollectionDesignManager.change_listener);
+	    name_textfield.getDocument().addDocumentListener(CollectionDesignManager.change_listener);
+	    small_icon_textfield.getDocument().addDocumentListener(CollectionDesignManager.change_listener);
+	    Gatherer.dictionary.register(beta_checkbox, null, false);
+	    Gatherer.dictionary.register(creator_label, null, false);
+	    Gatherer.dictionary.register(description_label, null, false);
+	    Gatherer.dictionary.register(icon_label, null, false);
+	    Gatherer.dictionary.register(instructions_textarea, null, false);
+	    Gatherer.dictionary.register(maintainer_label, null, false);
+	    Gatherer.dictionary.register(name_label, null, false);
+	    Gatherer.dictionary.register(public_checkbox, null, false);
+	    Gatherer.dictionary.register(small_icon_label, null, false);
+	    Gatherer.dictionary.register(title_label, null, false);
+	    // Layout
+	    instruction_panel.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
+	    instruction_panel.setLayout(new BorderLayout());
+	    instruction_panel.add(title_label, BorderLayout.NORTH);
+	    instruction_panel.add(new JScrollPane(instructions_textarea), BorderLayout.CENTER);
+	    
+	    creator_panel.setLayout(new BorderLayout());
+	    creator_panel.add(creator_label, BorderLayout.WEST);
+	    creator_panel.add(creator_emailfield, BorderLayout.CENTER);
+
+	    maintainer_panel.setLayout(new BorderLayout());
+	    maintainer_panel.add(maintainer_label, BorderLayout.WEST);
+	    maintainer_panel.add(maintainer_emailfield, BorderLayout.CENTER);
+
+	    name_panel.setLayout(new BorderLayout());
+	    name_panel.add(name_label, BorderLayout.WEST);
+	    name_panel.add(name_textfield, BorderLayout.CENTER);
+
+	    icon_panel.setLayout(new BorderLayout());
+	    icon_panel.add(icon_label, BorderLayout.WEST);
+	    icon_panel.add(icon_textfield, BorderLayout.CENTER);
+
+	    small_icon_panel.setLayout(new BorderLayout());
+	    small_icon_panel.add(small_icon_label, BorderLayout.WEST);
+	    small_icon_panel.add(small_icon_textfield, BorderLayout.CENTER);
+
+	    details_panel.setLayout(new GridLayout(7,1,5,0));
+	    details_panel.add(creator_panel);
+	    details_panel.add(maintainer_panel);
+	    details_panel.add(public_checkbox);
+	    details_panel.add(beta_checkbox);
+	    details_panel.add(name_panel);
+	    details_panel.add(icon_panel);
+	    details_panel.add(small_icon_panel);
+
+	    description_panel.setLayout(new BorderLayout());
+	    description_panel.add(description_label, BorderLayout.NORTH);
+	    description_panel.add(new JScrollPane(description_textarea), BorderLayout.CENTER);
+
+	    all_details_panel.setLayout(new BorderLayout());
+	    all_details_panel.add(details_panel, BorderLayout.NORTH);
+	    all_details_panel.add(description_panel, BorderLayout.CENTER);
+
+	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	    setLayout(new BorderLayout());
+	    add(instruction_panel, BorderLayout.NORTH);
+	    add(all_details_panel, BorderLayout.CENTER);
+	}
+	/** Destructor. */
+	public void destroy() {
+	    Gatherer.dictionary.deregister(beta_checkbox);
+	    Gatherer.dictionary.deregister(creator_label);
+	    Gatherer.dictionary.deregister(description_label);
+	    Gatherer.dictionary.deregister(icon_label);
+	    Gatherer.dictionary.deregister(instructions_textarea);
+	    Gatherer.dictionary.deregister(maintainer_label);
+	    Gatherer.dictionary.deregister(name_label);
+	    Gatherer.dictionary.deregister(public_checkbox);
+	    Gatherer.dictionary.deregister(small_icon_label);
+	    Gatherer.dictionary.deregister(title_label);
+	}
+	/** Called to refresh the components. */
+	public void gainFocus() {
+	    // Retrieve all of the elements that are dependant on default language.
+	    collection_extra_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_COLLECTIONEXTRA_STR);
+	    collection_name_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_COLLECTIONNAME_STR);
+	    creator_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_CREATOR_STR);
+	    icon_collection_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_ICONCOLLECTION_STR);
+	    icon_collection_small_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_ICONCOLLECTIONSMALL_STR);
+	    maintainer_collectionmeta = CollectionDesignManager.collectionmeta_manager.getMetadatum(CollectionConfiguration.COLLECTIONMETADATA_MAINTAINER_STR);
+	    // Make sure the components are up to date
+	    creator_emailfield.setText(creator_collectionmeta.getValue());
+	    creator_emailfield.setCaretPosition(0);
+	    description_textarea.setText(collection_extra_collectionmeta.getValue());
+	    description_textarea.setCaretPosition(0);
+	    icon_textfield.setText(icon_collection_collectionmeta.getValue());
+	    icon_textfield.setCaretPosition(0);
+	    maintainer_emailfield.setText(maintainer_collectionmeta.getValue());
+	    maintainer_emailfield.setCaretPosition(0);
+	    name_textfield.setText(collection_name_collectionmeta.getValue());
+	    name_textfield.setCaretPosition(0);
+	    small_icon_textfield.setText(icon_collection_small_collectionmeta.getValue());
+	    small_icon_textfield.setCaretPosition(0);
+	    ready = true;
+	}
+	/** Called to store the current value of the components. */
+	public void loseFocus() {
+	    // String values. Have to test if this component has actually ever recieved focus anyway.
+	    if(ready) {
+		// Boolean values
+		beta_collectionmeta.setValue((beta_checkbox.isSelected() ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+		public_collectionmeta.setValue((public_checkbox.isSelected() ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+		creator_collectionmeta.setValue(creator_emailfield.getText());
+		collection_extra_collectionmeta.setValue(description_textarea.getText());
+		icon_collection_collectionmeta.setValue(icon_textfield.getText());
+		maintainer_collectionmeta.setValue(maintainer_emailfield.getText());
+		collection_name_collectionmeta.setValue(name_textfield.getText());
+		icon_collection_small_collectionmeta.setValue(small_icon_textfield.getText());
+		ready = false;
+	    }
+	}
+
+    }
+    /** This tree provides a 'table of contents' for the various components of the design process (collection configuration in more technical terms). */
+    private class DesignTree
+	extends JTree {
+	private DesignNode root = null;
+	/** Constructor. Automatically generates all of the nodes, in the order of CONTENTS. */
+	public DesignTree() {
+	    super();
+	    root = new DesignNode("Root");
+	    this.setModel(new DefaultTreeModel(root));
+	    // Now add the design categories.
+	    for(int i = 0; i < CONTENTS.length; i++) {
+		root.add(new DesignNode(CONTENTS[i]));
+	    }
+	    expandRow(0);
+	    setRootVisible(false);
+	    setSelectionRow(0);
+	}
+	/** Set the current view to the one specified.
+	 * @param type the name of the desired view as a String
+	 */
+	public void setSelectedView(String type) {
+	    type = Gatherer.dictionary.get(type);
+	    for(int i = 0; i < root.getChildCount(); i++) {
+		DesignNode child = (DesignNode) root.getChildAt(i);
+		if(child.toString().equals(type)) {
+		    TreePath path = new TreePath(child.getPath());
+		    setSelectionPath(path);
+		}
+	    }
+	}
+    }
+    /** A tree node that retains a reference to one of the possible design sub-views relating to the different sub-managers. */
+    private class DesignNode
+	extends DefaultMutableTreeNode {
+	/** Constructor.
+	 * @param object The <strong>Object</strong> assigned to this node.
+		  */
+	public DesignNode(String object) {
+	    super(object);
+	}
+	/** Retrieve a textual representation of the object.
+	 * @return a String
+	 */
+	public String toString() {
+	    return get((String)getUserObject());
+	}
+    }
+    /** Listens for selection changes in the 'contents' tree, and switches to the appropriate view. */
+    private class TreeListener
+	implements TreeSelectionListener {
+	/** Called whenever the selection changes, we must update the view so it matches the node selected.
+	 * @param event A <strong>TreeSelectionEvent</strong> containing more information about the tree selection.
+	 * @see org.greenstone.gatherer.cdm.ClassifierManager
+	 * @see org.greenstone.gatherer.cdm.CollectionDesignManager
+	 * @see org.greenstone.gatherer.cdm.CollectionMetaManager
+	 * @see org.greenstone.gatherer.cdm.FormatManager
+	 * @see org.greenstone.gatherer.cdm.LanguageManager
+	 * @see org.greenstone.gatherer.cdm.MetadataSetManager
+	 * @see org.greenstone.gatherer.cdm.SubcollectionManager
+	 * @see org.greenstone.gatherer.cdm.PlugInManager
+	 */
+	public void valueChanged(TreeSelectionEvent event) {
+	    if(!tree.isSelectionEmpty()) {
+		TreePath path = tree.getSelectionPath();
+		DesignNode node = (DesignNode)path.getLastPathComponent();
+		String type = (String)node.getUserObject();
+		// Wait begins
+		Gatherer.g_man.wait(true);
+		// Save information in current view
+		view.loseFocus();
+		remove((JPanel)view);
+		// Change panes.
+		if(type.equals("General")) {
+		    view = getControls();
+		}		
+		else if(type.equals("Plugins")) {
+		    view = CollectionDesignManager.plugin_manager.getControls();
+		}
+		else if(type.equals("Indexes")) {
+		    view = CollectionDesignManager.index_manager.getControls();
+		}
+		else if(type.equals("Subcollections")) {
+		    view = CollectionDesignManager.subcollection_manager.getControls();
+		}
+		else if(type.equals("SuperCollection")) {
+		    view = CollectionDesignManager.supercollection_manager.getControls();
+		}
+		else if(type.equals("Classifiers")) {
+		    view = CollectionDesignManager.classifier_manager.getControls();
+		}
+		else if(type.equals("Formats")) {
+		    view = CollectionDesignManager.format_manager.getControls();
+		}
+		else if(type.equals("Translation")) {
+		    view = CollectionDesignManager.translation_view.getControls();
+		}
+		else if(type.equals("MetadataSets")) {
+		    view = CollectionDesignManager.metadataset_view.getControls();
+		}
+		add((JPanel)view, BorderLayout.CENTER);
+		// Update information on visible pane
+		view.gainFocus();
+		repaint();
+		// Wait ends
+		Gatherer.g_man.wait(false);
+	    }
+	}
+    }
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/Index.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/Index.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/Index.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,235 +25,227 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
-/**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
- * Written:      03/05/02
- * Revised:      17/11/02
- **************************************************************************************/
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.StringTokenizer;
-import java.util.Vector;
+
+import java.util.*;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
 import org.greenstone.gatherer.cdm.CollectionMeta;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.greenstone.gatherer.msm.ElementWrapper;
 import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.util.StaticStrings;
 import org.greenstone.gatherer.util.Utility;
-import org.w3c.dom.Element;
+import org.w3c.dom.*;
 /** This class encapsulates a single indexing pair.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
- * @version 2.2
+ * @version 2.3d
  */
 public class Index
-    implements Comparable {
-    /** A refernce to the main manager for access to other sub-managers. */
-    private CollectionDesignManager manager = null;
-    /** The level of this index. */
-    private int level = 0;
-    /** The sources for data this index is built apon, which are either fully qualified metadata element names or 'text'. */
-    private Vector sources = null;
-    /** An element in the index level enumeration. */
-    static public final int DOCUMENT = 0;
-    /** An element in the index level enumeration. */
-    static public final int PARAGRAPH = 1;
-    /** An element in the index level enumeration. */
-    static public final int SECTION = 2;
+    implements Comparable, DOMProxyListEntry {
     /** An values of items in the index level enumeration. */
     static public final String LEVEL[] = {"document","paragraph","section"};
 
-    /** Constructor.
-     * @param level The level of this index as a <strong>String</string>.
-     * @param metadata The fully qualified name of the metadata this index is built on as a <strong>String</strong>, or <i>null</i> in which case the data defaults to "text".
-     */
-    public Index(int level, Vector sources, CollectionDesignManager manager) {
+    private ArrayList sources = null;
+    /** The level of this index (if old sckool MG). */
+    private int level = -1;
+    /** The element this index is based upon. */
+    private Element element = null;
+    /** The unique, if cryptic, identifier of an index. */
+    private String id = null;
+
+    /** Default constructor, which should only be used during DOMProxyListModel creation. */
+    public Index() {
+    }
+
+    /** Constructor. */
+    public Index(Element element) {
+	this.element = element;
+    }
+
+    /** Constructor for a newly assigned index. */
+    public Index(ArrayList sources) {
+	this.sources = sources;
+	// Create a new element
+	Document document = CollectionDesignManager.collect_config.document;
+	element = document.createElement(CollectionConfiguration.INDEX_ELEMENT);
+	// For each source add a content element
+	int size = sources.size();
+	for(int i = 0; i < size; i++) {
+	    Element content_element = document.createElement(CollectionConfiguration.CONTENT_ELEMENT);
+	    Object source_object = sources.get(i);
+	    if(source_object instanceof ElementWrapper) {
+		content_element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, ((ElementWrapper)source_object).getName());
+	    }
+	    else {
+		content_element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, source_object.toString());
+	    }
+	    element.appendChild(content_element);
+	    content_element = null;
+	}
+	document = null;
+    }
+
+    /** Constructor for a newly assigned index with specified level (old skool). */
+    public Index(int level, ArrayList sources) {
+	this(sources);
 	this.level = level;
-	this.manager = manager;
-	this.sources = sources;
-	if(this.sources == null) {
-	    this.sources = new Vector();
-	    this.sources.add("text");
-	}
-    }
+	element.setAttribute(CollectionConfiguration.LEVEL_ATTRIBUTE, LEVEL[level]);
+    }
+
     /** Method to compare two indexes.
-	  * @param object The other index as an <strong>Object</strong>.
-	  * @return An <i>int</i> which indicates how the indexes compare.
-	  * @see java.lang.String
-	  */
+     * @param object The other index as an <strong>Object</strong>.
+     * @return An <i>int</i> which indicates how the indexes compare.
+     * @see java.lang.String
+     */
     public int compareTo(Object object) {
-	return toString(false).compareTo(object.toString());
-    }
+	return id.compareTo(((Index)object).getID());
+    }
+    
+    public DOMProxyListEntry create(Element element) {
+	return new Index(element);
+    }
+
     /** Method to test for the equality of two indexes.
-	  * @param object The other index as an <strong>Object</strong>.
-	  * @return A <i>boolean</i> which is <i>true</i> if the two indexes are equal, <i>false</i> otherwise.
-	  */
+     * @param object The other index as an <strong>Object</strong>.
+     * @return A <i>boolean</i> which is <i>true</i> if the two indexes are equal, <i>false</i> otherwise.
+     */
     public boolean equals(Object object) {
-	if(compareTo(object) == 0) {
-	    return true;
-	}
-	return false;
-    }
-    /** Method to get the data source of this index.
-	  * @return A <strong>String</string> which is a comma separated list of either fully qualified names of an assigned metadata elements, or "text".
-	  */
-    public String getData() {
-	String result = "";
-	Collections.sort(sources, new IndexComparator());
-	for(int i = 0; i < sources.size(); i++) {
-	    result = result + sources.get(i).toString();
-	    if(i < sources.size() - 1) {
-		result = result + ",";
-	    }
-	}
-	return result;
-    }
-    /** Method to get the data source of this index.
-     * @return A <strong>String</string> which is a comma separated list of either fully qualified names of an assigned metadata elements, or "text".
-     * Note, greenstone extracted metadata do not have the ex. 
-     */
-    public String getDataConfig() {
-	String result = "";
-	Collections.sort(sources, new IndexComparator());
-	for(int i = 0; i < sources.size(); i++) {
-	    String source = sources.get(i).toString();
-	    if (source.startsWith(Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP)) {
-		// remove the ex. bit
-		source = source.substring(source.indexOf(MSMUtils.NS_SEP)+1);
-	    }
-	    result = result + source;
-	    if(i < sources.size() - 1) {
-		result = result + ",";
-	    }
-	}
-	return result;
-    }
-	
+	return (compareTo(object) == 0);
+    }
+    
+    public Element getElement() {
+	return element;
+    }
+    
     /** Method to get the value of level.
-	  * @return The value of level as a <strong>String</strong>.
-	  */
+     * @return the level as a int
+     */
     public int getLevel() {
+	if(level == -1) {
+	    String level_str = element.getAttribute(CollectionConfiguration.LEVEL_ATTRIBUTE);
+	    for(int i = 0; level == -1 && i < LEVEL.length; i++) {
+		if(level_str.equals(LEVEL[i])) {
+		    level = i;
+		}
+	    }
+	    level_str = null;
+	}
 	return level;
     }
-    /** Method to get the value of metadata.
-	  * @return The value of metadata as an <strong>Vector</strong>.
-	  */
-    public Vector getMetadata() {
+
+    public String getID() {
+	if(id == null) {
+	    StringBuffer id_buffer = new StringBuffer();
+	    // Write level information, if any.
+	    int level = getLevel();
+	    if(0 <= level && level < 3) {
+		id_buffer.append(LEVEL[level]);
+		id_buffer.append(StaticStrings.COLON_CHARACTER);
+	    }
+	    // Write data information. Retrieve each of the content sources and add them in a comma separated list.
+	    ArrayList sources = getSources();
+	    int sources_size = sources.size();
+	    for(int i = 0; i < sources_size; i++) {
+		Object source_object = sources.get(i);
+		if(source_object instanceof ElementWrapper) {
+		    id_buffer.append(((ElementWrapper)source_object).getName());
+		}
+		else {
+		    id_buffer.append(source_object.toString());
+		}
+		id_buffer.append(StaticStrings.COMMA_CHARACTER);
+	    }
+	    sources = null;
+	    id = id_buffer.substring(0, id_buffer.length() - 1);
+	}
+	return id;
+    }
+
+    /** Retrieve the sources of this index.
+     * @return the sources as an ArrayList
+     */
+    public ArrayList getSources() {
+	if(sources == null) {
+	    sources = new ArrayList();
+	    NodeList content_elements = element.getElementsByTagName(CollectionConfiguration.CONTENT_ELEMENT);
+	    int content_elements_length = content_elements.getLength();
+	    for(int i = 0; i < content_elements_length; i++) {
+		Element content_element = (Element) content_elements.item(i);
+		sources.add(content_element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE));
+	    }
+	    content_elements = null;
+	    Collections.sort(sources);
+	}
 	return sources;
     }
-    /** Method to retrieve this indexes name.
-	  * @return A <strong>String</strong>.
-	  */
-    public String getName() {
-	if(manager != null) {
-	    String name = LEVEL[level] + ":" + getData();
-	    Language language = manager.languages.getDefaultLanguage();
-	    CollectionMeta metadata = manager.collectionmetadatum.getMetadata(name, language, true);
-	    if(metadata != null) {
-		return metadata.getValue();
-	    }
-	}
-	return "";
-    
-    }
+
+    public boolean isAssigned() {
+	return (element != null && !element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.FALSE_STR));
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    public void setElement(Element element) {
+	this.element = element;
+	this.level = -1;
+	this.id = null;
+	this.sources = null;
+    }
+
+    /** Method to set the level of this index which can only be used for the default index. 
+     * @param level the new level as an int
+     */
+    public void setLevel(int new_level) {
+	if(element != null && element.getNodeName().equals(CollectionConfiguration.INDEX_DEFAULT_ELEMENT)) {
+	    element.setAttribute(CollectionConfiguration.LEVEL_ATTRIBUTE, LEVEL[new_level]);
+	    this.id = null; // Regenerate ID.
+	    this.level = new_level;
+	}
+	else {
+	    Gatherer.println("Error! Called setLevel() of index other than the default.");
+	}
+    }
+
+    /** Method to set the sources for this index which can only be used for the default index.
+     * @param sources an ArrayList of source names
+     */
+    public void setSources(ArrayList sources) {
+	if(element != null && element.getNodeName().equals(CollectionConfiguration.INDEX_DEFAULT_ELEMENT)) {
+	    // Erase old sources
+	    MSMUtils.clear(element);
+	    // For each entry in the sources array add a new content element.
+	    int size = sources.size();
+	    for(int i = 0; i < size; i++) {
+		Element content_element = element.getOwnerDocument().createElement(CollectionConfiguration.CONTENT_ELEMENT);
+		content_element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, (String) sources.get(i));
+		element.appendChild(content_element);
+		content_element = null;
+	    }
+	    this.id = null; // Regenerate ID.
+	    this.sources = sources;
+	}
+	else {
+	    Gatherer.println("Error! Called setSource() of index other than the default.");
+	}
+    }
+
     /** Method to turn this object into a string representation ready to be placed in the collection configuration file.
-	  * @return A <strong>String</strong> containing the information of this class.
-	  */
+     * @return A <strong>String</strong> containing the information of this class.
+     */
     public String toString() {
-	return LEVEL[level] + ":" + getData();
-    }
-    /** Retrieve a textual representation of this index.
-	* @param show_name <i>true</i> if you want the name of this index, <i>false</i> for the gsdl index reference.
-	*/
-    public String toString(boolean show_name) {
-	if(show_name) {
-	    return getName();
-	}
-	return LEVEL[level] + ":" + getData();
-    }
-    
-    /** Retrieve a textual representation of this index, specifically for the configuration files. Extracted metadata is shown to teh user and stored as ex.MD, however in the config file it should have no namespace */
-    public String toStringConfig() {
-
-	return LEVEL[level] + ":" + getDataConfig();
-    }
-
-    /** Method to parse a configuration file index specification into an Index
-     * object. returns null if invalid syntax */
-    public static Index parseIndexConfig(String entry, CollectionDesignManager manager) {
-	// all (mg) indexes must start with level:
-	if(entry.indexOf(":") == -1) {
-	    return null;
-	}
-	
-	String level_str = entry.substring(0, entry.indexOf(":"));
-	String sources_raw = entry.substring(entry.indexOf(":") + 1);
-	Vector sources = new Vector();
-	StringTokenizer st = new StringTokenizer(sources_raw, ",");
-	while(st.hasMoreTokens()) {
-	    String token = st.nextToken();
-	    // We may have to replace old : with whatever namespace separator we are using.
-	    token = token.replace(':', MSMUtils.NS_SEP);
-	    // if there is no namespace, we need to add one, except for text
-	    if (token.indexOf(MSMUtils.NS_SEP)==-1) {
-		if (!token.equals("text")) {
-		    token = Utility.EXTRACTED_METADATA_NAMESPACE+MSMUtils.NS_SEP+token;
-		}
-	    }
-	    
-	    sources.add(token);
-	}
-	Index index = null;
-	for(int i = 0; i < Index.LEVEL.length; i++) {
-	    if(level_str.equals(Index.LEVEL[i])) {
-		return new Index(i, sources, manager);
-		
-	    }
-	}
-	// somethings gone wrong
-	return null;
-	
-    }
-    
-    /** A custom comparator for comparing Indexes. */
-    private class IndexComparator
-	implements Comparator {
-	/** Method to compare two objects, which may be either indexes or strings.
-	 * @param object1 One object as an <strong>Object</strong>.
-	 * @param object2 Another object as an <strong>Object</strong>.
-	 * @return An <i>int</i> which indicates how they compare.
-	 * @see java.lang.String
-	 */
-	public int compare(Object object1, Object object2) {
-	    if(object1 instanceof Index && object2 instanceof Index) {
-		Index index1 = (Index)object1;
-		Index index2 = (Index)object2;
-		return index1.toString(false).compareTo(index2.toString(false));
-	    }
-	    else if(object1 instanceof Index) {
-		Index index = (Index) object1;
-		return index.toString(false).compareTo(object2.toString());
-	    }
-	    else if(object2 instanceof Index) {
-		Index index = (Index) object2;
-		return object1.toString().compareTo(index.toString(false));
-	    }
-	    return object1.toString().compareTo(object2.toString());
-	}
-	/** Method to test for the equality of two objects, which may be indexes or strings.
-			* @param object Another object as an <strong>Object</strong>.
-			* @return A <i>boolean</i> which is <i>true</i> if the two objects are equal, <i>false</i> otherwise.
-			*/
-	public boolean equals(Object object) {
-	    if(compareTo(object) == 0) {
-		return true;
-	    }
-	    return false;
-	}
+	String id = getID();
+	StringBuffer text = new StringBuffer(id);
+	CollectionMeta metadatum = CollectionDesignManager.collectionmeta_manager.getMetadatum(StaticStrings.STOP_CHARACTER + id, false);
+	if(metadatum != null) {
+	    text.append(" \"");
+	    text.append(metadatum.getValue());
+	    text.append("\"");
+	}
+	return text.toString();
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/IndexManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/IndexManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/IndexManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,54 +25,21 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
-/**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
- * Written:      03/05/02
- * Revised:      17/11/02 - Commented
- **************************************************************************************/
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.util.Collections;
-import java.util.StringTokenizer;
-import java.util.Vector;
-import javax.swing.BorderFactory;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.DefaultListModel;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.event.ListDataEvent;
-import javax.swing.event.ListDataListener;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
 import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionMeta;
 import org.greenstone.gatherer.cdm.CommandTokenizer;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
 import org.greenstone.gatherer.cdm.Index;
 import org.greenstone.gatherer.msm.ElementWrapper;
 import org.greenstone.gatherer.msm.MSMUtils;
 import org.greenstone.gatherer.util.ExclusiveListSelectionListener;
-import org.w3c.dom.Element;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.w3c.dom.*;
 /** This class is resposible for storing the indexes which have been assigned to this collection and the default index, and providing methods for interacting with both these data pools. It also knows how to turn itself into a String as it would be displayed in the collection configuration file.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
@@ -90,251 +47,182 @@
  */
 public class IndexManager
-    extends DefaultListModel {
-    /** A reference to our creator, the collection design manager. */
-    private CollectionDesignManager manager = null;
+    extends DOMProxyListModel {
     /** The controls for editing the indexes. */
     private Control controls = null;
     /** A reference to ourselves so our inner methods have access. */
-    private DefaultListModel model = null;
-    /** A reference to the Gatherer, for access to the Dictionary and messaging purposes. */
-    private Gatherer gatherer = null;
+    private DOMProxyListModel model = null;
     /** The default index. */
     private Index default_index = null;
-    /** Constructor.
-     * @param gatherer A reference to the <strong>Gatherer</strong>.
-     * @param manager A reference to the <strong>CollectionDesignManager</strong>.
-     */
-    public IndexManager(Gatherer gatherer, CollectionDesignManager manager) {
-	super();
-	this.gatherer = gatherer;
-	this.manager = manager;
-	this.model = this;
+    /** Constructor. */
+    public IndexManager(Element indexes) {
+	super(indexes, CollectionConfiguration.INDEX_ELEMENT, new Index());
+	Gatherer.println("IndexManager: " + getSize() + " indexes parsed.");
+	model = this;
+	// Parse and retrieve the default index
+	NodeList default_index_elements = CollectionDesignManager.collect_config.getDocumentElement().getElementsByTagName(CollectionConfiguration.INDEX_DEFAULT_ELEMENT);
+	if(default_index_elements.getLength() > 0) {
+	    default_index = new Index((Element)default_index_elements.item(0));
+	}
     }
     /** Method to add a new index.
-	  * @param index The <strong>Index</strong> to add.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void addIndex(Index index) {
+     * @param index The <strong>Index</strong> to add.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
+    public void addIndex(Index index, CollectionMeta metadatum) {
+	///ystem.err.println("Adding an index: " + index.toString());
 	if(!contains(index)) {
-	    String index_str = index.toString(false).toLowerCase();
-				// Add alphabetically.
-	    for(int i = 0; i < size(); i++) {
-		Index sibling = (Index) get(i);
-		String sibling_str = sibling.toString(false).toLowerCase();
-		int position = index_str.compareTo(sibling_str);
-		// Sibling is before index.
-		if(position > 0) {
-		    // Carry on.
-		}
-		// Index is equal to, or before sibling. Insert it.
-		else if(position == 0 || position < 0) {
-		    add(i, index);
-		    gatherer.c_man.configurationChanged();
-		    return;
-		}
-	    }
-				// If we got this far, we haven't inserted index, and we are out of model so.
-	    addElement(index);
-	    gatherer.c_man.configurationChanged();
+	    CollectionDesignManager.collectionmeta_manager.addMetadatum(metadatum);
+	    // Retrieve the currently last index
+	    Index last_index = (Index)getElementAt(getSize() - 1);
+	    addAfter(index, last_index);
+	    Gatherer.c_man.configurationChanged();
 	}
 	else {
-	    JOptionPane.showMessageDialog(manager.gui, get("CDM.IndexManager.Index_Exists"), get("General.Warning"), JOptionPane.WARNING_MESSAGE);
-	}
-    }
-    /** Method to acquire the controls for editing the indexes.
-	  * @return A <strong>JPanel</strong> containing the controls.
-	  * @see org.greenstone.gatherer.cdm.IndexManager.Control
-	  */
-    public JPanel getControls() {
-	if(controls == null) {
-	    controls = new Control();
-	}
-	return controls;
-    }
-    /** Method to get the default index.
-	  * @return The default <strong>Index</strong>.
-	  */
-    public Index getDefault() {
-	return default_index;
-    }
-    /** Method to retrieve a certain index, as referenced by an index number.
-	  * @param index An <i>int</i> which indicates the position of the desired index.
-	  * @return The <strong>Index</strong> at the given index, or <i>null</i> if no such index exists.
-	  */
-    public Index getIndex(int index) {
-	if(0 <= index && index < size()) {
-	    return (Index)get(index);
-	}
-	return null;
-    }
-    /** Method to retrieve a certain index, given its name.
-	  * @param name The name of the index as a <Strong>String</strong>.
-	  * @return The <strong>Index</strong> that matches name, or <i>null</i> if no such index exists.
-	  */
-    public Index getIndex(String name) {
-	///ystem.err.println("Searching for index " + name);
-	for(int i = 0; i < size(); i++) {
-	    Index index = (Index) get(i);
-	    if(index.toString(false).equals(name)) {
-		///ystem.err.println("Found.");
-		return (Index)get(i);
-	    }
-	}
-	///ystem.err.println("No such index.");
-	return null;
-    }
-    /** A method to retrieve all of the indexes associated with this manager.
-	  * @return A <strong>Vector</strong> of <strong>Index</strong>es.
-	  */
-    public Vector getIndexes() {
-	Vector indexes = new Vector();
-	for(int i = 0; i < size(); i++) {
-	    indexes.add(get(i));
-	}
-	Collections.sort(indexes);
-	return indexes;
-    }
-    /** Mark the current set of controls, if any, as obsolete and deallocate them. If further need of the controls will cause new controls to be created.
-	* @see org.greenstone.gatherer.cdm.IndexManager.Control
-	*/
-    public void invalidateControls() {
+	    JOptionPane.showMessageDialog(Gatherer.g_man, get("CDM.IndexManager.Index_Exists"), get("General.Warning"), JOptionPane.WARNING_MESSAGE);
+	}
+    }
+
+    public void destroy() {
 	if(controls != null) {
 	    controls.destroy();
-	}
-	controls = null;
-    }
-    /** Method that attempts to parse an index related command from the given command. If such a command is parsed, it is immediately registered with this manager.
-	  * @param command The <strong>String</strong> to parse.
-	  * @return A <i>boolean</i> which is <i>true</i> if a command was parsed, <i>false</i> otherwise.
-	  * @see org.greenstone.gatherer.cdm.CommandTokenizer
-	  * @see org.greenstone.gatherer.cdm.Index
-	  */
-    public boolean parse(String command) {
-	String temp = command.toLowerCase();
-	if(temp.startsWith("indexes")) {
-	    CommandTokenizer ct = new CommandTokenizer(command);
-	    ct.nextToken(); // Throw away indexes.
-	    while(ct.hasMoreTokens()) {
-		String entry = ct.nextToken();
-		Index index = Index.parseIndexConfig(entry, manager);
-		if (index != null) {
-		    addIndex(index);
-		    
-		} else { // return false if there is one error??
-		    return false;
-		}
-	    }
-	    
-	    return true;
-	}
-	else if(temp.startsWith("defaultindex")) {
-	    CommandTokenizer ct = new CommandTokenizer(command);
-	    ct.nextToken();
-	    String entry = ct.nextToken();
-	    Index index = Index.parseIndexConfig(entry, manager);
-	    if (index != null) {
-		setDefault(index);
-		return true;
-	    } 
-	    return false;
-	}
-	
-	return false;
-    }
-
-    public void removeAll() {
-	removeAllElements();
+	    controls = null;
+	}
 	default_index = null;
-	gatherer.c_man.configurationChanged();
+	model = null;
+    }
+
+    /** Method to acquire the controls for editing the indexes.
+     * @return the Control
+     */
+    public Control getControls() {
+	if(controls == null) {
+	    // Build controls
+	    controls = new IndexControl();
+	}
+	return controls;
+    }
+
+    /** Method to get the default index.
+     * @return The default <strong>Index</strong>.
+     */
+    public Index getDefault() {
+	if(default_index != null && default_index.isAssigned()) {
+	    return default_index;
+	}
+	else {
+	    return null;
+	}
+    }
+
+    /** Method to retrieve a certain index, as referenced by an index number.
+     * @param index An <i>int</i> which indicates the position of the desired index.
+     * @return The <strong>Index</strong> at the given index, or <i>null</i> if no such index exists.
+     */
+    public Index getIndex(int index) {
+	if(0 <= index && index < getSize()) {
+	    return (Index)getElementAt(index);
+	}
+	return null;
+    }
+
+    /** Method to retrieve a certain index, given its id.
+     * @param id the id of the index as a String
+     * @return the Index that matches id, or null if no such index exists
+     */
+    public Index getIndex(String id) {
+	int size = getSize();
+	for(int i = 0; i < size; i++) {
+	    Index index = (Index) getElementAt(i);
+	    if(index.getID().equals(id)) {
+		return index;
+	    }
+	}
+	return null;
+    }
+
+    public ArrayList getIndexes() {
+	return children();
     }
 
     /** Method to remove a certain index.
-	  * @param index The <Strong>Index</strong> to remove.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.cdm.CollectionDesignManager
-	  * @see org.greenstone.gatherer.cdm.CollectionMetaManager
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
+     * @param index the Index to remove.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.cdm.CollectionDesignManager
+     * @see org.greenstone.gatherer.cdm.CollectionMetaManager
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
     public void removeIndex(Index index) {
 	if(index != null) {
-	    String name = index.getName();
-	    if(name != null) {
-		Language default_language = manager.languages.getDefaultLanguage();
-		CollectionMeta metadata = new CollectionMeta(manager, index, default_language, name);
-		manager.collectionmetadatum.removeMetadata(metadata);
-	    }
-	    removeElement(index);
+	    // Remove any current metadata from this index
+	    CollectionDesignManager.collectionmeta_manager.removeMetadata(StaticStrings.STOP_CHARACTER + index.getID());
+	    // Check if the index removed happens to be the default index
 	    if(default_index != null && default_index.equals(index)) {
-		default_index = null;
-	    }
-	    gatherer.c_man.configurationChanged();
-	}
-    }
+		default_index.setAssigned(false);
+	    }
+	    // Remove the index
+	    remove(index);
+	    Gatherer.c_man.configurationChanged();
+	}
+    }
+
     /** Method to set the default index.
-	  * @param index The new default <strong>Index</strong>.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
+     * @param index the new default Index
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
     public void setDefault(Index index) {
-	default_index = index;
-	if(index != null && !contains(index)) {
-	    addElement(index);
-	}
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method to print out the contents of this class as a string.
-	  * @return A <strong>String</strong> just as it would appear in the colleciton configuration file.
-	  */
-    public String toString() {
-	String text = "";
-	// First the indexes.
-	if(size() > 0) {
-	    text = "indexes      ";
-	    for(int i = 0; i < size(); i++) {
-		Index index = (Index) get(i);
-		text = text + index.toStringConfig();
-		if(i < size() - 1) {
-		    text = text + " ";
-		}
-	    }
-	    // Now the default index if there is one, or just the first index
-	    // if there isn't
+	if(index != null) {
+	    if(default_index == null) {
+		// Create the default index element, and place immediately after indexes element.
+		Element default_index_element = root.getOwnerDocument().createElement(CollectionConfiguration.INDEX_DEFAULT_ELEMENT);
+		default_index = new Index(default_index_element);
+		Node target_node = CollectionConfiguration.findInsertionPoint(default_index_element);
+		if(target_node != null) {
+		    root.getOwnerDocument().getDocumentElement().insertBefore(default_index_element, target_node);
+		}
+		else {
+		    root.getOwnerDocument().getDocumentElement().appendChild(default_index_element);
+		}
+	    }
+	    default_index.setAssigned(true);
+	    default_index.setLevel(index.getLevel());
+	    default_index.setSources(index.getSources());
+	}
+	else {
 	    if(default_index != null) {
-		text = text + "\ndefaultindex " + default_index.toStringConfig() + "\n";
-	    }
-	    text = text + "\n";
-	}
-	return text;
-    }
-
-
-
-
+		default_index.setAssigned(false);
+	    }
+	}
+	Gatherer.c_man.configurationChanged();
+    }
     
     /** Overloaded to call get with both a key and an empty argument array.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  */
+     * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+     * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
+     */
     private String get(String key) {
 	return get(key, null);
     }
+
     /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
-	  * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.Dictionary
-	  */
+     * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
+     * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+     * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
+     * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatically populated with formatting Strings of with argument String provided in the get call.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.Dictionary
+     */
     private String get(String key, String args[]) {
 	if(key.indexOf('.') == -1) {
 	    key = "CDM.IndexManager." + key;
 	}
-	return gatherer.dictionary.get(key, args);
-    }
+	return Gatherer.dictionary.get(key, args);
+    }
+
     /** This class creates a set of controls for editing the indexes. */
-    private class Control
-	extends JPanel {
+    private class IndexControl
+	extends JPanel 
+	implements Control {
 	/** The default size of a label on this control. */
 	private Dimension LABEL_SIZE = new Dimension(120,25);
@@ -406,10 +294,9 @@
 	 * @see org.greenstone.gatherer.util.ExclusiveListSelectionListener
 	 */
-	public Control() {
+	public IndexControl() {
 	    super();
 	    source_model = new Vector();
 	    source_model.add("text");
-	    source_model.addAll(gatherer.c_man.msm.getAssignedElements());
-	    //source_model.addAll(gatherer.c_man.msm.getElements());
+	    source_model.addAll(Gatherer.c_man.getCollection().msm.getAssignedElements());
 	    // Creation
 	    add = new JButton(get("Add"));
@@ -428,5 +315,5 @@
 	    default_pane = new JPanel();
 	    if(default_index != null) {
-		default_value = new JTextField(default_index.toString(true));
+		default_value = new JTextField(default_index.toString());
 		default_value.setCaretPosition(0);
 	    }
@@ -440,5 +327,4 @@
 	    index_label = new JLabel(get("Indexes"));
 	    index_list = new JList(model);
-	    index_list.setCellRenderer(new IndexListCellRenderer());
 	    instructions = new JTextArea(get("Instructions"));
 	    instructions.setEditable(false);
@@ -550,13 +436,9 @@
 	}
 	/* Destructor, removes persistant listeners from the Dictionary.
-			*/
+	 */
 	public void destroy() {
 	}
-	/** We override the updateUI method so that we can ensure we are scrolled to the top of the instructions box first.
-		  * @see org.greenstone.gatherer.Gatherer
-		  * @see org.greenstone.gatherer.collection.CollectionManager
-		  * @see org.greenstone.gatherer.msm.MetadataSetManager
-			*/
-	public void updateUI() {
+
+	public void gainFocus() {
 	    if(instructions != null) {
 		instructions.setCaretPosition(0);
@@ -566,12 +448,14 @@
 		source_model.clear();
 		source_model.add("text");
-		source_model.addAll(gatherer.c_man.msm.getAssignedElements());
+		source_model.addAll(Gatherer.c_man.getCollection().msm.getAssignedElements());
 		// reset the model in the list - needed if the model is larger than when it was first created, the list doesn't display
 		source_list.setListData(source_model);
 	    
 	    }
-	    // Faster than a NPE its the - 'Super class'.
-	    super.updateUI();
-	}
+	}
+
+	public void loseFocus() {
+	}
+
 	/** Listens for actions apon the 'add' button in the IndexManager controls, and if detected calls the add method of the manager with a newly created index. */
 	private class AddListener
@@ -589,15 +473,18 @@
 		if(!source_list.isSelectionEmpty() && name.getText().length() != 0) {
 		    Object object[] = source_list.getSelectedValues();
-		    Vector sources = new Vector();
+		    ArrayList sources = new ArrayList();
 		    for(int i = 0; i < object.length; i++) {
 			sources.add(object[i]);
 		    }
-		    Index index = new Index(level.getSelectedIndex(), sources, manager);
+		    object = null;
+		    Index index = new Index(level.getSelectedIndex(), sources);
+		    sources = null;
 		    // Before we add the index to the model, we have to add the collection metadata for this.
-		    Language language = manager.languages.getDefaultLanguage();
-		    CollectionMeta metadata = new CollectionMeta(manager, index, language, name.getText());
-		    manager.collectionmetadatum.addMetadata(metadata);
+		    CollectionMeta metadatum = new CollectionMeta(StaticStrings.STOP_CHARACTER + index.getID());
+		    metadatum.setValue(name.getText());
 		    // Finally add index.
-		    addIndex(index);
+		    addIndex(index, metadatum);
+		    metadatum = null;
+		    index = null;
 		}
 	    }
@@ -606,7 +493,7 @@
 	private class ClearDefaultListener
 	    implements ActionListener {
-				/** If called when an action occurs on a registered component, we clear the default index.
-				 * @param event An <strong>ActionEvent</strong> containing extra information about the action that occured.
-				*/
+	    /** If called when an action occurs on a registered component, we clear the default index.
+	     * @param event An <strong>ActionEvent</strong> containing extra information about the action that occured.
+	     */
 	    public void actionPerformed(ActionEvent event) {
 		setDefault(null);
@@ -643,5 +530,5 @@
 		if(default_index != null) {
 		    clear_default.setEnabled(true);
-		    default_value.setText(default_index.toString(true));
+		    default_value.setText(default_index.toString());
 		    default_value.setCaretPosition(0);
 		}
@@ -698,23 +585,4 @@
 	    }
 	}
-
-
-	private class IndexListCellRenderer
-	    extends DefaultListCellRenderer {
-	    
-	    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
-		StringBuffer text = new StringBuffer(value.toString());
-		// Retrieve the indexes name if any.
-		CollectionMeta metadata = manager.collectionmetadatum.getMetadata(value, manager.languages.getDefaultLanguage(), true);
-		if(metadata != null) {
-		    text.append(" \"");
-		    text.append(metadata.getValue());
-		    text.append("\"");
-		}
-		return super.getListCellRendererComponent(list, text.toString(), index, isSelected, cellHasFocus);
-	    }
-
-	}
-
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/Language.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/Language.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/Language.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,20 +25,15 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
  * Written:      08/05/02
  * Revised:      17/11/02 - Commented
+ *               07/07/03 - DOM support
  **************************************************************************************/
-
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.greenstone.gatherer.msm.MSMUtils;
+import org.w3c.dom.*;
 /** A pretty unexciting extension of a two character string, in that it has a field which details if its the default language.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
@@ -56,70 +41,101 @@
 */
 public class Language
-    implements Comparable {
-    /** Is this language the default one. */
-    private boolean default_language = false;
+    implements Comparable, DOMProxyListEntry {
+    /** The Element this language entry is based upon. */
+    private Element element = null;
+    /** The two character code for this language. */
+    private String code = null;
     /** The name of this language. */
     private String name = null;
-    /** The two character code for this language. */
-    private String value = null;
-    /** Constructor.
-     * @param value A <strong>String</strong> representing the code for this language.
-     * @param name A <strong>String</strong> representing the name of this language.
-     * @param default_language A <i>boolean</i> which is <i>true</i> if this language is the default one.
-	  */
-    public Language(String value, String name, boolean default_language) {
-	this.default_language = default_language;
-	this.name = name;
-	this.value = value.substring(0, 2);
+ 
+    public Language() {
     }
-    /** Copy constructor.
-	  * @param language The <strong>Language</strong> we want to copy.
-	  */
-    public Language(Language language) {
-	this.default_language = language.isDefault();
-	this.name = language.toString();
-	this.value = language.getCode();
+
+    public Language(Element element) {
+	this.element = element;
     }
+
+    /** Constructor for a brand new language. */
+    public Language(String code) {
+	this.code = code;
+	// Create the new element
+	element = CollectionDesignManager.collect_config.document.createElement(CollectionConfiguration.LANGUAGE_ELEMENT);
+	element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, code);
+	element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, CollectionConfiguration.TRUE_STR);
+    }
+
     /** Method to compare two languages for ordering purposes.
-	  * @param object The other language as an <strong>Object</strong>.
-	  * @return An <i>int</i> which indicates order using the same values as in String.compareTo().
-	  * @see java.lang.String#compareTo
-	  */
+     * @param object the other language as an Object
+     * @return an int which indicates the order between this language and the given one: < 0, 0 or > 0 for before, equal to or after respectively
+     */
     public int compareTo(Object object) {
 	return toString().compareTo(object.toString());
     }
+
+    public DOMProxyListEntry create(Element element) {
+	return new Language(element);
+    }
+
     /** Method to test for the equality of two languages.
-	  * @param object The other language as an <strong>Object</strong>.
-	  * @return <i>true</i> if the languages are equal, <i>false</i> otherwise.
-	  */
+     * @param object The other language as an <strong>Object</strong>.
+     * @return <i>true</i> if the languages are equal, <i>false</i> otherwise.
+     */
     public boolean equals(Object object) {
-	if(compareTo(object) == 0) {
-	    return true;
+	return (compareTo(object) == 0);
+    }
+
+    /** Method to retrieve the code of this language.
+     * @return A <strong>String</strong> representing the two letter code.
+     */
+    public String getCode() {
+	if(code == null && element != null) {
+	    code = element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE);
 	}
-	return false;
+	return code;
     }
-    /** Method to retrieve the code of this language.
-	  * @return A <strong>String</strong> representing the two letter code.
-	  */
-    public String getCode() {
-	return value;
+
+    public Element getElement() {
+	return element;
     }
-    /** Method to determine if this language is the default one.
-	  * @return A <i>boolean</i> which is <i>true</i> if this language is the default one.
-	  */
-    public boolean isDefault() {
-	return default_language;
+
+    public String getName() {
+	if(name == null) {
+	    String code = getCode();
+	    name = CollectionDesignManager.language_manager.getLanguageName(code);
+	}
+	return name;
     }
-    /** Method to set the value of default.
-	  * @param value The new value of default as a <i>boolean</i>.
-	  */
-    public void setDefault(boolean value) {
-	this.default_language = default_language;
+
+    public boolean isAssigned() {
+	return (element != null && element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.TRUE_STR));
     }
+
+    public void setAssigned(boolean value) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (value ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    public void setCode(String new_code) {
+	code = new_code;
+	// Set element
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, new_code);
+	}
+	// Reset name
+	name = null;
+    }
+
+    public void setElement(Element new_element) {
+	element = new_element;
+	code = null;
+	name = null;
+    }
+
     /** Method to display the language code.
-	  * @return A <strong>String</strong> representing the language code.
-	  */
+     * @return A <strong>String</strong> representing the language code.
+     */
     public String toString() {
-	return name;
+	return getName();
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/LanguageListCellRenderer.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/LanguageListCellRenderer.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/LanguageListCellRenderer.java	(revision 4932)
@@ -0,0 +1,43 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+/**************************************************************************************
+ * Written:      11/07/03
+ * Revised:      
+ **************************************************************************************/
+import java.awt.*;
+import javax.swing.*;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.LanguageManager;
+/** Provides a renderer which transforms a language code (such as en or fr) into its appropriate name (English and French respectively) before generating the rubberstamping component. */
+public class LanguageListCellRenderer
+    extends DefaultListCellRenderer {
+    
+    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+	return super.getListCellRendererComponent(list, CollectionDesignManager.language_manager.getLanguageName((String)value), index, isSelected, cellHasFocus);
+    }
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/LanguageManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/LanguageManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/LanguageManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,53 +25,24 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
  * Written:      08/05/02
  * Revised:      17/11/02 - Commented
+ *               07/07/03 - DOM support
  **************************************************************************************/
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.StringTokenizer;
-import java.util.Vector;
-import javax.swing.BorderFactory;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.DefaultListModel;
-import javax.swing.ListModel;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
 import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
 import org.greenstone.gatherer.cdm.Language;
+import org.greenstone.gatherer.cdm.LanguageListCellRenderer;
+import org.w3c.dom.*;
 /** This class manages the language commands, remembering both a list of languages to build indexes in, plus the default language.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
@@ -89,291 +50,31 @@
  */
 public class LanguageManager
-    extends DefaultListModel {
-    /** A reference to the collection design manager. */
-    private CollectionDesignManager manager = null;
+    extends DOMProxyListModel {
+
+    static final private Dimension LABEL_SIZE = new Dimension(125,25);
+
     /** The visual controls for this manager. */
     private Control controls = null;
-    /** A reference to the Gatherer. */
-    private Gatherer gatherer = null;
     /** A reference to this class as a model, for the inner controls class. */
-    private ListModel model = null;
+    private DOMProxyListModel model = null;
     /** A hashtable of code->name mappings of known languages. */
     private LinkedHashMap known_languages = null;
     /** The default language object. */
     private Language default_language = null;
-
-    static final private Dimension LABEL_SIZE = new Dimension(125,25);
-    /** Constructor.
-     * @param gatherer A reference to the <strong>Gatherer</strong>.
-     * @param manager A reference to the <strong>CollectionDesignManager</strong>.
-     */
-    public LanguageManager(Gatherer gatherer, CollectionDesignManager manager) {
-	super();
-	this.gatherer = gatherer;
-	this.known_languages = new LinkedHashMap();
-	this.manager = manager;
+ 
+   /** Constructor. */
+    public LanguageManager(Element languages_element) {
+	super(languages_element, CollectionConfiguration.LANGUAGE_ELEMENT, new Language());
+	
+	Gatherer.println("LanguageManager: " + getSize() + " languages parsed.");
+
 	this.model = this;
-	loadLanguages();
-    }
-    /** Method to add a new language.
-	  * @param language The <strong>Language</strong> to add.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void addLanguage(Language language) {
-	if(!contains(language)) {
-				// Add alphabetically.
-	    for(int index = 0; index < size(); index++) {
-		Language sibling = (Language) get(index);
-		int position = language.compareTo(sibling);
-		// Sibling is before language.
-		if(position > 0) {
-		    // Carry on.
-		}
-		// Language is equal to, or before sibling. Insert it.
-		else if(position == 0 || position < 0) {
-		    add(index, language);
-		    gatherer.c_man.configurationChanged();
-		    return;
-		}
-	    }
-				// If we got this far, we haven't inserted language, and we are out of model so.
-	    addElement(language);
-	    gatherer.c_man.configurationChanged();
-	}
-    }
-    /** Method to retrieve the control for this manager.
-	  * @return A <strong>JPanel</strong> containing the controls.
-	  */
-    public JPanel getControls() {
-	if(controls == null) {
-	    controls = new Control();
-	}
-	return controls;
-    }
-    /** Method to retrieve the default language code.
-	  * @return A <strong>Language</strong> containing a two letter code.
-	  */
-    public Language getDefaultLanguage() {
-	// If no default is set...
-	if(default_language == null) {
-				// And we have other assigned languages, use the first one of them.
-	    if(size() >= 1) {
-		default_language = (Language) get(0);
-	    }
-				// And we have nothing else, use English.
-	    else {
-		default_language = getLanguage("en", false);
-		// Remember to add it.
-		addLanguage(default_language);
-	    }
-	}
-	return default_language;
-    }
-    /** Method to retrieve a certain language object by its code.
-     * @param code The two letter code of a language, as a <strong>String</strong>.
-     * @param assigned_only If <i>true</i> only those languages currently having indexes built for them are checked, otherwise the known languages buffer is checked.
-     * @return The <strong>Language</strong> that matches the given code, or <i>null</i> if no such language exists.
-     */
-    public Language getLanguage(String code, boolean assigned_only) {
-	if(assigned_only) {
-	    for(int i = 0; i < size(); i++) {
-		Language pos = (Language)get(i);
-		///ystem.err.println("Comparing " + pos.getCode() + " and " + code);
-		if(pos.getCode().equals(code)) {
-		    return pos;
-		}
-	    }
-	}
-	else {
-	    if(known_languages.containsKey(code)) {
-		return new Language((Language)known_languages.get(code));
-	    }
-	}
-	return null;
-    }
-    /** Method to return a list of the known language codes.
-     * @return An <strong>ArrayList</strong> containing a series of alphabetically sorted two letter codes.
-     */
-    public ArrayList getLanguageCodes() {
-	ArrayList result = new ArrayList();
-	Iterator key_iter = known_languages.keySet().iterator();
-	while(key_iter.hasNext()) {
-	    result.add(known_languages.get(key_iter.next()));
-	}
-	//for(Enumeration keys = known_languages.keys(); keys.hasMoreElements(); ) {
-	//	result.add(known_languages.get(keys.nextElement()));
-	//}
-	//Collections.sort(result);
-	return result;
-    }
-    /** Mark the current set of controls, if any, as obsolete and deallocate them. If further need of the controls will cause new controls to be created.
-	* @see org.greenstone.gatherer.cdm.IndexManager.Control
-	*/
-    public void invalidateControls() {
-	if(controls != null) {
-	    controls.destroy();
-	}
-	controls = null;
-    }
-
-    /** Determine if the given language is the current default language.
-	  * @param language The <strong>Language</strong> to test.
-	  * @return <i>true</i> if the language is the default one, <i>false</i> otherwise.
-	  */
-    public boolean isDefaultLanguage(Language language) {
-	return (language.equals(default_language));
-    }
-    /** This method attempts to parse a language command from the given string. If such a command is parsed, it is immediately added to the list of languages.
-	  * @param command The <strong>String</strong> containing a possible language command.
-	  * @return A <i>boolean</i> which is <i>true</i> if a command was parsed, <i>false</i> otherwise.
-	  * @see org.greenstone.gatherer.cdm.Language
-	  */
-    public boolean parse(String command) {
-	String command_lc = command.toLowerCase();
-	if(command_lc.startsWith("languages")) {
-	    StringTokenizer tokenizer = new StringTokenizer(command);
-	    tokenizer.nextToken();
-	    while(tokenizer.hasMoreTokens()) {
-		String code = tokenizer.nextToken();
-		if(known_languages.containsKey(code)) {
-		    Language language = (Language)known_languages.get(code);
-		    addElement(new Language(language));
-		}
-		else {
-		    addElement(new Language(code, code, false));
-		}
-	    }
-	    return true;
-	}
-	if(command_lc.startsWith("defaultlanguage")) {
-	    StringTokenizer tokenizer = new StringTokenizer(command);
-	    tokenizer.nextToken();
-	    if(tokenizer.hasMoreTokens()) {
-		String code = tokenizer.nextToken();
-		Language language = getLanguage(code, true);
-		if(language == null) {
-		    language = new Language(code, (String)known_languages.get(code), true);
-		    addElement(language);
-		}
-		else {
-		    language.setDefault(true);
-		}
-		setDefault(language);
-	    }
-	    return true;
-	}
-	return false;
-    }
-    /** Method to cause the list appearance to update if the selection changes. */
-    public void refreshAppearance() {
-	fireContentsChanged(this, 0, size());
-    }
-    /** Method to remove a certain language.
-	  * @param language The <strong>Language</strong> to remove.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void removeLanguage(Language language) {
-	removeElement(language);
-	if(default_language != null && default_language.equals(language)) {
-	    default_language = null;
-	}
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method to set the default language.
-	  * @param language The <strong>Language</strong> to use as a default, or <i>null</i> for no default.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void setDefault(Language language) {
-	// Unset existing.
-	Language old = null;
-	if(default_language != null) {
-	    old = default_language;
-	    default_language.setDefault(false);
-	    default_language = null;
-	}
-	// Now set the new if its not null.
-	if(language != null) {
-	    default_language = language;
-	    default_language.setDefault(true);
-	}
-	// Now cause the model to refresh any lists that are listening.
-	int start = 0;
-	int end = size() - 1;
-	if(default_language != null && old != null) {
-	    int index_default = indexOf(default_language);
-	    int index_old = indexOf(old);
-	    if(index_default < index_old) {
-		start = index_default;
-		end = index_old;
-	    }
-	    else {
-		start = index_old;
-		end = index_default;
-	    }
-	}
-	else if(default_language != null) {
-	    start = end = indexOf(default_language);
-	}
-	else {
-	    start = end = indexOf(old);
-	}
-	fireContentsChanged(this, 0, size());
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method to translate this object into a block of commands as you you expect to find in the collection configuration file.
-     * @return A <strong>String</string> containing a series of commands.
-     */
-    public String toString() {
-	StringBuffer text = new StringBuffer();
-	if(size() > 1) {
-	    text.append("languages ");
-	    for(int i = 0; i < size(); i++) {
-		Language language = (Language) get(i);
-		text.append(language.getCode());
-		if(i < size() - 1) {
-		    text.append(" ");
-		}
-		else {
-		    text.append("\n");
-		}
-	    }
-				// Only bother with default language if there is more than one language.
-	    if(default_language != null) {
-		text.append("defaultlanguage ");
-		text.append(default_language.getCode());
-		text.append("\n");
-	    }
-	    text.append("\n");
-	}
-	return text.toString();
-    }
-    /** Overloaded to call get with both a key and an empty argument array.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  */
-    private String get(String key) {
-	return get(key, null);
-    }
-    /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned. <BR>
-	  * Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {<I>n</I>}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Farg<I>n</I>.
-	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
-	  * @param args A <strong>String[]</strong> used to populate argument fields within the complete String.
-	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.Dictionary
-	  */
-    private String get(String key, String args[]) {
-	if(key.indexOf('.') == -1) {
-	    key = "CDM.LanguageManager." + key;
-	}
-	return gatherer.dictionary.get(key, args);
-    }
-    /** This method loads a series of code->language mappings into known_languages, by reading from the 'languages.dat' file, which is essentially a verbatim copy of the ISO 639 Standard.
-	 * @see org.greenstone.gatherer.cdm.Language
-	  */
-    private void loadLanguages() {
+	// Retrieve the default language
+	NodeList default_language_elements = CollectionDesignManager.collect_config.getDocumentElement().getElementsByTagName(CollectionConfiguration.LANGUAGE_DEFAULT_ELEMENT);
+	if(default_language_elements.getLength() > 0) {
+	    default_language = new Language((Element)default_language_elements.item(0));
+	}
+	// Load a series of code->language mappings into known_languages, by reading from the 'languages.dat' file, which is essentially a subset of the ISO 639 Standard.
+	known_languages = new LinkedHashMap();
 	try {
 	    File in_file = new File("languages.dat");
@@ -384,11 +85,16 @@
 		if(!entry.startsWith("#")) {
 		    StringTokenizer tokenizer = new StringTokenizer(entry);
-		    String name = tokenizer.nextToken();
-		    String code = tokenizer.nextToken().toLowerCase();
-		    Language language = new Language(code, name, false);
-		    known_languages.put(code, language);
+		    if(tokenizer.countTokens() >= 2) {
+			String name = tokenizer.nextToken();
+			String code = tokenizer.nextToken().toLowerCase();
+			known_languages.put(code, name);
+		    }
 		}
 	    }
 	    in.close();
+	    in_reader.close();
+	    in = null;
+	    in_reader = null;
+	    in_file = null;
 	}
 	catch (Exception error) {
@@ -397,23 +103,156 @@
     }
 
+    /** Method to add a new language.
+     * @param language The <strong>Language</strong> to add.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
+    public void addLanguage(Language language) {
+	if(!contains(language)) {
+	    Element element = language.getElement();
+	    // Locate where we should insert this new subcollection. 
+	    Node target_node = CollectionConfiguration.findInsertionPoint(element);
+	    // Failing that we insert immediately after a language string
+	    add(root, language, target_node);
+	    Gatherer.c_man.configurationChanged();
+	}
+    }
+
+    public void destroy() {
+	if(controls != null) {
+	    controls.destroy();
+	    controls = null;
+	}
+	known_languages.clear();
+	known_languages = null;
+    }
+
+    /** Method to retrieve the control for this manager.
+     * @return the Control for editing the language partitions
+     */
+    public Control getControls() {
+	if(controls == null) {
+	    // Build controls
+	    controls = new LanguageControl();
+	}
+	return controls;
+    }
+
+    /** Method to retrieve the default language code.
+     * @return A <strong>Language</strong> containing a two letter code.
+     */
+    public Language getDefaultLanguage() {
+	// If no default is set...
+	if(default_language != null && default_language.isAssigned()) {
+	    return default_language;
+	}
+	return null;
+    }
+
+    /** Method to retrieve a certain language object by its code.
+     * @param code The two letter code of a language, as a <strong>String</strong>.
+     * @param assigned_only If <i>true</i> only those languages currently having indexes built for them are checked, otherwise the known languages buffer is checked.
+     * @return The <strong>Language</strong> that matches the given code, or <i>null</i> if no such language exists.
+     */
+    public Language getLanguage(String code) {
+	int size = getSize();
+	for(int i = 0; i < size; i++) {
+	    Language language = (Language) getElementAt(i);
+	    if(language.getCode().equals(code)) {
+		return language;
+	    }
+	}
+	return null;
+    }
+
+    public ArrayList getLanguages() {
+	return children();
+    }
+
+    /** Method to return a list of the known language codes.
+     * @return an ArrayList containing the series of known language codes as per the languages.dat file
+     */
+    public ArrayList getLanguageCodes() {
+	return new ArrayList(known_languages.keySet());
+    }
+
+    public String getLanguageName(String code) {
+	return (String) known_languages.get(code);
+    }
+
+    /** Method to remove a certain language.
+     * @param language The <strong>Language</strong> to remove.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
+    public void removeLanguage(Language language) {
+	remove(language);
+	if(default_language != null && default_language.equals(language)) {
+	    default_language = null;
+	}
+	Gatherer.c_man.configurationChanged();
+    }
+
+    /** Method to set the default language.
+     * @param language The <strong>Language</strong> to use as a default, or <i>null</i> for no default.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
+    public void setDefault(Language language) {
+	if(language != null) {
+	    if(default_language == null) {
+		// Create the default index element, and place immediately after indexes element.
+		Element default_language_element = root.getOwnerDocument().createElement(CollectionConfiguration.LANGUAGE_DEFAULT_ELEMENT);
+		default_language = new Language(default_language_element);
+		Node target_node = CollectionConfiguration.findInsertionPoint(default_language_element);
+		if(target_node != null) {
+		    root.getOwnerDocument().getDocumentElement().insertBefore(default_language_element, target_node);
+		}
+		else {
+		    root.getOwnerDocument().getDocumentElement().appendChild(default_language_element);
+		}
+	    }
+	    default_language.setAssigned(true);
+	    default_language.setCode(language.getCode());
+	}
+	else {
+	    if(default_language != null) {
+		default_language.setAssigned(false);
+	    }
+	}
+	Gatherer.c_man.configurationChanged();
+    }
+
+    /** Overloaded to call get with both a key and an empty argument array.
+	  * @param key A <strong>String</strong> which is mapped to a initial String within the ResourceBundle.
+	  * @return A <strong>String</strong> which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call.
+	  */
+    private String get(String key) {
+	if(key.indexOf('.') == -1) {
+	    key = "CDM.LanguageManager." + key;
+	}
+	return Gatherer.dictionary.get(key, (String[])null);
+    }
+
     /** This class represents the visual component of the Language Manager. */
-    private class Control
-	extends JPanel {
+    private class LanguageControl
+	extends JPanel 
+	implements Control {
 	/** The button to add a new language support. */
-	private JButton add = null;
+	private JButton add_button = null;
 	/** The button to clear the current default language. */
-	private JButton clear_default = null;
+	private JButton clear_button = null;
 	/** The button to remove a supported language. */
-	private JButton remove = null;
+	private JButton remove_button = null;
 	/** The button to set the current language as the default one. */
-	private JButton set_default = null;
+	private JButton set_button = null;
 	/** A combobox listing the available supported languages. */
-	private JComboBox selector = null;
+	private JComboBox selector_combobox = null;
 	/** A list of currently supported languages. */
-	private JList list = null;
+	private JList language_list = null;
 	/** A description of the language currently selected. */
-	private JTextArea description = null;
+	private JTextArea description_textarea = null;
 	/** The text field showing the currently name of the default language. Non-editable. */
-	private JTextField default_language_value = null;
+	private JTextField default_language_field = null;
 	/** Constructor.
 	 * @see org.greenstone.gatherer.cdm.LanguageManager.Control.AddListener
@@ -425,5 +264,5 @@
 	 * @see org.greenstone.gatherer.cdm.LanguageManager.Control.TranslateListener
 	 */
-	public Control() {
+	public LanguageControl() {
 	    super();
 	    // Creation.
@@ -434,6 +273,5 @@
 	    title_label.setOpaque(true);
 
-	    list = new JList(model);
-	    list.setCellRenderer(new ListRenderer());
+	    language_list = new JList(model);
 
 	    JPanel details_panel = new JPanel();
@@ -446,11 +284,11 @@
 
 	    if(default_language == null) {
-		default_language_value = new JTextField();
+		default_language_field = new JTextField();
 	    }
 	    else {
-		default_language_value = new JTextField(default_language.toString());
-	    }
-	    default_language_value.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
-	    default_language_value.setEditable(false);
+		default_language_field = new JTextField(default_language.toString());
+	    }
+	    default_language_field.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    default_language_field.setEditable(false);
 
 	    JPanel control_panel = new JPanel();
@@ -459,47 +297,42 @@
 	    selector_label.setPreferredSize(LABEL_SIZE);
 
-	    selector = new JComboBox(getLanguageCodes().toArray());
-	    selector.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    selector_combobox = new JComboBox(getLanguageCodes().toArray());
+	    selector_combobox.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    selector_combobox.setRenderer(new LanguageListCellRenderer());
 
 	    JPanel button_panel = new JPanel();
 
-	    add = new JButton(get("Add"));
-	    add.setMnemonic(KeyEvent.VK_A);
-	    if(selector.getSelectedItem() != null) {
-		add.setEnabled(true);
-	    }
-	    else {
-		add.setEnabled(false);
-	    }
-
-	    remove = new JButton(get("Remove"));
-	    remove.setMnemonic(KeyEvent.VK_R);
-	    remove.setEnabled(false);
-
-	    clear_default = new JButton(get("Clear_Default"));
-	    clear_default.setMnemonic(KeyEvent.VK_C);
-	    clear_default.setEnabled(false);
-
-	    set_default = new JButton(get("Set_Default"));
-	    set_default.setMnemonic(KeyEvent.VK_S);
-	    set_default.setEnabled(false);
+	    add_button = new JButton(get("Add"));
+	    add_button.setMnemonic(KeyEvent.VK_A);
+
+	    remove_button = new JButton(get("Remove"));
+	    remove_button.setMnemonic(KeyEvent.VK_R);
+	    remove_button.setEnabled(false);
+
+	    clear_button = new JButton(get("Clear_Default"));
+	    clear_button.setMnemonic(KeyEvent.VK_C);
+	    clear_button.setEnabled(false);
+
+	    set_button = new JButton(get("Set_Default"));
+	    set_button.setMnemonic(KeyEvent.VK_S);
+	    set_button.setEnabled(false);
 
 	    // Set up and connect listeners.
-	    add.addActionListener(new AddListener());
-	    clear_default.addActionListener(new ClearDefaultListener());
-	    remove.addActionListener(new RemoveListener());
-	    selector.addActionListener(new SelectorListener());
-	    set_default.addActionListener(new SetDefaultListener());
-	    list.addListSelectionListener(new ListListener());
+	    add_button.addActionListener(new AddListener());
+	    clear_button.addActionListener(new ClearDefaultListener());
+	    remove_button.addActionListener(new RemoveListener());
+	    selector_combobox.addActionListener(new SelectorListener());
+	    set_button.addActionListener(new SetDefaultListener());
+	    language_list.addListSelectionListener(new ListListener());
 	    // Layout components.
 	    default_panel.setBorder(BorderFactory.createRaisedBevelBorder());
 	    default_panel.setLayout(new BorderLayout());
 	    default_panel.add(default_label, BorderLayout.WEST);
-	    default_panel.add(default_language_value, BorderLayout.CENTER);
+	    default_panel.add(default_language_field, BorderLayout.CENTER);
 
 	    control_panel.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
 	    control_panel.setLayout(new BorderLayout());
 	    control_panel.add(selector_label, BorderLayout.WEST);
-	    control_panel.add(selector, BorderLayout.CENTER);
+	    control_panel.add(selector_combobox, BorderLayout.CENTER);
 
 	    details_panel.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
@@ -510,12 +343,12 @@
 	    center_panel.setLayout(new BorderLayout());
 	    center_panel.add(title_label, BorderLayout.NORTH);
-	    center_panel.add(new JScrollPane(list), BorderLayout.CENTER);
+	    center_panel.add(new JScrollPane(language_list), BorderLayout.CENTER);
 	    center_panel.add(details_panel, BorderLayout.SOUTH);
 
 	    button_panel.setLayout(new GridLayout(2,2,5,5));
-	    button_panel.add(add);
-	    button_panel.add(remove);
-	    button_panel.add(clear_default);
-	    button_panel.add(set_default);
+	    button_panel.add(add_button);
+	    button_panel.add(remove_button);
+	    button_panel.add(clear_button);
+	    button_panel.add(set_button);
 
 	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
@@ -524,8 +357,14 @@
 	    add(button_panel, BorderLayout.SOUTH);
 	}
-	/** Destructor.
-			*/
+	/** Destructor.	*/
 	public void destroy() {
 	}
+
+	public void gainFocus() {
+	}
+
+	public void loseFocus() {
+	}
+
 	/** Listens for actions apon the 'add' button in the LanguageManager controls, and if detected calls the add method of the manager with a newly created language. */
 	private class AddListener
@@ -536,10 +375,12 @@
 	     */
 	    public void actionPerformed(ActionEvent event) {
-		Language language = (Language)selector.getSelectedItem();
-		if(language != null) {
-		    addLanguage(new Language(language));
-		}
-	    }
-	}
+		String language_code = (String) selector_combobox.getSelectedItem();
+		if(language_code != null) {
+		    addLanguage(new Language(language_code));
+		}
+		add_button.setEnabled(false);
+	    }
+	}
+
 	/** Listens for actions apon the 'clear default' button in the LanguageManager controls, and if detected calls the setDefault method of the manager with <i>null</i>. */
 	private class ClearDefaultListener
@@ -550,8 +391,9 @@
 	    public void actionPerformed(ActionEvent event) {
 		setDefault(null);
-		clear_default.setEnabled(false);
-		default_language_value.setText("");
-	    }
-	}
+		clear_button.setEnabled(false);
+		default_language_field.setText("");
+	    }
+	}
+
 	/** Listens for actions apon the 'remove' button in the LanguageManager controls, and if detected calls the remove method of the manager with the language selected for removal. */
 	private class RemoveListener
@@ -562,14 +404,18 @@
 	     */
 	    public void actionPerformed(ActionEvent event) {
-		if(!list.isSelectionEmpty()) {
-		    removeLanguage((Language)list.getSelectedValue());
-		    if(default_language == null) {
-			clear_default.setEnabled(false);
-			default_language_value.setText("");
+		Language delete_me = (Language)language_list.getSelectedValue();
+		if(delete_me != null) {
+		    removeLanguage(delete_me);
+		    if(default_language != null && default_language.equals(delete_me)) {
+			setDefault(null);
+			clear_button.setEnabled(false);
+			default_language_field.setText("");
 		    }
 		}
-	    }
-	}
-	/** Listens for selections wihtin the combobox on the LanguageManager controls, and if a change is detected enables, or disables, controls appropriately. */
+		remove_button.setEnabled(false);
+	    }
+	}
+
+	/** Listens for selections within the combobox on the LanguageManager controls, and if a change is detected enables, or disables, controls appropriately. */
 	private class SelectorListener
 	    implements ActionListener {
@@ -578,29 +424,29 @@
 	     */
 	    public void actionPerformed(ActionEvent event) {
-		if(selector.getSelectedItem() != null) {
-		    add.setEnabled(true);
-		    //description.setText((String)known_languages.get(code));
+		if(selector_combobox.getSelectedItem() != null) {
+		    add_button.setEnabled(true);
 		}
 		else {
-		    add.setEnabled(false);
-		    //description.setText("");
-		}
-	    }
-	}
+		    add_button.setEnabled(false);
+		}
+	    }
+	}
+
 	/** Listens for actions apon the 'set default' button in the LanguageManager controls, and if detected calls the <i>setDefault()</i> method of the manager with the language selected for default. */
 	private class SetDefaultListener
 	    implements ActionListener {
-				/** Set the default index to the one currently selected, if any.
-				 * @param event An <strong>ActionEvent</strong>.
-				 * @see org.greenstone.gatherer.cdm.Language
-				 */
+	    /** Set the default index to the one currently selected, if any.
+	     * @param event An <strong>ActionEvent</strong>.
+	     * @see org.greenstone.gatherer.cdm.Language
+	     */
 	    public void actionPerformed(ActionEvent event) {
-		if(!list.isSelectionEmpty()) {
-		    setDefault((Language)list.getSelectedValue());
-		    clear_default.setEnabled(true);
-		    default_language_value.setText(default_language.toString());
-		}
-	    }
-	}
+		if(!language_list.isSelectionEmpty()) {
+		    setDefault((Language)language_list.getSelectedValue());
+		    clear_button.setEnabled(true);
+		    default_language_field.setText(default_language.toString());
+		}
+	    }
+	}
+
 	/** Listens for selections within the list on the LanguageManager controls, and if a change is detected enables, or disables, controls appropriately. */
 	private class ListListener
@@ -610,37 +456,12 @@
 	     */
 	    public void valueChanged(ListSelectionEvent event) {
-		if(list.isSelectionEmpty()) {
-		    remove.setEnabled(false);
-		    set_default.setEnabled(false);
+		if(language_list.isSelectionEmpty()) {
+		    remove_button.setEnabled(false);
+		    set_button.setEnabled(false);
 		}
 		else {
-		    remove.setEnabled(true);
-		    set_default.setEnabled(true);
-		}
-		refreshAppearance();
-	    }
-	}
-	/** Our list cel renderer which renders the default cell just a little different. */
-	private class ListRenderer
-	    extends DefaultListCellRenderer {
-	    /** Method to produce the component used to display an entry in the list.
-	     * @param list The <strong>JList</strong> the component will be placed in.
-	     * @param value A value to be displayed for this component, as a <strong>Object</strong>.
-	     * @param index The position in the list it will occupy as an <i>int</i>.
-	     * @param isSelected Whether the user has currently selected this component, as a <i>boolean</i>.
-	     * @param cellHasFocus Whether the component currently has focus, again as a <i>boolean</i>.
-	     * @return A <strong>Component</strong> to be used as the entry in the list.
-	     * @see org.greenstone.gatherer.cdm.Language
-	     */
-	    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
-		Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
-		Language language = (Language) value;
-		if(language.isDefault() && !isSelected) {
-		    component.setBackground(Gatherer.config.getColor("coloring.workspace_selection_background", false));
-		}
-		if(component instanceof JLabel) {
-		    ((JLabel)component).setOpaque(true);
-		}
-		return component;
+		    remove_button.setEnabled(true);
+		    set_button.setEnabled(true);
+		}
 	    }
 	}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/MetadataSetView.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/MetadataSetView.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/MetadataSetView.java	(revision 4932)
@@ -0,0 +1,329 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+import java.awt.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.cdm.ElementWrapper;
+import org.greenstone.gatherer.msm.MetadataSet;
+import org.greenstone.gatherer.msm.MSMEvent;
+import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.msm.MSMListener;
+import org.w3c.dom.*;
+/** Unlike its namesake in the msm package, this class really only knows how to produce a simple visual representation of the currently imported metadata sets. It is also read-only, so should be fairly straight forward. (It has no namesake any longer as my co-programmers found this far too confusing - however I have now added a CollectionConfiguration to the cdm package just to keep them on their toes!)
+ * @author John Thompson, Greenstone Digital Library, University of Waikato
+ * @version 2.3d
+ * @see org.greenstone.gatherer.msm.MetadataSetManager
+ */
+public class MetadataSetView
+    extends DynamicListModel
+    implements MSMListener {
+    /** The visual contols used to review the metadata sets. */
+    private Control controls = null;
+    /** A reference to ourselves so our inner classes can refer to us. */
+    private DynamicListModel model = null;
+    /** Constructor.
+     */
+    public MetadataSetView() {
+	Gatherer.println("MetadataSetView: Initialized.");
+	model = this;
+	Gatherer.c_man.getCollection().msm.addMSMListener(this);
+	loadMetadataSets();
+	// Build the controls
+	controls = new MetadataSetControl();
+    }
+    /** Destructor. Remove any references of this class from persistant objects.
+		* @see org.greenstone.gatherer.Gatherer
+	  * @see org.greenstone.gatherer.collection.CollectionManager
+	  * @see org.greenstone.gatherer.msm.MetadataSetManager
+	  */
+    public void destroy() {
+	controls.destroy();
+	controls = null;
+	Gatherer.c_man.msm.removeMSMListener(this);
+	model = null;
+    }
+    /** Called when an element is changed within a set in the MSM, prompting us to refresh our list of elements being shown.
+     * @param event A <strong>MSMEvent</strong> which encapsulates relevant data about the change.
+     * @see org.greenstone.gatherer.cdm.MetadataSetManager.Control
+     */
+    public void elementChanged(MSMEvent event) {
+	// Get the controls to refresh element list.
+	((MetadataSetControl)controls).refreshElementList();
+    }
+    /** A method for retrieve the controls for this manager.
+     * @see org.greenstone.gatherer.Dictionary
+     * @see org.greenstone.gatherer.gui.Coloring
+     * @see org.greenstone.gatherer.msm.MetadataSetManager
+     */
+    public Control getControls() {
+	return controls;
+    }
+    /** Called when a metadata value has undergone significant change.
+     * @param event A <strong>MSMEvent</strong> which encapsulates relevant data about the change.
+     */
+    public void metadataChanged(MSMEvent event) {
+	// Couldn't care less.
+    }
+    /** Called when a set is added or removed from the MSM.
+     * @param event A <strong>MSMEvent</strong> which encapsulates relevant data about the change.
+     */
+    public void setChanged(MSMEvent event) {
+	// Reload model.
+	clear();
+	loadMetadataSets();
+    }
+    /** Select the selected element, given its name, and return the bounds of the selection. Used during search and replace.
+     * @param element The elements fully qualified name as a <strong>String</strong>.
+     * @return The bounds of the selection as a <strong>Rectangle</strong>.
+     * @see org.greenstone.gatherer.cdm.MetadataSetManager.Control
+     */
+    public Rectangle setSelectedElement(String element) {
+	return ((MetadataSetControl)controls).setSelectedElement(element);
+    }
+    /** Prints out the contents of this manager, as they would appear in the collection configuration file.
+     * @return A <strong>String</strong> containing a block of commands.
+     * @see org.greenstone.gatherer.cdm.MetadataSetManager.SetWrapper
+     */
+    public String toString() {
+	String text = "";
+	for(int i = 0; i < size(); i++) {
+	    SetWrapper set = (SetWrapper)get(i);
+	    text = text + set.toString() + "\n";
+	}
+	text = text + "\n";
+	return text;
+    }
+    /** Called when a significant change has occured to a value tree for a certain element, however we take no further action.
+     * @param event A <strong>MSMEvent</strong> containing information relevant to the event.
+     */
+    public void valueChanged(MSMEvent event) {
+    }
+    /** Method to retrieve a phrase from the dictionary based on a key.
+     * @param key A <strong>String</strong> used to find the correct phrase.
+     * @param args A <strong>String[]</strong> of arguments used in formatting and filling out the phrase.
+     * @return A <strong>String</strong> containing the correct phrase with the correct formatting.
+     */
+    private String get(String key) {
+	return get(key, null);
+    }
+    /** Method to retrieve a phrase from the dictionary based on a key.
+     * @param key A <strong>String</strong> used to find the correct phrase.
+     * @return A <strong>String</strong> containing the correct phrase with the correct formatting.
+     * @see org.greenstone.gatherer.Dictionary
+     * @see org.greenstone.gatherer.Gatherer
+     */
+    private String get(String key, String args[]) {
+	if(key.indexOf(".") == -1) {
+	    key = "CDM.MetadataSetManager." + key;
+	}
+	return Gatherer.dictionary.get(key, args);
+    }
+    /** Retrieves the current list of loaded metadata sets, and builds a list model around them.
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.cdm.MetadataSetManager.SetWrapper
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     * @see org.greenstone.gatherer.msm.MetadataSet
+     * @see org.greenstone.gatherer.msm.MetadataSetManager
+     */
+    private void loadMetadataSets() {
+	// We initialize the set_model with wrapped metadata sets. Note that when we call getSets() we also end up adding ourselves as a listener to the metadata set manager.
+	Vector sets = Gatherer.c_man.getCollection().msm.getSets();
+	for(int i = 0; i < sets.size(); i++) {
+	    addElement(new SetWrapper((MetadataSet)sets.get(i)));
+	}
+    }
+    /** This class creates and lays-out the various controls for reviewing the metadata sets, and their commands as they would appear in the collection configuration file. */
+    private class MetadataSetControl
+	extends JPanel 
+	implements Control {
+	/** The label denoting the element list. */
+	private JLabel element_label = null;
+	/** The label denoting the set list. */
+	private JLabel set_label = null;
+	/** The title of these controls. */
+	private JLabel title = null;
+	/** The list of elements for the choosen set. */
+	private JList element_list = null;
+	/** The list of sets in this collection. */
+	private JList set_list = null;
+	/** The panel onto which all other panels will be placed. */
+	private JPanel central_pane = null;
+	/** The panel onto which the element list will be placed. */
+	private JPanel element_pane = null;
+	/** The panel containing the title and instructions. */
+	private JPanel header_pane = null;
+	/** The panel containing the set list. */
+	private JPanel set_pane = null;
+	/** The text area of inline instructions. */
+	private JTextArea instructions = null;
+	/** The element model for the currently selected set. */
+	private Vector element_model = null;
+	/* Constructor.
+	 * @see org.greenstone.gatherer.Coloring;
+	 * @see org.greenstone.gatherer.Dictionary
+	 * @see org.greenstone.gatherer.cdm.MetadataSetManager.ListListener
+	 * @see org.greenstone.gatherer.msm.MetadataSetManager
+	 */
+	public MetadataSetControl() {
+				// Create visual components
+	    central_pane = new JPanel();
+	    element_label = new JLabel(get("Elements"));
+	    element_label.setHorizontalAlignment(JLabel.CENTER);
+	    element_label.setOpaque(true);
+	    element_list = new JList();
+	    element_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+	    element_pane = new JPanel();
+	    header_pane = new JPanel();
+	    instructions = new JTextArea(get("Instructions"));
+	    instructions.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    instructions.setEditable(false);
+	    instructions.setLineWrap(true);
+	    instructions.setRows(5);
+	    instructions.setWrapStyleWord(true);
+	    set_label = new JLabel(get("Sets"));
+	    set_label.setHorizontalAlignment(JLabel.CENTER);
+	    set_label.setOpaque(true);
+	    set_list = new JList(model);
+	    set_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+	    set_pane = new JPanel();
+	    title = new JLabel(get("Title"));
+	    title.setHorizontalAlignment(JLabel.CENTER);
+	    title.setOpaque(true);
+				// Add listeners
+	    set_list.addListSelectionListener(new ListListener());
+				// Layout
+	    instructions.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
+	    header_pane.setLayout(new BorderLayout());
+	    header_pane.add(title, BorderLayout.NORTH);
+	    header_pane.add(new JScrollPane(instructions), BorderLayout.CENTER);
+
+	    set_pane.setLayout(new BorderLayout());
+	    set_pane.add(set_label, BorderLayout.NORTH);
+	    set_pane.add(new JScrollPane(set_list), BorderLayout.CENTER);
+
+	    element_pane.setLayout(new BorderLayout());
+	    element_pane.add(element_label, BorderLayout.NORTH);
+	    element_pane.add(new JScrollPane(element_list), BorderLayout.CENTER);
+
+	    central_pane.setLayout(new GridLayout(2,1));
+	    central_pane.add(set_pane);
+	    central_pane.add(element_pane);
+
+	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	    setLayout(new BorderLayout());
+	    add(header_pane, BorderLayout.NORTH);
+	    add(central_pane, BorderLayout.CENTER);
+	}
+	/** Destructor. */
+	public void destroy() {
+	}
+	/** Update the element list. */
+	public void refreshElementList() {
+	    element_list.updateUI();
+	}
+	/** Select the selected element, given its name, and return the bounds of the selection. Used during search and replace.
+		* @param element The elements fully qualified name as a <strong>String</strong>.
+		* @return The bounds of the selection as a <strong>Rectangle</strong>.
+		* @see org.greenstone.gatherer.cdm.MetadataSetManager.Control
+		* @see org.greenstone.gatherer.cdm.MetadataSetManager.Control.ElementWrapper
+		*/
+	public Rectangle setSelectedElement(String element) {
+	    Rectangle bounds = null;
+				// Parse off namespace
+	    String namespace = element.substring(0, element.indexOf("."));
+				// Force the set list to show the correct entry.
+	    ListModel s_model = set_list.getModel();
+	    for(int i = 0; i < s_model.getSize(); i++) {
+		SetWrapper sw = (SetWrapper) s_model.getElementAt(i);
+		if(sw.getSet().getNamespace().equals(namespace)) {
+		    set_list.setSelectedValue(sw, true);
+		}
+	    }
+				// Force the element list to show that name.
+	    ListModel e_model = element_list.getModel();
+	    for(int i = 0; i < e_model.getSize(); i++) {
+		ElementWrapper ew = (ElementWrapper) e_model.getElementAt(i);
+		if(ew.name().equals(element)) {
+		    element_list.setSelectedValue(ew, true);
+		    bounds = element_list.getCellBounds(i, i);
+		}
+	    }
+				// Return the bounds of the selected row.
+	    return bounds;
+	}
+	/** Overriden to ensure the instruction area is scrolled to top.
+		  */
+	public void gainFocus() {
+	    if(instructions != null) {
+		instructions.setCaretPosition(0);
+	    }
+	}
+
+	public void loseFocus() {
+	}
+
+	private class ListListener
+	    implements ListSelectionListener {
+	    public void valueChanged(ListSelectionEvent event) {
+		if(!set_list.isSelectionEmpty()) {
+		    MetadataSet set = ((SetWrapper)set_list.getSelectedValue()).getSet();
+		    NodeList elements = set.getElements();
+		    element_model = new Vector();
+		    for(int i = 0; i < elements.getLength(); i++) {
+			element_model.add(new ElementWrapper(elements.item(i)));
+		    }
+		    Collections.sort(element_model);
+		    element_list.setListData(element_model);
+		}
+	    }
+	}
+    }
+    /** Provides a convience wrapper around a metadata set, that allows it to appear in the <strong>JList</strong> the same way it would in the collection configuration file. */
+    private class SetWrapper {
+	private MetadataSet set = null;
+	public SetWrapper(MetadataSet set) {
+	    this.set = set;
+	}
+	public MetadataSet getSet() {
+	    return set;
+	}
+	public String toString() {
+	    String namespace = set.getNamespace();
+				// Watch out for the greenstone set, with its namespace of ""
+	    if(namespace == null || namespace.length() == 0) {
+		namespace = "greenstone";
+	    }
+	    return "metadataset " + namespace + " \"" + set.getFile().getName() + "\"";
+	}
+    }
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/PlugIn.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/PlugIn.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/PlugIn.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,109 +25,147 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
-/**
- * Title:        The Gatherer<br>
- * Description:  The Gatherer: a tool for gathering and enriching digital collections.<br>
- * Copyright:    Copyright (c) 2001<br>
- * Company:      The University of Waikato<br>
- * First Coded:  01/05/02
- * @author John Thompson, Greenstone Digital Libraries
- * @version 2.1
- */
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
+
+import java.io.*;
+import java.util.*;
+import org.greenstone.gatherer.Gatherer;
 import org.greenstone.gatherer.cdm.Argument;
 import org.greenstone.gatherer.cdm.ArgumentContainer;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.w3c.dom.*;
 /** This class is responsible for storing information from a parsed pluginfo call in such a way that it allows easy access to parsed details for the purposes of user design and specification of plugins. */
-// ####################################################################################
-// Optimization                          Saving
-// ####################################################################################
-// Vector -> ArrayList                   + Memory, + Processor (pos. - Processor)
-// ####################################################################################
 public class PlugIn 
-    extends ArrayList 
-    implements ArgumentContainer, Comparable, Serializable {
-    /** A reference to the plugin that this one inherits from. */
-    private PlugIn super_plugin = null;
-    /** A string of custom arguments to pass to the plugin. */
-    private String custom = null;
-    /** A description of this plugin. */
-    private String desc = null;
-    /** The name of the plugin as it would appear in the collect.cfg file. */
-    private String name = null;
-    /** Default Constructor.
+    extends ArrayList
+    implements ArgumentContainer, Comparable, DOMProxyListEntry, Serializable {
+    /** The DOM Element this assigned PlugIn is modelled on. */
+    private Element element;
+    /** The parent PlugIn this one inherits from, if any. */
+    private PlugIn super_plugin;
+    private String description;
+    private String name;
+
+    /** Constructor used only in DOMProxyListModel initializations.
      */
     public PlugIn() {
+    }
+
+    public PlugIn(Element element, PlugIn base_plugin) {
 	super();
-    }
-    /** Constructor.
-	  * @param name The name of this plugin as a <strong>String</strong>.
-	  * @param desc A description of this plugin as a <strong>String</strong>.
-	  * @param super_plugin The super class of this plugin, as a <strong>PlugIn</strong>.
-	  */
-    public PlugIn(String name, String desc, PlugIn super_plugin) {
+	this.element = element;
+	this.name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	///atherer.println("Establishing Plugin: " + name);
+	// Parse in any argument options for this plugin, keeping a list of the ones found
+	HashMap known_arguments = new HashMap();
+	NodeList option_elements = element.getElementsByTagName(StaticStrings.OPTION_ELEMENT);
+	int option_elements_length = option_elements.getLength();
+	for(int i = 0; i < option_elements_length; i++) {
+	    Element option_element = (Element) option_elements.item(i);
+	    Argument argument = new Argument(option_element);
+	    ///atherer.println("Rebuilding existing argument: " + argument.getName());
+	    argument.setOwner(name);
+	    add(argument);
+	    known_arguments.put(argument.getName(), argument);
+	}
+	// If a base plugin was given
+	if(base_plugin != null) {
+	    // Copy the details, and add a reference to whatever base_plugins super plugin is.
+	    description = base_plugin.getDescription();
+	    // Now search through the 'dummy' arguments belonging to the base plugin. For each found, if it is already assigned, fill out further details such as type. If any are found that are not already assigned for this plugin, copy them and add them, but without a value.
+	    ArrayList all_arguments = base_plugin.getArguments(true, true);
+	    int argument_count = all_arguments.size();
+	    for(int j = 0; j < argument_count; j++) {
+		Argument base_argument = (Argument) all_arguments.get(j);
+		String base_argument_name = base_argument.getName();
+		///atherer.println("Library indicates this plugin should have an argument: " + base_argument_name);
+		Argument existing_argument = (Argument) known_arguments.get(base_argument_name);
+		// Found an existing argument. Complete its details
+		if(existing_argument != null) {
+		    ///atherer.println("Found existing argument. Filling out details.");
+		    existing_argument.setCustomArgument(false);
+		    existing_argument.setDefaultValue(base_argument.getDefaultValue());
+		    existing_argument.setDescription(base_argument.getDescription());
+		    existing_argument.setOptions(base_argument.getOptions());
+		    existing_argument.setRequired(base_argument.isRequired());
+		    existing_argument.setType(base_argument.getType());
+		}
+		// No existing argument. Copy base_argument and add it, but do not set its assigned flag. That should be set the first time its changed by the user.
+		else {
+		    ///atherer.println("No such argument. Adding new, unassigned, argument.");
+		    // The trick thing is that we have to create a new element in the DOM as well.
+		    Argument new_argument = base_argument.copy();
+		    Element argument_element = CollectionDesignManager.collect_config.document.createElement(StaticStrings.OPTION_ELEMENT);
+		    argument_element.setAttribute(StaticStrings.NAME_ATTRIBUTE, base_argument_name);
+		    argument_element.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.FALSE_STR);
+		    argument_element.setAttribute(StaticStrings.CUSTOM_ATTRIBUTE, StaticStrings.FALSE_STR);
+		    new_argument.setElement(argument_element);
+		    // All done. Add it.
+		    element.appendChild(argument_element);
+		    add(new_argument);
+		}
+	    }
+	}
+    }
+
+    /** This constructor is only used for library level Plugins.
+     * @param name
+     * @param description
+     * @param super_plugin
+     */
+    public PlugIn(String name, String description, PlugIn super_plugin) {
 	super();
-	this.desc = desc;
+	this.description = description;
 	this.name = name;
 	this.super_plugin = super_plugin;
     }
-    /** Method to add an argument to this plugin. Only adds the argument if it isn't already present. 
-	  * @param argument The <strong>Argument</strong> to add.
-	  */
+
+    /** Method to add an argument to this base plugin. Only adds the argument if it isn't already present, and only if this is a base plugin (ie not based on DOM). 
+     * @param argument the Argument to add
+     */
     public void addArgument(Argument argument) {
-	if(!contains(argument)) {
+	if(element == null && !contains(argument)) {
 	    add(argument);
 	    argument.setOwner(name);
 	}
     }
+
     /** Method to compare two plugins for ordering.
-	  * @param object The plugin we are comparing to, as an <strong>Object</strong>.
-	  * @return An <i>int</i> specifying the plugin order, using values as set out in <strong>String</strong>.
-	  * @see java.lang.String#compareTo
-	  */
+     * @param object The plugin we are comparing to, as an <strong>Object</strong>.
+     * @return An <i>int</i> specifying the plugin order, using values as set out in <strong>String</strong>.
+     * @see java.lang.String#compareTo
+     */
     public int compareTo(Object object) {
-	if(object != null && object instanceof PlugIn) {
-	    PlugIn plugin = (PlugIn) object;
-	    return name.compareTo(plugin.getName());
+	if(object instanceof PlugIn) {
+	    return name.compareTo(((PlugIn)object).getName());
 	}
 	return -1;
     }
-    /** This method produces a deep copy of this plugin. Note that this also creates a new copy of each of the super classes of plugins as well. This is the way it should be, as each assigned plugin may have different values for the higher plugins (such as BasPlug).
-	  * @return A newly created <strong>PlugIn</strong> with the same details and <strong>Argument</strong>s as this one.
-	  */
-    public PlugIn copy() {
-	PlugIn copy = null;
-	if(super_plugin == null) {
-	    copy = new PlugIn(name, desc, null);
-	}
-	else {
-	    copy = new PlugIn(name, desc, super_plugin.copy());
-	}
-	for(int i = 0; i < size(); i++) {
-	    copy.addArgument(((Argument)get(i)).copy());
-	}
-	return copy;
-    }
+
+    /** The assigned plugin constructor.
+     * @param element the DOM Element this plugin is based upon
+     * @param base_plugin the PlugIn from the stored library showing details about this plugin, may be null
+     */
+    public DOMProxyListEntry create(Element element) {
+	String plugin_name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	// Determine the base plugin from the plugin name
+	PlugIn base_plugin = CollectionDesignManager.plugin_manager.getBasePlugIn(plugin_name);
+	PlugIn plugin = new PlugIn(element, base_plugin);
+	base_plugin = null;
+	plugin_name = null;
+	return plugin;
+    }
+
     /** Method to determine if two plugins are equal.
-	  * @param object The plugin to test against, as an <strong>Object</strong>.
-	  * @return <i>true</i> if the plugin names match, <i>false</i> otherwise.
-	  */
+     * @param object The plugin to test against, as an <strong>Object</strong>.
+     * @return <i>true</i> if the plugin names match, <i>false</i> otherwise.
+     */
     public boolean equals(Object object) {
-	if(object != null && compareTo(object) == 0) {
-	    return true;
-	}
-	return false;
-    }
+	return (compareTo(object) == 0);
+    }
+
     /** Method to retrieve an argument by its name.
-	  * @param name The name of the argument as a <strong>String</strong>.
-	  * @return The <strong>Argument</strong> requested, or <i>null</i> if no such argument.
-	  */
+     * @param name The name of the argument as a <strong>String</strong>.
+     * @return The <strong>Argument</strong> requested, or <i>null</i> if no such argument.
+     */
     public Argument getArgument(String name) {
 	// The name given may still include the '-'
@@ -145,5 +173,5 @@
 	    name = name.substring(1);
 	}
-	ArrayList arguments = getArguments();
+	ArrayList arguments = getArguments(true, true);
 	for(int i = 0; i < arguments.size(); i++) {
 	    Argument argument = (Argument)arguments.get(i);
@@ -154,74 +182,194 @@
 	return null;
     }
-    /** Method to retrieve all of the arguments available to a plugin, including both specific and general ones.
-	  * @return A <strong>Hashtable</strong> of arguments, with <name> -> <argument> entries.
-	  */
-    public ArrayList getArguments() {
-	ArrayList all_arguments = new ArrayList(this);
-	Collections.sort(all_arguments);
+
+    /** Retrieve all of the arguments available to this base plugin, including its super plugins arguments. Some complexity is added by allowing the caller to choose whether they want normal arguments, custom arguments, or both.
+     * @return an ArrayList of all of the arguments, starting with those for this plugin and ending with the arguments for basplug or similiar root plugin
+     */
+    public ArrayList getArguments(boolean include_normal, boolean include_custom) {
+	ArrayList arguments = new ArrayList();
+	if(include_normal && include_custom) {
+	    arguments.addAll(this);
+	}
+	else {
+	    int size = size();
+	    for(int i = 0; i < size; i++) {
+		Argument argument = (Argument) get(i);
+		if(argument.isCustomArgument()) {
+		    if(include_custom && !arguments.contains(argument)) {
+			arguments.add(argument);
+		    }
+		}
+		else {
+		    if(include_normal && !arguments.contains(argument)) {
+			arguments.add(argument);
+		    }
+		}
+		argument = null;
+	    }
+	}
 	if(super_plugin != null) {
-	    ArrayList super_arguments = super_plugin.getArguments();
-	    for(int i = 0; i < super_arguments.size(); i++) {
-		Object argument = super_arguments.get(i);
-		if(!all_arguments.contains(argument)) {
-		    all_arguments.add(argument);
-		}
-	    }
-	}
-	return all_arguments;
-    }
-    /** Method to retrieve a plugins custom argument information.
-	  * @return The custom arguments as a <strong>String</strong>.
-	  */
+	    ArrayList remainder = super_plugin.getArguments(include_normal, include_custom);
+	    remainder.removeAll(arguments);
+	    arguments.addAll(remainder);
+	}
+	return arguments;
+    }
+
+    /** Method to retrieve a plugins custom argument information. Custom arguments are defined to be those that have not got matching arguments in the base reference plugin from the library. Of course if there is no base plugin then all arguments are considered to be custom.
+     * @return the custom arguments as a String
+     */
     public String getCustom() {
-	return custom;
-    }
+	StringBuffer custom_text = new StringBuffer();
+	// Retrieve all of the arguments, and append any that are custom into one long string
+	ArrayList arguments = getArguments(false, true);
+	int arguments_size = arguments.size();
+	boolean first = true;
+	for(int i = 0; i < arguments_size; i++) {
+	    Argument argument = (Argument) arguments.get(i);
+	    if(argument.isAssigned()) {
+		if(!first) {
+		    custom_text.append(" ");
+		}
+		custom_text.append(argument.toString());
+		first = false;
+	    }
+	}
+	return custom_text.toString();
+    }
+
+    public String getDescription() {
+	return description;
+    }
+
+    public Element getElement() {
+	return element;
+    }
+
     /** Method to retrieve a plugins name.
-	  * @return A <strong>String</strong> containing the plugins name.
-	  */
+     * @return A <strong>String</strong> containing the plugins name.
+     */
     public String getName() {
+	if(name == null && element != null) {
+	    name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	}
 	return name;
     }
-    public void setCustom(String custom) {
-	this.custom = custom;
-    }
+
+    public boolean isAssigned() {
+	return (element != null && !element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.FALSE_STR));
+    }
+
+    public boolean isSeparator() {
+	return (element != null && element.getAttribute(StaticStrings.SEPARATOR_ATTRIBUTE).equals(StaticStrings.TRUE_STR));
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    /** Set the custom arguments. This turns out to be quite tricky. We must parse in the string, searching for arguments (for that we use a handy method in CollectionConfiguration). Next, for each argument, we check if we already know about it. If so we update its value, otherwise we create a new argument and assign it (must assign!). 
+     * @param custom_str the custom arguments all splodged together in one String
+     */
+    public void setCustom(String custom_str) {
+	HashMap raw_arguments = CollectionConfiguration.parseArguments(new CommandTokenizer(custom_str));
+	ArrayList custom_arguments = getArguments(false, true);
+	int size = custom_arguments.size();
+	for(int i = 0; i < size; i++) {
+	    Argument argument = (Argument) custom_arguments.get(i);
+	    String original_argument_name = StaticStrings.MINUS_CHARACTER + argument.getName();
+	    if(raw_arguments.containsKey(original_argument_name)) {
+		// Set as assigned
+		argument.setAssigned(true);
+		String argument_value = (String)raw_arguments.remove(original_argument_name);
+		if(argument_value != null) {
+		    argument.setValue(argument_value);
+		    argument_value = null;
+		}
+	    }
+	    // We've removed it from our custom statement, so unassign
+	    else {
+		argument.setAssigned(false);
+	    }
+	    argument = null;
+	}
+	// Any left over, add to the plugin
+	Iterator argument_names = raw_arguments.keySet().iterator();
+	while(argument_names.hasNext()) {
+	    String argument_name = (String) argument_names.next();
+	    String argument_value = (String) raw_arguments.get(argument_name);
+	    // The tricky thing is that we have to create a new element in the DOM as well.
+	    Element argument_element = CollectionDesignManager.collect_config.document.createElement(StaticStrings.OPTION_ELEMENT);
+	    argument_element.setAttribute(StaticStrings.NAME_ATTRIBUTE, argument_name.substring(1));
+	    argument_element.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
+	    argument_element.setAttribute(StaticStrings.CUSTOM_ATTRIBUTE, StaticStrings.TRUE_STR);
+	    Argument argument = new Argument(argument_element);
+	    argument_name = null;
+	    if(argument_value != null) {
+		argument.setValue(argument_value);
+		argument_value = null;
+	    }
+	    // All done. Add it.
+	    element.appendChild(argument_element);
+	    add(argument);
+	    argument_element = null;
+	}
+	raw_arguments = null;
+    }
+
     /** Method to set the value of desc. 
-	  * @param desc The new value of desc as a <strong>String</strong>.
-	  */
-    public void setDesc(String desc) {
-	this.desc = desc;
-    }
+     * @param desc The new value of desc as a <strong>String</strong>.
+     */
+    public void setDescription(String desc) {
+	this.description = description;
+    }
+
+    public void setElement(Element element) {
+	this.element = element;
+    }
+
     /** Method to set the value of name. 
-	  * @param name The new value of name as a <strong>String</strong>.
-	  */
+     * @param name The new value of name as a <strong>String</strong>.
+     */
     public void setName(String name) {
 	this.name = name;
     }
+
     /** Method to set the value of the super_plugin.
-	  * @param super_plugin The new value of super_plugin as a <strong>PlugIn</strong>, or <i>null</i> if this class has no inheritance. 
-	  */
+     * @param super_plugin The new value of super_plugin as a <strong>PlugIn</strong>, or <i>null</i> if this class has no inheritance. 
+     */
     public void setSuper(PlugIn super_plugin) {
 	this.super_plugin = super_plugin;
     }
+
     /** Method to print out this plugin as it would appear as a command within the collection configuration file.
-	  * @return A <strong>String</strong> containing a single plugin command.
-	  */
+     * @return A <strong>String</strong> containing a single plugin command.
+     */
     public String toString() {
-	String text = "plugin " + name + " ";
-	ArrayList arguments = getArguments();
-	for(int i = 0; i < arguments.size(); i++) {
-	    Argument argument = (Argument)arguments.get(i);
-	    if(argument.isAssigned()) {
-		text = text + argument.toString();
-		if(i < arguments.size() - 1) {
-		    text = text + " ";
-		}
-	    }
-	}
-	// Now print custom arguments if any.
-	if(custom != null) {
-	    text = text + " " + custom;
-	} 
-	return text;
+	if(element != null) {
+	    if(name == null) {
+		name = element.getAttribute(StaticStrings.TYPE_ATTRIBUTE);
+	    }
+	    StringBuffer text = new StringBuffer(StaticStrings.PLUGIN_STR);
+	    text.append(" ");
+	    text.append(name);
+	    text.append(" ");
+	    ArrayList arguments = getArguments(true, true);
+	    int arguments_size = arguments.size();
+	    for(int i = 0; i < arguments_size; i++) {
+		Argument argument = (Argument)arguments.get(i);
+		if(argument.isAssigned()) {
+		    text.append(argument.toString());
+		    text.append(" ");
+		}
+		argument = null;
+	    }
+	    return text.substring(0, text.length() - 1);
+	}
+	// Basic Plugin
+	else {
+	    return name;
+	}
     }		  
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/PlugInManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/PlugInManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/PlugInManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,20 +25,6 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
-/**
- * Title:        The Gatherer<br>
- * Description:  The Gatherer: a tool for gathering and enriching digital collections.<br>
- * Copyright:    Copyright (c) 2001<br>
- * Company:      The University of Waikato<br>
- * First Coded:  01/05/02
- * @author John Thompson, Greenstone Digital Libraries
- * @version 2.1
- */
+
 import java.awt.*;
 import java.awt.event.*;
@@ -57,94 +33,97 @@
 import javax.swing.*;
 import javax.swing.event.*;
-import javax.swing.plaf.basic.BasicArrowButton;
-import org.apache.xerces.parsers.DOMParser;
+import javax.swing.plaf.basic.*;
+import org.apache.xerces.parsers.*;
 import org.greenstone.gatherer.Gatherer;
 import org.greenstone.gatherer.cdm.Argument;
 import org.greenstone.gatherer.cdm.ArgumentConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
 import org.greenstone.gatherer.cdm.CommandTokenizer;
+import org.greenstone.gatherer.cdm.Control;
 import org.greenstone.gatherer.cdm.DynamicListModel;
 import org.greenstone.gatherer.cdm.PlugIn;
-import org.greenstone.gatherer.msm.ElementWrapper;
+import org.greenstone.gatherer.gui.GComboBox;
 import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.util.StaticStrings;
 import org.greenstone.gatherer.util.Utility;
 import org.w3c.dom.*;
-import org.xml.sax.InputSource;
+import org.xml.sax.*;
 /** This class is resposible for maintaining a list of known plug-ins, and importing new plugins using the parser. */
-public class PlugInManager {
-    /** A reference to the main manager for this module. */
-    private CollectionDesignManager manager = null;
+public class PlugInManager 
+    extends DOMProxyListModel {
+    /** The library 'reserve' of base plugins. */
+    private ArrayList library = null;
     /** The controls for editing the contents of this manager. */
     private Control controls = null;
-    /** A list of assigned plugins. */
-    private DynamicListModel assigned = null;
-    /** A list of those plugins that have not yet been assigned. Begins as a copy of reserve. */
-    private DynamicListModel available = null;
-    /** A list of known, but currently unassigned, plug-ins. */
-    private DynamicListModel reserve = null;
-    /** A reference to the Gatherer. */
-    private Gatherer gatherer = null;
-    /** The current index of the separator. */
-    private JPanel separator = null;
+    private DOMProxyListModel model;
+    private JPanel separator;
+    private PlugIn separator_plugin; 
     /** The default size for a label. */
     static final private Dimension LABEL_SIZE = new Dimension(140, 20);
     /** Constructor.
      */
-    public PlugInManager(Gatherer gatherer, CollectionDesignManager manager) {
-	this.assigned = new DynamicListModel();
-	this.gatherer = gatherer;
-	this.manager = manager;
-	this.separator = getSeparator();
-	// Add the movement separator
-	assigned.addElement(separator);
+    public PlugInManager() {
+	super(CollectionDesignManager.collect_config.getDocumentElement(), CollectionConfiguration.PLUGIN_ELEMENT, new PlugIn());
+	Gatherer.println("PlugInManager: " + getSize() + " plugins parsed.");
+	model = this;
+	// Reload/Create the library
 	loadPlugIns();
 	savePlugIns();
-    }
-    /** Method to add a new plugin to reserve.
-	  * @param plugin The new <strong>PlugIn</strong>.
-	  */
+	// Create the separator, cause we can reuse it.
+	separator = getSeparator();
+    }
+    /** Method to add a new plugin to the library
+     * @param plugin the new base PlugIn
+     */
     public void addPlugIn(PlugIn plugin) {
-	if(!reserve.contains(plugin)) {
-	    reserve.addElement(plugin);
-	    available.addElement(plugin);
-	}
-    }
-    /** Method to assign a plugin.
-	  * @param plugin The reserve <strong>PlugIn</strong> to assign.
-	  */
+	if(!library.contains(plugin)) {
+	    library.add(plugin);
+	}
+    }
+
+    /** Method to assign a plugin
+     * @param plugin the PlugIn to assign
+     */
     public void assignPlugIn(PlugIn plugin) {
-	if(!assigned.contains(plugin)) {
-	    if(plugin.getName().equals("RecPlug") || plugin.getName().equals("ArcPlug")) {
-		assigned.addElement(plugin); // Adds after separator
-	    }
-	    else {
-		int index = assigned.indexOf(separator);
-		assigned.add(index, plugin);
-	    }
-				// Remove from available
-	    available.removeElement(plugin);
-	    gatherer.c_man.configurationChanged();
-	}
-    }
+	if(plugin.getName().equals(StaticStrings.RECPLUG_STR) || plugin.getName().equals(StaticStrings.ARCPLUG_STR)) {
+	    addAfter(plugin, separator_plugin); // Adds after separator
+	}
+	else {
+	    addBefore(plugin, separator_plugin);
+	}
+	Gatherer.c_man.configurationChanged();
+    }
+
+    /** Destructor. */
+    public void destroy() {
+	if(controls != null) {
+	    controls.destroy();
+	    controls = null;
+	}
+	library.clear();
+	library = null;
+    }
+
     /** Method to retrieve the control for this manager.
-	  * @return A <strong>JPanel</strong> containing the controls.
-	  */
-    public JPanel getControls() {
+     * @return the Control
+     */
+    public Control getControls() {
 	if(controls == null) {
-	    controls = new Control();
+	    // Build controls
+	    controls = new PlugInControl();
 	}
 	return controls;
     }
-    /** Method to retrieve the named plugin.
-	  * @param name The name of the desired plugin as a <strong>String</strong>.
-	  * @return The requested <strong>PlugIn</strong> or <i>null</i> if no such plugin exists.
-	  */
-    public PlugIn getPlugIn(String name) {
-	for(int i = 0; i < reserve.size(); i++) {
-	    Object object = reserve.get(i);
-	    if(object instanceof PlugIn) {
-		PlugIn plugin = (PlugIn) object;
-		if(plugin.getName().equals(name)) {
-		    return plugin;
-		}
+
+    /** Retrieve the base pluging of the given name, or null if no such plugin.
+     * @param name the name of the base plugin to retrieve as a String
+     * @return the PlugIn requested or null if no such plugin
+     */
+    public PlugIn getBasePlugIn(String name) {
+	int library_size = library.size();
+	for(int i = 0; i < library_size; i++) {
+	    PlugIn plugin = (PlugIn) library.get(i);
+	    if(plugin.getName().equals(name)) {
+		return plugin;
 	    }
 	}
@@ -152,16 +131,202 @@
 	return null;
     }
-    /** Method to invalidate controls after a significant change in the system state.
-	  */
-    public void invalidateControls() {
-	if(controls != null) {
-	    controls.destroy();
-	}
-	controls = null;
-    }
+
+    /** Method to move a plugin in the list order.
+     * @param plugin the PlugIn you want to move.
+     * @param direction true to move the plugin up, false to move it down.
+     * @param all true to move to move all the way, false for a single step.
+     */
+    public void movePlugIn(PlugIn plugin, boolean direction, boolean all) {
+	// Can't ever move RecPlug or ArcPlug.
+	if(getSize() < 3) {
+	    Gatherer.println("Not enough plugins to allow moving.");
+	    return;
+	}
+	if(plugin.getName().equals(StaticStrings.ARCPLUG_STR) || plugin.getName().equals(StaticStrings.RECPLUG_STR)) {
+	    JOptionPane.showMessageDialog(Gatherer.g_man, get("CDM.Move.Fixed"), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
+	    return;
+	}
+	if(all) {
+	    // Move to top
+	    if(direction) {
+		// Remove the moving plugin
+		remove(plugin);
+		// Retrieve the first plugin
+		PlugIn first_plugin = (PlugIn) getElementAt(0);
+		// Add the moving plugin before the first plugin
+		addBefore(plugin, first_plugin);
+		first_plugin = null;
+		Gatherer.c_man.configurationChanged();
+	    }
+	    else {
+		// Remove the moving plugin
+		remove(plugin);
+		// Locate the plugin immediately before the separator. We have to ensure the separator index is up to date, given we've just removed a plugin.
+		int separator_index = -1;
+		if((separator_index = findSeparatorIndex()) != -1) {
+		    PlugIn separator_plugin = (PlugIn) getElementAt(separator_index);
+		    // Add the moving plugin before the separator
+		    addBefore(plugin, separator_plugin);
+		    Gatherer.c_man.configurationChanged();	    
+		}
+		// Otherwise we aren't moving anywhere!
+	    }
+	}
+	else {
+	    // Try to move the plugin one step in the desired direction.
+	    int index = indexOf(plugin);
+	    ///ystem.err.println("Index of " + plugin + " = " + index);
+	    if(direction) {
+		index--;
+		if(index < 0) {
+		    String args[] = new String[2];
+		    args[0] = get("CDM.PlugInManager.PlugIn_Str");
+		    args[1] = plugin.getName();
+		    JOptionPane.showMessageDialog(Gatherer.g_man, get("CDM.Move.At_Top", args), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
+		    return;
+		}
+		remove(plugin);
+		add(index, plugin);
+		Gatherer.c_man.configurationChanged(); 
+	    }
+	    else {
+		index++;
+		PlugIn next_plugin = (PlugIn) getElementAt(index);
+		if(next_plugin.isSeparator()) {
+		    String args[] = new String[1];
+		    args[0] = plugin.getName();
+		    JOptionPane.showMessageDialog(Gatherer.g_man, get("CDM.Move.Cannot", args), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
+		    // Still not going to move RecPlug or ArcPlug.
+		    return;
+		}
+		remove(plugin);
+		add(index, plugin);
+		Gatherer.c_man.configurationChanged();
+	    }
+	}
+    }
+
+    /** We attempt to place the separator between the unfixed and the fixed plugins. Since we only know of two fixed plugins, we search for either of them, and place the separator before them.
+     */
+    public void placeSeparator() {
+	///ystem.err.println("Placing separator.");
+	int separator_index = getSize();
+	if(separator_index > 0) {
+	    boolean found_fixed = false;
+	    int index = separator_index - 1;
+	    while(index > 0) {
+		PlugIn plugin = (PlugIn) getElementAt(index);
+		String name = plugin.getName();
+		if(name.equals(StaticStrings.RECPLUG_STR) || name.equals(StaticStrings.ARCPLUG_STR)) {
+		    found_fixed = true;
+		    index--;
+		}
+		else {
+		    if(found_fixed) {
+			separator_index = index + 1;
+			index = -1;
+		    }
+		    else {
+			index--;
+		    }
+		}
+		name = null;
+		plugin = null;
+	    }
+	}
+	Element element = CollectionDesignManager.collect_config.document.createElement(CollectionConfiguration.PLUGIN_ELEMENT);
+	element.setAttribute(CollectionConfiguration.TYPE_ATTRIBUTE, CollectionConfiguration.SEPARATOR_ATTRIBUTE);
+	element.setAttribute(CollectionConfiguration.SEPARATOR_ATTRIBUTE, CollectionConfiguration.TRUE_STR);
+	separator_plugin = new PlugIn(element, null);
+	///atherer.println("Adding plugin separator at: " + separator_index);
+	add(separator_index, separator_plugin);
+    }
+
+    /** This method removes an assigned plugin. I was tempted to call it unassign, but remove is more consistant. Note that there is no way to remove a plugin from the library.
+     * @param plugin The <strong>PlugIn</strong> to remove.
+     */
+    public void removePlugIn(PlugIn plugin) {
+	remove(plugin);
+	Gatherer.c_man.configurationChanged();
+    }
+
+    /** Method to cache the current contents of library (known plugins) to file.
+     */
+    public void savePlugIns() {
+	try {
+	    FileOutputStream file = new FileOutputStream(Utility.BASE_DIR + "plugins.dat");
+	    ObjectOutputStream out = new ObjectOutputStream(file);
+	    out.writeObject(library);
+	    out.close();
+	}
+	catch (Exception error) {
+	    Gatherer.printStackTrace(error);
+	}
+    }
+
+    /** Determine the current separator index. */
+    private int findSeparatorIndex() {
+	int separator_index = getSize() - 1;
+	while(separator_index >= 0) {
+	    PlugIn search = (PlugIn) getElementAt(separator_index);
+	    if(search.isSeparator()) {
+		return separator_index;
+	    }
+	    separator_index--;
+	}
+	return separator_index;
+    }
+
+    /* Retrieve a phrase from the dictionary based on a certain key.
+     * @param key The search <strong>String</strong>.
+     * @return The matching phrase from the Dictionary.
+     */
+    private String get(String key) {
+	return get(key, (String[])null);
+    }
+    
+    private String get(String key, String arg) {
+	String args[] = new String[1];
+	args[0] = arg;
+	return get(key, args);
+    }
+
+    /* Retrieve a phrase from the dictionary based on a certain key and arguments.
+     * @param key The search <strong>String</strong>.
+     * @param args A <strong>String[]</strong> of arguments used to complete and format the choosen phrase.
+     * @return The matching phrase from the Dictionary.
+     */
+    private String get(String key, String args[]) {
+	if(key.indexOf(".") == -1) {
+	    key = "CDM.PlugInManager." + key;
+	}
+	return Gatherer.dictionary.get(key, args);
+    }	 
+
+    /** Retrieve a list of those plugins that are in library but not in the assigned plugins. */
+    private Object[] getAvailable() {
+	ArrayList available = new ArrayList();
+	available.addAll(library);
+	// Now go through the assigned plugins, and remove any that match.
+	available.removeAll(children());
+	return available.toArray();
+    }
+
+    /** Method to extract just the plugins name from a file object.
+     * @param plugin The <strong>File</strong> which references a certain plugin.
+     * @return A <strong>String</strong> containing just the plugins name, without extension.
+     */
+    private String getPlugInName(File plugin) {
+	String name = plugin.getName();
+	if(name.indexOf(".") != -1) {
+	    name = name.substring(0, name.indexOf("."));
+	}
+	return name;
+    }
+
     /** Method to load the details of a single plug-in.
-	  * @param plugin The plugin <strong>File</strong> you wish to load.
-	  */
-    public void loadPlugIn(File plugin) {
+     * @param plugin The plugin <strong>File</strong> you wish to load.
+     */
+    private void loadPlugIn(File plugin) {
 	Document document = null;
 	// Run pluginfo on this plugin, and then send the results for parsing.
@@ -170,11 +335,11 @@
 	    if(Utility.isWindows()) {
 		args = new String[4];
-		if(gatherer.config.perl_path != null) {
-		    args[0] = gatherer.config.perl_path;
+		if(Gatherer.config.perl_path != null) {
+		    args[0] = Gatherer.config.perl_path;
 		}
 		else {
 		    args[0] = "Perl.exe";
 		}
-		args[1] = gatherer.config.gsdl_path + "bin" + File.separator + "script" + File.separator + "pluginfo.pl";
+		args[1] = Gatherer.config.gsdl_path + "bin" + File.separator + "script" + File.separator + "pluginfo.pl";
 		args[2] = "-xml";
 		args[3] = getPlugInName(plugin);
@@ -229,280 +394,8 @@
 	}
 	if(document != null) {
-	    parse(document.getDocumentElement());
-	}
-    }
-    /** Method to move a plugin higher in the list order.
-	  * @param plugin The <strong>PlugIn</strong> you want to move.
-	  * @param direction <i>true</i> to move the plugin up, <i>false</i> to move it down.
-	  * @param all <i>true</i> to move to move all the way, <i>false</i> for a single step.
-	  */
-    public void movePlugIn(PlugIn plugin, boolean direction, boolean all) {
-	// Can't ever move RecPlug or ArcPlug.
-	if(plugin.getName().equals("ArcPlug") || plugin.getName().equals("RecPlug")) {
-	    JOptionPane.showMessageDialog(manager.gui, get("CDM.Move.Fixed"), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
-	    return;
-	}
-	if(all) {
-	    if(direction) {
-		assigned.removeElement(plugin);
-		assigned.add(0, plugin);
-		gatherer.c_man.configurationChanged();
-	    }
-	    else {
-		assigned.removeElement(plugin);
-		int index = assigned.size() - 1;
-		boolean found = false;
-		while(!found) {
-		    Object temp = assigned.get(index);
-		    if(temp instanceof PlugIn) {
-			PlugIn current = (PlugIn) temp;
-			if(current.getName().equals("ArcPlug") || current.getName().equals("RecPlug")) {
-			    index--;
-			}
-			else {
-			    found = true;
-			}
-		    }
-		    else {
-			found = true;
-		    }
-		}
-		assigned.add(index, plugin);
-		gatherer.c_man.configurationChanged();
-	    }
-	}
-	else {
-				// Try to move the plugin one step in the desired direction.
-	    int index = assigned.indexOf(plugin);
-				///ystem.err.println("Index of " + plugin + " = " + index);
-	    if(direction) {
-		index--;
-		if(index < 0) {
-		    String args[] = new String[1];
-		    args[0] = plugin.getName();
-		    JOptionPane.showMessageDialog(manager.gui, get("CDM.Move.At_Top", args), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
-		    return;
-		}
-		assigned.removeElement(plugin);
-		assigned.add(index, plugin);
-		gatherer.c_man.configurationChanged(); 
-	    }
-	    else {
-		index++;
-		Object object = assigned.get(index);
-		if(index == assigned.size()) {
-		    String args[] = new String[1];
-		    args[0] = plugin.getName();
-		    JOptionPane.showMessageDialog(manager.gui, get("CDM.Move.At_Bottom", args), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
-		    // Still not going to move RecPlug or ArcPlug.
-		    return;
-		}
-		else if(!(object instanceof PlugIn) || ((PlugIn)object).getName().equals("ArcPlug") || ((PlugIn)object).getName().equals("RecPlug")) {
-		    String args[] = new String[1];
-		    args[0] = plugin.getName();
-		    JOptionPane.showMessageDialog(manager.gui, get("CDM.Move.Cannot", args), get("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE);
-		    // Still not going to move RecPlug or ArcPlug.
-		    return;
-		}
-		assigned.removeElement(plugin);
-		assigned.add(index, plugin);
-		gatherer.c_man.configurationChanged();
-	    }
-	}
-    }
-
-    /** This method attempts to parse a plugin command from a command string taken from the collection configuration file. This process is quite complex as not only must the correct plugin be matched by also all of the parameters given must be legal. If such a command is found, the plugin is immediately assigned.
-	  * @param command The coomand <strong>String</strong> that may include plugin information.
-	  * @return <i>true</i> if a plugin command was parsed, <i>false</i> otherwise.
-	  */
-    public boolean parse(String command) {
-	String command_lc = command.toLowerCase();
-	if(command_lc.startsWith("plugin")) {
-	    CommandTokenizer tokenizer = new CommandTokenizer(command);
-	    if(tokenizer.countTokens() >= 2) {
-		tokenizer.nextToken(); // Throw away 'plugin'
-		String name = tokenizer.nextToken();
-		// Try to locate the plugin with this name.
-		PlugIn plugin = getPlugIn(name);
-		// And if successful start to parse the arguments.
-		if(plugin != null) {
-		    // Take a copy.
-		    plugin = plugin.copy();
-		    String key = null;
-		    while(tokenizer.hasMoreTokens()) {
-			if(key == null) {
-			    key = tokenizer.nextToken();
-			}
-			// Try to retrieve a matching argument.
-			Argument argument = plugin.getArgument(key);
-			if(argument != null) {
-			    // Set as assigned.
-			    argument.setAssigned(true);
-			    // And if the argument is of a parameter type, parse a parameter.
-			    if(argument.getType() != Argument.FLAG && tokenizer.hasMoreTokens()) {
-				String value = tokenizer.nextToken();
-				ElementWrapper element = null;
-				if(argument.getType() == Argument.METADATA) {
-				    value = value.replace(':', MSMUtils.NS_SEP);
-				    if (value.indexOf(MSMUtils.NS_SEP)==-1){
-					value = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + value;
-				    }
-				    // Now retrieve the element this refers to, if available.
-				    element = Gatherer.c_man.getCollection().msm.getElement(value);
-				}
-				if(element != null) {
-				    argument.setElementValue(element);
-				    element = null;
-				}
-				else {
-				    argument.setValue(value);
-				}
-			    }
-			    key = null;
-			}
-			// Argument cannot be matched.
-			else {
-			    String cur_key = key;
-			    String value = tokenizer.nextToken();
-			    if(value.startsWith("-")) {
-				key = value;
-				value = null;
-			    }
-			    else {
-				key = null;
-			    }
-			    String custom = plugin.getCustom();
-			    if(custom == null) {
-				if(value == null) {
-				    plugin.setCustom(cur_key);
-				}
-				else {
-				    plugin.setCustom(cur_key + " " + value);
-				}
-			    }
-			    else {
-				if(value == null) {
-				    plugin.setCustom(custom + " " + cur_key);
-				}
-				else {
-				    plugin.setCustom(custom + " " + cur_key + " " + value);
-				}
-			    }
-			}
-		    }
-		    // Slight tweak. If the plugin is the RecPlug we want to set use_metadata_files by default.
-		    if(plugin.getName().equalsIgnoreCase("RecPlug")) {
-			Argument argument = plugin.getArgument("-use_metadata_files");
-			if(argument != null) {
-			    argument.setAssigned(true);
-			}
-		    }
-		    // Second tweak. If the plugin is the HTMLPlug we want to modify the block expression so our backup files are ignored.
-		    if(plugin.getName().equalsIgnoreCase("HTMLPlug")) {
-			Argument argument = plugin.getArgument("-block_exp");
-			if(argument != null) {
-			    argument.setValue(argument.getDefaultValue() + "|(~$)");
-			}
-		    }
-		    // Add the plugin to our reserve
-		    assignPlugIn(plugin);
-		    return true;
-		}
-		else {
-		    //ystem.err.println("Unknown plugin");
-		}
-	    }
-	}
-	return false;
-    }
-    /** This method removes an assigned plugin. I was tempted to call it unassign, by remove is more consistant. Note that there is no way to remove a plugin from the reserve.
-	  * @param plugin The <strong>PlugIn</strong> to remove.
-	  */
-    public void removePlugIn(PlugIn plugin) {
-	assigned.removeElement(plugin);
-	available.addElement(plugin);
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method to cache the current contents of reserve (known plugins) to file.
-	  */
-    public void savePlugIns() {
-	try {
-	    FileOutputStream file = new FileOutputStream(Utility.BASE_DIR + "plugins.dat");
-	    ObjectOutputStream out = new ObjectOutputStream(file);
-	    out.writeObject(reserve);
-	    out.close();
-	}
-	catch (Exception error) {
-	}
-    }
-    /** Method used to determine the number of plugins that have been assigned.
-	  * @return An <i>int</i> which is the number of plugins.
-	  */
-    public int size() {
-	return assigned.size();
-    }
-    /** Method to print out a block of plugin commands, much like you'd find in a collection configuration file.
-	  * @return A <strong>String</strong> containing a series of plugin commands separated by new lines.
-	  */
-    public String toString() {
-	String text = "";
-	for(int i = 0; i < assigned.size(); i++) {
-	    Object object = assigned.get(i);
-	    if(object instanceof PlugIn) {
-		PlugIn plugin = (PlugIn) object;
-		text = text + plugin.toString() + "\n";
-	    }
-	}
-	text = text + "\n";
-	return text;
-    }
-    /* Retrieve a phrase from the dictionary based on a certain key.
-	  * @param key The search <strong>String</strong>.
-	  * @return The matching phrase from the Dictionary.
-	  */
-    private String get(String key) {
-	return get(key, (String[])null);
-    }
-
-    private String get(String key, String arg) {
-	String args[] = new String[1];
-	args[0] = arg;
-	return get(key, args);
-    }
-
-    /* Retrieve a phrase from the dictionary based on a certain key and arguments.
-	  * @param key The search <strong>String</strong>.
-	  * @param args A <strong>String[]</strong> of arguments used to complete and format the choosen phrase.
-	  * @return The matching phrase from the Dictionary.
-	  */
-    private String get(String key, String args[]) {
-	if(key.indexOf(".") == -1) {
-	    key = "CDM.PlugInManager." + key;
-	}
-	return gatherer.dictionary.get(key, args);
-    }	 
-    /** Method to extract just the plugins name from a file object.
-	  * @param plugin The <strong>File</strong> which references a certain plugin.
-	  * @return A <strong>String</strong> containing just the plugins name, without extension.
-	  */
-    private String getPlugInName(File plugin) {
-	String name = plugin.getName();
-	if(name.indexOf(".") != -1) {
-	    name = name.substring(0, name.indexOf("."));
-	}
-	return name;
-    }
-    /** Method to retrieve the CData value from a node. Requires a search for the #text node.
-	  * @param node The <strong>Node</strong> whose value we wish to find.
-	  * @return The value of node as a <strong>String</strong>, or <i>null</i> if no value exists.
-	  */
-    private String getValue(Node node) {
-	if(node.hasChildNodes()) {
-	    Node text = node.getFirstChild();
-				//ystem.err.println("Value of " + node.getNodeName() + " = " + text.getNodeValue());
-	    return text.getNodeValue();
-	}
-	return node.getNodeValue();
-    }
+	    parseXML(document.getDocumentElement());
+	}
+    }
+
     /** Method to initially load information from the standard plug-ins within the gsdl Perl library.
 	  */
@@ -512,15 +405,12 @@
 	    FileInputStream file = new FileInputStream(Utility.BASE_DIR + "plugins.dat");
 	    ObjectInputStream input = new ObjectInputStream(file);
-	    reserve = (DynamicListModel) input.readObject();
-	    available = reserve.shallowCopy();
+	    library = (ArrayList) input.readObject();
 	}
 	catch (Exception error) {
 	}
-	if(reserve == null) {
-	    available = new DynamicListModel();
-	    reserve = new DynamicListModel();
-	    reserve.setAutoOrder(true);
-				// Retrieve the gsdl home directory...
-	    String directory = gatherer.config.gsdl_path;
+	if(library == null) {
+	    library = new ArrayList();
+	    // Retrieve the gsdl home directory...
+	    String directory = Gatherer.config.gsdl_path;
 	    directory = directory + "perllib" + File.separator + "plugins" + File.separator;
 	    loadPlugIns(new File(directory));
@@ -533,5 +423,5 @@
 	File files[] = directory.listFiles();
 	if(files != null) {
-				// Create a progress indicator.
+	    // Create a progress indicator.
 	    ParsingProgress progress = new ParsingProgress(get("CDM.PlugInManager.Parsing.Title"), get("CDM.PlugInManager.Parsing.Message"), files.length);
 	    for(int i = 0; i < files.length; i++) {
@@ -546,5 +436,5 @@
     }
 
-    private PlugIn parse(Node root) {
+    private PlugIn parseXML(Node root) {
 	PlugIn plugin = new PlugIn();
 	String node_name = null;
@@ -553,7 +443,7 @@
 	    node_name = node.getNodeName();
 	    if(node_name.equals("Name")) {
-		String name = getValue(node);
+		String name = MSMUtils.getValue(node);
 		// We can save ourselves some processing time if a plugin with this name already exists in our manager. If so retrieve it and return it.
-		PlugIn existing = getPlugIn(name);
+		PlugIn existing = getBasePlugIn(name);
 		if(existing != null) {
 		    return existing;
@@ -562,5 +452,5 @@
 	    }
 	    else if(node_name.equals("Desc")) {
-		plugin.setDesc(getValue(node));
+		plugin.setDescription(MSMUtils.getValue(node));
 	    }
 				// Parse the multitude of arguments.
@@ -575,14 +465,14 @@
 			    node_name = det.getNodeName();
 			    if(node_name.equals("Name")) {
-				argument.setName(getValue(det));
+				argument.setName(MSMUtils.getValue(det));
 			    }
 			    else if(node_name.equals("Desc")) {
-				argument.setDesc(getValue(det));
+				argument.setDescription(MSMUtils.getValue(det));
 			    }
 			    else if(node_name.equals("Type")) {
-				argument.setType(getValue(det));
+				argument.setType(MSMUtils.getValue(det));
 			    }
 			    else if(node_name.equals("Default")) {
-				argument.setDefault(getValue(det));
+				argument.setDefaultValue(MSMUtils.getValue(det));
 			    }
 			    else if(node_name.equals("List")) {
@@ -595,8 +485,8 @@
 					    node_name = subvalue.getNodeName();
 					    if(node_name.equals("Name")) {
-						key = getValue(subvalue);
+						key = MSMUtils.getValue(subvalue);
 					    }
 					    else if(node_name.equals("Desc")) {
-						desc = getValue(subvalue);
+						desc = MSMUtils.getValue(subvalue);
 					    }
 					}
@@ -608,5 +498,5 @@
 			    }
 			    else if(node_name.equals("Required")) {
-				String v = getValue(det);
+				String v = MSMUtils.getValue(det);
 				if(v != null && v.equals("yes")) {
 				    argument.setRequired(true);
@@ -618,5 +508,5 @@
 		    // A super plugin class.
 		    else if(node_name.equals("PlugInfo")) {
-			PlugIn super_plugin = parse(arg);
+			PlugIn super_plugin = parseXML(arg);
 			plugin.setSuper(super_plugin);
 		    }
@@ -632,6 +522,7 @@
 
     /** A class which provodes controls for assigned and editing plugins. */
-    private class Control
-	extends JPanel {
+    private class PlugInControl
+	extends JPanel 
+	implements Control {
 	/** Button for adding plugins. */
 	private JButton add = null;
@@ -649,5 +540,5 @@
 	private JButton remove = null;
 	/** A combobox containing all of the known plugins, including those that may have already been assigned. */
-	private JComboBox plugin = null;
+	private GComboBox plugin = null;
 	/** The label next to the plugin combobox. */
 	private JLabel plugin_label = null;
@@ -674,6 +565,6 @@
 	/** Constructor.
 	 */
-	public Control() {
-	    Object plugins[] = reserve.toArray();
+	public PlugInControl() {
+	    Object plugins[] = library.toArray();
 	    Vector plugin_model = new Vector();
 	    for(int i = 0; i < plugins.length; i++) {
@@ -681,5 +572,5 @@
 	    }
 	    Collections.sort(plugin_model);
-				// Create
+	    // Create
 	    add = new JButton(get("Add"));
 	    add.setMnemonic(KeyEvent.VK_A);
@@ -697,5 +588,5 @@
 	    instructions.setWrapStyleWord(true);
 	    move_bottom = new JButton();
-	    JLabel move_bottom_label = new JLabel(get("Move_Bottom"));
+	    JLabel move_bottom_label = new JLabel(get("CDM.Move.Move_Bottom"));
 	    move_bottom_label.setHorizontalAlignment(JLabel.CENTER);
 	    move_bottom_label.setPreferredSize(LABEL_SIZE);
@@ -706,5 +597,5 @@
 	    move_bottom.setMnemonic(KeyEvent.VK_B);
 	    move_down = new JButton();
-	    JLabel move_down_label = new JLabel(get("Move_Down"));
+	    JLabel move_down_label = new JLabel(get("CDM.Move.Move_Down"));
 	    move_down_label.setHorizontalAlignment(JLabel.CENTER);
 	    move_down_label.setPreferredSize(LABEL_SIZE);
@@ -715,5 +606,5 @@
 	    move_down.setMnemonic(KeyEvent.VK_D);
 	    move_top = new JButton();
-	    JLabel move_top_label = new JLabel(get("Move_Top"));
+	    JLabel move_top_label = new JLabel(get("CDM.Move.Move_Top"));
 	    move_top_label.setHorizontalAlignment(JLabel.CENTER);
 	    move_top_label.setPreferredSize(LABEL_SIZE);
@@ -724,5 +615,5 @@
 	    move_top.setMnemonic(KeyEvent.VK_T);
 	    move_up = new JButton();
-	    JLabel move_up_label = new JLabel(get("Move_Up"));
+	    JLabel move_up_label = new JLabel(get("CDM.Move.Move_Up"));
 	    move_up_label.setHorizontalAlignment(JLabel.CENTER);
 	    move_up_label.setPreferredSize(LABEL_SIZE);
@@ -733,9 +624,15 @@
 	    move_up.setMnemonic(KeyEvent.VK_U);
 	    movement_pane = new JPanel();
-	    plugin = new JComboBox(available);
-	    //plugin.setEditable(true);
+
+	    plugin = new GComboBox(getAvailable());
+	    plugin.setBackgroundNonSelectionColor(Gatherer.config.getColor("coloring.editable", false));
+	    plugin.setBackgroundSelectionColor(Gatherer.config.getColor("coloring.collection_selection_background", false));
+	    plugin.setEditable(true);
 	    plugin.setSelectedIndex(0);
+	    plugin.setTextNonSelectionColor(Gatherer.config.getColor("coloring.workspace_tree_foreground", false));
+	    plugin.setTextSelectionColor(Gatherer.config.getColor("coloring.collection_selection_foreground", false));
+
 	    plugin_label = new JLabel(get("PlugIn"));
-	    plugin_list = new JList(assigned);
+	    plugin_list = new JList(model);
 	    plugin_list.setCellRenderer(new ListRenderer());
 	    plugin_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -822,13 +719,8 @@
 	public void destroy() {
 	}
-	/** This method is overridden in some control classes to allow for the updating of collection configuration when the focus is lost to another view. In this case however the updates are asynchronous and so no 'has the configuration changed without the user updating' check is needed.
-			* @return <i>true</i> if this control has focus, <i>false</i> otherwise.
-			*/
-	public boolean hasFocus() {
-	    return super.hasFocus();
-	}
+
 	/** This method is overridden to ensure the instructions are scrolled to top, before the super classes updateUI() is called.
-			*/
-	public void updateUI() {
+	 */
+	public void gainFocus() {
 	    if(instructions != null) {
 		instructions.setCaretPosition(0);
@@ -836,93 +728,95 @@
 	    super.updateUI();
 	}
-	/** This class listens for actions upon the add button in the controls, and if detected calls the <i>assignPlugIn()</i> method.
-			*/
+
+	public void loseFocus() {
+
+	}
+
+	/** This class listens for actions upon the add button in the controls, and if detected calls the <i>assignPlugIn()</i> method. */
 	private class AddListener
 	    implements ActionListener {
-				/** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
-				 * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
-				 */
+	    /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
+	     * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
+	     */
 	    public void actionPerformed(ActionEvent event) {
-		PlugIn new_plugin = null;
-		Object object = plugin.getSelectedItem();
-		if(object instanceof PlugIn) {
-		    PlugIn target = (PlugIn)object;
-		    new_plugin = target.copy();
-		}
-		else {
-		    new_plugin = new PlugIn(object.toString(), "", null);
-		}
-		object = null;
-		// Automatically chain to configuration. This ensures required arguments are filled out.
-		ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, new_plugin);
-		if(ac.display()) {
-		    assignPlugIn(new_plugin);
-		    plugin_list.setSelectedValue(new_plugin, true);
-		}
-		ac = null;
-		new_plugin = null;
-		plugin.setSelectedIndex(0);
-	    }
-	}
-	/** This class listens for actions upon the configure button in the controls, and if detected creates a new <strong>ArgumentConfiguration</strong> dialog box to allow for configuration.
-			* @see org.greenstone.gatherer.cdm.ArgumentConfiguration
-			*/
-	private class ConfigureListener
-	    implements ActionListener {
-				/** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
-				 * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
-				 */
-	    public void actionPerformed(ActionEvent event) {
-		if(!plugin_list.isSelectionEmpty()) {
-		    Object object = plugin_list.getSelectedValue();
-		    if(object instanceof PlugIn) {
-			ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, (PlugIn)object);
+		Object selected_object = plugin.getSelectedItem();
+		// If there is something in the combobox, but we haven't registered a selection, then add the object and select it!
+		if(selected_object != null && plugin.getSelectedIndex() == -1) {
+		    plugin.insertItemAt(selected_object, plugin.getItemCount());
+		}
+		if(selected_object != null) {
+		    // Create a new element in the DOM
+		    Element element = CollectionDesignManager.collect_config.document.createElement(CollectionConfiguration.PLUGIN_ELEMENT);
+		    // Remember that the plugin supplied might be a custom string rather than a base plugin
+		    PlugIn new_plugin = null;
+		    if(selected_object instanceof PlugIn) {
+			PlugIn base_plugin = (PlugIn) selected_object;
+			element.setAttribute(CollectionConfiguration.TYPE_ATTRIBUTE, base_plugin.getName());
+			new_plugin = new PlugIn(element, base_plugin);
+			base_plugin = null;
+		    }
+		    else {
+			element.setAttribute(CollectionConfiguration.TYPE_ATTRIBUTE, selected_object.toString());
+			new_plugin = new PlugIn(element, null);
+		    }
+		    if(!model.contains(new_plugin)) {
+			// Automatically chain to configuration. This ensures required arguments are filled out.
+			ArgumentConfiguration ac = new ArgumentConfiguration(new_plugin);
 			if(ac.display()) {
-			    assigned.refresh();
+			    assignPlugIn(new_plugin);
+			    plugin_list.setSelectedValue(new_plugin, true);
 			}
-		    }
-		}
-	    }
-	}
-	/** A special list renderer which is able to render separating lines as well. */
-	private class ListRenderer
-	    extends DefaultListCellRenderer {
-				/** Return a component that has been configured to display the specified value. That component's paint method is then called to "render" the cell. If it is necessary to compute the dimensions of a list because the list cells do not have a fixed size, this method is called to generate a component on which getPreferredSize  can be invoked.
-				 * @param list - The <strong>JList</strong> we're painting.
-				 * @param value - The value returned by list.getModel().getElementAt(index) as an <strong>Object</strong>.
-				 * @param index - The cells index as an <i>int</i>.
-				 * @param isSelected - <i>true</i> if the specified cell was selected.
-				 * @param cellHasFocus - <i>true</i> if the specified cell has the focus.
-				 * @return A <strong>Component</strong> whose paint() method will render the specified value.
-				 * @see javax.swing.JList
-				 * @see javax.swing.JSeparator
-				 * @see javax.swing.ListModel
-				 * @see javax.swing.ListSelectionModel
-				 */
-	    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
-		if(value instanceof JPanel) {
-		    return (JPanel) value;
-		}
-		else {
-		    return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
-		}
-	    }
-	}
+			ac = null;
+			new_plugin = null;
+			plugin.setSelectedIndex(0);
+		    }
+		    else {
+			JOptionPane.showMessageDialog(Gatherer.g_man, get("PlugIn_Exists"), get("General.Error"), JOptionPane.ERROR_MESSAGE);
+		    }
+		}
+	    }
+	}
+
 	/** Listens for double clicks apon the list and react as if the configure button was pushed. */
 	private class ClickListener
 	    extends MouseAdapter {
-				/** Called whenever the mouse is clicked over a registered component, we use this to chain through to the configure prompt. 
-				 * @param event A <strong>MouseEvent</strong> containing information about the mouse click.
-				 */
+	    /** Called whenever the mouse is clicked over a registered component, we use this to chain through to the configure prompt. 
+	     * @param event A <strong>MouseEvent</strong> containing information about the mouse click.
+	    */
 	    public void mouseClicked(MouseEvent event) {
 		if(event.getClickCount() == 2 ) {
 		    if(!plugin_list.isSelectionEmpty()) {
-			Object object = plugin_list.getSelectedValue();
-			if(object instanceof PlugIn) {
-			    ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, (PlugIn)object);
+			PlugIn plugin = (PlugIn) plugin_list.getSelectedValue();
+			if(!plugin.isSeparator()) {
+			    ArgumentConfiguration ac = new ArgumentConfiguration(plugin);
 			    if(ac.display()) {
-				assigned.refresh();
+				refresh(plugin);
 			    }
+			    ac.destroy();
+			    ac = null;
 			}
+		    }
+		}
+	    }
+	}
+
+	/** This class listens for actions upon the configure button in the controls, and if detected creates a new <strong>ArgumentConfiguration</strong> dialog box to allow for configuration.
+	 * @see org.greenstone.gatherer.cdm.ArgumentConfiguration
+	 */
+	private class ConfigureListener
+	    implements ActionListener {
+	    /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
+	     * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
+	     */
+	    public void actionPerformed(ActionEvent event) {
+		if(!plugin_list.isSelectionEmpty()) {
+		    PlugIn plugin = (PlugIn) plugin_list.getSelectedValue();
+		    if(!plugin.isSeparator()) {
+			ArgumentConfiguration ac = new ArgumentConfiguration(plugin);
+			if(ac.display()) {
+			    refresh(plugin);
+			}
+			ac.destroy();
+			ac = null;
 		    }
 		}
@@ -946,4 +840,31 @@
 	    }
 	}
+
+	/** A special list renderer which is able to render separating lines as well. */
+	private class ListRenderer
+	    extends DefaultListCellRenderer {
+	    /** Return a component that has been configured to display the specified value. That component's paint method is then called to "render" the cell. If it is necessary to compute the dimensions of a list because the list cells do not have a fixed size, this method is called to generate a component on which getPreferredSize  can be invoked.
+	     * @param list - The <strong>JList</strong> we're painting.
+	     * @param value - The value returned by list.getModel().getElementAt(index) as an <strong>Object</strong>.
+	     * @param index - The cells index as an <i>int</i>.
+	     * @param isSelected - <i>true</i> if the specified cell was selected.
+	     * @param cellHasFocus - <i>true</i> if the specified cell has the focus.
+	     * @return A <strong>Component</strong> whose paint() method will render the specified value.
+	     * @see javax.swing.JList
+	     * @see javax.swing.JSeparator
+	     * @see javax.swing.ListModel
+	     * @see javax.swing.ListSelectionModel
+	     */
+	    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+		PlugIn plugin = (PlugIn) value;
+		if(plugin.isSeparator()) {
+		    return separator;
+		}
+		else {
+		    return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+		}
+	    }
+	}
+
 
 	/** Listens for actions apon the move buttons in the manager controls, and if detected calls the <i>movePlugIn()</i> method of the manager with the appropriate details. */
@@ -999,6 +920,6 @@
     }
     /** Creates a list separator.
-	  * Code courtesy of Farwell, Paul. Contact <a href="mailto:PFarwell@Kronos.com">PFarwell@Kronos.com</a>
-	  */
+     * Code courtesy of Farwell, Paul. Contact <a href="mailto:PFarwell@Kronos.com">PFarwell@Kronos.com</a>
+     */
     static private JPanel getSeparator() {
 	// we put the separator inside a panel to control
@@ -1014,6 +935,2 @@
     }
 }
-
-
-
-
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/Subcollection.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/Subcollection.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/Subcollection.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,209 +25,233 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
  * Written:      03/05/02
  * Revised:      17/11/02 - Commenting
+ *               04/07/03 - Completely rewritten to support DOM
  **************************************************************************************/
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.greenstone.gatherer.msm.ElementWrapper;
 import org.greenstone.gatherer.msm.MSMUtils;
-import org.w3c.dom.Element;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Troolean;
+import org.w3c.dom.*;
 /** This class encapsulates one subcollection entry in the collection configuration file.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
- * @version 2.3
+ * @version 2.4
  */
 public class Subcollection
-    implements Comparable {
+    implements Comparable, DOMProxyListEntry {
     /** A <i>boolean</i> which is <i>true</i> if the condition is an include one, <i>false</i> otherwise. */
-    private boolean include = true;
-    /** Either the fully qualified name of the metadata whose value should be matched against the given expression, or <i>null</i> if you wish to match against the file name. */
-    private String element = null;
-    /** A String containing a Perl expression which is used as the filter for this subcollection. */
-    private String exp = null;
+    private Troolean include = new Troolean();
+    /** The DOM Element this Subcollection is based upon. */
+    private Element element = null;
+    /** A String containing a Perl expression which is used as the pattern by which to filter this subcollection. */
+    private String pattern = null;
     /** A series of flags to be used when matching the expression. */
     private String flags = null;
     /** A String which is a unique identifier of a subcollection. */
     private String name = null;
-    /** Constructor.
-     * @param name A <strong>String</strong> which is a unique identifier of a subcollection.
-     * @param pattern A <strong>String</strong> containing the inclusion, the element to filter, a Perl expression which is used as the filter for this subcollection, and a series of argument flags.
-     */
-    public Subcollection(String name, String pattern) {
+    /** Either the fully qualified name of the metadata whose value should be matched against the given expression, or <i>null</i> if you wish to match against the file name. */
+    private String source = null;
+    private String text = null;
+
+    /** Default constructor which should only be used during DOMProxyListModel initialization. */
+    public Subcollection() {
+    }
+
+    /** Constructor for representing an existing assigned Subcollection.
+     * @param element the Element this subcollection is based upon
+     */
+    public Subcollection(Element element) {
+	this.element = element;
+    }
+
+    /** Constructor for assigning a brand new Subcollection.
+     * @param name a unique identifier for this collection as a String
+     * @param include true if this the pattern should be an inclusion filter, false for exclusion
+     * @param source either the fully qualified name of an element as a String, or null to filter filenames
+     * @param pattern the matching pattern as a String
+     * @param flags any flags to use while matching, as a String
+     */
+    public Subcollection(String name, boolean include, String source, String pattern, String flags) {
+	// Cache the details
+	this.flags = flags;
+	this.include.set(include);
 	this.name = name;
-	// Have to do some work on pattern.
-	// Remove quote marks.
-	if(pattern.startsWith("\"") || pattern.startsWith("'")) {
-	    pattern = pattern.substring(1, pattern.length() - 1);
-	}
-	// Test for exclusion
-	if(pattern.startsWith("!")) {
-	    this.include = false;
-	    pattern = pattern.substring(1, pattern.length());
+	this.pattern = pattern;
+	if(source != null) {
+	    this.source = source;
 	}
 	else {
-	    this.include = true;
-	}
-	if(pattern.indexOf("/") != -1) {
-				// Now element (which may be Filename)
-	    this.element = pattern.substring(0, pattern.indexOf("/"));
-	    if(this.element.toLowerCase().equals("filename")) {
-		this.element = null;
+	    this.source = StaticStrings.FILENAME_STR;
+	}
+	// Create a new DOM Element with the appropriate attributes and text value
+	element = CollectionDesignManager.collect_config.document.createElement(StaticStrings.SUBCOLLECTION_ELEMENT);
+	element.setAttribute(StaticStrings.CONTENT_ATTRIBUTE, source);
+	element.setAttribute(StaticStrings.NAME_ATTRIBUTE, name);
+	element.setAttribute(StaticStrings.OPTIONS_ATTRIBUTE, flags);
+	element.setAttribute(StaticStrings.TYPE_ATTRIBUTE, (include ? StaticStrings.INCLUDE_STR : StaticStrings.EXCLUDE_STR));
+	MSMUtils.setValue(element, pattern);
+    }
+
+    /** Method to compare two subcollections.
+     * @param object he other subcollection to compare to, as an Object
+     * @return an int which is &gt;0, 0, or &lt;0 if this subcollection is before, equal to, or after the target object respectively.
+     */
+    public int compareTo(Object object) {
+	return getName().compareTo(((Subcollection)object).getName());
+    }
+
+    public DOMProxyListEntry create(Element element) {
+	return new Subcollection(element);
+    }
+
+    /** Method to check two subcollections for equality.
+     * @param object the other subcollection to compare to, as an Object
+     * @return true if the subcollections are equal, false otherwise.
+     */
+    public boolean equals(Object object) {
+	return (compareTo(object) == 0);
+    }
+
+    /** Retrieve the DOM Element this Subcollection is based upon.
+     * @return an Element
+     */
+    public Element getElement() {
+	return element;
+    }
+
+    /** Method to get the value of flags.
+     * @return the value of flags as a String
+     */
+    public String getFlags() {
+	if(flags == null && element != null) {
+	    flags = element.getAttribute(StaticStrings.OPTIONS_ATTRIBUTE);
+	}
+	return flags;
+    }
+
+    /** Method to get the value of name.
+     * @param String The value of name as a <strong>String</string>.
+     */
+    public String getName() {
+	if(name == null && element != null) {
+	    name = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
+	}
+	return name;
+    }
+
+    public String getPattern() {
+	if(pattern == null && element != null) {
+	    pattern = MSMUtils.getValue(element);
+	}
+	return pattern;
+    }
+
+    /** Method to get the name of the source of the strings used in pattern matching.
+     * @return a String which is either the fully qualified name of a metadata element, or filename
+     */
+    public String getSource() {
+	if(source == null &&element != null) {
+	    source = element.getAttribute(StaticStrings.CONTENT_ATTRIBUTE);
+	    // If this is a MSM Element then retrieve the appropriate ElementWrapper and use the language specific name
+	    if(!source.equals(StaticStrings.FILENAME_STR)) {
+		ElementWrapper element_wrapper = Gatherer.c_man.getCollection().msm.getElement(source);
+		if(element_wrapper != null) {
+		    source = element_wrapper.toString();
+		}
 	    }
-				// Extract Perl expression
-	    if(pattern.indexOf("/") != pattern.lastIndexOf("/")) {
-		this.exp = pattern.substring(pattern.indexOf("/") + 1,
-					     pattern.lastIndexOf("/"));
-		this.flags = pattern.substring(pattern.lastIndexOf("/") + 1);
-	    }
-	    else {
-		this.exp = pattern.substring(pattern.indexOf("/") + 1);
-	    }
-	}
-	else {
-				// Hmmm. Well I don't know what this is but it isn't a proper subcollection definition. Can't really 'return null' or something. So I'll just not set include, element, exp and flag expressions.
-	}
-    }
-    /** Constructor.
-	  * @param name A <strong>String</strong> which is a unique identifier of a subcollection.
-	  * @param include A <i>boolean</i> which is <i>true</i> if the condition is an include one, <i>false</i> otherwise.
-	  * @param exp A <strong>String</strong> containing a Perl expression which is used as the filter for this subcollection.
-	  */
-    public Subcollection(String name, boolean include, String exp, String flags) {
-	this.element = null;
-	this.exp = exp;
-	this.flags = flags;
-	this.include = include;
-	this.name = name;
-    }
-    /** Constructor.
-	  * @param name A <strong>String</strong> which is a unique identifier of a subcollection.
-	  * @param include A <i>boolean</i> which is <i>true</i> if the condition is an include one, <i>false</i> otherwise.
-	  * @param element Either the fully qualified name of the metadata whose value should be matched against the given expression, or <i>null</i> if you wish to match against the file name.
-	  * @param exp A <strong>String</strong> containing a Perl expression which is used as the filter for this subcollection.
-	  * @param flags A <strong>String</strong> of flags to be taken into account when matching the expression.
-	  */
-    public Subcollection(String name, boolean include, String element, String exp, String flags) {
+	}
+	return source;
+    }
+
+    public boolean isAssigned() {
+	return (element != null && !element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.FALSE_STR));
+    }
+
+    /** Method to get the value of include.
+     * @param boolean true if this is an inclusion filter, false otherwise
+     */
+    public boolean isInclusive() {
+	if(!include.isDecided() && element != null) {
+	    include.set(element.getAttribute(StaticStrings.TYPE_ATTRIBUTE).equals(StaticStrings.INCLUDE_STR));
+	}
+	return include.isTrue();
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    public void setElement(Element element) {
 	this.element = element;
-	this.exp = exp;
-	this.flags = flags;
-	this.include = include;
-	this.name = name;
-    }
-    /** Method to compare two subcollections.
-	  * @param object The other subcollection to compare to, as an <strong>Object</strong>.
-	  * @return An <i>int</i> which is greater than, equal to or less than zero if the this object is before, equal to, or after the target object respectively.
-	  */
-    public int compareTo(Object object) {
-	Subcollection sub = (Subcollection) object;
-	return getName().compareTo(sub.getName());
-    }
-    /** Method to check two subcollections for equality.
-	  * @param object The other subcollection to compare to, as an <strong>Object</strong>.
-	  * @return A <i>boolean</i> which is <i>true</i> if the subcollections are equal, <i>false</i> otherwise.
-	  */
-    public boolean equals(Object object) {
-	if(compareTo(object) == 0) {
-	    return true;
-	}
-	return false;
-    }
-    /** Method to get the value of element.
-	  * @return The value of element as an <strong>String</strong>.
-	  */
-    public String getElement() {
-	return element;
-    }
-    /** Method to get the value of expression.
-	  * @return The value of exp as a <strong>String</strong>.
-	  */
-    public String getExpression() {
-	return exp;
-    }
-    /** Method to get the value of flags.
-	  * @return The value of flags as a <strong>String</strong>.
-	  */
-    public String getFlags() {
-	return flags;
-    }
-    /** Method to get the value of include.
-	  * @param boolean The value of include as a <i>boolean</i>.
-	  */
-    public boolean getInclude() {
-	return include;
-    }
-    /** Method to get the value of name.
-	  * @param String The value of name as a <strong>String</string>.
-	  */
-    public String getName() {
-	return name;
-    }
-    /** Method to get the name of the source of the strings used in pattern matching.
-	  * @return A <strong>String</strong> which is either the fully qualified name of a metadata element, or "Filename".
-	  */
-    public String getSource() {
-	if(element != null) {
-	    return element;
-	}
-	return "Filename";
-    }
+	include.reset();
+	flags = null;
+	name = null;
+	pattern = null;
+	source = null;
+	text = null;
+    }
+
+    public void setFlags(String new_flags) {
+	if(element != null) {
+	    // Change element
+	    element.setAttribute(StaticStrings.OPTIONS_ATTRIBUTE, new_flags);
+	    flags = new_flags;
+	    text = null;
+	}
+    }
+
+    public void setInclusive(boolean new_include) {
+	if(element != null) {
+	    // Change element
+	    element.setAttribute(StaticStrings.TYPE_ATTRIBUTE, (new_include ? StaticStrings.INCLUDE_STR : StaticStrings.EXCLUDE_STR));
+	    include.set(new_include);
+	    text = null;
+	}
+    }
+
+    public void setName(String new_name) {
+	if(element != null) {
+	    // Change element
+	    element.setAttribute(StaticStrings.NAME_ATTRIBUTE, new_name);
+	    name = new_name;
+	    text = null;
+	}
+    }
+
+    public void setPattern(String new_pattern) {
+	if(element != null) {
+	    // Change element
+	    MSMUtils.setValue(element, new_pattern);
+	    pattern = new_pattern;
+	    text = null;
+	}
+    }
+
+    public void setSource(String new_source) {
+	if(element != null) {
+	    // Change element
+	    element.setAttribute(StaticStrings.CONTENT_ATTRIBUTE, new_source);
+	    source = new_source;
+	    text = null;
+	}
+    }
+
     /** Method to display the contents of this class as it would appear in the collection configuration file.
-	  * @return A <strong>String</strong> which could be used as a subcollection entry in collect.cfg.
-	  */
+     * @return a String representing this subcollection
+     */
     public String toString() {
-	String text = "subcollection        ";
-	text = text + name + " ";
-	text = text + "\"";
-	if(!include) {
-	    text = text + "!";
-	}
-	if(element != null) {
-	    text = text + element;
-	}
-	else {
-	    text = text + "Filename";
-	}
-	text = text + "/";
-	text = text + exp;
-	text = text + "/";
-	if(flags != null) {
-	    text = text + flags;
-	}
-	text = text + "\"\n";
+	if(text == null && element != null) {
+	    text = CollectionConfiguration.toString(element, true);
+	}
 	return text;
     }
-
-    /** Update certain fields of this subcollection.
-	  * @param name A <strong>String</strong> which is a unique identifier of a subcollection.
-	  * @param source Either the fully qualified name of the metadata whose value should be matched against the given expression, or <i>null</i> if you wish to match against the file name.
-	  * @param exp A <strong>String</strong> containing a Perl expression which is used as the filter for this subcollection.
-	  * @param include A <i>boolean</i> which is <i>true</i> if the condition is an include one, <i>false</i> otherwise.
-	  * @param flags A <strong>String</strong> of flags to be taken into account when matching the expression.
-		*/
-    public void update(String name, String source, String exp, boolean include, String flags) {
-	this.name = name;
-	this.element = source;
-	this.exp = exp;
-	this.include = include;
-	this.flags = flags;
-    }
-
-    /** Method to update the name of a metadata element, if it changes.
-	  * @param old_name The current name of the element as a <strong>String</strong>.
-	  * @param new_name The new name of the element as a <strong>String</strong>.
-	  */
-    public void updateElement(String old_name, String new_name) {
-	if(element.equals(old_name)) {
-	    element = new_name;
-	}
-    }
 }
 
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionIndex.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionIndex.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionIndex.java	(revision 4932)
@@ -0,0 +1,195 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+/**************************************************************************************
+ * Written:     04/07/03
+ * Revised:     
+ **************************************************************************************/
+import java.util.*;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.CollectionMeta;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.greenstone.gatherer.msm.MSMUtils;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
+/** This class encapsulates a single indexing pair.
+ * @author John Thompson, Greenstone Digital Library, University of Waikato
+ * @version 2.4
+ */
+public class SubcollectionIndex
+    implements Comparable, DOMProxyListEntry {
+
+    private ArrayList sources = null;
+    /** The element this index is based upon. */
+    private Element element = null;
+    /** The unique, if cryptic, identifier of an index. */
+    private String id = null;
+
+    /** Default constructor, which should only be used during DOMProxyListModel creation. */
+    public SubcollectionIndex() {
+    }
+
+    /** Constructor. */
+    public SubcollectionIndex(Element element) {
+	this.element = element;
+    }
+
+    public SubcollectionIndex(Object[] raw_sources) {
+	this.sources = new ArrayList();
+	// Create a new element
+	Document document = CollectionDesignManager.collect_config.document;
+	element = document.createElement(CollectionConfiguration.INDEX_ELEMENT);
+	// For each source add a content element
+	int size = raw_sources.length;
+	for(int i = 0; i < size; i++) {
+	    Subcollection subcollection = (Subcollection) raw_sources[i];
+	    String subcollection_name = subcollection.getName();
+	    sources.add(subcollection_name);
+	    Element content_element = document.createElement(CollectionConfiguration.CONTENT_ELEMENT);
+	    content_element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, subcollection_name);
+	    element.appendChild(content_element);
+	    content_element = null;
+	}
+	document = null;
+    }
+
+    /** Method to compare two indexes.
+     * @param object The other index as an <strong>Object</strong>.
+     * @return An <i>int</i> which indicates how the indexes compare.
+     * @see java.lang.String
+     */
+    public int compareTo(Object object) {
+	return id.compareTo(((SubcollectionIndex)object).getID());
+    }
+    
+    public DOMProxyListEntry create(Element element) {
+	return new SubcollectionIndex(element);
+    }
+
+    /** Method to test for the equality of two indexes.
+     * @param object The other index as an <strong>Object</strong>.
+     * @return A <i>boolean</i> which is <i>true</i> if the two indexes are equal, <i>false</i> otherwise.
+     */
+    public boolean equals(Object object) {
+	return (compareTo(object) == 0);
+    }
+    
+    public Element getElement() {
+	return element;
+    }
+
+    public String getID() {
+	if(id == null) {
+	    StringBuffer id_buffer = new StringBuffer();
+	    // Write data information. Retrieve each of the content sources and add them in a comma separated list.
+	    ArrayList sources = getSources();
+	    int sources_size = sources.size();
+	    for(int i = 0; i < sources_size; i++) {
+		id_buffer.append((String)sources.get(i));
+		id_buffer.append(StaticStrings.COMMA_CHARACTER);
+	    }
+	    sources = null;
+	    id = id_buffer.substring(0, id_buffer.length() - 1);
+	}
+	return id;
+    }
+
+    /** Retrieve the sources of this index.
+     * @return the sources as an ArrayList
+     */
+    public ArrayList getSources() {
+	if(sources == null) {
+	    sources = new ArrayList();
+	    NodeList content_elements = element.getElementsByTagName(CollectionConfiguration.CONTENT_ELEMENT);
+	    int content_elements_length = content_elements.getLength();
+	    for(int i = 0; i < content_elements_length; i++) {
+		Element content_element = (Element) content_elements.item(i);
+		sources.add(content_element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE));
+	    }
+	    content_elements = null;
+	    Collections.sort(sources);
+	}
+	return sources;
+    }
+
+    public boolean isAssigned() {
+	return (element != null && !element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.FALSE_STR));
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    public void setElement(Element element) {
+	this.element = element;
+	this.id = null;
+	this.sources = null;
+    }
+
+    /** Method to set the sources for this index which can only be used for the default index.
+     * @param sources an ArrayList of source names
+     */
+    public void setSources(ArrayList sources) {
+	if(element != null) {
+	    // Erase old sources
+	    MSMUtils.clear(element);
+	    // For each entry in the sources array add a new content element.
+	    int size = sources.size();
+	    for(int i = 0; i < size; i++) {
+		Element content_element = element.getOwnerDocument().createElement(CollectionConfiguration.CONTENT_ELEMENT);
+		content_element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, (String) sources.get(i));
+		element.appendChild(content_element);
+		content_element = null;
+	    }
+	    this.id = null; // Regenerate ID.
+	    this.sources = sources;
+	}
+	else {
+	    Gatherer.println("Error! Called setSource() of index other than the default.");
+	}
+    }
+
+    /** Method to turn this object into a string representation ready to be placed in the collection configuration file.
+     * @return A <strong>String</strong> containing the information of this class.
+     */
+    public String toString() {
+	String id = getID();
+	StringBuffer text = new StringBuffer(id);
+	CollectionMeta metadatum = CollectionDesignManager.collectionmeta_manager.getMetadatum(StaticStrings.STOP_CHARACTER + id, false);
+	if(metadatum != null) {
+	    text.append(" \"");
+	    text.append(metadatum.getValue());
+	    text.append("\"");
+	}
+	return text.toString();
+    }
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionIndexManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionIndexManager.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionIndexManager.java	(revision 4932)
@@ -0,0 +1,425 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
+import org.greenstone.gatherer.cdm.Subcollection;
+import org.greenstone.gatherer.cdm.SubcollectionIndex;
+import org.greenstone.gatherer.msm.ElementWrapper;
+import org.greenstone.gatherer.util.ExclusiveListSelectionListener;
+import org.greenstone.gatherer.util.StaticStrings;
+import org.w3c.dom.*;
+/** This class maintains a list of indexes partitions for the purpose of defining subcollections.
+ * @author John Thompson, Greenstone Digital Library, University of Waikato
+ * @version 2.4
+ */
+public class SubcollectionIndexManager
+    extends DOMProxyListModel {
+
+    private Control controls;
+    private DOMProxyListModel model;
+    private SubcollectionIndex default_index;
+
+    /** Constructor. */
+    public SubcollectionIndexManager(Element subindexes) {
+	super(subindexes, CollectionConfiguration.INDEX_ELEMENT, new SubcollectionIndex());
+	Gatherer.println("SubcollectionIndexManager: " + getSize() + " subcollection indexes parsed.");
+	model = this;
+	// Parse and retrieve the default index
+	NodeList default_index_elements = CollectionDesignManager.collect_config.getDocumentElement().getElementsByTagName(CollectionConfiguration.SUBCOLLECTION_DEFAULT_INDEX_ELEMENT);
+	if(default_index_elements.getLength() > 0) {
+	    default_index = new SubcollectionIndex((Element)default_index_elements.item(0));
+	}
+    }
+
+    /** Method to add a subindex.
+     * @param subindex a SubcollectionIndex
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
+    public void addSubcollectionIndex(SubcollectionIndex subindex) {
+	if(!contains(subindex)) {
+	    add(getSize(), subindex);
+	    Gatherer.c_man.configurationChanged();
+	}
+    }
+
+    public void destroy() {
+	if(controls != null) {
+	    controls.destroy();
+	    controls = null;
+	}
+	default_index = null;
+	model = null;
+    }
+
+    public Control getControls() {
+	if(controls == null) {
+	    controls = new SubcollectionIndexControl();
+	}
+	return controls;
+    }
+
+    /** Method to retrieve the default index.
+     * @return the default Index, or null if no such index assigned
+     */
+    public SubcollectionIndex getDefaultSubcollectionIndex() {
+	if(default_index != null && default_index.isAssigned()) {
+	    return default_index;
+	}
+	else {
+	    return null;
+	}
+    }
+
+    /** Retrieve a certain subindex given its name.
+     * @param id the String identifier of a subcollectionindex
+     * @return the SubcollectionIndex requested or null if no such subindex
+     */
+    public SubcollectionIndex getSubcollectionIndex(String id) {
+	int size = getSize();
+	for(int i = 0; i < size; i++) {
+	    SubcollectionIndex subindex = (SubcollectionIndex) getElementAt(i);
+	    if(subindex.getID().equals(id)) {
+		return subindex;
+	    }
+	}
+	return null;
+    }
+
+    /** Method to get all of the subindexes set.
+     * @return an ArrayList containing all the defined indexes
+     */
+    public ArrayList getSubcollectionIndexes() {
+	return children();
+    }
+
+    /** Method to remove a certain subindex.
+     * @param subindex the Index you wish to remove
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
+    public void removeSubcollectionIndex(SubcollectionIndex subindex) {
+	if(subindex != null) {
+	    // Remove any current metadata from this index
+	    CollectionDesignManager.collectionmeta_manager.removeMetadata(StaticStrings.STOP_CHARACTER + subindex.getID());
+	    // Check if the index removed happens to be the default index
+	    if(default_index != null && default_index.equals(subindex)) {
+		default_index.setAssigned(false);
+	    }
+	    // Remove the index
+	    remove(subindex);
+	    Gatherer.c_man.configurationChanged();
+	}
+    }
+    /** Method to remove all of the subindexes that contain a certain subcollection.
+     * @param sub the Subcollection that has been removed
+     * @see org.greenstone.gatherer.cdm.Subcollection
+     * @see org.greenstone.gatherer.cdm.SubcollectionIndex
+     */
+    public void removeSubcollectionIndexes(Subcollection subcollection) {
+	String subcollection_name = subcollection.getName();
+	int size = getSize();
+	for(int i = size - 1; i >= 0; i--) {
+	    SubcollectionIndex subindex = (SubcollectionIndex)getElementAt(i);
+	    if(subindex.getSources().contains(subcollection_name)) {
+		removeSubcollectionIndex(subindex);
+	    }
+	    subindex = null;
+	}
+	subcollection_name = null;
+    }
+
+    /** Method to set the default subcollection index.
+	  * @param subcollection The <strong>Subcollection</strong> to use as the default index.
+	  * @see org.greenstone.gatherer.Gatherer
+	  * @see org.greenstone.gatherer.collection.CollectionManager
+	  */
+    public void setDefaultSubcollectionIndex(SubcollectionIndex index) {
+	if(index != null) {
+	    if(default_index == null) {
+		// Create the default index element, and place immediately after indexes element.
+		Element default_index_element = root.getOwnerDocument().createElement(CollectionConfiguration.SUBCOLLECTION_DEFAULT_INDEX_ELEMENT);
+		default_index = new SubcollectionIndex(default_index_element);
+		Node target_node = CollectionConfiguration.findInsertionPoint(default_index_element);
+		if(target_node != null) {
+		    root.getOwnerDocument().getDocumentElement().insertBefore(default_index_element, target_node);
+		}
+		else {
+		    root.getOwnerDocument().getDocumentElement().appendChild(default_index_element);
+		}
+	    }
+	    default_index.setAssigned(true);
+	    default_index.setSources(index.getSources());
+	}
+	else {
+	    if(default_index != null) {
+		default_index.setAssigned(false);
+	    }
+	}
+	Gatherer.c_man.configurationChanged();
+    }
+
+    /** Method to retrieve a phrase from the dictionary based on a key.
+     * @param key A <strong>String</strong> used to find the correct phrase.
+     * @param args A <strong>String[]</strong> of arguments used in formatting and filling out the phrase.
+     * @return A <strong>String</strong> containing the correct phrase with the correct formatting.
+     */
+    private String get(String key) {
+	if(key.indexOf(".") == -1) {
+	    key = "CDM.SubcollectionIndexManager." + key;
+	}
+	return Gatherer.dictionary.get(key);
+    }
+
+    private class SubcollectionIndexControl
+	extends JPanel
+	implements Control {
+
+	private JButton add_index_button;
+	private JButton clear_default_button;
+	private JButton remove_index_button;
+	private JButton set_default_button;
+	private JList subcollection_list;
+	private JList subcollectionindexes_list;
+	private JTextField default_value_field;
+	private JTextField subcollectionindex_name_field;
+
+	public SubcollectionIndexControl() {
+	    super();
+	    // Creation
+	    JPanel subindex_pane = new JPanel();
+	    JPanel subcollection_panel = new JPanel();
+	    JPanel subindex_name_panel = new JPanel();
+	    JLabel subindex_name_label = new JLabel(get("PartitionName"));
+	    subcollectionindex_name_field = new JTextField();
+
+	    add_index_button = new JButton(get("Add_Subindex"));
+	    add_index_button.setMnemonic(KeyEvent.VK_A);
+	    add_index_button.setEnabled(false);
+
+	    JPanel button_pane = new JPanel();
+
+	    clear_default_button = new JButton(get("Clear_Default_Subindex"));
+	    clear_default_button.setMnemonic(KeyEvent.VK_C);
+	    if(default_index == null) {
+		clear_default_button.setEnabled(false);
+	    }
+
+	    JLabel default_label = new JLabel(get("Default_Subindex"));
+	    JPanel default_pane = new JPanel();
+	    if(default_index == null) {
+		default_value_field = new JTextField();
+	    }
+	    else {
+		default_value_field = new JTextField(default_index.toString());
+	    }
+	    default_value_field.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    default_value_field.setEditable(false);
+	    remove_index_button = new JButton(get("Remove_Subindex"));
+	    remove_index_button.setMnemonic(KeyEvent.VK_R);
+	    set_default_button = new JButton(get("Set_Default_Subindex"));
+	    set_default_button.setMnemonic(KeyEvent.VK_S);
+	    JLabel subcollection_label = new JLabel(get("Subcollection"));
+	    subcollection_list = new JList(CollectionDesignManager.subcollection_manager);
+	    JPanel list_pane = new JPanel();
+	    JLabel subindexes_label = new JLabel(get("Subindexes"));
+	    subcollectionindexes_list = new JList(model);
+	    subcollectionindexes_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+	    JPanel subindexes_pane = new JPanel();
+
+	    // Add listeners
+	    ExclusiveListSelectionListener ell = new ExclusiveListSelectionListener();
+	    ell.add(subcollection_list);
+	    ell.add(subcollectionindexes_list);
+	    add_index_button.addActionListener(new AddSubIndexListener());
+	    clear_default_button.addActionListener(new ClearDefaultSubIndexListener());
+	    remove_index_button.addActionListener(new RemoveSubIndexListener());
+	    set_default_button.addActionListener(new SetDefaultSubIndexListener());
+
+	    subcollectionindex_name_field.getDocument().addDocumentListener(new SubcollectionIndexListener());
+	    subcollection_list.addListSelectionListener(new SubcollectionIndexListener());
+
+	    // Layout
+	    subindex_name_panel.setLayout(new BorderLayout());
+	    subindex_name_panel.add(subindex_name_label, BorderLayout.WEST);
+	    subindex_name_panel.add(subcollectionindex_name_field, BorderLayout.CENTER);
+
+	    list_pane.setLayout(new BorderLayout());
+	    list_pane.add(subcollection_label, BorderLayout.NORTH);
+	    list_pane.add(new JScrollPane(subcollection_list), BorderLayout.CENTER);
+	    
+	    subcollection_panel.setLayout(new BorderLayout());
+	    subcollection_panel.add(subindex_name_panel, BorderLayout.NORTH);
+	    subcollection_panel.add(list_pane, BorderLayout.CENTER);
+
+	    default_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
+
+	    default_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5,5,5,5), BorderFactory.createRaisedBevelBorder()), BorderFactory.createEmptyBorder(2,2,2,2)));
+	    default_pane.setLayout(new BorderLayout());
+	    default_pane.add(default_label, BorderLayout.WEST);
+	    default_pane.add(default_value_field, BorderLayout.CENTER);
+
+	    subindexes_pane.setLayout(new BorderLayout());
+	    subindexes_pane.add(subindexes_label, BorderLayout.NORTH);
+	    subindexes_pane.add(new JScrollPane(subcollectionindexes_list), BorderLayout.CENTER);
+	    subindexes_pane.add(default_pane, BorderLayout.SOUTH);
+	    
+	    subindex_pane.setLayout(new GridLayout(1,2,5,0));
+	    subindex_pane.add(subcollection_panel);
+	    subindex_pane.add(subindexes_pane);
+
+	    button_pane.setLayout(new GridLayout(2,2));
+	    button_pane.add(add_index_button);
+	    button_pane.add(remove_index_button);
+	    button_pane.add(clear_default_button);
+	    button_pane.add(set_default_button);
+	    
+	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	    setLayout(new BorderLayout());
+	    add(subindex_pane, BorderLayout.CENTER);
+	    add(button_pane, BorderLayout.SOUTH);
+	}
+
+	public void destroy() {
+	}
+
+	public void gainFocus() {
+	}
+
+	public void loseFocus() {
+	}
+
+	/** Listens for actions apon the 'add subindex' button in the SubcollectionManager controls, and if detected calls the addSubindex method of the manager with a newly created subindex. */
+	private class AddSubIndexListener
+	    implements ActionListener {
+	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to check if they have a series of subcollections selected, and if so build an new subindex based apon them.
+	     * @param event An <strong>ActionEvent</strong> containing information about the event.
+	     * @see org.greenstone.gatherer.cdm.SubIndex
+	     */
+	    public void actionPerformed(ActionEvent event) {
+		if(!subcollection_list.isSelectionEmpty() && subcollectionindex_name_field.getText().length() > 0) {
+		    SubcollectionIndex subindex = new SubcollectionIndex(subcollection_list.getSelectedValues());
+		    addSubcollectionIndex(subindex);
+		    // Add the subindexes name.
+		    CollectionMeta metadatum = new CollectionMeta("." + subindex.getID());
+		    metadatum.setValue(subcollectionindex_name_field.getText());
+		    CollectionDesignManager.collectionmeta_manager.addMetadatum(metadatum);
+		}
+	    }
+	}
+
+	/** Listens for actions apon the 'clear default subindex' button in the SubcollectionManager controls, and if detected calls the setDefaultSubcollectionIndex() method of the manager with <i>null</i>. */
+	private class ClearDefaultSubIndexListener
+	    implements ActionListener {
+	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to clear the default subindex.
+	     * @param event An <strong>ActionEvent</strong> containing information about the event.
+	     */
+	    public void actionPerformed(ActionEvent event) {
+		setDefaultSubcollectionIndex(null);
+		clear_default_button.setEnabled(false);
+		default_value_field.setText("");
+	    }
+	}
+
+	/** Listens for actions apon the 'remove subindex' button in the SubcollectionManager controls, and if detected calls the removeSubcollectionIndex method of the manager with the SubcollectionIndex selected for removal. */
+	private class RemoveSubIndexListener
+	    implements ActionListener {
+	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to check if they have a subindex selected, and if so remove it.
+	     * @param event An <strong>ActionEvent</strong> containing information about the event.
+	     * @see org.greenstone.gatherer.cdm.SubIndex
+	     */
+	    public void actionPerformed(ActionEvent event) {
+		if(!subcollectionindexes_list.isSelectionEmpty()) {
+		    removeSubcollectionIndex((SubcollectionIndex)subcollectionindexes_list.getSelectedValue());
+		}
+	    }
+	}
+	/** Listens for actions apon the 'set default subindex' button in the SubcollectionManager controls, and if detected calls the setDefaultSubcollectionIndex method of the manager with the SubIndex selected for default. */
+	private class SetDefaultSubIndexListener
+	    implements ActionListener {
+	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to check if they have a subindex selected, and if so set it as default.
+	     * @param event An <strong>ActionEvent</strong> containing information about the event.
+	     * @see org.greenstone.gatherer.cdm.DefaultSubIndex
+	     * @see org.greenstone.gatherer.cdm.SubIndex
+	     */
+	    public void actionPerformed(ActionEvent event) {
+		if(!subcollectionindexes_list.isSelectionEmpty()) {
+		    setDefaultSubcollectionIndex((SubcollectionIndex)subcollectionindexes_list.getSelectedValue());
+		    clear_default_button.setEnabled(true);
+		    default_value_field.setText(default_index.toString());
+		}
+	    }
+	}
+	
+	private class SubcollectionIndexListener
+	    implements DocumentListener, ListSelectionListener {
+
+	    /** Gives notification that an attribute or set of attributes changed. */
+	    public void changedUpdate(DocumentEvent e) {
+		update();
+	    }
+	    /** Gives notification that there was an insert into the document. */
+	    public void insertUpdate(DocumentEvent e) {
+		update();
+	    }
+	    /** Gives notification that a portion of the document has been removed. */
+	    public void removeUpdate(DocumentEvent e) {
+		update();
+	    }
+
+	    public void valueChanged(ListSelectionEvent event) {
+		if(!event.getValueIsAdjusting()) {
+		    update();
+		}
+	    }
+
+	    /** The text area has changed in some way. Given that this can only happed when we are editing or adding a text fragment we better respond appropriately. */
+	    private void update() {
+		if(!subcollection_list.isSelectionEmpty() && subcollectionindex_name_field.getText().length() > 0) {
+		    if (getSubcollectionIndex(subcollectionindex_name_field.getText()) == null) {
+			add_index_button.setEnabled(true);
+		    } else {
+			add_index_button.setEnabled(false);
+		    }
+		}
+		else {
+		    add_index_button.setEnabled(false);
+		}
+	    }
+	}
+    }
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/SubcollectionManager.java	(revision 4932)
@@ -6,13 +6,7 @@
  * University of Waikato, New Zealand.
  *
- * <BR><BR>
- *
  * Author: John Thompson, Greenstone Digital Library, University of Waikato
  *
- * <BR><BR>
- *
  * Copyright (C) 1999 New Zealand Digital Library Project
- *
- * <BR><BR>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -21,12 +15,8 @@
  * (at your option) any later version.
  *
- * <BR><BR>
- *
  * This program 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 General Public License for more details.
- *
- * <BR><BR>
  *
  * You should have received a copy of the GNU General Public License
@@ -35,18 +25,8 @@
  *########################################################################
  */
-
- 
-
-
-
-
 package org.greenstone.gatherer.cdm;
 /**************************************************************************************
- * Title:        Gatherer
- * Description:  The Gatherer: a tool for gathering and enriching a digital collection.
- * Copyright:    Copyright (c) 2001
- * Company:      The University of Waikato
- * Written:      02/05/02
- * Revised:      17/11/02 - Commented
+ * Written:      ??/??/02
+ * Revised:      04/07/03 - DOM support
  **************************************************************************************/
 import java.awt.*;
@@ -56,152 +36,62 @@
 import javax.swing.event.*;
 import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
 import org.greenstone.gatherer.cdm.Subcollection;
-import org.greenstone.gatherer.cdm.SubIndex;
-import org.greenstone.gatherer.cdm.SubIndexes;
+import org.greenstone.gatherer.gui.NonWhitespaceField;
 import org.greenstone.gatherer.msm.ElementWrapper;
+import org.greenstone.gatherer.msm.MSMUtils;
 import org.greenstone.gatherer.util.ExclusiveListSelectionListener;
-import org.w3c.dom.Element;
-/** This class maintains a list of subcollections within our collection, and also records which of these subcollections we are building indexes for.
+import org.greenstone.gatherer.util.StaticStrings;
+import org.greenstone.gatherer.util.Utility;
+import org.w3c.dom.*;
+/** This class maintains a list of subcollections within our collection.
  * @author John Thompson, Greenstone Digital Library, University of Waikato
- * @version 2.1
+ * @version 2.4
  */
 public class SubcollectionManager
-    extends DefaultListModel {
-    /** A reference to the manager so we can get access to languages. */
-    private CollectionDesignManager manager;
+    extends DOMProxyListModel {
+
+    static final private String CLASS_DICTIONARY_NAME = "CDM.SubcollectionManager.";
+
     /** The controls used to edit the settings of this manager. */
     private Control controls = null;
-    /** A reference to this class so we can use it as a model in our inner classes. */
-    private DefaultListModel model = null;
-    /** The default index for the subcollections. */
-    private DefaultSubIndex default_index = null;
-    /** A reference to the Gatherer. */
-    private Gatherer gatherer = null;
-    /** A hashtable of subcollections, with mappings from name to subcollection. */
-    private Hashtable subcollections = null;
-    /** A vector-type structure of subcollection indexes. */
-    private SubIndexes subindexes = null;
-    /** A vector containing all of the subcollection commands that are unresolved, or in other words require that all 'subcollection <name> "<exp>"' to have been previously parsed before we can be certain their references will make sense. */
-    private ArrayList unresolved = null;
-    /** Constructor.
-     * @param gatherer A reference to the <Strong>Gatherer</strong>.
-     * @see org.greenstone.gatherer.cdm.SubIndexes
-     */
-    public SubcollectionManager(Gatherer gatherer, CollectionDesignManager manager) {
-	super();
-	this.gatherer = gatherer;
-	this.manager = manager;
+
+    private DOMProxyListModel model;
+
+    /** Constructor. 
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.cdm.CollectionConfiguration
+     * @see org.greenstone.gatherer.cdm.CollectionDesignManager
+     * @see org.greenstone.gatherer.cdm.DOMProxyListModel
+     * @see org.greenstone.gatherer.cdm.Subcollection
+     */
+    public SubcollectionManager() {
+	super(CollectionDesignManager.collect_config.getDocumentElement(), StaticStrings.SUBCOLLECTION_ELEMENT, new Subcollection());
+	Gatherer.println("SubcollectionManager: " + getSize() + " subcollections parsed.");
 	this.model = this;
-	this.subcollections = new Hashtable();
-	this.subindexes = new SubIndexes();
-	this.unresolved = new ArrayList();
-    }
-    /** Method to add a subindex.
-     * @param subindex A <strong>SubIndex</strong>.
+    }
+
+    /** Method to add a new subcollection.
+     * @param subcollection the Subcollection to add
      * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.cdm.CollectionConfiguration
+     * @see org.greenstone.gatherer.cdm.DOMProxyListModel
      * @see org.greenstone.gatherer.collection.CollectionManager
      */
-    public void addSubIndex(SubIndex subindex) {
-	if(!subindexes.contains(subindex)) {
-	    subindexes.addElement(subindex);
-	    gatherer.c_man.configurationChanged();
-	}
-    }
-    /** Method to add a new subcollection. Adds it to both the underlying list model and the hashtable mapping names to subcollections.
-	  * @param sub The <strong>Subcollection</strong> to add.
-	  * @return A <i>boolean</i> indicating if the addition was successful (<i>true</i>) or not (<i>false</i>).
-		* @see org.greenstone.gatherer.Gatherer
-		* @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public boolean addSubcollection(Subcollection sub) {
-	if(!contains(sub)) {
-	    addElement(sub);
-	    subcollections.put(sub.getName(), sub);
-	    gatherer.c_man.configurationChanged();
-	    return true;
-	}
-	return false;
-    }
-    /** A method to add a new subcollection, by specifying its name, metadata element if applicable and Perl expression to filter pages into or out of this collection.
-     * @param name A <>>String</>> which is a unique identifier of a subcollection.
-     * @param include A <i>boolean</i> indicating whether this is an inclusion filter (<i>true</i>) or an exclusion one (<i>false</i>).
-     * @param element An <>>Element</>> which is either the metadata whose value should be matched against the given expression, or <i>null</i> if you wish to match against the file name.
-     * @param exp A <>>String</>> containing a Perl expression which is used as the filter for this subcollection.
-     * @param flags A <strong>String<strong> listing any special flags to be applied when matching the expression.
-     * @return A <i>boolean</i> with a value of <i>true</i> if the addition was successful.
-     * @see org.greenstone.gatherer.cdm.Subcollection
-     */
-    public boolean addSubcollection(String name, boolean include, String element, String exp, String flags) {
-	Subcollection sub = null;
-	if(element != null) {
-	    sub = new Subcollection(name, include, element, exp, flags);
-	}
-	else {
-	    sub = new Subcollection(name, include, exp, flags);
-	}
-	return addSubcollection(sub);
-    }
-    /** Refresh all derived components using this manager as a model.
-     */
-    public void changed() {
-	fireContentsChanged(this, 0, getSize() - 1);
-    }
-    /** Method to retrieve the controls for this manager.
-     * @return A <Strong>Control</strong> object which contains the controls used to edit the subcollection data.
-     */
-    public Control getControls() {
-	if(controls == null) {
-	    controls = new Control();
-	}
-	return controls;
-    }
-    /** Method to retrieve the default index.
-	  * @return A <strong>DefaultSubIndex</strong> object.
-	  */
-    public DefaultSubIndex getDefaultSubIndex() {
-	return default_index;
-    }
-    /** Method to retrieve a certain subcollection by its name.
-     * @param name A <strong>String</strong> which is used as the key for finding the matching subcollection.
-     * @return The requested <strong>Subcollection</strong> or <i>null</i> if no such subcollection exists.
-     */
-    public Subcollection getSubcollection(String name) {
-	return (Subcollection) subcollections.get(name);
-    }
-
-    /** Retrieve a certain subindex given its name.
-     * @param name the String representation of a subindex.
-     * @return the SubIndex requested or null if no such subindex.
-     */
-    public SubIndex getSubIndex(String name) {
-	for(int i = 0; i < subindexes.size(); i++) {
-	    SubIndex subindex = (SubIndex) subindexes.get(i);
-	    if(subindex.toString().equals(name)) {
-		return subindex;
-	    }
-	}
-	return null;
-    }
-
-    /** Method to get all of the subindexes set.
-     * @return A <strong>SubIndexes</strong> object containing all the defined indexes.
-     */
-    public SubIndexes getSubIndexes() {
-	return subindexes;
-    }
-    /** Method to get all of the subcollections defined.
-     * @return A <strong>Vector</strong> of subcollections.
-     */
-    public Vector getSubcollections() {
-	Vector subcollections = new Vector();
-	for(int i = 0; i < size(); i++) {
-	    subcollections.add(get(i));
-	}
-	return subcollections;
-    }
-    /** Mark the current controls, if any, as invalid and deallocate them. Any further use of the controls will requires them being rebuilt.
-     * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control
-     */
-    public void invalidateControls() {
+    public void addSubcollection(Subcollection subcollection) {
+	if(!contains(subcollection)) {
+	    Element element = subcollection.getElement();
+	    // Locate where we should insert this new subcollection. 
+	    Node target_node = CollectionConfiguration.findInsertionPoint(element);
+	    // Failing that we insert immediately after a language string
+	    add(root, subcollection, target_node);
+	    Gatherer.c_man.configurationChanged();
+	}
+    }
+
+    public void destroy() {
 	if(controls != null) {
 	    controls.destroy();
@@ -209,322 +99,189 @@
 	}
     }
-    /** This method attempts to parse a subcollection related command from the given command string. If such a string is successfully parsed, it is immediately added to the data within this collection.
-     * @param command The <strong>String</strong> to be parsed.
-     * @param finished This <i>boolean</i> is usually <i>false</i> when called, unless this parse call is being made -after- the entire collection configuration file has been read in in which case it is <i>true</i>.
-     * @return A <i>boolean</i> which is <i>true</i> if a command was parsed, <i>false</i> otherwise.
-     * @see org.greenstone.gatherer.cdm.CommandTokenizer
-     * @see org.greenstone.gatherer.cdm.DefaultSubIndex
-     * @see org.greenstone.gatherer.cdm.Subcollection
-     * @see org.greenstone.gatherer.cdm.SubIndex
-     */
-    public boolean parse(String command, boolean finished) {
-	String temp = command.toLowerCase();
-	CommandTokenizer tokenizer = new CommandTokenizer(command);
-	tokenizer.nextToken(); // Throw away head.
-	if(temp.startsWith("subcollection")) {
-	    if(tokenizer.countTokens() >= 2) {
-		String name = tokenizer.nextToken();
-		String pattern = tokenizer.nextToken();
-		addSubcollection(new Subcollection(name, pattern));
-		return true;
-	    }
-	}
-	else if(temp.startsWith("indexsubcollections")) {
-				// These entries depend on what subcollections have previously been set, so we cannot safely parse them until after the file is complete.
-	    if(!finished) {
-		unresolved.add(command);
-	    }
-	    else {
-		while(tokenizer.hasMoreTokens()) {
-		    addSubIndex(new SubIndex(tokenizer.nextToken(), this));
-		}
-	    }
-	    return true;
-	}
-	else if(temp.startsWith("defaultsubcollection")) {
-				// These entries depend on what subcollections have previously been set, so we cannot safely parse them until after the file is complete.
-	    if(!finished) {
-		unresolved.add(command);
-	    }
-	    else {
-		if(tokenizer.hasMoreTokens()) {
-		    setDefaultSubIndex(new DefaultSubIndex(tokenizer.nextToken(), this));
-		}
-	    }
-	    return true;
-	}
-	return false;
-    }
-    /** Method to remove a certain subindex.
-	  * @param subindex The <strong>SubIndex</strong> you wish to remove.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void removeSubIndex(SubIndex subindex) {
-	subindexes.removeElement(subindex);
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method to remove all of the subindexes that contain a certain subcollection.
-	  * @param sub The <strong>Subcollection</strong> that you wish to remove.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.cdm.SubIndex
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void removeSubIndexes(Subcollection sub) {
-	for(int i = subindexes.size() - 1; i >= 0; i--) {
-	    SubIndex subindex = (SubIndex)subindexes.get(i);
-	    if(subindex.containsSubcollection(sub.getName())) {
-		subindexes.removeElement(subindex);
-	    }
-	}
-	gatherer.c_man.configurationChanged();
-    }
+
+    /** Method to retrieve the controls for this manager.
+     * @return the Control used to edit the subcollection data
+     */
+    public Control getControls() {
+	if(controls == null) {
+	    controls = new SubcollectionControl();
+	}
+	return controls;
+    }
+
+    /** Method to retrieve a certain subcollection by its name.
+     * @param name a String which is used as the key for finding the matching subcollection
+     * @return the requested Subcollection or null if no such subcollection exists.
+     */
+    public Subcollection getSubcollection(String name) {
+	Subcollection result = null;
+	int size = getSize();
+	for(int i = 0; i < size; i++) {
+	    Subcollection subcollection = (Subcollection) getElementAt(i);
+	    if(subcollection.getName().equals(name)) {
+		result = subcollection;
+	    }
+	}
+	return result;
+    }
+
+    /** Method to get all of the subcollections defined.
+     * @return an ArrayList of subcollections
+     */
+    public ArrayList getSubcollections() {
+	return children();
+    }
+
     /** Method to remove the given subcollection.
-	  * @param sub The <strong>Subcollection</strong> you want to remove.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void removeSubcollection(Subcollection sub) {
-	removeElement(sub);
-	subcollections.remove(sub);
-	gatherer.c_man.configurationChanged();
-    }
-    /** Method to retry the parsing of commands that were previously unable to be parsed as they referenced subcollections that may not have been instantiated.
-	  */
-    public void reparseUnresolved() {
-	for(int i = 0; i < unresolved.size(); i++) {
-	    parse((String)unresolved.get(i), true);
-	}
-	unresolved.clear();
-    }
-    /** Method to set the default subcollection index.
-	  * @param subcollection The <strong>Subcollection</strong> to use as the default index.
-	  * @see org.greenstone.gatherer.Gatherer
-	  * @see org.greenstone.gatherer.collection.CollectionManager
-	  */
-    public void setDefaultSubIndex(DefaultSubIndex subindex) {
-	this.default_index = subindex;
-	if(subindex != null) {
-	    addSubIndex(subindex.getSubIndex());
-	}
-	gatherer.c_man.configurationChanged();
-    }
-    /** This method causes the contents of this manager to be converted to a string, which is accomplished by calling <i>toString()</i> on each subcollection, then printing out the contents of the index vector.
-	  * @return A <strong>String</strong> containing a block of configuration commands.
-		* @see org.greenstone.gatherer.cdm.SubIndex
-		* @see org.greenstone.gatherer.cdm.Subcollection
-	  */
-    public String toString() {
-	String text = "";
-	// Retrieve the subcollection names and sort them.
-	if(subcollections.size() > 0) {
-	    Vector names = new Vector(subcollections.keySet());
-	    Collections.sort(names);
-	    for(int i = 0; i < names.size(); i++) {
-		Subcollection sub =
-		    (Subcollection) subcollections.get(names.get(i));
-		text = text + sub.toString();
-	    }
-				// Now add a entry, separated by spaces for each subcollection
-				// index.
-	    if(subindexes.size() > 0) {
-		text = text + subindexes.toString();
-	    }
-				// Finally add the default subcollection index if necessary.
-	    if(default_index != null) {
-		text = text + default_index.toString();
-	    }
-	    text = text + "\n";
-	}
-	// Otherwise if there were no subcollections, there aren't going to be
-	// subcollection indexes, nor a default subcollection index are there.
-	return text;
-    }
+     * @param subcollection the Subcollection you want to remove
+     * @see org.greenstone.gatherer.Gatherer
+     * @see org.greenstone.gatherer.collection.CollectionManager
+     */
+    public void removeSubcollection(Subcollection subcollection) {
+	remove(subcollection);
+	Gatherer.c_man.configurationChanged();
+    }
+
+    public void updateSubcollection(Subcollection subcollection, String name, boolean include, String source, String pattern, String flags) {
+	subcollection.setFlags(flags);
+	subcollection.setInclusive(include);
+	subcollection.setName(name);
+	subcollection.setPattern(pattern);
+	subcollection.setSource(source);
+	refresh(subcollection);
+	Gatherer.c_man.configurationChanged();
+    }
+
     /** Method to retrieve a phrase from the dictionary based on a key.
-	  * @param key A <strong>String</strong> used to find the correct phrase.
-	  * @param args A <strong>String[]</strong> of arguments used in formatting and filling out the phrase.
-	  * @return A <strong>String</strong> containing the correct phrase with the correct formatting.
-	  */
+     * @param key A <strong>String</strong> used to find the correct phrase.
+     * @param args A <strong>String[]</strong> of arguments used in formatting and filling out the phrase.
+     * @return A <strong>String</strong> containing the correct phrase with the correct formatting.
+     */
     private String get(String key) {
-	return get(key, null);
-    }
-    /** Method to retrieve a phrase from the dictionary based on a key.
-	  * @param key A <strong>String</strong> used to find the correct phrase.
-	  * @return A <strong>String</strong> containing the correct phrase with the correct formatting.
-	  * @see org.greenstone.gatherer.Dictionary
-	  * @see org.greenstone.gatherer.Gatherer
-	  */
-    private String get(String key, String args[]) {
 	if(key.indexOf(".") == -1) {
-	    key = "CDM.SubcollectionManager." + key;
-	}
-	return gatherer.dictionary.get(key, args);
-    }
+	    key = CLASS_DICTIONARY_NAME + key;
+	}
+	return Gatherer.dictionary.get(key);
+    }
+
     /** This class creates a JPanel containing serveral more controls used for editing subcollection information. */
-    private class Control
-	extends JPanel {
-	/** <i>true</i> if the current selection has changed. */
-	private boolean changed = false;
-	/** Button to add a subcollection. */
-	private JButton add = null;
-	/** Button to add a subindex. */
-	private JButton add_index = null;
-	/** Button to clear the default subindex. */
-	private JButton clear_default = null;
-	/** Button to remove a subcollection. */
-	private JButton remove = null;
-	/** Button to remove a subindex. */
-	private JButton remove_index = null;
-	/** Button to set the default subindex. */
-	private JButton set_default = null;
-	/** Button to cause an update of the subcollections. */
-	private JButton update = null;
-	/** A combobox allowing you to choose which field the data for matching should be taken from. Includes text body and filename, as well as all assigned metadata elements. */
-	private JComboBox source = null;
-	/** The list of assigned subcollections. */
-	private JList subcollection_list = null;
-	/** The list of subcollections available for the creation of subindexes. */
-	private JList subcollection_list_2 = null;
-	/** The list of assigned subindexes. */
-	private JList subindexes_list = null;
-	/** The label denoting the list of subindexes. */
-	private JLabel subindexes_label = null;
-	/** The panel containing the subcollection controls. */
-	private JPanel subcollection_pane = null;
-	/** The panel containing the subindex controls. */
-	private JPanel subindex_pane = null;
-	/** The tabbed pane used to store the subcollection and subindex controls. */
-	private JTabbedPane tabbed_pane = null;
-	/** The area used to display inline instructions. */
-	private JTextArea instructions = null;
-	/** The field displaying the name of the default subindex. */
-	private JTextField default_value = null;
-	/** A field used for specifying flags for the PERL expression matching, such as 'i' for case insensitive. */
-	private JTextField flags = null;
-	/** The pattern the source text must match. */
-	private JTextField match = null;
-	/** The name of this subcollection. */
-	private JTextField name = null;
-	/** The name of this subindex. */
-	private JTextField subindex_name;
-	/** When this button is selected the filter matching files are excluded. */
-	private JToggleButton exclude = null;
-	/** When this button is selected the filter matching files are included. */
-	private JToggleButton include = null;
-	/** The existing subcollection whose details you are reviewing, if any. */
-	private Subcollection current = null;
-	/** Constructor, creates the outer parts of the view, then calls two methods in turn to create the subcollection controls and subindex controls.
-	 */
-	public Control() {
+    private class SubcollectionControl
+	extends JPanel 
+	implements Control {
+
+	private JButton add_button;
+	private JButton remove_button;
+	private JButton update_button;
+	private JComboBox source_combobox;
+	private JList subcollection_list;
+	private JTabbedPane tabbed_pane;
+	private JTextArea instructions_area;
+	private JTextField flags_field;
+	private JTextField match_field;
+	private JTextField name_field;
+	private JToggleButton exclude_button;
+	private JToggleButton include_button;
+
+	/** Constructor */
+	public SubcollectionControl() {
 	    // Create
 	    JPanel border_pane = new JPanel();
 	    JPanel header_pane = new JPanel();
-	    instructions = new JTextArea(get("Instructions"));
-	    instructions.setEditable(false);
-	    instructions.setLineWrap(true);
-	    instructions.setRows(5);
-	    instructions.setWrapStyleWord(true);
+	    instructions_area = new JTextArea(get("Instructions"));
+	    instructions_area.setEditable(false);
+	    instructions_area.setLineWrap(true);
+	    instructions_area.setRows(5);
+	    instructions_area.setWrapStyleWord(true);
 	    tabbed_pane = new JTabbedPane();
 	    JLabel title = new JLabel(get("Title"));
 	    title.setHorizontalAlignment(JLabel.CENTER);
-	    createSubCollection();
-	    createSubIndex();
+	    
+	    JPanel button_pane_3 = new JPanel();
+	    add_button = new JButton(get("Add"));
+	    add_button.setMnemonic(KeyEvent.VK_A);
+	    add_button.setEnabled(false);
+	    remove_button = new JButton(get("Remove"));
+	    remove_button.setMnemonic(KeyEvent.VK_R);
+	    remove_button.setEnabled(false);
+	    update_button = new JButton(get("Update"));
+	    update_button.setMnemonic(KeyEvent.VK_C);
+	    update_button.setEnabled(false);
+
+	    JPanel button_pane = new JPanel();
+	    JPanel button_pane_1 = new JPanel();
+	    exclude_button = new JToggleButton(get("Exclude"));
+	    exclude_button.setMnemonic(KeyEvent.VK_X);
+	    flags_field = new NonWhitespaceField();
+	    JLabel flags_label = new JLabel(get("Flags"));
+	    include_button = new JToggleButton(get("Include"));
+	    include_button.setMnemonic(KeyEvent.VK_I);
+	    JLabel inclusive_label = new JLabel(get("Inclusive"));
+	    JPanel inclusive_pane = new JPanel();
+	    match_field = new JTextField();
+	    JLabel match_label = new JLabel(get("Match"));
+	    name_field = new NonWhitespaceField();
+	    JLabel name_label = new JLabel(get("Name"));
+	    Vector source_model = Gatherer.c_man.getCollection().msm.getAssignedElements();
+	    source_model.add(0, StaticStrings.FILENAME_STR);
+	    source_combobox = new JComboBox(source_model);
+	    JLabel source_label = new JLabel(get("Source"));
+	    subcollection_list = new JList(model);
+	    subcollection_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+	    JPanel subcollection_pane = new JPanel();
+	    ButtonGroup bg = new ButtonGroup();
+	    bg.add(include_button);
+	    bg.add(exclude_button);
+	    include_button.setSelected(true);
+	    JPanel subcollection_list_pane = new JPanel();
+	    JLabel subcollection_list_label = new JLabel(get("Assigned"));
+
 	    // Add listeners
+	    SubCollectionChangeListener cl = new SubCollectionChangeListener();
+	    add_button.addActionListener(new AddSubCollectionListener());
+	    remove_button.addActionListener(new RemoveSubCollectionListener());
+	    update_button.addActionListener(new UpdateSubCollectionListener());
+	    exclude_button.addActionListener(cl);
+	    include_button.addActionListener(cl);
+	    source_combobox.addActionListener(cl);
+	    flags_field.getDocument().addDocumentListener(cl);
+	    match_field.getDocument().addDocumentListener(cl);
+	    name_field.getDocument().addDocumentListener(cl);
+	    subcollection_list.addListSelectionListener(new SubCollectionListListener());
+
 	    // Layout
-	    instructions.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+	    instructions_area.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
 	    header_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
 	    header_pane.setLayout(new BorderLayout());
 	    header_pane.add(title, BorderLayout.NORTH);
-	    header_pane.add(new JScrollPane(instructions), BorderLayout.CENTER);
-	    tabbed_pane.addTab(get("Subcollection_Controls"), subcollection_pane);
-	    tabbed_pane.addTab(get("Subindex_Controls"), subindex_pane);
-	    tabbed_pane.addTab(get("Language_Controls"), manager.languages.getControls());
-	    border_pane.setBorder(BorderFactory.createEmptyBorder(0,5,5,5));
-	    border_pane.setLayout(new BorderLayout());
-	    border_pane.add(tabbed_pane, BorderLayout.CENTER);
-	    setLayout(new BorderLayout());
-	    add(header_pane, BorderLayout.NORTH);
-	    add(border_pane, BorderLayout.CENTER);
-	}
-	/** Create the subcollection controls.
-	 * @see org.greenstone.gatherer.Gatherer
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.AddListener
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.ListListener
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.RemoveListener
-	 * @see org.greenstone.gatherer.collection.CollectionManager
-	 * @see org.greenstone.gatherer.msm.MetadataSetManager
-	 */
-	public void createSubCollection() {
-	    // Create
-	    add = new JButton(get("Add"));
-	    add.setMnemonic(KeyEvent.VK_A);
-	    add.setEnabled(false);
-	    JPanel button_pane = new JPanel();
-	    JPanel button_pane_1 = new JPanel();
-	    JPanel button_pane_3 = new JPanel();
-	    exclude = new JToggleButton(get("Exclude"));
-	    exclude.setMnemonic(KeyEvent.VK_X);
-	    flags = new JTextField();
-	    JLabel flags_label = new JLabel(get("Flags"));
-	    include = new JToggleButton(get("Include"));
-	    include.setMnemonic(KeyEvent.VK_I);
-	    JLabel inclusive_label = new JLabel(get("Inclusive"));
-	    JPanel inclusive_pane = new JPanel();
-	    match = new JTextField();
-	    JLabel match_label = new JLabel(get("Match"));
-	    name = new JTextField();
-	    JLabel name_label = new JLabel(get("Name"));
-	    remove = new JButton(get("Remove"));
-	    remove.setMnemonic(KeyEvent.VK_R);
-	    remove.setEnabled(false);
-	    Vector source_model = gatherer.c_man.msm.getAssignedElements();
-	    source_model.add(0, "Filename");
-	    source = new JComboBox(source_model);
-	    JLabel source_label = new JLabel(get("Source"));
-	    subcollection_list = new JList(model);
-	    subcollection_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-	    subcollection_pane = new JPanel();
-	    ButtonGroup bg = new ButtonGroup();
-	    bg.add(include);
-	    bg.add(exclude);
-	    include.setSelected(true);
-	    JPanel subcollection_list_pane = new JPanel();
-	    JLabel subcollection_list_label = new JLabel(get("Assigned"));
-
-	    // Add listeners
-	    SubCollectionChangeListener cl = new SubCollectionChangeListener();
-	    add.addActionListener(new AddSubCollectionListener());
-	    remove.addActionListener(new RemoveSubCollectionListener());
-	    subcollection_list.addListSelectionListener(new SubCollectionListListener());
-	    //exclude.addActionListener(cl);
-	    //include.addActionListener(cl);
-	    //source.addActionListener(cl);
-	    //flags.addDocumentListener(cl);
-	    match.getDocument().addDocumentListener(cl);
-	    name.getDocument().addDocumentListener(cl);
-
-	    // Layout
+	    header_pane.add(new JScrollPane(instructions_area), BorderLayout.CENTER);
+
 	    inclusive_pane.setLayout(new GridLayout());
-	    inclusive_pane.add(include);
-	    inclusive_pane.add(exclude);
+	    inclusive_pane.add(include_button);
+	    inclusive_pane.add(exclude_button);
+
 	    button_pane_1.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
 	    button_pane_1.setLayout(new GridLayout(5, 2));
 	    button_pane_1.add(name_label);
-	    button_pane_1.add(name);
+	    button_pane_1.add(name_field);
 	    button_pane_1.add(source_label);
-	    button_pane_1.add(source);
+	    button_pane_1.add(source_combobox);
 	    button_pane_1.add(match_label);
-	    button_pane_1.add(match);
+	    button_pane_1.add(match_field);
 	    button_pane_1.add(inclusive_label);
 	    button_pane_1.add(inclusive_pane);
 	    button_pane_1.add(flags_label);
-	    button_pane_1.add(flags);
+	    button_pane_1.add(flags_field);
+
 	    button_pane_3.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
-	    button_pane_3.setLayout(new GridLayout(1,2));
-	    button_pane_3.add(add);
-	    button_pane_3.add(remove);
+	    button_pane_3.setLayout(new GridLayout(1,3));
+	    button_pane_3.add(add_button);
+	    button_pane_3.add(update_button);
+	    button_pane_3.add(remove_button);
+
 	    button_pane.setLayout(new BorderLayout());
 	    button_pane.add(button_pane_1, BorderLayout.CENTER);
 	    button_pane.add(button_pane_3, BorderLayout.SOUTH);
+
 	    subcollection_list_pane.setLayout(new BorderLayout());
 	    subcollection_list_pane.add(subcollection_list_label, BorderLayout.NORTH);
@@ -534,164 +291,33 @@
 	    subcollection_pane.add(subcollection_list_pane, BorderLayout.CENTER);
 	    subcollection_pane.add(button_pane, BorderLayout.SOUTH);
-	}
-	/** Create the subindex controls.
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.AddSubIndexListener
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.ClearDefaultListener
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.ChangeListener
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.RemoveSubIndexListener
-	 * @see org.greenstone.gatherer.cdm.SubcollectionManager.Control.SetDefaultListener
-	 * @see org.greenstone.gatherer.util.ExclusiveListListener
-	 */
-	public void createSubIndex() {
-	    // Creation
-	    JPanel subindex_name_panel = new JPanel();
-	    JLabel subindex_name_label = new JLabel(get("PartitionName"));
-	    subindex_name = new JTextField();
-
-	    add_index = new JButton(get("Add_Subindex"));
-	    add_index.setMnemonic(KeyEvent.VK_A);
-	    add_index.setEnabled(false);
-	    JPanel button_pane_2 = new JPanel();
-	    clear_default = new JButton(get("Clear_Default_Subindex"));
-	    clear_default.setMnemonic(KeyEvent.VK_C);
-	    if(default_index == null) {
-		clear_default.setEnabled(false);
-	    }
-	    JLabel default_label = new JLabel(get("Default_Subindex"));
-	    JPanel default_pane = new JPanel();
-	    if(default_index == null) {
-		default_value = new JTextField();
-	    }
-	    else {
-		default_value = new JTextField(default_index.getSubIndex().toString());
-	    }
-	    default_value.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
-	    default_value.setEditable(false);
-	    JPanel subindex_inner_pane_1 = new JPanel();
-	    JPanel subindex_inner_pane_2 = new JPanel();
-	    remove_index = new JButton(get("Remove_Subindex"));
-	    remove_index.setMnemonic(KeyEvent.VK_R);
-	    set_default = new JButton(get("Set_Default_Subindex"));
-	    set_default.setMnemonic(KeyEvent.VK_S);
-	    JLabel subcollection_label = new JLabel(get("Subcollection"));
-	    subcollection_list_2 = new JList(model);
-	    JPanel list_2_pane = new JPanel();
-	    subindex_pane = new JPanel();
-	    JLabel subindexes_label = new JLabel(get("Subindexes"));
-	    subindexes_list = new JList(subindexes);
-	    subindexes_list.setCellRenderer(new SubIndexListCellRenderer());
-	    subindexes_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-	    JPanel subindexes_pane = new JPanel();
-
-	    // Add listeners
-	    ExclusiveListSelectionListener ell = new ExclusiveListSelectionListener();
-	    ell.add(subcollection_list_2);
-	    ell.add(subindexes_list);
-	    add_index.addActionListener(new AddSubIndexListener());
-	    clear_default.addActionListener(new ClearDefaultSubIndexListener());
-	    remove_index.addActionListener(new RemoveSubIndexListener());
-	    set_default.addActionListener(new SetDefaultSubIndexListener());
-
-	    subindex_name.getDocument().addDocumentListener(new SubIndexNameDocumentListener());
-	    subcollection_list_2.addListSelectionListener(new SubCollectionList2Listener());
-
-	    // Layout
-	    subindex_name_panel.setLayout(new BorderLayout());
-	    subindex_name_panel.add(subindex_name_label, BorderLayout.WEST);
-	    subindex_name_panel.add(subindex_name, BorderLayout.CENTER);
-
-	    list_2_pane.setLayout(new BorderLayout());
-	    list_2_pane.add(subcollection_label, BorderLayout.NORTH);
-	    list_2_pane.add(new JScrollPane(subcollection_list_2), BorderLayout.CENTER);
-	    
-	    subindexes_pane.setLayout(new BorderLayout());
-	    subindexes_pane.add(subindexes_label, BorderLayout.NORTH);
-	    subindexes_pane.add(new JScrollPane(subindexes_list), BorderLayout.CENTER);
-	    
-	    subindex_inner_pane_2.setLayout(new GridLayout(1,2,0,5));
-	    subindex_inner_pane_2.add(list_2_pane);
-	    subindex_inner_pane_2.add(subindexes_pane);
-	    
-	    default_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
-	    default_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5,5,5,5), BorderFactory.createRaisedBevelBorder()), BorderFactory.createEmptyBorder(2,2,2,2)));
-	    default_pane.setLayout(new BorderLayout());
-	    default_pane.add(default_label, BorderLayout.WEST);
-	    default_pane.add(default_value, BorderLayout.CENTER);
-	    
-	    subindex_inner_pane_1.setLayout(new BorderLayout());
-	    subindex_inner_pane_1.add(subindex_name_panel, BorderLayout.NORTH);
-	    subindex_inner_pane_1.add(subindex_inner_pane_2, BorderLayout.CENTER);
-	    subindex_inner_pane_1.add(default_pane, BorderLayout.SOUTH);
-	    
-	    button_pane_2.setLayout(new GridLayout(2,2));
-	    button_pane_2.add(add_index);
-	    button_pane_2.add(remove_index);
-	    button_pane_2.add(clear_default);
-	    button_pane_2.add(set_default);
-	    
-	    subindex_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-	    subindex_pane.setLayout(new BorderLayout());
-	    subindex_pane.add(subindex_inner_pane_1, BorderLayout.CENTER);
-	    subindex_pane.add(button_pane_2, BorderLayout.SOUTH);
-	}
-	
-	
+
+	    tabbed_pane.addTab(get("Subcollection_Controls"), subcollection_pane);
+	    tabbed_pane.addTab(get("Subindex_Controls"), (JPanel) CollectionDesignManager.subcollectionindex_manager.getControls());
+	    tabbed_pane.addTab(get("Language_Controls"), (JPanel) CollectionDesignManager.language_manager.getControls());
+
+	    border_pane.setBorder(BorderFactory.createEmptyBorder(0,5,5,5));
+	    border_pane.setLayout(new BorderLayout());
+	    border_pane.add(tabbed_pane, BorderLayout.CENTER);
+
+	    setLayout(new BorderLayout());
+	    add(header_pane, BorderLayout.NORTH);
+	    add(border_pane, BorderLayout.CENTER);
+	}
+       	
 	/** Method to unregister any listeners to avoid memory leaks.
 	 */
 	public void destroy() {
 	}
-	/** We have overriden this method to provide completely different functionality, given that our JPanel will never actually have focus. We call this method in GUI on the view we are about to replace, and this method checks if a change has occured and if so updates the current object.
-	 * @return A <i>boolean</i> which is always <i>false</i>.
-	 * @see org.greenstone.gatherer.msm.ElementWrapper
-	 */
-	public boolean hasFocus() {
-	    // If we have a current metadata open, and something has changed then save the change.
-	    if(changed && current != null) {
-		String n = name.getText();
-		String s = null;
-		Object o = source.getSelectedItem();
-		if(o instanceof ElementWrapper) {
-		    ElementWrapper e = (ElementWrapper)o;
-		    s = e.toString();
-		}
-		String e = match.getText();
-		String f = flags.getText();
-		if(n != null && n.length() > 0 && (s == null || s.length() > 0) && e != null && e.length() > 0) {
-		    current.update(n, s, e, include.isSelected(), f);
-		    int index = model.indexOf(current);
-		    changed();
-		    subindexes.changed();
-		    if(default_index != null) {
-			default_value.setText(default_index.getSubIndex().toString());
-		    }
-		}
-		changed = false;
-	    }
-	    return false;
-	}
-	/** Overriden to ensure the instructions are scrolled to top.
-	 */
-	public void updateUI() {
-	    if(instructions != null) {
-		instructions.setCaretPosition(0);
-	    }
-	    super.updateUI();
-	}
-
-	/** Method to validate the current subcollection editor values, and enable or disable controls (add button) based on said values. 
-	 * we want to disable add until a new name is present 
-	 */
-	protected void validateAdd() {
-	    if(changed && name.getText().length() > 0 && match.getText().length() > 0) {
-		if (subcollections.containsKey(name.getText())) {
-		    add.setEnabled(false);
-		} else {
-		    add.setEnabled(true);
-		}
-	    }
-	    else {
-		add.setEnabled(false);
-	    }
-	}
+
+	public void gainFocus() {
+	    // Rebuild the sources combobox
+	    Vector source_model = Gatherer.c_man.getCollection().msm.getAssignedElements();
+	    source_model.add(0, "Filename"); // Add filename as a possible source.
+	    source_combobox.setModel(new DefaultComboBoxModel(source_model));
+	}
+
+	public void loseFocus() {
+	}
+
 	/** Listens for actions apon the 'add' button in the SubcollectionManager controls, and if detected calls the addSubcollection method of the manager with a newly created subcollection. */
 	private class AddSubCollectionListener
@@ -703,48 +329,30 @@
 	     */
 	    public void actionPerformed(ActionEvent event) {
-		String n = name.getText(); // not allowed spaces!
-		if (n.indexOf(' ')!=-1) {
-		    n = n.substring(0, n.indexOf(' '));
-		    name.setText(n);
-		}
-		String s = null;
-		Object o = source.getSelectedItem();
-		if(o instanceof ElementWrapper) {
-		    ElementWrapper e = (ElementWrapper)o;
-		    s = e.toString();
-		}
-		String e = match.getText();
-		String f = flags.getText();
-		if(n != null && n.length() > 0 && (s == null || s.length() > 0) && e != null && e.length() > 0) {
-		    Subcollection sub = new Subcollection(n, include.isSelected(), s, e, f);
-		    addSubcollection(sub);
-		}
-		changed = false;
-		validateAdd();
-	    }
-	}
-	/** Listens for actions apon the 'add subindex' button in the SubcollectionManager controls, and if detected calls the addSubindex method of the manager with a newly created subindex. */
-	private class AddSubIndexListener
-	    implements ActionListener {
-	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to check if they have a series of subcollections selected, and if so build an new subindex based apon them.
-	     * @param event An <strong>ActionEvent</strong> containing information about the event.
-	     * @see org.greenstone.gatherer.cdm.SubIndex
-	     */
-	    public void actionPerformed(ActionEvent event) {
-		if(!subcollection_list_2.isSelectionEmpty() && subindex_name.getText().length() > 0) {
-		    Vector selected = new Vector();
-		    Object raw[] = subcollection_list_2.getSelectedValues();
-		    for(int i = 0; i < raw.length; i++) {
-			selected.add(raw[i]);
+		String name = name_field.getText();
+		String source = null;
+		Object object = source_combobox.getSelectedItem();
+		if(object instanceof ElementWrapper) {
+		    ElementWrapper element_wrapper = (ElementWrapper)object;
+		    source = element_wrapper.getName();
+		    if(source.indexOf(MSMUtils.NS_SEP) == -1) {
+			source = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + source;
 		    }
-		    SubIndex subindex = new SubIndex(selected);
-		    addSubIndex(subindex);
-		    // Add the subindexes name.
-		    CollectionMeta metadata = new CollectionMeta(manager, subindex, manager.languages.getDefaultLanguage(), subindex_name.getText());
-		    manager.collectionmetadatum.addMetadata(metadata);
-		}
-	    }
-	}
-	/** This class listens for any key entry in a text field, selection change in a combobox or button click, and when detected sets the changed flag to true. Its also convenient to use this class to test if the add button should be active yet. */
+		}
+		else {
+		    source = object.toString();
+		}
+		String pattern = match_field.getText();
+		String flags = flags_field.getText();
+		if(name.length() > 0 && (source == null || source.length() > 0) && pattern.length() > 0) {
+		    Subcollection subcollection = new Subcollection(name, include_button.isSelected(), source, pattern, flags);
+		    addSubcollection(subcollection);
+		    // Change the selection to the new subcollection
+		    subcollection_list.setSelectedValue(subcollection, true);
+		}
+		add_button.setEnabled(false);
+	    }
+	}
+
+	/** This class listens for any key entry in a text field, selection change in a combobox or button click, and updates a subcollection as appropriate. Its also convenient to use this class to test if the add button should be active yet. */
 	private class SubCollectionChangeListener
 	    implements DocumentListener, ActionListener {
@@ -753,14 +361,12 @@
 	     */
 	    public void actionPerformed(ActionEvent event) {
-		changed = true;
 		validateAdd();
 	    }
 
 	    public void changedUpdate(DocumentEvent event) {
-		changed = true;
 		validateAdd();
 	    }
+
 	    public void insertUpdate(DocumentEvent event) {
-		changed = true;
 		validateAdd();
 
@@ -768,20 +374,22 @@
 
 	    public void removeUpdate(DocumentEvent event) {
-		changed = true;
 		validateAdd();
 	    }
-	}
-	/** Listens for actions apon the 'clear default subindex' button in the SubcollectionManager controls, and if detected calls the setDefaultSubIndex() method of the manager with <i>null</i>. */
-	private class ClearDefaultSubIndexListener
-	    implements ActionListener {
-	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to clear the default subindex.
-	     * @param event An <strong>ActionEvent</strong> containing information about the event.
-	     */
-	    public void actionPerformed(ActionEvent event) {
-		setDefaultSubIndex(null);
-		clear_default.setEnabled(false);
-		default_value.setText("");
-	    }
-	}
+
+	    /** Method to validate the current subcollection editor values, and enable or disable controls (add button) based on said values. */
+	    private void validateAdd() {
+		if(name_field.getText().length() > 0 && match_field.getText().length() > 0) {
+		    if (getSubcollection(name_field.getText()) == null) {
+			add_button.setEnabled(true);
+		    } else {
+			add_button.setEnabled(false);
+		    }
+		}
+		else {
+		    add_button.setEnabled(false);
+		}
+	    }
+	}
+
 	/** Listens for actions apon the 'remove' button in the SubcollectionManager controls, and if detected calls the remove method of the manager with the SubIndex selected for removal. */
 	private class RemoveSubCollectionListener
@@ -793,40 +401,39 @@
 	    public void actionPerformed(ActionEvent event) {
 		if(!subcollection_list.isSelectionEmpty()) {
-		    Subcollection sub_col = (Subcollection)subcollection_list.getSelectedValue();
-		    removeSubIndexes(sub_col);
-		    removeSubcollection(sub_col);
-		}
-	    }
-	}
-	/** Listens for actions apon the 'remove subindex' button in the SubcollectionManager controls, and if detected calls the removeSubIndex method of the manager with the SubIndex selected for removal. */
-	private class RemoveSubIndexListener
+		    Subcollection subcollection = (Subcollection)subcollection_list.getSelectedValue();
+		    removeSubcollection(subcollection);
+		    // And remove subcollection indexes dependant on this subcollection
+		    CollectionDesignManager.subcollectionindex_manager.removeSubcollectionIndexes(subcollection);
+		}
+		remove_button.setEnabled(false);
+	    }
+	}
+
+	private class UpdateSubCollectionListener
 	    implements ActionListener {
-	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to check if they have a subindex selected, and if so remove it.
-	     * @param event An <strong>ActionEvent</strong> containing information about the event.
-	     * @see org.greenstone.gatherer.cdm.SubIndex
-	     */
 	    public void actionPerformed(ActionEvent event) {
-		if(!subindexes_list.isSelectionEmpty()) {
-		    removeSubIndex((SubIndex)subindexes_list.getSelectedValue());
-		}
-	    }
-	}
-	/** Listens for actions apon the 'set default subindex' button in the SubcollectionManager controls, and if detected calls the setDefaultSubIndex method of the manager with the SubIndex selected for default. */
-	private class SetDefaultSubIndexListener
-	    implements ActionListener {
-	    /** Any implementation of ActionListener must include this method so we can be informed when an action has been performed on one of our target controls. In this case we want to check if they have a subindex selected, and if so set it as default.
-	     * @param event An <strong>ActionEvent</strong> containing information about the event.
-	     * @see org.greenstone.gatherer.cdm.DefaultSubIndex
-	     * @see org.greenstone.gatherer.cdm.SubIndex
-	     */
-	    public void actionPerformed(ActionEvent event) {
-		if(!subindexes_list.isSelectionEmpty()) {
-		    setDefaultSubIndex(new DefaultSubIndex((SubIndex)subindexes_list.getSelectedValue()));
-		    clear_default.setEnabled(true);
-		    default_value.setText(default_index.getSubIndex().toString());
-		}
-	    }
-	}
-
+		if(!subcollection_list.isSelectionEmpty()) {
+		    Subcollection subcollection = (Subcollection)subcollection_list.getSelectedValue();
+		    String name = name_field.getText();
+		    String source = null;
+		    Object object = source_combobox.getSelectedItem();
+		    if(object instanceof ElementWrapper) {
+			ElementWrapper element_wrapper = (ElementWrapper)object;
+			source = element_wrapper.getName();
+			if(source.indexOf(MSMUtils.NS_SEP) == -1) {
+			    source = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + source;
+			}
+		    }
+		    else {
+			source = object.toString();
+		    }
+		    String pattern = match_field.getText();
+		    String flags = flags_field.getText();
+		    if(name.length() > 0 && (source == null || source.length() > 0) && pattern.length() > 0) {
+			updateSubcollection(subcollection, name, include_button.isSelected(), source, pattern, flags);
+		    }
+		}
+	    }
+	}
 
 	/** This class listens for selections in the list on the subcollections pane of the SubcollectionManager, and updates the controls as necessary to reflect selection. */
@@ -839,123 +446,61 @@
 	     */
 	    public void valueChanged(ListSelectionEvent event) {
-		// If we have a previous collection and the users changed something, but not added, then update subcollection.
-		if(changed && current != null) {
-		    String n = name.getText();
-		    String s = null;
-		    Object o = source.getSelectedItem();
-		    if(o instanceof ElementWrapper) {
-			ElementWrapper e = (ElementWrapper)o;
-			s = e.toString();
-		    }
-		    String e = match.getText();
-		    String f = flags.getText();
-		    if(n != null && n.length() > 0 && (s == null || s.length() > 0) && e != null && e.length() > 0) {
-			current.update(n, s, e, include.isSelected(), f);
-			int index = model.indexOf(current);
-			changed();
-			subindexes.changed();
-			if(default_index != null) {
-			    default_value.setText(default_index.getSubIndex().toString());
-			}
-		    }
-		}
-		// Now load the new entry.
+		// Now the entry
 		if(!subcollection_list.isSelectionEmpty()) {
-		    current = (Subcollection) subcollection_list.getSelectedValue();
-		    flags.setText(current.getFlags());
-		    include.setSelected(current.getInclude());
-		    exclude.setSelected(!current.getInclude());
-		    match.setText(current.getExpression());
-		    name.setText(current.getName());
-		    String s = current.getSource();
+		    Subcollection subcollection = (Subcollection) subcollection_list.getSelectedValue();
+		    flags_field.setText(subcollection.getFlags());
+		    include_button.setSelected(subcollection.isInclusive());
+		    exclude_button.setSelected(!subcollection.isInclusive());
+		    match_field.setText(subcollection.getPattern());
+		    name_field.setText(subcollection.getName());
+		    String s = subcollection.getSource();
 		    int pos = 0;
-		    Object value = source.getItemAt(pos);
+		    Object value = source_combobox.getItemAt(pos);
+		    //ystem.err.println("Search for: " + s);
 		    while(value != null) {
 			if(value instanceof ElementWrapper) {
 			    ElementWrapper e = (ElementWrapper) value;
-			    if(e.toString().equals(s)) {
-				source.setSelectedIndex(pos);
+			    String e_name = e.getName();
+			    if(e_name.indexOf(MSMUtils.NS_SEP) == -1) {
+				e_name = Utility.EXTRACTED_METADATA_NAMESPACE + MSMUtils.NS_SEP + e_name;
+			    }
+			    //ystem.err.print("Compare to: " + e_name);
+			    if(e_name.equals(s)) {
+				source_combobox.setSelectedIndex(pos);
 				value = null;
+				//ystem.err.println(" - Match");
 			    }
 			    else {
 				pos++;
-				value = source.getItemAt(pos);
+				value = source_combobox.getItemAt(pos);
+				//ystem.err.println(" - Fail");
 			    }
 			}
 			else if(value.toString().equals(s)) {
-			    source.setSelectedIndex(pos);
+			    source_combobox.setSelectedIndex(pos);
 			    value = null;
 			}
 			else {
 			    pos++;
-			    value = source.getItemAt(pos);
+			    value = source_combobox.getItemAt(pos);
 			}
 		    }
 		    // Can't add one thats already there.
-		    add.setEnabled(false);
-		    // You can remove it though...
-		    remove.setEnabled(true);
+		    add_button.setEnabled(false);
+		    // You can update or remove it though...
+		    remove_button.setEnabled(true);
+		    update_button.setEnabled(true);
 		}
 		else {
-		    flags.setText("");
-		    include.setSelected(true);
-		    match.setText("");
-		    name.setText("");
-		    source.setSelectedIndex(0);
-		    remove.setEnabled(false);
-		}
-		// Have to do this after so we don't get called when nothings actually changed.
-		changed = false;
-		
-	    }
-	}
-
-	private class SubCollectionList2Listener
-	    implements ListSelectionListener {
-
-	    public void valueChanged(ListSelectionEvent event) {
-		if(!event.getValueIsAdjusting()) {
-		    add_index.setEnabled(!subcollection_list_2.isSelectionEmpty() && subindex_name.getText().length() > 0);
-		}
-	    }
-	}
-	
-	private class SubIndexListCellRenderer
-	    extends DefaultListCellRenderer {
-	    
-	    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
-		StringBuffer text = new StringBuffer(value.toString());
-		// Retrieve the indexes name if any.
-		CollectionMeta metadata = manager.collectionmetadatum.getMetadata(value, manager.languages.getDefaultLanguage(), true);
-		if(metadata != null) {
-		    text.append(" \"");
-		    text.append(metadata.getValue());
-		    text.append("\"");
-		}
-		return super.getListCellRendererComponent(list, text.toString(), index, isSelected, cellHasFocus);
-	    }
-
-	}
-	
-	private class SubIndexNameDocumentListener
-	    implements DocumentListener {
-	    /** Gives notification that an attribute or set of attributes changed. */
-	    public void changedUpdate(DocumentEvent e) {
-		update();
-	    }
-	    /** Gives notification that there was an insert into the document. */
-	    public void insertUpdate(DocumentEvent e) {
-		update();
-	    }
-	    /** Gives notification that a portion of the document has been removed. */
-	    public void removeUpdate(DocumentEvent e) {
-		update();
-	    }
-	    /** The text area has changed in some way. Given that this can only happed when we are editing or adding a text fragment we better respond appropriately. */
-	    private void update() {
-		add_index.setEnabled(!subcollection_list_2.isSelectionEmpty() && subindex_name.getText().length() > 0);
-	    }
-	}
-
+		    flags_field.setText("");
+		    include_button.setSelected(true);
+		    match_field.setText("");
+		    name_field.setText("");
+		    source_combobox.setSelectedIndex(0);
+		    remove_button.setEnabled(false);
+		    update_button.setEnabled(false);
+		}
+	    }
+	}
     }
 }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/SuperCollection.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/SuperCollection.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/SuperCollection.java	(revision 4932)
@@ -0,0 +1,100 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+* This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+/**************************************************************************************
+ * Written:     02/07/03
+ * Revised:     
+ **************************************************************************************/
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.DOMProxyListEntry;
+import org.w3c.dom.Element;
+
+public class SuperCollection
+    implements DOMProxyListEntry {
+
+    private Element element;
+    private String name;
+
+    public SuperCollection() {
+	element = null;
+	name = null;
+    }
+
+    public SuperCollection(Element element) {
+	this.element = element;
+	name = null;
+    }
+
+    public int compareTo(Object other) {
+	return this.getName().compareTo(other.toString());
+    }
+
+    public DOMProxyListEntry create(Element element) {
+	return new SuperCollection(element);
+    } 
+
+    public boolean equals(Object other) {
+	return (compareTo(other) == 0);
+    }
+
+    public Element getElement() {
+	return element;
+    }
+
+    public String getName() {
+	if(name == null && element != null) {
+	    name = element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE);
+	}
+	return name;
+    }
+
+    public boolean isAssigned() {
+	return (element != null && element.getAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE).equals(CollectionConfiguration.TRUE_STR));
+    }
+
+    public void setAssigned(boolean assigned) {
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.ASSIGNED_ATTRIBUTE, (assigned ? CollectionConfiguration.TRUE_STR : CollectionConfiguration.FALSE_STR));
+	}
+    }
+
+    public void setElement(Element element) {
+	this.element = element;
+	name = null;
+    }
+
+    public void setName(String name) {
+	this.name = name;
+	if(element != null) {
+	    element.setAttribute(CollectionConfiguration.NAME_ATTRIBUTE, name);
+	}
+    }
+
+    public String toString() {
+	return getName();
+    }
+}
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/SuperCollectionManager.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/SuperCollectionManager.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/SuperCollectionManager.java	(revision 4932)
@@ -28,17 +28,21 @@
 
 import java.awt.*;
-import java.awt.event.*;
 import java.io.*;
 import java.util.*;
 import javax.swing.*;
+import javax.swing.event.*;
 import org.greenstone.gatherer.Configuration;
 import org.greenstone.gatherer.Dictionary;
 import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.DOMProxyListModel;
 import org.greenstone.gatherer.checklist.CheckList;
 import org.greenstone.gatherer.checklist.Entry;
 import org.greenstone.gatherer.collection.CollectionConfiguration;
+import org.greenstone.gatherer.util.StaticStrings;
 import org.greenstone.gatherer.util.Utility;
-
-public class SuperCollectionManager {
+import org.w3c.dom.*;
+/** This class contains the information about what supercollection has been specified (if any) and methods for changing this information. Note that there is a major difference between this manager and the others in that its DOM model is never used directly in any list component. It is only used to decide whether a certain entry in the actual checklist is checked. */
+public class SuperCollectionManager 
+    extends DOMProxyListModel {
 
     static final public String SUPERCOLLECTION_COMMAND = "supercollection";
@@ -46,113 +50,55 @@
 
     private String current_coll_name = null;
-    private Control controls;
-    private ArrayList collection_checklist_model;
-
-    SuperCollectionManager() {
-	// We start by building a model of the installed collections.
-	collection_checklist_model = new ArrayList();
-	File gsdl_collection_directory = new File(Utility.getCollectionDir(Gatherer.config.gsdl_path));
-	current_coll_name = Gatherer.c_man.getCollection().getName();
-	File[] possible_collections = gsdl_collection_directory.listFiles();
-	for(int i = 0; possible_collections != null && i < possible_collections.length; i++) {
-	    // The simpliest case is if the directory etc/collect.cfg file and a metadata/ in it. Thus it can easily be built upon.
-	    File collect_cfg_file = new File(possible_collections[i], Utility.CONFIG_DIR);
-	    if(collect_cfg_file.exists()) {
-		CollectionConfiguration collect_cfg = new CollectionConfiguration(collect_cfg_file);
-		String collection_title = collect_cfg.getName();
-		String collection_name = possible_collections[i].getName();
-		// We do have to block the model collection.
-		if(!collection_title.equals("**title**")) {
-		    Entry entry = new Entry(collection_title);
-		    entry.setProperty(collection_name);
-		    if (collection_name.equals(current_coll_name)) {
-			// the current collection is always selected
-			entry.setFixed(true);
-			entry.setSelected(true);
-		    }
-		    collection_checklist_model.add(entry);
-		    entry = null;
-
-		}
-		collection_name = null;
-		collection_title = null;
-		collect_cfg = null;
+    private Control controls = null;
+    private ArrayList collection_checklist_model = null; // Model used to actually populate list
+
+    public SuperCollectionManager(Element supercollections_element) {
+	super(supercollections_element, StaticStrings.COLLECTION_ELEMENT, new SuperCollection());
+	Gatherer.println("SuperCollectionManager: " + getSize() + " supercollection members parsed.");
+    }
+
+    public void destroy() {
+	if(controls != null) {
+	    controls.destroy();
+	    controls = null;
+	}
+	if(collection_checklist_model != null) {
+	    collection_checklist_model.clear();
+	    collection_checklist_model = null;
+	}
+    }
+
+    public void addSuperCollection(SuperCollection supercollection) {
+	if(!contains(supercollection)) {
+	    add(getSize(), supercollection);
+	}
+    }
+
+    /** Method to retrieve the control for this manager.
+     * @return the Control for editing supercollection settings
+     */
+    public Control getControls() {
+	if(controls == null) {
+	    // Build controls
+	    this.controls = new SuperCollectionControl();
+	}
+	return controls;
+    }
+
+    public SuperCollection getSuperCollection(String collection_name) {
+	SuperCollection result = null;
+	int size = getSize();
+	for(int i = 0; result == null && i < size; i++) {
+	    SuperCollection supercollection = (SuperCollection) getElementAt(i);
+	    if(supercollection.getName().equals(collection_name)) {
+		result = supercollection;
 	    }
-	    collect_cfg_file = null;
-	}
-	possible_collections = null;
-	gsdl_collection_directory = null;
-	// Sort the result.
-	Collections.sort(collection_checklist_model);
-    }
-
-    public boolean parse(String command) {
-	String command_lc = command.toLowerCase();
-	if(command_lc.startsWith(SUPERCOLLECTION_COMMAND) || command_lc.startsWith(CCS_COMMAND)) {
-	    StringTokenizer tokenizer = new StringTokenizer(command);
-	    tokenizer.nextToken();
-	    // Read in each of the collection names.
-	    ArrayList collection_names = new ArrayList();
-	    while(tokenizer.hasMoreTokens()) {
-		collection_names.add(tokenizer.nextToken());
-	    }
-	    // Now that we have the collection names, select any entries in the checklist that match.
-	    for(int i = 0; i < collection_checklist_model.size(); i++) {
-		Entry entry = (Entry) collection_checklist_model.get(i);
-		String collection_name = entry.getProperty();
-		if(collection_names.contains(collection_name)) {
-		    entry.setSelected(true);
-		    collection_names.remove(collection_name); // Makes successive runs faster.
-		}
-		collection_name = null;
-		entry = null;
-	    }
-	    // Done.
-	    return true;
-	}
-	return false;
-    }
-
-    /** Method to retrieve the control for this manager.
-     * @return A JPanel containing the controls.
-     */
-    public JPanel getControls() {
-	if(controls == null) {
-	    controls = new Control();
-	}
-	return controls;
-    }
-
-    public void invalidateControls() {
-	controls = null;
-    }
-
-    /** Return the contents of this manager as a command or block of commands ready to be written to the collection configuration file. 
-     * @return A String containing a configuration command or block of commands, terminated with a new line. This value will be null if there was no supercollection command set. */
-    public String toString() {
-	StringBuffer command = new StringBuffer(SUPERCOLLECTION_COMMAND);
-	// Now for each entry selected output its collection name (property setting)
-	boolean found_entry = false;
-	for(int i = 0; i < collection_checklist_model.size(); i++) {
-	    Entry entry = (Entry) collection_checklist_model.get(i);
-	    if(entry.isSelected()) {
-		if (!entry.getProperty().equals(current_coll_name)){
-		    found_entry = true;
-		}
-		command.append(" ");
-		command.append(entry.getProperty());
-	    }
-	    entry = null;
-	}
-	command.append("\n");
-	command.append("\n");
-	
-	// It is possible that there was only one entry - the current collection -  in which case we return null.
-	if(found_entry) {
-	    return command.toString();
-	}
-	else {
-	    return null;
-	}
+	    supercollection = null;
+	}
+	return result;
+    }
+
+    public void removeSuperCollection(SuperCollection supercollection) {
+	remove(supercollection);
     }
 
@@ -169,12 +115,16 @@
 
     /** Provides controls for altering the SuperCollection settings. */
-    private class Control
-	extends JPanel {
+    private class SuperCollectionControl
+	extends JPanel 
+	implements Control {
 
 	private boolean init = true;
+	private CheckList collection_checklist = null;
 	//private JSplitPane center_pane;
 
-	Control() {
+	SuperCollectionControl() {
 	    super();
+
+	    buildModel();
 	    // Creation
 	    JPanel header_panel = new JPanel();
@@ -194,5 +144,6 @@
 	    instructions.setWrapStyleWord(true);
 
-	    CheckList collection_checklist = new CheckList(collection_checklist_model);
+	    collection_checklist = new CheckList(collection_checklist_model);
+
 	    // Layout
 	    //center_pane.setTopComponent(new JScrollPane(instructions));
@@ -209,12 +160,74 @@
 	}
 
-	public void paint(Graphics g) {
-	    super.paint(g);
-	    // If this is the first time we've painted this control, then set the divider 1/3:2/3
-	    //if(init) {
-	    //	init = false;
-	    //	center_pane.setDividerLocation(0.3);
-	    //	super.paint(g);
-	    //}
+	public void destroy() {
+	}
+
+	public void gainFocus() {
+	}
+
+	public void loseFocus() {
+	    // Retrieve the current supercollections
+	    ArrayList supercollections = children();
+	    // Now iterate through the checklist, and for each checked box found ensure the Supercollection exists, and ensure its assigned. Remove any supercollections altered in this way from the temporary array list
+	    int size = collection_checklist_model.size();
+	    for(int i = 0; i < size; i++) {
+		Entry entry = (Entry) collection_checklist_model.get(i);
+		if(entry.isSelected()) {
+		    String collection_name = (String) entry.getProperty();
+		    SuperCollection supercollection = getSuperCollection(collection_name);
+		    // Create the supercollection element if necessary
+		    if(supercollection == null) {
+			Element supercollection_element = root.getOwnerDocument().createElement(StaticStrings.COLLECTION_ELEMENT);
+			supercollection = new SuperCollection(supercollection_element);
+			supercollection.setName(collection_name);
+			addSuperCollection(supercollection);
+		    }
+		    else {
+			supercollections.remove(supercollection);
+		    }
+		    supercollection.setAssigned(true);
+		}
+	    }
+	    // Any collections left in the temporary list have been unselected, so delete them
+	    for(int j = supercollections.size(); j > 0; j--) {
+		SuperCollection supercollection = (SuperCollection) supercollections.get(j - 1);
+		removeSuperCollection(supercollection);
+	    } 
+	}
+
+	private void buildModel() {
+	    // We start by building a model of the installed collections.
+	    collection_checklist_model = new ArrayList();
+	    File gsdl_collection_directory = new File(Utility.getCollectionDir(Gatherer.config.gsdl_path));
+	    current_coll_name = Gatherer.c_man.getCollection().getName();
+	    File[] possible_collections = gsdl_collection_directory.listFiles();
+	    for(int i = 0; possible_collections != null && i < possible_collections.length; i++) {
+		// The simpliest case is if the directory etc/collect.cfg file and a metadata/ in it. Thus it can easily be built upon.
+		File collect_cfg_file = new File(possible_collections[i], Utility.CONFIG_DIR);
+		if(collect_cfg_file.exists()) {
+		    CollectionConfiguration collect_cfg = new CollectionConfiguration(collect_cfg_file);
+		    String collection_title = collect_cfg.getName();
+		    String collection_name = possible_collections[i].getName();
+		    // We do have to block the model collection.
+		    if(!collection_title.equals("**title**")) {
+			Entry entry = new Entry(collection_title);
+			entry.setProperty(collection_name);
+			// Check if a collection with this name exists. The current collection is always selected.
+			entry.setSelected(getSuperCollection(collection_name) != null || collection_name.equals(current_coll_name));
+			entry.setFixed(collection_name.equals(current_coll_name));
+			collection_checklist_model.add(entry);
+			entry = null;
+			
+		    }
+		    collection_name = null;
+		    collection_title = null;
+		    collect_cfg = null;
+		}
+		collect_cfg_file = null;
+	    }
+	    possible_collections = null;
+	    gsdl_collection_directory = null;
+	    // Sort the result.
+	    Collections.sort(collection_checklist_model);
 	}
     }
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/TranslationView.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/TranslationView.java	(revision 4932)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/TranslationView.java	(revision 4932)
@@ -0,0 +1,581 @@
+/**
+ *#########################################################################
+ *
+ * A component of the Gatherer application, part of the Greenstone digital
+ * library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: John Thompson, Greenstone Digital Library, University of Waikato
+ *
+ * Copyright (C) 1999 New Zealand Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *########################################################################
+ */
+package org.greenstone.gatherer.cdm;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.*;
+import org.greenstone.gatherer.Dictionary;
+import org.greenstone.gatherer.Gatherer;
+import org.greenstone.gatherer.cdm.CollectionConfiguration;
+import org.greenstone.gatherer.cdm.CollectionDesignManager;
+import org.greenstone.gatherer.cdm.CollectionMeta;
+import org.greenstone.gatherer.cdm.CollectionMetaManager;
+import org.greenstone.gatherer.cdm.Control;
+import org.greenstone.gatherer.cdm.Index;
+import org.greenstone.gatherer.cdm.LanguageListCellRenderer;
+import org.greenstone.gatherer.cdm.LanguageManager;
+import org.greenstone.gatherer.cdm.SubcollectionIndex;
+
+/** This class provides a graphical interface to allow a user to quickly and conviently (ie all in one place) translate the text fragments associated with general metadata and indexes into each of the assigned languages in the collection. It should provide clear controls for the editing of these text fragments, plus clear indicate what languages still need further translation, which it will do through a combination of coloring and other visual indicators.
+ * @author John Thompson, Greenstone Digital Library, University of Waikato
+ * @version 2.3
+ */
+public class TranslationView {
+
+    private Control controls;
+
+    static final private Dimension LABEL_SIZE = new Dimension(225,25);
+    static final private int LANGUAGE_WIDTH = 75;
+    static final private String GENERAL_STR = "General:";
+    static final private String INDEX_STR = "Index:";
+    static final private String PARTITION_STR = "Partitions:";
+
+    public TranslationView() {
+	Gatherer.println("TranslationView: Initialized.");
+    }
+
+    public void destroy() {
+	if(controls != null) {
+	    controls.destroy();
+	    controls = null;
+	}
+    }
+
+    public Control getControls() {
+	if(controls == null) {
+	    controls = new TranslationControl();
+	}
+	return controls;
+    }
+
+    private String get(String key) {
+	if(key.indexOf(".") == -1) {
+	    key = "CDM.TranslationManager." + key;
+	}
+	return Gatherer.dictionary.get(key);
+    }
+
+    private Object[] getFeaturesList() {
+	ArrayList features_model = new ArrayList();
+
+	// Add the indexes
+	ArrayList indexes = CollectionDesignManager.index_manager.getIndexes();
+	int indexes_size = indexes.size();
+	for(int j = 0; j < indexes_size; j++) {
+	    Object bob = new BobTheMagicalComparableWrapper(indexes.get(j));
+	    if(!features_model.contains(bob)) {
+		features_model.add(bob);
+	    }
+	}
+
+	// We also add subindexes.
+	ArrayList subindexes = CollectionDesignManager.subcollectionindex_manager.getSubcollectionIndexes();
+	int subindexes_size = subindexes.size();
+	for(int j = 0; j < subindexes_size; j++) {
+	    Object bob = new BobTheMagicalComparableWrapper(subindexes.get(j));
+	    if(!features_model.contains(bob)) {
+		features_model.add(bob);
+	    }
+	}
+
+	// Finally we add any remaining metadata as general
+	ArrayList general_metadata = CollectionDesignManager.collectionmeta_manager.getMetadata();
+	for(int i = 0; i < general_metadata.size(); i++) {
+	    CollectionMeta metadata = (CollectionMeta) general_metadata.get(i);
+	    if(!metadata.isSpecial()) {
+		Object bob = new BobTheMagicalComparableWrapper(metadata.getName());
+		if(!features_model.contains(bob)) {
+		    features_model.add(bob);
+		}
+	    }
+	}
+
+	Collections.sort(features_model);
+
+	return features_model.toArray();
+    }
+
+    private class BobTheMagicalComparableWrapper
+	implements Comparable {
+	private Object content;
+	private String text;
+	BobTheMagicalComparableWrapper(Object content) {
+	    this.content = content;
+	}
+	public int compareTo(Object object) {
+	    if(object == null) {
+		object = "";
+	    }
+	    if(text == null) {
+		toString();
+	    }
+	    return text.compareTo(object.toString());
+	}
+
+	/** Equals is used by contains and since we want to prevent double up of metadata we compare differently than in compareTo. */
+	public boolean equals(Object object) {
+	    return (object != null && content.toString().equals(((BobTheMagicalComparableWrapper)object).getContent().toString()));
+	}
+
+	public Object getContent() {
+	    return content;
+	}
+
+	public String getName() {
+	    if(content instanceof Index) {
+		return CollectionConfiguration.STOP_CHARACTER + ((Index)content).getID();
+	    }
+	    else if (content instanceof SubcollectionIndex) {
+	    	return CollectionConfiguration.STOP_CHARACTER + ((SubcollectionIndex)content).getID();
+	    }
+	    else {
+		return content.toString();
+	    }
+	}
+
+	public String toString() {
+	    if(text == null) {
+		String temp = content.toString();
+		if(temp.indexOf(CollectionConfiguration.SPACE_CHARACTER) != -1) {
+		    temp = temp.substring(0, temp.indexOf(CollectionConfiguration.SPACE_CHARACTER));
+		}
+		if(content instanceof Index) {
+		    text = INDEX_STR + temp;
+		}
+		else if(content instanceof SubcollectionIndex) {
+		    text = PARTITION_STR + temp;
+		}
+		else {
+		    text = GENERAL_STR + temp;
+		}
+	    }
+	    return text;
+	}
+    }
+
+    private class TranslationControl
+	extends JPanel 
+	implements Control {
+
+	private boolean ignore_event = false;
+	private FragmentTableModel fragment_table_model;
+	private JComboBox language_combobox;
+	private JList features_list;
+	private JTable fragment_table;
+	private JTextArea default_area;
+	private JTextArea translation_area;
+
+	TranslationControl() {
+	    fragment_table_model = new FragmentTableModel(new ArrayList());
+	    // Creation
+	    JPanel header_panel = new JPanel();
+	    JLabel title_label = new JLabel(get("Title"));
+	    title_label.setHorizontalAlignment(JLabel.CENTER);
+	    JTextArea instruction_area = new JTextArea(get("Instructions"));
+	    instruction_area.setEditable(false);
+	    instruction_area.setLineWrap(true);
+	    instruction_area.setRows(3);
+	    instruction_area.setWrapStyleWord(true);
+
+	    JPanel center_panel = new JPanel();
+
+	    JPanel selection_panel = new JPanel();
+
+	    JPanel component_selection_panel = new JPanel();
+	    JLabel component_label = new JLabel(get("Affected_Features"));
+	    features_list = new JList(getFeaturesList());
+	    features_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+	    JPanel fragment_selection_panel = new JPanel();
+	    JLabel fragment_label = new JLabel(get("Assigned_Fragments"));
+
+	    fragment_table = new JTable(fragment_table_model);
+	    fragment_table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
+	    fragment_table.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    fragment_table.setColumnSelectionAllowed(false);
+	    fragment_table.setDefaultRenderer(Object.class, new FragmentTableRenderer());
+	    fragment_table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+	    JScrollPane table_scroll = new JScrollPane(fragment_table);
+	    table_scroll.getViewport().setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    table_scroll.setOpaque(true);
+
+	    JPanel south_panel = new JPanel();
+
+	    JPanel text_panel = new JPanel();
+
+	    JPanel language_panel = new JPanel();
+	    JLabel language_label = new JLabel(get("Language"));
+	    language_label.setPreferredSize(LABEL_SIZE);
+	    language_combobox = new JComboBox(CollectionDesignManager.language_manager.getLanguageCodes().toArray());
+	    language_combobox.setRenderer(new LanguageListCellRenderer());
+
+	    JPanel default_text_panel = new JPanel();
+	    JLabel default_label = new JLabel(get("Default_Text"));
+	    default_area = new JTextArea();
+	    default_area.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+	    default_area.setEditable(false);
+	    default_area.setLineWrap(true);
+	    default_area.setWrapStyleWord(true);
+
+	    JPanel translated_text_panel = new JPanel();
+	    JLabel translation_label = new JLabel(get("Translation"));
+	    translation_area = new JTextArea();
+	    translation_area.setBackground(Gatherer.config.getColor("coloring.disabled", false));
+	    translation_area.setEnabled(false);
+	    translation_area.setLineWrap(true);
+	    translation_area.setWrapStyleWord(true);
+
+	    // Connection
+	    language_combobox.addActionListener(new LanguageActionListener());
+	    translation_area.getDocument().addDocumentListener(new TranslationDocumentListener());
+	    features_list.addListSelectionListener(new FeaturesListSelectionListener());
+	    fragment_table.getSelectionModel().addListSelectionListener(new FragmentListSelectionListener());
+	    // Layout
+	    header_panel.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
+	    header_panel.setLayout(new BorderLayout());
+	    header_panel.add(title_label, BorderLayout.NORTH);
+	    header_panel.add(new JScrollPane(instruction_area), BorderLayout.CENTER);
+
+	    component_selection_panel.setLayout(new BorderLayout());
+	    component_selection_panel.add(component_label, BorderLayout.NORTH);
+	    component_selection_panel.add(new JScrollPane(features_list), BorderLayout.CENTER);
+
+	    fragment_selection_panel.setLayout(new BorderLayout());
+	    fragment_selection_panel.add(fragment_label, BorderLayout.NORTH);
+	    fragment_selection_panel.add(table_scroll, BorderLayout.CENTER);
+
+	    selection_panel.setLayout(new GridLayout(2,1,0,5));
+	    selection_panel.add(component_selection_panel);
+	    selection_panel.add(fragment_selection_panel);
+
+	    default_text_panel.setLayout(new BorderLayout());
+	    default_text_panel.add(default_label, BorderLayout.NORTH);
+	    default_text_panel.add(new JScrollPane(default_area), BorderLayout.CENTER);
+
+	    translated_text_panel.setLayout(new BorderLayout());
+	    translated_text_panel.add(translation_label, BorderLayout.NORTH);
+	    translated_text_panel.add(new JScrollPane(translation_area), BorderLayout.CENTER);
+
+	    text_panel.setLayout(new GridLayout(1,2,5,0));
+	    text_panel.add(default_text_panel);
+	    text_panel.add(translated_text_panel);
+
+	    language_panel.setLayout(new BorderLayout());
+	    language_panel.add(language_label, BorderLayout.WEST);
+	    language_panel.add(language_combobox, BorderLayout.CENTER);
+
+	    south_panel.setLayout(new BorderLayout());
+	    south_panel.add(language_panel, BorderLayout.NORTH);
+	    south_panel.add(text_panel, BorderLayout.CENTER);
+
+	    center_panel.setLayout(new GridLayout(2,1,0,5));
+	    center_panel.add(selection_panel);
+	    center_panel.add(south_panel);
+
+	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	    setLayout(new BorderLayout());
+	    add(header_panel, BorderLayout.NORTH);
+	    add(center_panel, BorderLayout.CENTER);
+	}
+
+	public void destroy() {
+	}
+
+	public void gainFocus() {
+	    // Rebuild the features model, just incase features have been added or removed.
+	    if(features_list != null) {
+		Object selected_feature = features_list.getSelectedValue();
+		features_list.setListData(getFeaturesList());
+		if(selected_feature != null) {
+		    features_list.setSelectedValue(selected_feature, true);
+		}
+	    }
+	}
+
+	public void loseFocus() {
+	}
+
+	private class FeaturesListSelectionListener
+	    implements ListSelectionListener {
+	    public void valueChanged(ListSelectionEvent event) {
+		if(!event.getValueIsAdjusting()) {
+		    if(!ignore_event) { // And it shouldn't ever be.
+			ignore_event = true;
+			///ystem.err.println("FeaturesListSelectionListener");
+			if(!features_list.isSelectionEmpty()) {
+			    // Determine the features name. Remember to remove our helpful source prefix (delimited by ':').
+			    BobTheMagicalComparableWrapper selected_feature = (BobTheMagicalComparableWrapper)features_list.getSelectedValue();
+			    // Retrieve the default language translation, or otherwise the first match, to be default text.
+			    CollectionMeta default_metadata = CollectionDesignManager.collectionmeta_manager.getMetadatum(selected_feature.getName());
+			    if(default_metadata != null) {
+				default_area.setText(default_metadata.getValue());
+			    }
+			    // Update the text fragment table.
+			    fragment_table.clearSelection();
+			    fragment_table_model.setData(CollectionDesignManager.collectionmeta_manager.getMetadata(selected_feature.getName()));
+			    selected_feature = null;
+			    // Now we check whatever value is currently selected in the combobox to see if it is valid to add.
+			    String language_name = (String) language_combobox.getSelectedItem();
+			    int index = fragment_table_model.getMetadataIndexByLanguage(language_name);
+			    if(index != -1) {
+				CollectionMeta metadata = fragment_table_model.getMetadata(index);
+				fragment_table.setRowSelectionInterval(index, index);
+				translation_area.setText(metadata.getValue());
+				metadata = null;
+			    }
+			    else {
+				translation_area.setText("");
+			    }
+			    // Update and enable the text area
+			    translation_area.setEnabled(true);
+			    translation_area.setBackground(Gatherer.config.getColor("coloring.editable", false));
+			}
+			else {
+			    default_area.setText("");
+			    fragment_table.clearSelection();
+			    fragment_table_model.setData(new ArrayList());
+			    translation_area.setText("");
+			    // Update and disable the text area
+			    translation_area.setText("");
+			    translation_area.setEnabled(false);
+			    translation_area.setBackground(Gatherer.config.getColor("coloring.disabled", false));
+			}
+			ignore_event = false;
+		    }
+		}
+	    }
+	}
+
+	private class FragmentListSelectionListener
+	    implements ListSelectionListener {
+	    public void valueChanged(ListSelectionEvent event) {
+		if(!event.getValueIsAdjusting() && !ignore_event) {
+		    ignore_event = true;
+		    ///ystem.err.println("FragmentListSelectionListener");
+		    int index = -1;
+		    if((index = fragment_table.getSelectedRow()) != -1) {
+			// A row has been selected. Retrieve the collection metadata.
+			CollectionMeta metadata = fragment_table_model.getMetadata(index);
+			if(metadata != null) {
+			    // Update the combobox to show the current language.
+			    String language = metadata.getLanguage();
+			    ///ystem.err.println("Searching for the language: " + language_name);
+			    for(int i = 0; i < language_combobox.getItemCount(); i++) {
+				if(language_combobox.getItemAt(i).toString().equals(language)) {
+				    language_combobox.setSelectedIndex(i);
+				}
+			    }
+			}
+			translation_area.setText(metadata.getValue());
+		    }
+		    else {
+			translation_area.setText("");
+		    }
+		    ignore_event = false;
+		}
+	    }
+	}
+
+	private class FragmentTableModel
+	    extends AbstractTableModel {
+
+	    private ArrayList metadata;
+
+	    FragmentTableModel(ArrayList metadata) {
+		this.metadata = metadata;
+	    }
+	    public int getRowCount() {
+		return metadata.size();
+	    }
+	    public int getColumnCount() {
+		return 2;
+	    }
+	    public String getColumnName(int column) {
+		return get("Column_Name_" + column);
+	    }
+
+	    public CollectionMeta getMetadata(int row) {
+		if(0 <= row && row < metadata.size()) {
+		    return (CollectionMeta) metadata.get(row);
+		}
+		return null;
+	    }
+
+	    public int getMetadataIndexByLanguage(String language) {
+		for(int i = 0; i < metadata.size(); i++) {
+		    CollectionMeta metadatum = (CollectionMeta) metadata.get(i);
+		    if(language.equals(metadatum.getLanguage())) {
+			return i;
+		    }
+		}
+		return -1;
+	    }
+
+	    public Object getValueAt(int row, int column) {
+		if(0 <= row && row < metadata.size()) {
+		    CollectionMeta metadatum = (CollectionMeta) metadata.get(row);
+		    switch(column) {
+		    case 0:
+			return metadatum.getLanguage();
+		    default:
+			return metadatum.getValue();
+		    }
+		}
+		return "#Error";
+	    }
+
+	    public void remove(int index) {
+		metadata.remove(index);
+		fireTableRowsDeleted(index, index);
+	    }
+
+	    public void setData(ArrayList metadata) {
+		this.metadata = metadata;
+		fireTableDataChanged();
+	    }
+
+	    public int size() {
+		return metadata.size();
+	    }
+	}
+
+	private class FragmentTableRenderer
+	    extends DefaultTableCellRenderer {
+	    /** Retrieve the component used to rubberstamp the table based on the given parameters.
+	     * @param table The <strong>JTable</strong> to rendered.
+	     * @param value The <strong>Object</strong> whose representation will be shown in the table row.
+	     * @param isSelected <i>true</i> iff the column/row is selected.
+	     * @param hasFocus <i>true</i> iff the column/row is in focus.
+	     * @param row An <i>int</i> specifying the target row.
+	     * @param column An <i>int</i> specifying the target column.
+	     * @see org.greenstone.gatherer.cdm.Language
+	     */
+	    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+		JComponent component = (JComponent) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+		if(isSelected) {
+		    component.setBackground(Gatherer.config.getColor("coloring.workspace_heading_background", false));
+		}
+		else {
+		    component.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
+		}
+		return component;
+	    }
+	}
+
+	private class LanguageActionListener
+	    implements ActionListener {
+	    public void actionPerformed(ActionEvent event) {
+		if(!ignore_event) {
+		    ignore_event = true;
+		    ///ystem.err.println("LanguageActionListener");
+		    // If the newly selected language value already exists in the fragment table, and that row isn't the one selected, then select that row the text area.
+		    String language_name = language_combobox.getSelectedItem().toString();
+		    int index = fragment_table_model.getMetadataIndexByLanguage(language_name);
+		    if(index != -1) {
+			CollectionMeta metadata = fragment_table_model.getMetadata(index);
+			fragment_table.setRowSelectionInterval(index, index);
+			translation_area.setText(metadata.getValue());
+		    }
+		    else {
+			fragment_table.clearSelection();
+			translation_area.setText("");
+		    }
+		    // Ready the text area
+		    translation_area.setEnabled(true);
+		    translation_area.setBackground(Gatherer.config.getColor("coloring.editable", false));
+		    ignore_event = false;
+		}
+	    }
+	}
+
+	private class TranslationDocumentListener
+	    implements DocumentListener {
+	    /** Gives notification that an attribute or set of attributes changed. */
+	    public void changedUpdate(DocumentEvent e) {
+		update();
+	    }
+	    /** Gives notification that there was an insert into the document. */
+	    public void insertUpdate(DocumentEvent e) {
+		update();
+	    }
+	    /** Gives notification that a portion of the document has been removed. */
+	    public void removeUpdate(DocumentEvent e) {
+		update();
+	    }
+	    /** The text area has changed in some way. Given that this can only happed when we are editing or adding a text fragment we better respond appropriately. */
+	    private void update() {
+		if(!ignore_event) {
+		    ignore_event = true;
+		    String translation_text = translation_area.getText();
+		    // If there is no current fragment table selection, but translation text is not an empty string, then this is a new fragment. Create new collection metadata, refresh the fragment table, then ensure that the new metadata is selected. Remember that it is the selected metadata to which changes will be applied.
+		    int index = -1;
+		    if((index = fragment_table.getSelectedRow()) == -1 && translation_text.length() > 0) {
+			BobTheMagicalComparableWrapper selected_feature = (BobTheMagicalComparableWrapper)features_list.getSelectedValue();
+			String language = (String) language_combobox.getSelectedItem();
+			CollectionMeta metadatum = new CollectionMeta(selected_feature.getName(), language);
+			metadatum.setValue(translation_text);
+			CollectionDesignManager.collectionmeta_manager.addMetadatum(metadatum);
+			fragment_table.clearSelection();
+			ArrayList metadata = CollectionDesignManager.collectionmeta_manager.getMetadata(selected_feature.getName());
+			fragment_table_model.setData(metadata);
+			index = fragment_table_model.getMetadataIndexByLanguage(language);
+			fragment_table.setRowSelectionInterval(index, index);
+			metadatum = null;
+			language = null;
+			selected_feature = null;
+		    }
+		    else {
+			CollectionMeta metadata = fragment_table_model.getMetadata(index);
+			// If the text is now empty, and there was previously a metadata fragment selected, then remove the fragments metadata and update the table
+			if(translation_text.length() == 0) {
+			    ///ystem.err.println("Removing collectionmeta! fireTableRowsDeleted(" + index + "," + index + ")");
+			    CollectionDesignManager.collectionmeta_manager.removeMetadata(metadata);
+			    fragment_table_model.remove(index);
+			}
+			// Otherwise update the fragment metadata and ask the table to repaint the appropriate row
+			else {
+			    ///ystem.err.println("Updating collectionmeta!");
+			    metadata.setValue(translation_text);
+			    fragment_table_model.fireTableRowsUpdated(index, index);
+			}
+			metadata = null;
+		    }
+		    translation_text = null;
+		    ignore_event = false;
+		}
+	    }
+	}
+    }
+
+}
+
+
Index: /trunk/gli/src/org/greenstone/gatherer/cdm/custom/CustomAZList.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/cdm/custom/CustomAZList.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/cdm/custom/CustomAZList.java	(revision 4932)
@@ -45,5 +45,4 @@
 import org.greenstone.gatherer.cdm.ClassifierManager;
 import org.greenstone.gatherer.cdm.CustomClassifier;
-import org.greenstone.gatherer.cdm.Language;
 import org.greenstone.gatherer.file.FileNode;
 import org.greenstone.gatherer.msm.ElementWrapper;
@@ -629,5 +628,5 @@
 	}
 	else {
-	    String language_code = Gatherer.config.interface_language.getCode();
+	    String language_code = Gatherer.config.interface_language;
 	    hidden_mde = hidden_mds.addElement(element.toString().replace('.','_'), language_code);
 	    found = false;
Index: /trunk/gli/src/org/greenstone/gatherer/checklist/CheckList.java
===================================================================
--- /trunk/gli/src/org/greenstone/gatherer/checklist/CheckList.java	(revision 4931)
+++ /trunk/gli/src/org/greenstone/gatherer/checklist/CheckList.java	(revision 4932)
@@ -219,4 +219,5 @@
     private class CheckListListener 
 	extends MouseAdapter {
+
 	//private Entry previous_checkbox = null;
 	/** Called whenever the mouse is clicked over our list. We find the nearest checkbox and change its state.
@@ -227,26 +228,8 @@
 	    int index = list.locationToIndex(e.getPoint());
 	    Entry checkbox = (Entry)list.getModel().getElementAt(index);
-	    // If this is the same checkbox as was recently selected, change the tick.
-	    if (list.isSelectedIndex(index)) {
-		if(!checkbox.isFixed()) {
-		    checkbox.setSelected(!checkbox.isSelected());
-		}
-		checkbox.grabFocus();
-	    }
-	    /*
-	      if(list.isSelectedIndex(index) && checkbox == previous_checkbox) {
-	      if(!checkbox.isFixed()) {
-	      checkbox.setSelected(!checkbox.isSelected());
-	      }
-	      checkbox.grabFocus();
-	      }
-	      // Otherwise the selection has just changed, so select the new checkbox
-	      else if(list.isSelectedIndex(index)) {
-	      previous_checkbox = checkbox;
-	      }
-	      else {
-	      previous_checkbox = null;
-	      }
-	    */
+	    if(!checkbox.isFixed()) {
+		checkbox.setSelected(!checkbox.isSelected());
+	    }
+	    checkbox.grabFocus();
 	}
     }
