/**
 *#########################################################################
 *
 * 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.
 *
 * <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
 * 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.
 *
 * <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
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.collection;

import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.cdm.CollectionDesignManager;
import org.greenstone.gatherer.cdm.CollectionMeta;
import org.greenstone.gatherer.cdm.CollectionMetaManager;
import org.greenstone.gatherer.cdm.CommandTokenizer;
import org.greenstone.gatherer.cdm.BuildTypeManager;
import org.greenstone.gatherer.cdm.CollectionConfiguration;
import org.greenstone.gatherer.greenstone.Classifiers;
import org.greenstone.gatherer.greenstone.LocalGreenstone;
import org.greenstone.gatherer.greenstone.LocalLibraryServer;
import org.greenstone.gatherer.greenstone.Plugins;
import org.greenstone.gatherer.greenstone3.ServletConfiguration;
import org.greenstone.gatherer.gui.LockFileDialog;
import org.greenstone.gatherer.gui.ModalProgressPopup;
import org.greenstone.gatherer.gui.WarningDialog;
import org.greenstone.gatherer.metadata.DocXMLFileManager;
import org.greenstone.gatherer.metadata.FilenameEncoding;
import org.greenstone.gatherer.metadata.MetadataChangedListener;
import org.greenstone.gatherer.metadata.MetadataSet;
import org.greenstone.gatherer.metadata.MetadataSetManager;
import org.greenstone.gatherer.metadata.MetadataXMLFileManager;
import org.greenstone.gatherer.metadata.ProfileXMLFileManager;
import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
import org.greenstone.gatherer.shell.GShell;
import org.greenstone.gatherer.shell.GShellEvent;
import org.greenstone.gatherer.shell.GShellListener;
import org.greenstone.gatherer.shell.GShellProgressMonitor;
import org.greenstone.gatherer.util.Codec;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;

import org.greenstone.gatherer.util.GS3ServerThread;

/** This class manages many aspects of the collection, from its creation via scripts, data access via methods and its importing and building into the final collection. It is also responsible for firing appropriate event when significant changes have occured within the collection, and for creating a new metadata set manager as necessary.
 * @author John Thompson
 * @version 2.3
 */
public class CollectionManager
    implements GShellListener, MetadataChangedListener
{
    /** Are we currently in the process of building? */
    static private boolean building = false;
    /** Are we currently in the process of importing? */
    static private boolean importing = false;
    /** Are we currently in the process of scheduling? */ 
    static private boolean scheduling = false; 
    /** The objects listening for CollectionContentsChanged events. */
    static private ArrayList collection_contents_changed_listeners = new ArrayList();
    /** The collection this manager is managing! */
    static private Collection collection = null;
    /** The collection tree (used in both Gather and Enrich panes). */
    static private CollectionTree collection_tree = null;
  /** The collection tree used in Files pane */
  static private FullCollectionTree full_collection_tree = null;
    /** The collection tree model for Gather. */
    static private CollectionTreeModel collection_tree_model = null;
    /** The collection tree model for Files. */
    static private FullCollectionTreeModel full_collection_tree_model = null;
    /** An inner class listener responsible for noting tree changes and resetting saved when they occur. */
    static private FMTreeModelListener fm_tree_model_listener = null;
    /** The monitor responsible for parsing the build process. */
    static private GShellProgressMonitor build_monitor = null;
    /** The monitor responsible for parsing the import process. */
    static private GShellProgressMonitor import_monitor = null;
    /** The monitor responsible for parsing the scheduler process. */ 
    static private GShellProgressMonitor schedule_monitor = null; 
    ///** The monitor responsible for parsing the fedora collection delete process. */ 
    //static private GShellProgressMonitor fedora_coldelete_monitor = null;

    static private String delete_collection_name = null;

    /** The name of the standard lock file. */
    static final public String LOCK_FILE = "gli.lck";

    /** Used to indicate the source of the message is the file collection methods. */
    static final public int COLLECT   = 3;
    /** Used to indicate the source of the message is the building methods. */
    static final public int BUILDING  = 5;
    /** Used to indicate the source of the message is in the scheduling methods...?  */ 
    static final public int SCHEDULING = 7; 

    /** To store the path to the perl scripts. In the case of local Greenstone servers, 
     * this will be the local bin/script folder. */
    static private String scriptPath = "";

    private Boolean canPreview = null;
    // Temporarily instantiated member variable: for use in inner class.
    // It has to be made a member variable for changes friendly to GLI automated testing to
    // be able to invoke code involving the lockFileDialog on the Event Dispatch Thread, EDT.
    LockFileDialog lockFileDialog = null;
	
    /** Constructor. */
    public CollectionManager() {
	// Initialisation.
	this.building = false;
	this.importing = false;
	this.scheduling = false; 
	this.collection = null;

	MetadataXMLFileManager.addMetadataChangedListener(this);

	// If using a remote Greenstone server, delete the local collect directory because it will be out of date
	if (Gatherer.isGsdlRemote) {
	    System.err.println("Deleting user's local collect directory...");
	    Utility.delete(new File(Gatherer.getCollectDirectoryPath()));
	    System.err.println("Done.");
	    new File(Gatherer.getCollectDirectoryPath()).mkdirs();

	    scriptPath = ""; // remote greenstone: scriptPath will be determined on remote server side
	} 
	else { // local greenstone case: scripts are inside bin/script
	    scriptPath = LocalGreenstone.getBinScriptDirectoryPath();
	}
    }
    
    
    static public void addCollectionContentsChangedListener(CollectionContentsChangedListener listener)
    {
	collection_contents_changed_listeners.add(listener);
    }


    /** This method calls the builcol.pl scripts via a GShell so as to not lock up the processor.
     * @see org.greenstone.gatherer.Configuration
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.Collection
     * @see org.greenstone.gatherer.gui.BuildOptions
     * @see org.greenstone.gatherer.shell.GShell
     * @see org.greenstone.gatherer.shell.GShellListener
     * @see org.greenstone.gatherer.shell.GShellProgressMonitor
     * @see org.greenstone.gatherer.util.Utility
     */
    public void buildCollection()
    {
    
	DebugStream.println("In CollectionManager.buildCollection(), CollectionDesignManager.isCompleteBuild(): " + CollectionDesignManager.isCompleteBuild());
	DebugStream.println("Is event dispatch thread: " + SwingUtilities.isEventDispatchThread());
	building = true;

	// Generate the buildcol.pl command
	ArrayList command_parts_list = new ArrayList();
	if (!Gatherer.isGsdlRemote) {
	    command_parts_list.add(Configuration.perl_path);
	    command_parts_list.add("-S");
	}

	if (Configuration.fedora_info.isActive()) {
	    command_parts_list.add(scriptPath + "g2f-buildcol.pl");

	    command_parts_list.add("-hostname");
	    command_parts_list.add(Configuration.fedora_info.getHostname());

	    command_parts_list.add("-port");
	    command_parts_list.add(Configuration.fedora_info.getPort());

	    command_parts_list.add("-username");
	    command_parts_list.add(Configuration.fedora_info.getUsername());

	    command_parts_list.add("-password");
	    command_parts_list.add(Configuration.fedora_info.getPassword());

	    command_parts_list.add("-protocol");
	    command_parts_list.add(Configuration.fedora_info.getProtocol());

	}
	else {
		
		if ( !CollectionDesignManager.isCompleteBuild() && CollectionDesignManager.index_manager.isLucene() ) {
			command_parts_list.add(scriptPath + "incremental-buildcol.pl");
			CollectionDesignManager.setBuildcolWasFull(false);
		} else {
			command_parts_list.add(scriptPath + "full-buildcol.pl");
			CollectionDesignManager.setBuildcolWasFull(true);
		}
	}

	command_parts_list.add("-gli");
	command_parts_list.add("-language");
	command_parts_list.add(Configuration.getLanguage());

	// For now, for solr collections, we pass in -activate
	// but GLI won't do moving building to index
	
	//if(CollectionManager.isSolrCollection()) {
	    command_parts_list.add("-activate");
	    //}

	if(Gatherer.GS3) {
	    command_parts_list.add("-site");
	    command_parts_list.add(Configuration.site_name);

	    // Whether remote or local, contact the correct (current) library
	    // to actually get this collection activated
	    command_parts_list.add("-library_name");
	    String library_servlet_name = Configuration.servlet_path;
	    if(library_servlet_name.charAt(0) == '/') {
		library_servlet_name = library_servlet_name.substring(1);
	    }
	    command_parts_list.add(library_servlet_name);
	}

	if(!Gatherer.isGsdlRemote) {
	    command_parts_list.add("-collectdir");
	    command_parts_list.add(getCollectDirectory()); // <../collect/>
	}	

	String[] build_options = collection.build_options.getValues();
	for (int i = 0; i < build_options.length; i++) {
	    command_parts_list.add(build_options[i]);
	}

	command_parts_list.add(collection.getGroupQualifiedName(false)); // (colgroup/)colname

	// Run the buildcol.pl and
	String[] command_parts = (String[]) command_parts_list.toArray(new String[0]);
	GShell shell = new GShell(command_parts, GShell.BUILD, BUILDING, this, build_monitor, GShell.GSHELL_BUILD);
	shell.addGShellListener(Gatherer.g_man.create_pane);
	Gatherer.g_man.create_pane.setGShell(shell); // allow the create pane to call shell.cancel()
	shell.addGShellListener(Gatherer.g_man.format_pane);
	shell.start();
	
    }

    /*probably repeating alot of work, but I want to keep this separate... wendy*/
    public void scheduleBuild()
    {
	DebugStream.println("In CollectionManager.scheduleBuild(), CollectionDesignManager.isCompleteBuild(): " + CollectionDesignManager.isCompleteBuild()); 
	DebugStream.println("Is event dispatch thread: " + SwingUtilities.isEventDispatchThread()); 

	ArrayList sched_list = new ArrayList(); 
	if (!Gatherer.isGsdlRemote) {
	    sched_list.add(Configuration.perl_path);
	    sched_list.add("-S");
	}
	sched_list.add(scriptPath + "schedule.pl"); 
	sched_list.add("-colname"); 
	sched_list.add(collection.getName()); 
	sched_list.add("-gli"); 

	// First, generate the import.pl command, also converting to a string
	// Generate the import.pl command
	ArrayList import_list = new ArrayList();
	if (!Gatherer.isGsdlRemote) {
	    import_list.add(Configuration.perl_path);
	    import_list.add("-S");
	}

	String cmdPrefix = CollectionDesignManager.isCompleteBuild() ? "full-" : "incremental-";
	import_list.add(scriptPath + cmdPrefix + "import.pl");
	import_list.add("-language");
	import_list.add(Configuration.getLanguage());

	if(Gatherer.GS3) {
	    import_list.add("-site");
	    import_list.add(Configuration.site_name);
	}
	
	if(!Gatherer.isGsdlRemote) {
	    import_list.add("-collectdir");
	    import_list.add(getCollectDirectory());
	}
	// import.pl does not at present need to know the library/servlet_path
	// whether in a remote or local GS server situation

	String[] import_options = collection.import_options.getValues();
	int i = 0; 
	for (i = 0; i < import_options.length; i++) {
	    import_list.add(import_options[i]);
	}

	import_list.add(collection.getGroupQualifiedName(false)); // (colgroup/)colname

	String[] import_parts = (String[]) import_list.toArray(new String[0]); 
	String command = "";
	i = 0; 
	for (i = 0; i < import_parts.length-1; i++) {
	    command = command + import_parts[i] + " "; 
	} 
	command = command + import_parts[i]; 

	sched_list.add("-import"); 
	sched_list.add("\"" + command + "\""); 

	// Generate the buildcol.pl command, also converting to a string
	ArrayList build_list = new ArrayList();

	// i'm not doing this in schedule.pl right now - should i be? 
	if (!Gatherer.isGsdlRemote) {
	    build_list.add(Configuration.perl_path);
	    build_list.add("-S");
	}

	String buildType = (new CollectionMeta( CollectionDesignManager.collect_config.getBuildType() )).getValue(CollectionMeta.TEXT);
	if ( !CollectionDesignManager.isCompleteBuild() && buildType.equals( BuildTypeManager.BUILD_TYPE_LUCENE ) ) {
		build_list.add(scriptPath + "incremental-buildcol.pl");
	} else {
		build_list.add(scriptPath + "full-buildcol.pl");
	}

	build_list.add("-language");
	build_list.add(Configuration.getLanguage());

	if(Gatherer.GS3) {
	    build_list.add("-site");
	    build_list.add(Configuration.site_name);

	    // Whether GS is remote or local, contact the correct (current) library
	    // to actually get this collection activated
	    build_list.add("-library_name");
	    String library_servlet_name = Configuration.servlet_path;
	    if(library_servlet_name.startsWith("/")) {
		library_servlet_name = library_servlet_name.substring(1);
	    }
	    build_list.add(library_servlet_name);	    
	}

	if(!Gatherer.isGsdlRemote) {
	    build_list.add("-collectdir");
	    build_list.add(getCollectDirectory());
	}

	String[] build_options = collection.build_options.getValues();
	for (i = 0; i < build_options.length; i++) {
	    build_list.add(build_options[i]);
	}

	build_list.add(collection.getGroupQualifiedName(false)); // (colgroup/)colname
	
	//build actual string
	String[] build_parts = (String[]) build_list.toArray(new String[0]);
	String command2 = "";
        for(i = 0; i < build_parts.length-1; i++) {
            command2 = command2 + build_parts[i] + " ";
        }
	command2 = command2 + build_parts[i]; 

	sched_list.add("-build"); 
	sched_list.add("\"" + command2 + "\""); 
	
	//next, the scheduling frequency goes here
	String[] schedule_options = collection.schedule_options.getValues();  
	for(i = 0; i < schedule_options.length; i++) { 
	    sched_list.add(schedule_options[i]); 
	}

	//now, hope it will run. ;) 
	String[] sched_parts = (String[]) sched_list.toArray(new String[0]); 

    	GShell shell = new GShell(sched_parts, GShell.SCHEDULE, SCHEDULING, this, schedule_monitor, GShell.GSHELL_SCHEDULE);
	shell.addGShellListener(Gatherer.g_man.create_pane);
	Gatherer.g_man.create_pane.setGShell(shell); // allow the create pane to call shell.cancel()
	shell.addGShellListener(Gatherer.g_man.format_pane);
	shell.start();
    }

    /** Calling c_man.built() is expensive in isGsdlRemote case. If you don't need built status recalculated
     * call this method to get the last built() result,
     * as the private canPreview variable stores the recalculated result after each call to built().
     * For example, this method should especially be used by the FormatPane for determining if the Preview
     * button should be available as queried after every character has been input. The Preview Button should
     * be available in such cases if the collection's last-known status indicated that an index folder from
     * the most recent or prior build process was available.
     * @return A boolean indicating the last known built status of the collection.
     */
    public boolean previewAvailable() {
	if(canPreview == null) {
	    built(); // this will set the variable canPreview, if not already set by CreatePane's calls to built()
	}
	return this.canPreview.booleanValue();
    }
    
    /** Used to determine whether the currently active collection has been built.
     * @return A boolean indicating the built status of the collection.
     */
    public boolean built() {
	boolean been_built = false;
	
	if(collection != null) {
	    // Determine if the collection has been built by looking for the build.cfg (gs2) 
	    // buildConfig.xml (gs3) or export.inf (fedora) file
	    String file_name = "";
	    
	    if (Configuration.fedora_info != null && Configuration.fedora_info.isActive()) { // FLI case
		// Fedora build
		//file_name = getLoadedCollectionArchivesDirectoryPath() + "import.inf";
		//file_name = getLoadedCollectionExportDirectoryPath() + "export.inf"; // export.pl no longer generates this
		file_name = getLoadedCollectionExportDirectoryPath() + "archiveinf-doc.gdb";
	    } else { 
		// GLI is running, check if it's greenstone 3 or greenstone 2
		if (Gatherer.GS3) { // GS3 GLI
		    file_name = getLoadedCollectionIndexDirectoryPath() + Utility.BUILD_CONFIG_XML;
		}
		else { // greenstone 2 GLI
		    file_name = getLoadedCollectionIndexDirectoryPath() + Utility.BUILD_CFG;		   
		}
	    }
	    File test_file = new File(file_name);

	    if(Gatherer.isGsdlRemote) {
		been_built = Gatherer.remoteGreenstoneServer.exists(collection.getGroupQualifiedName(false), test_file);
	    } else {
		been_built = test_file.exists();
	    }
	}

	// store recalculated result
	this.canPreview = been_built ? Boolean.FALSE : Boolean.TRUE;

	return been_built; //or: this.canPreview.booleanValue();
    }

    /** Used to determine whether the currently active collection has been imported.
     * @return A boolean indicating the imported status of the collection.
     */
    public boolean imported() {
        if ( collection != null ) {
            String file_name = getLoadedCollectionDirectoryPath() + "archives";
            File test_file = new File(file_name);
            return test_file.exists();
        }
        return false;
    }

    /** a test method to see if we can delete a directory/file - returns false is the file or any of the contents of a directory cannot be deleted */
    static private boolean canDelete(File file)
    {
	if (!file.isDirectory()) {
	    return file.canWrite();
	}
	File [] file_list = file.listFiles();
	for (int i=0; i<file_list.length; i++) {
	    if (!canDelete(file_list[i])) {
		return false;
	    }
	}
	return true;
    }


    /** Called to close the current collection and remove its lock file.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.Collection
     * @see org.greenstone.gatherer.util.Utility
     */
    public void closeCollection() {
	if (collection == null) {
	    return;
	}
	DebugStream.println("Close collection: " + collection.getName());

	// Remove the lock on this file, then remove the collection.
	File lock_file = new File(getLoadedCollectionDirectoryPath() + LOCK_FILE);
	lock_file.delete();
	if (lock_file.exists()) {
	    System.err.println("Warning: Lockfile was not successfully deleted.");
	}

	// Remove the lock file on the server
	if (Gatherer.isGsdlRemote) {
	    Gatherer.remoteGreenstoneServer.deleteCollectionFile(collection.getGroupQualifiedName(false), lock_file);
	}
	
	//  release the current build_log file - else we're unable to delete the last closed collection 
	// until another collection is opened and its opening a new build_log closes this old one
	Gatherer.g_man.create_pane.options_pane.closeCurrentLogDocument();

	MetadataSetManager.clearMetadataSets();
	MetadataXMLFileManager.clearMetadataXMLFiles();
	DocXMLFileManager.clearDocXMLFiles();
	ProfileXMLFileManager.clearProfileXMLFile();
	
	collection.destroy();
	collection = null;
	collection_tree_model = null;
        full_collection_tree_model = null;
	//Configuration.setCollectionConfiguration(null);
	Gatherer.refresh(Gatherer.COLLECTION_CLOSED);
	if (Gatherer.g_man != null) {
	    Gatherer.g_man.updateUI();  // !!! Necessary?
	}
    }

//This method is no longer used in gs3 since the modification of CollectionConfiguration.java
//    public void convertToGS3Collection() {
//	// Generate the convert_coll_from_gs2.pl command
//	ArrayList command_parts_list = new ArrayList();
//	if ((Utility.isWindows()) && (!Gatherer.isGsdlRemote)) {
//	    command_parts_list.add(Configuration.perl_path);
//	    command_parts_list.add("-S");
//	}
//	command_parts_list.add(Configuration.getGS3ScriptPath() + "convert_coll_from_gs2.pl");
//
//	command_parts_list.add("-site");
//	command_parts_list.add(Configuration.site_name);
//
//	command_parts_list.add("-collectdir");
//	command_parts_list.add(getCollectDirectory());
//    	command_parts_list.add(collection.getGroupQualifiedName(false)); // (colgroup/)colname
//
//	// Run the convert_coll_from_gs2.pl command
//	String[] command_parts = (String[]) command_parts_list.toArray(new String[0]);
//	GShell process = new GShell(command_parts, GShell.CONVERT, COLLECT, this, null, GShell.GSHELL_CONVERT);
//	process.addGShellListener(this);
//	process.run(); // Don't bother threading this... yet
//       
//    }

    /** When basing a new collection on an existing one, we need to copy 
     *	over some extra directories: all except import, archives, building, index 
     * really we just want images, macros, perllib, but there may also be eg style, or other dirs.
     */
    private boolean copyExtraBaseCollStuff(File new_coll_dir, File base_coll_dir) {
	if (!new_coll_dir.isDirectory() || !base_coll_dir.isDirectory()) {
	    return false;
	}
	DebugStream.println("Copying extra dirs from the base collection");
	
	
	File subdirs[] = base_coll_dir.listFiles();
	for (int i = 0; subdirs != null && i < subdirs.length; i++) {
	  File subdir = subdirs[i];
	  if (subdir.isDirectory()) {
	    String dir_name = subdir.getName();
	    // ignore those we don't need, (archives, buildng, index) and 
	    // those we are handling in another place (import, etc, metadata)
	    if (dir_name.startsWith ("import") || dir_name.startsWith("archives") || dir_name.startsWith("building") || dir_name.startsWith("index") || dir_name.startsWith("etc") || dir_name.startsWith("metadata") || dir_name.startsWith("log") || dir_name.startsWith("tmp")) {
	      continue;
	    }
	    try {
	      // copy the directory
	      File new_coll_subdir = new File(new_coll_dir, dir_name);
	      new_coll_subdir.mkdirs();
	      // Copy with force overwrite, since it's a new collection that's currently
	      // being created and won't have anything in it but at most modelcol stuff
	      Gatherer.f_man.getQueue().copyDirectoryContents(subdir, new_coll_subdir, true);
	    }
	    catch (Exception e) {
	      DebugStream.println("Couldn't copy over the" + subdir+" dir from the base collection: "+e.toString());
	    }
	  }
	}

	return true;
    }
  
    /** Used to set the current collection to the given collection. Note that this call should -always- be proceeded by a ready call, and if the collection is ready and the saved flag is unset then the user should be prompted to save. Also note that this method creates yet another GShell to run buildcol.pl.
     * @param description a description of the collection as a String
     * @param email the email address of the author/maintainer as a String
     * @param name the short name of the collection, which will subsequently be used to refer to this particular collection, as a String
     * @param title the longer title of the collection as a String
     * @param base_collection_directory if the user has chosen to base their new collection on an existing one, this is the directory where this base collection can be found, as a File, otherwise its null
     * @param metadata_sets if the user has decided to select several metadata sets with which to initially populate the GLI then this is an ArrayList of metadata set file names, otherwise its null
     */
    public void createCollection(String description, String email, String name, String title, File base_collection_directory, ArrayList metadata_sets)
    {
	// Display a modal progress popup to indicate that the collection is being loaded
	ModalProgressPopup create_collection_progress_popup = new ModalProgressPopup(Dictionary.get("CollectionManager.Creating_Collection"), Dictionary.get("CollectionManager.Creating_Collection_Please_Wait"));
	create_collection_progress_popup.display();

	// Create the collection on a separate thread so the progress bar updates correctly
	// Note for GLI automated testing: the switch to the EDT (event dispatch thread) is
	// placed deeper in the code being called, inside the CreateCollTask thread (rather
	// than replacing the "new Thread.start()" code pattern below with a call here to
	// Gatherer.invokeOnEDT_replacesThreadStart() in entirety). Switching to the EDT later,
	// at the appropriate juncture, allows the create collection progress bar to update well.
	(new CreateCollectionTask(description, email, name, title, base_collection_directory, metadata_sets, create_collection_progress_popup)).start();
    }


    private class CreateCollectionTask
	extends Thread
    {
	private String description = null;
	private String email = null;
	private String name = null;
	private String title = null;
	private File base_collection_directory = null;
	private ArrayList metadata_sets = null;
	private ModalProgressPopup create_collection_progress_popup = null;

	public CreateCollectionTask(String description, String email, String name, String title, File base_collection_directory, ArrayList metadata_sets, ModalProgressPopup create_collection_progress_popup)
	{
	    this.description = description;
	    this.email = email;
	    this.name = name;
	    this.title = title;
	    this.base_collection_directory = base_collection_directory;
	    this.metadata_sets = metadata_sets;
	    this.create_collection_progress_popup = create_collection_progress_popup;
	}

	public void run()
	{
	    //System.err.println("Running in thread name: " + Thread.currentThread().getName()
	    //	       + " - class " + Thread.currentThread().getClass().getName());
	    createCollectionInternal(description, email, name, title, base_collection_directory, metadata_sets);
	    create_collection_progress_popup.close();
	}
    }


    private void createCollectionInternal(String description, String email, String name, String title, File base_collection_directory, ArrayList metadata_sets)
    {
	try {
	    // first make sure that the collect directory exists
	    File collect_dir = new File(getDefaultCollectDirectory());
	    if (!collect_dir.exists()) {
		collect_dir.mkdirs();
	    }

	    // Create the new collection
	    makeCollection(name, email);

	    // Check that the collection has been created successfully
	    String collection_directory_path = getCollectionDirectoryPath(name);
	    if (!new File(collection_directory_path).exists()) {
		// If there is no collection directory then the creation was unsuccessful, or cancelled
	      
		return;
	    }

	    // Check for the existence of the collection configuration file
	    String file_name = ((Gatherer.GS3 == true)? Utility.COLLECTION_CONFIG_XML : Utility.COLLECT_CFG);
	    File collect_cfg_file = new File(collection_directory_path + "etc" + File.separator + file_name);

	    if (!collect_cfg_file.exists()) {
		System.err.println("Error: no " + file_name + " file has been created!");
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Create_Collection_With_Reason", Dictionary.get("CollectionManager.No_Config_File")), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); 
		return;
	    }
 	    
	    // ACTIVE_DIR/log/
	    File log_dir = new File(collection_directory_path + "log");
	    log_dir.mkdirs();

	    // Make sure an import folder exists
	    File collection_import_directory = new File(collection_directory_path + "import");
	    if (!collection_import_directory.exists()) {
		collection_import_directory.mkdirs();
		if (Gatherer.isGsdlRemote) {
		    Gatherer.remoteGreenstoneServer.newCollectionDirectory(name, collection_import_directory);
		}
	    }

	    // Now create the collection object around the directory.
	    collection = new Collection(new File(collection_directory_path, "gli.col"));

	    // for remote case, scheduling causes an Exception on creating a new collection that 
	    // can't be recovered from. For GS3, it doesn't work since it it trying to access etc/main.cfg
	    if (canDoScheduling()) {
		scheduling();
	    }
	    	    
	    MetadataSetManager.clearMetadataSets();
	    MetadataXMLFileManager.clearMetadataXMLFiles();
	    DocXMLFileManager.clearDocXMLFiles();

	    // Import default metadata sets, if any
	    // for (int i = 0; metadata_sets != null && i < metadata_sets.size(); i++) {
	    // importMetadataSet((MetadataSet) metadata_sets.get(i));
	    // }

	    ProfileXMLFileManager.loadProfileXMLFile(new File(collection_directory_path + "metadata"));

	    // Before creating the CollectionDesignManager check if we are basing it upon some other collection
	    if (base_collection_directory != null) {
		DebugStream.println("Basing new collection on existing one: " + base_collection_directory);

		// If we're using a remote Greenstone server, download the collection shell to get the files needed
		if (Gatherer.isGsdlRemote) {
		    String base_collection_name = base_collection_directory.getName();
		    Gatherer.remoteGreenstoneServer.downloadCollection(base_collection_name);
		}

		collection.setBaseCollection(base_collection_directory.getAbsolutePath());
		// copy over other needed directories
		copyExtraBaseCollStuff(new File(collection_directory_path), base_collection_directory);

		// Try to import any existing metadata sets for this collection
		// Look in base_collection_directory/metadata and import any metadata sets found.
		File base_metadata_directory = new File(base_collection_directory, "metadata");
		ArrayList base_metadata_sets = MetadataSetManager.listMetadataSets(base_metadata_directory);
		if (base_metadata_sets != null) {
		    for (int i = 0; i < base_metadata_sets.size(); i++) {
			importMetadataSet((MetadataSet) base_metadata_sets.get(i));
		    }
		}
		else {
		    DebugStream.println("This base collection has no metadata directory.");
		}

		// Now we update our collect.cfg
		DebugStream.println("Copy and update " + file_name + " from base collection.");

		if (Gatherer.GS3 == true) {
		    updateCollectionConfigXML(new File(base_collection_directory, Utility.CONFIG_GS3_FILE),
					new File(collection_directory_path, Utility.CONFIG_GS3_FILE)); 
		} else {
		    updateCollectionCFG(new File(base_collection_directory, Utility.CONFIG_FILE),
					new File(collection_directory_path, Utility.CONFIG_FILE),
					description, email, title);
		}
	    }
	    else {
	      // only load metadata sets here if we have not based the collection on any other.
	      // Load the default metadata sets
	      addDefaultMetadataSets();
	      
	      // Make sure we always have the extracted metadata set
	      addRequiredMetadataSets();
	    }
	    
	    collection.cdm = new CollectionDesignManager(new File(getLoadedCollectionCfgFilePath()));

	    // We always set title and description here rather than calling mkcol.pl with Unicode arguments

	    // First though, if we based this collection on another collection and thereyby inherited multilingual
	    // collection names and descriptions, clear those before setting description and name to new values.
	    ArrayList colname_metas = collection.cdm.collectionmeta_manager.getMetadata(StaticStrings.COLLECTIONMETADATA_COLLECTIONNAME_STR); // retrieves all languages
	    ArrayList coldesc_metas = collection.cdm.collectionmeta_manager.getMetadata(StaticStrings.COLLECTIONMETADATA_COLLECTIONEXTRA_STR);
	    for(int i = 0; i < colname_metas.size(); i++) {
		CollectionMeta colname_meta = (CollectionMeta)colname_metas.get(i);
		colname_meta.setValue("");
	    } 
	    for(int i = 0; i < coldesc_metas.size(); i++) {
		CollectionMeta coldesc_meta = (CollectionMeta)coldesc_metas.get(i);
		coldesc_meta.setValue("");
	    }

	    // set the collection name and description (in the default language)
	    CollectionMeta collection_name_collectionmeta = collection.cdm.collectionmeta_manager.getMetadatum(StaticStrings.COLLECTIONMETADATA_COLLECTIONNAME_STR); // retrieves default language
	    collection_name_collectionmeta.setValue(title);
	    CollectionMeta collection_extra_collectionmeta = collection.cdm.collectionmeta_manager.getMetadatum(StaticStrings.COLLECTIONMETADATA_COLLECTIONEXTRA_STR);
	    collection_extra_collectionmeta.setValue(description);

	    // Now that we have a CDM, update several settings, such as if we created this collection by basing it on another, set it as public automatically. This update is done to the internal xml structure which may be saved into collect.cfg or collectionConfig.xml accordingly.
	    if (base_collection_directory != null) {
		// Update the creator and maintainer
		CollectionMeta creator_collectionmeta = new CollectionMeta(collection.cdm.collect_config.getCreator());
		creator_collectionmeta.setValue(email);
		creator_collectionmeta = null;
		CollectionMeta maintainer_collectionmeta = new CollectionMeta(collection.cdm.collect_config.getMaintainer());
		maintainer_collectionmeta.setValue(email);
		maintainer_collectionmeta = null;

		// All collections based on others are automatically public
		CollectionMeta public_collectionmeta = new CollectionMeta(collection.cdm.collect_config.getPublic());
		public_collectionmeta.setValue(StaticStrings.TRUE_STR);
		public_collectionmeta = null;

		// Finally reset the icons
		CollectionMeta icon_collection_collectionmeta = collection.cdm.collectionmeta_manager.getMetadatum(StaticStrings.COLLECTIONMETADATA_ICONCOLLECTION_STR);
		icon_collection_collectionmeta.setValue(StaticStrings.EMPTY_STR);
		icon_collection_collectionmeta = null;
		CollectionMeta icon_collection_small_collectionmeta = collection.cdm.collectionmeta_manager.getMetadatum(StaticStrings.COLLECTIONMETADATA_ICONCOLLECTIONSMALL_STR);
		icon_collection_small_collectionmeta.setValue(StaticStrings.EMPTY_STR);
		icon_collection_small_collectionmeta = null;
	    }

	    saveCollection();

	    // Create a lock file
	    createLockFile(new File(collection_directory_path, LOCK_FILE));

	    // We're done. Let everyone know.
	    Gatherer.refresh(Gatherer.COLLECTION_OPENED);
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
    }

    private void scheduling() 
	throws Exception
    {
	//try to obtain email address of collection owner if it exists... 
	String stmp = Configuration.getEmail(); 
	if(stmp != null) {
	    collection.schedule_options.setValue("toaddr", false, Configuration.getEmail());
	}
	
	//The next few items deal with updating the SMTP server, and the to: and from: addresses
	//from main.cfg and the collection configuration. if no changes are made, or the 
	//values are result to NULL, any existing values are kept. 

	//try to obtain email address of Greenstone installation webmaster for - used to indicate "sender". 
	File mcfg = new File(LocalGreenstone.getDirectoryPath() + File.separator + "etc" + File.separator + "main.cfg");
//	BufferedReader maincfg = new BufferedReader(new FileReader(mcfg));
        BufferedReader maincfg = new BufferedReader(new InputStreamReader(new FileInputStream(mcfg), "UTF-8"));
	    stmp = "";
	    String fromaddr = ""; 
	    while((stmp = maincfg.readLine()) != null) { 
		if(stmp.startsWith("maintainer")) {
		       fromaddr = stmp.substring(10); //length of MailServer 
		   fromaddr = fromaddr.trim();  
		   break; 
		}
	    }
	    maincfg.close(); 
	    if(!fromaddr.equals("NULL") && !fromaddr.equals("null")) {
		collection.schedule_options.setValue("fromaddr", false, fromaddr);	    
	    }

	    //try to obtain an smtp server address from main.cfg. If that fails,
	    //try mail.server if an email address exists. If that fails,
	    //maybe a message to set attribute in main.cfg? 
	    //i'm pretty sure there exists functionality to do this, but 
	    //i'll finish this faster if I just wrote it


	    //maincfg = new BufferedReader(new FileReader(mcfg));
            maincfg = new BufferedReader(new InputStreamReader(new FileInputStream(mcfg), "UTF-8"));
	    String smtptmp = "NULL";
	    while((stmp = maincfg.readLine()) != null) { 
		if(stmp.startsWith("MailServer")) {
		       smtptmp = stmp.substring(10); //length of MailServer 
		   smtptmp = smtptmp.trim(); 
		   break; 
		}
	    }
            maincfg.close();

            //try if lookup fails
            if(smtptmp.equals("NULL") || smtptmp.equals("null")) {
		String email2=fromaddr; 
		if(!email2.equals("NULL") && !email2.equals("null")) {
		    int loc = email2.indexOf('@'); 
		    email2 = email2.substring(loc+1); 
		    smtptmp = "mail."+email2;
	        }
            }
	    if(!smtptmp.equals("NULL") && !smtptmp.equals("null")) { 
		collection.schedule_options.setValue("smtp", false, smtptmp);
	    }

    }


    private void createLockFile(File lock_file)
    {
	try {
	    Document default_lockfile = XMLTools.parseXMLFile("xml/" + LOCK_FILE, true);

	    String user_name = System.getProperty("user.name");

	    if (Gatherer.isWebswing) {
		// More helpful to update this to be the username that
		// is ogged into GS3 to edit the collection
		user_name = Gatherer.webswingAuthenticator.getUsername();		
	    }
	    
	    Element person_element = (Element) XMLTools.getNodeFromNamed(default_lockfile.getDocumentElement(), "User");
	    person_element.appendChild(default_lockfile.createTextNode(user_name));
	    person_element = null;
	    user_name = null;

	    String machine_name = Utility.getMachineName();
	    Element machine_element = (Element) XMLTools.getNodeFromNamed(default_lockfile.getDocumentElement(), "Machine");
	    machine_element.appendChild(default_lockfile.createTextNode(machine_name));
	    machine_element = null;
	    machine_name = null;

	    String date_time = Utility.getDateString();
	    Element date_element = (Element) XMLTools.getNodeFromNamed(default_lockfile.getDocumentElement(), "Date");
	    date_element.appendChild(default_lockfile.createTextNode(date_time));
	    date_element = null;
	    date_time = null;

	    XMLTools.writeXMLFile(lock_file, default_lockfile);
	}
	catch (Exception exception) {
	    DebugStream.printStackTrace(exception);
	}
    }


    public boolean deleteCollection(String collection_name)
    {
	// First we must release the collection from the local library, if it's running
	if (LocalLibraryServer.isRunning() == true) {
	    LocalLibraryServer.releaseCollection(collection_name);
	}

	// if we're running FLI, it's a fedora collection, and we need to call a special
	// delete script to delete the collection and all its documents from the fedora
	// repository and from fedora-gsearch's index.
	if (Configuration.fedora_info.isActive()) {

	    // Generate the buildcol.pl command
	    ArrayList command_parts_list = new ArrayList();
	    if (!Gatherer.isGsdlRemote) {
		command_parts_list.add(Configuration.perl_path);
		command_parts_list.add("-S");
	    }

	    command_parts_list.add(scriptPath + "g2f-deletecol.pl");

	    command_parts_list.add("-hostname");
	    command_parts_list.add(Configuration.fedora_info.getHostname());

	    command_parts_list.add("-port");
	    command_parts_list.add(Configuration.fedora_info.getPort());

	    command_parts_list.add("-username");
	    command_parts_list.add(Configuration.fedora_info.getUsername());

	    command_parts_list.add("-password");
	    command_parts_list.add(Configuration.fedora_info.getPassword());

	    command_parts_list.add("-protocol");
	    command_parts_list.add(Configuration.fedora_info.getProtocol());

	    command_parts_list.add("-gli");
	    command_parts_list.add("-language");
	    command_parts_list.add(Configuration.getLanguage());
	    
	    if(Gatherer.GS3) {
		command_parts_list.add("-site");
		command_parts_list.add(Configuration.site_name);
	    }

	    if(!Gatherer.isGsdlRemote) {
		command_parts_list.add("-collectdir");
		command_parts_list.add(getCollectDirectory()); // <../collect/>
	    }
	    
	    command_parts_list.add(collection_name);
	    
	    // Run the g2f-deletecol.pl to remove the fedora collection
	    String[] command_parts = (String[]) command_parts_list.toArray(new String[0]);
	    GShell shell = new GShell(command_parts, GShell.DELETE, COLLECT, this, null, GShell.GSHELL_FEDORA_COLDELETE); // fedora_coldelete_monitor set to null
	    shell.start();

	    delete_collection_name = collection_name;
	    return true; // we assume it succeeded for now, wait until the process returns
	    // and then proceed to delete the collection directory
	}
	else { // not a fedora collection

	    // Delete the collection on the server if we're using a remote Greenstone
	    if (Gatherer.isGsdlRemote) {
		Gatherer.remoteGreenstoneServer.deleteCollection(collection_name);
	    }
	    
	    // if Greenstone3, need to deactivate the collection on the server
	    if (Gatherer.GS3) {
		Gatherer.configGS3Server(Configuration.site_name, ServletConfiguration.DEACTIVATE_COMMAND + collection_name);
	    }	    
	    // Now delete the collection directory
	    return Utility.delete(new File(getCollectionDirectoryPath(collection_name)));
	}
    }


    public void fireFileAddedToCollection(File file)
    {
	// Send the event off to all the CollectionContentsChangedListeners
	for (int i = 0; i < collection_contents_changed_listeners.size(); i++) {
	    ((CollectionContentsChangedListener) collection_contents_changed_listeners.get(i)).fileAddedToCollection(file);
	}
    }


    /** Retrieve the current collection.
     * @return The <strong>Collection</strong> itself.
     */
    public Collection getCollection() {
	return collection;
    }


    /** Returns the absolute filename of the specified collection's directory.
     */
    static public String getCollectionDirectoryPath(String collection_name)
    {
	return Gatherer.getCollectDirectoryPath() + collection_name + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's archives directory.
     */
    static public String getLoadedCollectionArchivesDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "archives" + File.separator;
    }

    /** Returns the absolute filename of the loaded collection's archives_keepold directory.
     * Part of the file-level document-version (fldv) history mechanism.
     */
    static public String getLoadedCollectionArchivesKeepOldDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "archives_keepold" + File.separator;
    }
    

    /** Returns the absolute filename of the loaded collection's export directory.
     */
    static public String getLoadedCollectionExportDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "export" + File.separator;
    }



    /** Returns the absolute filename of the loaded collection's building directory.
     */
    static public String getLoadedCollectionBuildingDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "building" + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's collect.cfg file.
     */
    static public String getLoadedCollectionCfgFilePath()
    {
	String path = (Gatherer.GS3 == true)? Utility.COLLECTION_CONFIG_XML : Utility.COLLECT_CFG;
	return getLoadedCollectionEtcDirectoryPath() + path;
    }


    /** Returns the absolute filename of the loaded collection's directory.
     */
    static public String getLoadedCollectionDirectoryPath()
    {
	return collection.getCollectionDirectory().getPath() + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's etc directory.
     */
    static public String getLoadedCollectionEtcDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "etc" + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's .col file.
     */
    static public String getLoadedCollectionColFilePath()
    {
	return getLoadedCollectionDirectoryPath() + "gli.col";
    }


    /** Returns the absolute filename of the loaded collection's images directory.
     */
    static public String getLoadedCollectionImagesDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "images" + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's import directory.
     */
    static public String getLoadedCollectionImportDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "import" + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's index directory.
     */
    static public String getLoadedCollectionIndexDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "index" + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's log directory.
     */
    static public String getLoadedCollectionLogDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "log" + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's macros directory.
     */
    static public String getLoadedCollectionMacrosDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "macros" + File.separator;
    }


    /** Returns the absolute filename of the loaded collection's metadata directory.
     */
    static public String getLoadedCollectionMetadataDirectoryPath()
    {
	return getLoadedCollectionDirectoryPath() + "metadata" + File.separator;
    }


    /** Returns the (group-qualified) name of the loaded collection with 
     * OS-dependent file separator.
     */
    static public String getLoadedCollectionName()
    {
	return CollectionManager.getLoadedCollectionName(false);
    }

    /** Returns the (group-qualified) name of the loaded collection with 
     * OS-dependent space separator.
     * @url true if url-type forward slashes, false if OS-dependent filesystem slashes.
     */
    static public String getLoadedCollectionName(boolean url)
    {
	if (collection != null) {
	    //return collection.getName();
	    return collection.getGroupQualifiedName(url);
	}

	return null;
    }

    /** @return the group-name of any collection (stripped of any subcol). */
    static public String getLoadedCollectionGroupName()
    {
	if (collection != null) {
	    return collection.getCollectionGroupName();
	}

	return null;
    }

    /** @return the subname of any collection (stripped of any collection-group). */
    static public String getLoadedCollectionTailName()
    {
	if (collection != null) {
	    return collection.getCollectionTailName();
	}

	return null;
    }

    /** Returns the "collectionGroupName/collectionName" or just the collectionName 
     *  depending on whether the collection is part of a collection group or not.
     * If url = true, then returns the sub-path as a URL (containing / only), 
     * and if url = false, then the sub-path is returned in filepath form 
     * (\ or /, depending on the OS).
     */
    static public String getLoadedGroupQualifiedCollectionName(boolean url)
    {
	if (collection != null) {
	    return collection.getGroupQualifiedName(url);
	}

	return null;
    }

    public CollectionTree getCollectionTree()
    {
	if (collection_tree == null) {
          collection_tree = new CollectionTree(collection_tree_model, true);
	}

	return collection_tree;
    }
     public FullCollectionTree getFullCollectionTree()
     {
         if (full_collection_tree == null) {
           full_collection_tree = new FullCollectionTree(full_collection_tree_model, true);
         }

         return full_collection_tree;
     }


    /** Retrieve the tree model associated with the import folder of the current collection. */
    public CollectionTreeModel getCollectionTreeModel()
    {
	if (collection_tree_model == null && collection != null) {
	    // Use the import directory to generate a new CollectionTreeModel
	    collection_tree_model = new CollectionTreeModel(new CollectionTreeNode(new File(getLoadedCollectionImportDirectoryPath())));
	    // Ensure that the manager is a change listener for the tree.
	    if (fm_tree_model_listener == null) {
		fm_tree_model_listener = new FMTreeModelListener();
	    }
	    collection_tree_model.addTreeModelListener(fm_tree_model_listener);
	}
	return collection_tree_model;
    }
    /** Retrieve the tree model associated with the current collection's top level folder. */
    public FullCollectionTreeModel getFullCollectionTreeModel()
    {
	if (full_collection_tree_model == null && collection != null) {
	    // Use the collection's toplevel  directory to generate a new FullCollectionTreeModel
	    full_collection_tree_model = new FullCollectionTreeModel(new FullCollectionTreeNode(new File(getLoadedCollectionDirectoryPath())));
	    // Ensure that the manager is a change listener for the tree.
            // this is marking the collection changed for any change in the tree nodes
            // need to think about if it applies here...
	    if (fm_tree_model_listener == null) {
		fm_tree_model_listener = new FMTreeModelListener();
	    }
	    full_collection_tree_model.addTreeModelListener(fm_tree_model_listener);
	}
	return full_collection_tree_model;
    }


    /** This method when called, creates a new GShell in order to run the import.pl script.
     * @see org.greenstone.gatherer.Configuration
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.gui.BuildOptions
     * @see org.greenstone.gatherer.shell.GShell
     * @see org.greenstone.gatherer.shell.GShellListener
     * @see org.greenstone.gatherer.shell.GShellProgressMonitor
     * @see org.greenstone.gatherer.util.Utility
     */
    public void importCollection() {
	importing = true;

	if (!saved()) {
	    DebugStream.println("CollectionManager.importCollection().forcesave");
	    import_monitor.saving();
	    saveCollection();
	}

	DebugStream.println("CollectionManager.importCollection()");
	DebugStream.println("Is event dispatch thread: " + SwingUtilities.isEventDispatchThread());
	//check that we can remove the old index before starting import
	File index_dir = new File(getLoadedCollectionIndexDirectoryPath());
	if (index_dir.exists()) {
	    DebugStream.println("Old Index = " + index_dir.getAbsolutePath()+", testing for deletability");
	    if (!canDelete(index_dir)) {
		// tell the user
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Delete_Index"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); 
		// tell the gui manager
		// a message for the building log 
		GShellEvent event = new GShellEvent(this, 0, GShell.IMPORT, Dictionary.get("CollectionManager.Cannot_Delete_Index_Log"), GShell.ERROR);
		Gatherer.g_man.create_pane.message(event);
		event = new GShellEvent(this, 0, GShell.IMPORT, "", GShell.ERROR);
		Gatherer.g_man.create_pane.processComplete(event);
		importing = false;
		return;
	    }
	}

	// Generate the import.pl command
	ArrayList command_parts_list = new ArrayList();
	if (!Gatherer.isGsdlRemote) {
	    command_parts_list.add(Configuration.perl_path);
	    command_parts_list.add("-S");
	}

	if (Configuration.fedora_info != null && Configuration.fedora_info.isActive()) {
	    command_parts_list.add(scriptPath + "g2f-import.pl");

	    command_parts_list.add("-hostname");
	    command_parts_list.add(Configuration.fedora_info.getHostname());

	    command_parts_list.add("-port");
	    command_parts_list.add(Configuration.fedora_info.getPort());

	    command_parts_list.add("-username");
	    command_parts_list.add(Configuration.fedora_info.getUsername());

	    command_parts_list.add("-password");
	    command_parts_list.add(Configuration.fedora_info.getPassword());

	    command_parts_list.add("-protocol");
	    command_parts_list.add(Configuration.fedora_info.getProtocol());
	}
	else {
	    String cmdPrefix = null;
	    if ( CollectionDesignManager.isCompleteBuild() ) {
		cmdPrefix = "full-";
		CollectionDesignManager.setImportWasFull( true );
	    } else {
		cmdPrefix = "incremental-";
		CollectionDesignManager.setImportWasFull( false );
	    }
	    command_parts_list.add(scriptPath + cmdPrefix + "import.pl"); // scriptPath already set according to local or remote case
	}

	command_parts_list.add("-gli");
	command_parts_list.add("-language");
	command_parts_list.add(Configuration.getLanguage());

	if(Gatherer.GS3) {
	    command_parts_list.add("-site");
	    command_parts_list.add(Configuration.site_name);
	}

	if(!Gatherer.isGsdlRemote) {
	    command_parts_list.add("-collectdir");
	    command_parts_list.add(getCollectDirectory());
	}	

	String[] import_options = collection.import_options.getValues();
	for (int i = 0; i < import_options.length; i++) {
        System.err.println( "Tacking on option: " + import_options[i] );
	    command_parts_list.add(import_options[i]);
	}

	command_parts_list.add(collection.getGroupQualifiedName(false)); // (colgroup/)colname

	// Run the import.pl command
	String[] command_parts = (String[]) command_parts_list.toArray(new String[0]);
	GShell shell = new GShell(command_parts, GShell.IMPORT, BUILDING, this, import_monitor, GShell.GSHELL_IMPORT);
	//shell.setEventProperty("is_incremental", Boolean.toString(is_incremental));
	shell.addGShellListener(Gatherer.g_man.create_pane);
	Gatherer.g_man.create_pane.setGShell(shell); // allow the create pane to call shell.cancel()
        shell.addGShellListener(Gatherer.g_man.format_pane);
	shell.start();
	DebugStream.println("CollectionManager.importCollection().return");

	importing = false;
    }


    public void importMetadataSet(MetadataSet external_metadata_set)
    {
	// Copy the .mds file into the collection's "metadata" folder...
	File external_metadata_set_file = external_metadata_set.getMetadataSetFile();

	// ...but not if it is the redundant "hidden.mds" file
	if (external_metadata_set_file.getName().equals("hidden.mds")) {
	    return;
	}

	// ...and only if it doesn't already exist
	File metadata_set_file = new File(getLoadedCollectionMetadataDirectoryPath(), external_metadata_set_file.getName());
	if (!metadata_set_file.exists()) {
	    try {
		Gatherer.f_man.getQueue().copyFile(external_metadata_set_file, metadata_set_file, false);

		// If we're using a remote Greenstone server, upload the metadata file
		if (Gatherer.isGsdlRemote) {
		    Gatherer.remoteGreenstoneServer.uploadCollectionFile(collection.getGroupQualifiedName(false), metadata_set_file);
		}
	    }
	    catch (Exception exception) {
		DebugStream.printStackTrace(exception);
	    }

	    // Load it into the MetadataSetManager
	    MetadataSetManager.loadMetadataSet(metadata_set_file);
	}
    }
    
    /** 
     * In the case of a remote GS, this method takes care of reuploading a modified mds file after its
     * Metadata Set has been edited.
     * This method is mostly a copy of importMetadataSet() above, except no need to handle the hidden.mds
     * file as that should not even have been made available for editing in GLI.
     * Nor any need for MetadataSetManager.loadMetadataSet(File) as that was already done by the caller,
     * MetadataSetDialog.EditButtonListener.actionPerformed().
     */
    public void updateMetadataSet(MetadataSet external_metadata_set) {
	try {
		File external_metadata_set_file = external_metadata_set.getMetadataSetFile();
		File metadata_set_file = new File(getLoadedCollectionMetadataDirectoryPath(), external_metadata_set_file.getName());

		// If we're using a remote Greenstone server, upload the metadata file
		if (Gatherer.isGsdlRemote) {
		    Gatherer.remoteGreenstoneServer.uploadCollectionFile(collection.getGroupQualifiedName(false), metadata_set_file);
		}
	} catch (Exception exception) {
	    DebugStream.printStackTrace(exception);
	}
    }
    

    /** Determine if we are currently in the middle of importing (and thus, in this case, we can't allow the log writer to exit). Boy was this a mission to track down. The cascade of crap rolls out something like this: Joe Schmo clicks 'Build Collection', which calls the importCollection() method above, which in turn saves the collection with a saveTask, which fires a collectionChanged message once its finished, which drives the list of logs shown on the create pane to update, which fires a itemChanged() event to the OptionsPane who dutifully tells the current log writer thread to finish up writing (all zero lines its been asked to write) and then die. Wereapon Joe Schmo gets a pretty log to look at, but it isn't actually being written to file so the next time he tries to view it faeces hits the air motion cooling device. Joy.
     * @return true if the gli is currently importing
     */
    public boolean isImporting() {
	return importing;
    }

    
    public void reloadAfterConfigFileEdited() {
	// Copying just the part of LoadCollectionInternal below that loads the config file
	// Need to reload the *existing* config file, however. So calling custom method reloadConfig()
	
	// Don't see why this stuff here (vs LoadCollectionInternal) would need to happen in its own
	// Thread for my use: when the modal Edit > collectionConfig.xml dialog has saved and closed.
	
	// reload current collection's config
	collection.cdm.reloadConfig();

	// We're done. Let everyone know.
	DebugStream.println(Dictionary.get("CollectionManager.Loading_Successful", collection.getName()));
	Gatherer.refresh(Gatherer.COLLECTION_OPENED);
    }
    
    public void loadCollection(String collection_file_path)
    {
	// Display a modal progress popup to indicate that the collection is being loaded
	ModalProgressPopup load_collection_progress_popup = new ModalProgressPopup(Dictionary.get("CollectionManager.Loading_Collection"), Dictionary.get("CollectionManager.Loading_Collection_Please_Wait"));
	load_collection_progress_popup.display();

	// Load the collection on a separate thread so the progress bar updates correctly
	(new LoadCollectionTask(collection_file_path, load_collection_progress_popup)).start();
	// Note for GLI automated testing: the switch to the Event Dispatch Thread happens
	// deeper inside the LoadCollectionTask being executed. It is not proper to do it here
	// as switching to the EDT here causes the progressbar to not display updates properly.
    }


    private class LoadCollectionTask
	extends Thread
    {
	private String collection_file_path = null;
	private ModalProgressPopup load_collection_progress_popup = null;

	public LoadCollectionTask(String collection_file_path, ModalProgressPopup load_collection_progress_popup)
	{
	    this.collection_file_path = collection_file_path;
	    this.load_collection_progress_popup = load_collection_progress_popup;
	}

	public void run()
	{
	    loadCollectionInternal(collection_file_path);
	    load_collection_progress_popup.close();
	    Gatherer.setMenuBarEnabled(true);
	}
    }


    /** Attempts to load the given collection. Currently uses simple serialization of the collection class.
     * @param location The path to the collection as a <strong>String</strong>.
     * @see org.greenstone.gatherer.Configuration
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.Collection
     * @see org.greenstone.gatherer.util.Utility
     */
    private void loadCollectionInternal(String location)
    {
	DebugStream.println("Loading collection " + location + "...");


	// Check we have actually been given a .col file.
	if (!location.endsWith(".col")) {
	    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Not_Col_File", location), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
	    DebugStream.println("CollectionManager.loadCollection: Haven't been given a .col file.");
	    return;
	}
	
	// Check that the collection configuration file is available
	File collection_file = new File(location);

	//String collection_name = collection_directory.getName();
	String collection_name = "";		
	File collection_directory = collection_file.getParentFile();

	// To get colname = (colgroup/)coltailname, subtract Gatherer.getCollectDirectoryPath() from collection_directory:
	int index = collection_directory.getAbsolutePath().indexOf(Gatherer.getCollectDirectoryPath());	
	if(index == -1) {
	    System.err.println("*** ERROR: collection directory " + collection_directory + " is not located in collect folder: " + Gatherer.getCollectDirectoryPath());
	} else {
	    index += Gatherer.getCollectDirectoryPath().length();	    
	    collection_name = collection_directory.getAbsolutePath().substring(index);
	}

	if (Gatherer.isGsdlRemote) {
	    if (Gatherer.remoteGreenstoneServer.downloadCollection(collection_name).equals("")) {
		return;
	    }
	}
	
	// Ensure that the collection directory exists
	if (collection_directory == null || !collection_directory.exists()) {
	    // We can't open this
	    System.err.println("CollectionManager.loadCollection: No collection directory.");
	    return;
	}

	String file_str = (Gatherer.GS3)? Utility.CONFIG_GS3_FILE : Utility.CONFIG_FILE;
	final File collection_config_file = new File(collection_directory, file_str);
	if (!collection_config_file.exists()) {
	    System.err.println("CollectionManager.loadCollection: No config file.");
	    collection_directory = null;
	    return;
	}

	// Ensure that an import directory exists for this collection
	File collection_import_directory = new File(collection_directory, "import");
	if (!collection_import_directory.exists()) {
	    collection_import_directory.mkdir();
	}

	// Special case of a user trying to open an old greenstone collection.
	boolean non_gli_collection = false;
	File collection_metadata_directory = new File(collection_directory, "metadata");
	if (!collection_metadata_directory.exists()) {
	    DebugStream.println("Loading non-gatherer collection...");
	    // Show a warning message in case user wants to quit now
	    non_gli_collection = true;
	    WarningDialog legacy_dialog = new WarningDialog("warning.LegacyCollection", Dictionary.get("LegacyCollection.Title"), Dictionary.get("LegacyCollection.Message"), null, true);
	    if (legacy_dialog.display()==JOptionPane.CANCEL_OPTION) {
		legacy_dialog.dispose();
		collection_directory = null;
		return;
	    }
	    legacy_dialog.dispose();

	}

	
	// Now determine if a lock already exists on this collection.
	final File lock_file = new File(collection_file.getParentFile(), LOCK_FILE);
	final String coll_name_copy = collection_name;
	if (lock_file.exists()) {
	    Gatherer.invokeInEDT_replacesProceedInCurrThread(
	     "CollectionManager.loadCollectionInternal() - lockfile",
	     Gatherer.SYNC,
	     //Gatherer.BYPASS_WHEN_NOT_TESTING,
	     new Runnable() {	    
		 public void run() {
		     lockFileDialog = new LockFileDialog(Gatherer.g_man, coll_name_copy, lock_file);
		     //int choice = lockFileDialog.getChoice();			
		 }
	     });
	
	    if(lockFileDialog.getChoice() != LockFileDialog.YES_OPTION) {
		// user has cancelled
		collection_directory = null;
		return;
	    }
	    
	    lockFileDialog.dispose();
	    lockFileDialog = null;
	    lock_file.delete();
	}

	// now we are using gli.col - old colls may have used the collection name
	if (!collection_file.exists()) {
	  File old_coll_file = new File(collection_directory, collection_name+".col");
	  if (old_coll_file.exists()) {
	    try {
	      old_coll_file.renameTo(collection_file);
	    } catch (Exception e) {
	      DebugStream.println("Couldn't rename "+old_coll_file.getName()+" to gli.col. Will just carry on with default gli.col");
	      // but just carry on.
	    }
	  }
	}

	try {
	    // Create a lock file.
	    createLockFile(lock_file);
	    // This lock file may not have been created so check
	    if(!lock_file.canWrite()) {
		// The lock file cannot be written to. Most likely cause incorrect file permissions.
		System.err.println("Cannot write lock file!");
		String args[] = new String[2];
		args[0] = location;		
		args[1] = Dictionary.get("FileActions.Write_Not_Permitted_Message", new String[]{lock_file.getAbsolutePath()});		
		if(Gatherer.client_operating_system.toUpperCase().indexOf("WINDOWS")!=-1){		  
		  //if(Gatherer.client_operating_system.toUpperCase().indexOf("VISTA")!=-1){		    
		    args[1] += Dictionary.get("FileActions.File_Permission_Detail", new String[]{Configuration.gsdl_path, System.getProperty("user.name")});
		  //} 
		}
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Open_With_Reason", args), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
		args = null;
		return;
	    }
	    
	    // need to fix this up as currently it craps out if the .col file is not there, which is may not always be.
	    if (canDoScheduling() && collection_file.exists()) {
	      //THIS LOOKS LIKE THE BEST PLACE TO TRY AND UPDATE .col FILES FOR EXISTING COLLECTIONS...Wendy
	      // Don't need to update anything if collection_file doesn't exist yet.
		//First, see if "Schedule" exists in the XMl File... 
		//BufferedReader bir = new BufferedReader(new FileReader(collection_file));
              BufferedReader bir = new BufferedReader(new InputStreamReader(new FileInputStream(collection_file), "UTF-8"));
		boolean flag = false; 
		try {
		    String stmp = new String(); 
	       
		    while((stmp = bir.readLine()) != null) {
			stmp = stmp.trim();  
			if(stmp.equals("<Schedule>") || stmp.equals("<Schedule/>")) {
			    flag = true; 
			    break; 
			}
		    }
		    bir.close(); 
		    
		} catch (IOException ioe) {
		    DebugStream.printStackTrace(ioe);
		}
		
		//modify if old .col (i.e. no Schedule exists in XML file)
		if(!flag) {
		    File new_collection_file = new File(collection_directory.getAbsolutePath() + "/tmp.col");
		    
		    
		    //BufferedWriter bor = new BufferedWriter(new FileWriter(new_collection_file));
                    BufferedWriter bor = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new_collection_file), "UTF-8"));
		    //bir = new BufferedReader(new FileReader(collection_file));
                    bir = new BufferedReader(new InputStreamReader(new FileInputStream(collection_file), "UTF-8"));
		    
		    try { 
			String stmp = new String(); 
			while((stmp = bir.readLine()) != null) {
			    String stmp2 = stmp.trim(); 
			    if(stmp2.startsWith("<!ELEMENT Argument")) {
				bor.write("  <!ELEMENT Schedule          (Arguments*)>\n"); 			   
			    }
			    else if(stmp2.equals("</BuildConfig>")) {
				bor.write("      <Schedule/>\n"); 
			    }
			    
			    bor.write(stmp + "\n"); 
			    
			}
			bir.close(); 
			bor.close();
		    } catch (IOException ioe) {  
			DebugStream.printStackTrace(ioe);
		    }  
		    
		    //copy over tmp.col to replace 
		    try { 
			collection_file.delete();
			new_collection_file.renameTo(collection_file); 
		    } catch (Exception e) { 
			DebugStream.printStackTrace(e);
		    } 
		}
	    }
	    
	    // Open the collection file
	    this.collection = new Collection(collection_file);
	    if (collection.error) {
		collection = null;
		// Remove lock file
		if (lock_file.exists()) {
		    lock_file.delete();
		}
		throw(new Exception(Dictionary.get("CollectionManager.Missing_Config"))); // this error message does not agree with the error
	    }
	    
	    if (canDoScheduling()) {
		scheduling();
	    }
	  
	    // These may have been set in the past, but are no longer used 
	    // by GLI
	    collection.import_options.removeValue("removeold");
	    collection.import_options.removeValue("keepold");

            MetadataSetManager.clearMetadataSets();
	    MetadataSetManager.loadMetadataSets(collection_metadata_directory);

	    // Make sure we always have the extracted metadata set
	    addRequiredMetadataSets();

	    ProfileXMLFileManager.loadProfileXMLFile(collection_metadata_directory);
 
	    // If this is a non-GLI (legacy) collection, load the default metadata sets
	    if (non_gli_collection) {
		addDefaultMetadataSets();

		// Recurse the import folder tree, backing up the metadata.xml files before they are edited
		LegacyCollectionImporter.backupMetadataXMLFiles(collection_directory);
	    }

	    // Read through the metadata.xml files in the import directory, building up the metadata value trees
	    MetadataXMLFileManager.clearMetadataXMLFiles();
	    MetadataXMLFileManager.loadMetadataXMLFiles(collection_import_directory,collection.toSkimFile());

	    
	    // get rid of the previous scan through docxml files
	    DocXMLFileManager.clearDocXMLFiles();

	    if (Configuration.fedora_info.isActive()) { // FLI case
		// Read through the docmets.xml files in the export directory
		File collection_export_directory = new File(getLoadedCollectionExportDirectoryPath());
		DocXMLFileManager.loadDocXMLFiles(collection_export_directory,"docmets.xml");
	    }
	    else {
		// Read through the doc.xml files in the archives directory
		File collection_archives_directory = new File(getLoadedCollectionArchivesDirectoryPath());
		DocXMLFileManager.loadDocXMLFiles(collection_archives_directory,"doc.xml");
	    }


	    // Get a list of the collection specific classifiers and plugins
	    Classifiers.loadClassifiersList(collection_name);
	    Plugins.loadPluginsList(collection_name);

	    collection.cdm = new CollectionDesignManager(collection_config_file);
	    if (non_gli_collection) {
		// Change the classifiers to use the namespaced element names
		LegacyCollectionImporter.updateClassifiers(collection.cdm);
	    }

	    // We're done. Let everyone know.
	    DebugStream.println(Dictionary.get("CollectionManager.Loading_Successful", collection_name));
	    Gatherer.refresh(Gatherer.COLLECTION_OPENED);
	}
	catch (Exception error) {
	    // There is obviously no existing collection present.
	    DebugStream.printStackTrace(error);
	    error.printStackTrace();
	    if(error.getMessage() != null) {
		String[] args = new String[2];
		args[0] = location;		
		args[1] = error.getMessage();
		//args[1] = "The Librarian Interface does not have permission to write to... Please check file permissions.";
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Open_With_Reason", args), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
	    }
	    else {
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Open", location), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
	    }
	}

	//lock_file = null; // local variable, now final, will expire at function's end anyway
	collection_directory = null;
    }

    /** At present, scheduling only works for GS2, only when GS2 is local and only when GLI runs from
     * within a GS2 installation. This method can be adjusted as scheduling becomes available for more
     * more situations. */
    public static boolean canDoScheduling() {
	// Would be nice to support more of these, rather than returning false
	if(Gatherer.isGsdlRemote) {
	    return false;
	} 
	if(Gatherer.GS3) {
	    return false;
	}
	if (Configuration.fedora_info.isActive()) {
	    return false;
	}

	// GS2's etc/main.cfg is necessary for scheduling, but scheduling looks for it locally: 
	// it assumes GLI is inside a GS2 installation
	File mcfg = new File(LocalGreenstone.getDirectoryPath() + File.separator + "etc" + File.separator + "main.cfg");
	if(!mcfg.exists()) {
	    System.out.println("Cannot do scheduling, since there is no file: " + mcfg.getAbsolutePath() 
			       + ".\nScheduling presently depends on GLI running from inside a GS2.");
	    return false;
	}
	
	return true;
    }

    private void makeCollection(String name, String email)
    {
	// Generate the mkcol.pl command
	ArrayList command_parts_list = new ArrayList();
	if (!Gatherer.isGsdlRemote) {
	    command_parts_list.add(Configuration.perl_path);
	    command_parts_list.add("-S");
	}
	command_parts_list.add(scriptPath + "mkcol.pl");
	if(Gatherer.GS3) {
	    command_parts_list.add(Utility.GS3MODE_ARGUMENT); // add '-gs3mode' 
	    command_parts_list.add("-site");
	    command_parts_list.add(Configuration.site_name);
	}

	if(!Gatherer.isGsdlRemote) {
	    command_parts_list.add("-collectdir");
	    command_parts_list.add(getDefaultCollectDirectory());
 	}
// why was this even still here?
//	command_parts_list.add("-win31compat");
//	command_parts_list.add((Gatherer.isGsdlRemote) ? "false" : "true");

	if (email != null && !email.equals("")) {
	    command_parts_list.add("-creator");
	    command_parts_list.add(email);
	}

	command_parts_list.add(name);
	
	// Run the mkcol.pl command
	String[] command_parts = (String[]) command_parts_list.toArray(new String[0]);
	//for(int i = 0; i < command_parts.length; i++) {
	///ystem.err.println("\""+command_parts[i]+"\"");
	//}
	
	GShell process = new GShell(command_parts, GShell.NEW, COLLECT, this, null, GShell.GSHELL_NEW);
	process.run(); // Don't bother threading this... yet
    }


    /** Any implementation of GShellListener must include this method to allow the GShell to send messages to listeners. However in this case the CollectionManager is in no way interested in what the messages are, just the import events which have a specific type and are handled elsewhere. Thus we can safely ignore this event.
     * @param event A <strong>GShellEvent</strong> which contains a the message.
     */
    public synchronized void message(GShellEvent event) {
      
    }


    public void metadataChanged(CollectionTreeNode[] file_nodes)
    {
	if (collection != null) {
	    collection.setMetadataChanged(true);
	    
		// we're only going to refresh the tree's already visible nodes (and reselect them) IFF 
		// gs.FilenameEncoding meta was set on any files/folders and the filenames of the
		// affected CollectionTreeNodes need to be recalculated
	    if(FilenameEncoding.isRefreshRequired()) {
			
			// refreshes the relevant part of the tree
			TreePath[] paths = collection_tree.getSelectionPaths();	       
			if(paths != null) {
			    for(int i = 0; i < paths.length; i++) {
				collection_tree_model.refresh(paths[i]);
				collection_tree.setSelectionPath(paths[i]);
			    }
			}
			// refreshed the tree, so turn off the requirement to refresh
			FilenameEncoding.setRefreshRequired(false);		
	    }
	}
    }


    public void openCollectionFromLastTime() {
      // If there was an open collection last session, reopen it. 
	  // (this method doesn't get called if there was no open collection or only a collect folder 
	  // instead of any previously-opened collection)
	  
		// Load the collection now
		loadCollection(Gatherer.open_collection_file_path);      
    }


    /** This call is fired whenever a process within a GShell created by this class begins.
     * @param event A <strong>GShellEvent</strong> containing information about the GShell process.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.gui.GUIManager
     * @see org.greenstone.gatherer.shell.GShell
     */
    public synchronized void processBegun(GShellEvent event) {
	DebugStream.println("CollectionManager.processBegun(" + event.getType() + ")");
	///ystem.err.println("ProcessBegun " + event.getType());
	// If this is one of the types where we wish to lock user control
	Gatherer.g_man.lockCollection((event.getType() == GShell.IMPORT), true);
    }
    /** This call is fired whenever a process within a GShell created by this class ends.
     * @param event A <strong>GShellEvent</strong> containing information about the GShell process.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.gui.GUIManager
     * @see org.greenstone.gatherer.shell.GShell
     */
    public synchronized void processComplete(GShellEvent event) {
	//ystem.err.println("CollectionManager.processComplete(" + event.getType() + ")");
	Gatherer.g_man.lockCollection((event.getType() == GShell.IMPORT), false);
	///ystem.err.println("Received process complete event - " + event);
	// If we were running an import, now run a build.
	if(event.getType() == GShell.IMPORT && event.getStatus() == GShell.OK) {
	    // Finish import.
	    collection.setImported(true);
	    collection.setFilesChanged(false);
	    collection.setMetadataChanged(false);
	    buildCollection();
	}
	else if(event.getType() == GShell.SCHEDULE && event.getStatus() == GShell.OK ) {

	   	WarningDialog collection_built_warning_dialog = new WarningDialog("warning.ScheduleBuilt", Dictionary.get("ScheduleBuilt.Title"), Dictionary.get("ScheduleBuilt.Message"), null, false);
  		collection_built_warning_dialog.setMessageOnly(true); // Not a warning
  		collection_built_warning_dialog.display();
  		collection_built_warning_dialog.dispose();
  		collection_built_warning_dialog = null;   
	} 
	// If we were running a build, now is when we move files across.
	else if(event.getType() == GShell.BUILD && event.getStatus() == GShell.OK) {
            
        if ( CollectionDesignManager.buildcolWasFull() ) {

	    // No installCollection() for GS3 solr collection: activate will take care of that
	    // and no call to configGS3Server for solr collection
	    if(CollectionManager.isSolrCollection()) {
		
		DebugStream.println("Solr collection build complete: building already moved to index by activate.pl.");
		
		// Finished building,
		// For now, for a GS3 solr collection, we'd have stopped the GS3 server before building
		// and will now need to restart it.
		/*GS3ServerThread thread = new GS3ServerThread(Configuration.gsdl3_src_path, "restart");
		  thread.start();*/
		
		// Give the GS3 server time to restart:
		// the GS3ServerThread above waits for the process to terminate. ant restart target calls the start-tomcat ant target
		// and that takes waits 5 seconds and polls to see if a GS3 server index page has loaded. So no need to sleep here		
		
	    }
	    
	    else if(installCollection()) {
		    // If we have a local library running then ask it to add our newly created collection
		    if (LocalLibraryServer.isRunning() == true) {
		        LocalLibraryServer.addCollection(collection.getName());
		    }
		    else if (Gatherer.GS3) {
		        //xiao comment out this: convertToGS3Collection();
		        Gatherer.configGS3Server(Configuration.site_name, ServletConfiguration.ADD_COMMAND + collection.getName());
		    }  

		    // Fire a collection changed first to update the preview etc buttons
		    Gatherer.refresh(Gatherer.COLLECTION_REBUILT);

      		// Now display a message dialog saying its all built
      		WarningDialog collection_built_warning_dialog = new WarningDialog("warning.CollectionBuilt", Dictionary.get("CollectionBuilt.Title"), Dictionary.get("CollectionBuilt.Message"), null, false);
      		collection_built_warning_dialog.setMessageOnly(true); // Not a warning
      		collection_built_warning_dialog.display();
      		collection_built_warning_dialog.dispose();
      		collection_built_warning_dialog = null;

		    //Set nothing as needing rebuilding, as a build has just finished :-)
		    CollectionDesignManager.resetRebuildTypeRequired();
	        }
	        else {
		    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Preview_Ready_Failed"), Dictionary.get("CollectionManager.Preview_Ready_Title"), JOptionPane.ERROR_MESSAGE);
		    Gatherer.refresh(Gatherer.COLLECTION_REBUILT);
		    DebugStream.println("Status is ok but !installCollection()");
	        }
        }
	}

	else if(event.getType() == GShell.DELETE) {

	    // we can only get here if we tried to delete a fedora collection

	    if(event.getStatus() == GShell.ERROR) { // error purging the collection from fedora
		
		JOptionPane.showMessageDialog(Gatherer.g_man,Dictionary.get("DeleteCollectionPrompt.Failed_Fedora_Delete", new String[]{delete_collection_name}),Dictionary.get("DeleteCollectionPrompt.Failed_Title"),JOptionPane.WARNING_MESSAGE);
		delete_collection_name = null; // re-zero
	    }
	    
	    else if(event.getStatus() == GShell.OK) { // fedora purge was successful

		if(delete_collection_name != null) {
		    if (Gatherer.isGsdlRemote) {
			Gatherer.remoteGreenstoneServer.deleteCollection(delete_collection_name);
		    }
		    
		    // if Greenstone3, need to deactivate the collection on the server
		    if (Gatherer.GS3) {
			Gatherer.configGS3Server(Configuration.site_name, ServletConfiguration.DEACTIVATE_COMMAND + delete_collection_name);
		    }

		    // Now at last, can delete the collection directory as for a normal collection
		    boolean success = Utility.delete(new File(getCollectionDirectoryPath(delete_collection_name)));

		    if (!success) {
			JOptionPane.showMessageDialog(Gatherer.g_man,Dictionary.get("DeleteCollectionPrompt.Failed_Delete", new String[]{delete_collection_name}),Dictionary.get("DeleteCollectionPrompt.Failed_Title"),JOptionPane.WARNING_MESSAGE);
		    }
		    delete_collection_name = null; // re-zero
		}

	    }
	}

	else if (event.getStatus() == GShell.CANCELLED) {
	    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Build_Cancelled"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
	    Gatherer.g_man.repaint();
	}
	else if (event.getStatus() == GShell.ERROR) {	    
	    if (event.getType() == GShell.NEW) {	 
	      String name = event.getMessage();
	      String collectDir = getCollectionDirectoryPath(name);
	      String errMsg = "";
	      if (!new File(getCollectionDirectoryPath(name)).exists() || !new File(getCollectionDirectoryPath(name)).canWrite()) {	      
		String reason = Dictionary.get("FileActions.Write_Not_Permitted_Message", new String[]{collectDir});
		errMsg = Dictionary.get("CollectionManager.Cannot_Create_Collection_With_Reason", new String[]{reason});
		if(Gatherer.client_operating_system.toUpperCase().indexOf("WINDOWS") != -1){		  
		  //if(Gatherer.client_operating_system.toUpperCase().indexOf("VISTA")!=-1){		    
		    errMsg += Dictionary.get("FileActions.File_Permission_Detail", new String[]{Configuration.gsdl_path, System.getProperty("user.name")});
		  //} 
		}
	      } else {
		errMsg = Dictionary.get("CollectionManager.Cannot_Create_Collection");
	      }
	      JOptionPane.showMessageDialog(Gatherer.g_man, errMsg, Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
	    } 
	    else if(event.getType() == GShell.SCHEDULE) { 
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Schedule_Failed"), Dictionary.get("CollectionManager.Schedule_Ready_Title"), JOptionPane.ERROR_MESSAGE);
	    }
	    else {
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Preview_Ready_Failed"), Dictionary.get("CollectionManager.Preview_Ready_Title"), JOptionPane.ERROR_MESSAGE);
		Gatherer.refresh(Gatherer.COLLECTION_REBUILT);
	    }
	    
	    Gatherer.g_man.repaint(); // It appears Java's own dialogs have the same not always painting correct area bug that I suffer from. Well I don't suffer from it personally, but my ModalDialog components do.
	}
    }


    /** Determine if the manager is ready for actions apon its collection.
     * @return A <i>boolean</i> which is <i>true</i> to indicate a collection has been loaded and thus the collection is ready for editing, <i>false</i> otherwise.
     */
    static public synchronized boolean ready() {
	if(collection != null) {
	    return true;
	}
	else {
	    return false;
	}
    }


    /** This method associates the collection build monitor with the build monitor created in CreatePane.
     * @param monitor A <strong>GShellProgressMonitor</strong> which we will use as the build monitor.
     */
    public void registerBuildMonitor(GShellProgressMonitor monitor) {
	build_monitor = monitor;
    }
    /** This method associates the collection import monitor with the import monitor created in CreatePane.
     * @param monitor A <strong>GShellProgressMonitor</strong> which we will use as the import monitor.
     */
    public void registerImportMonitor(GShellProgressMonitor monitor) {
	import_monitor = monitor;
    }

    public void registerScheduleMonitor(GShellProgressMonitor monitor) { 
	schedule_monitor = monitor; 
    }


    static public void removeCollectionContentsChangedListener(CollectionContentsChangedListener listener)
    {
	collection_contents_changed_listeners.remove(listener);
    }


    public void removeMetadataSet(MetadataSet metadata_set)
    {
	DebugStream.println("Removing metadata set...");

	// Delete the .mds file from the collection's "metadata" folder...
	File metadata_set_file = metadata_set.getMetadataSetFile();

	// ...but not if it is the "ex.mds" file
	if (metadata_set_file.getName().equals("ex.mds")) {
	    return;
	}

	// ...and only if it exists
	if (metadata_set_file.exists()) {
	    metadata_set_file.delete();

	    // Unload it from the MetadataSetManager
	    MetadataSetManager.unloadMetadataSet(metadata_set);

	    // If we're using a remote Greenstone server, delete the metadata file on the server
	    if (Gatherer.isGsdlRemote) {
		Gatherer.remoteGreenstoneServer.deleteCollectionFile(collection.getGroupQualifiedName(false), metadata_set_file);
	    }
	}
    }


    /** Used to check whether all open collections have a 'saved' state.
     * @return A <i>boolean</i> which is <i>true</i> if the collection has been saved.
     * @see org.greenstone.gatherer.collection.Collection
     */
    public boolean saved() {
	boolean result = true;
	if(collection != null) {
	    result = collection.getSaved();
	}
	return result;
    }


    /** Saves the currently loaded collection. */
    public void saveCollection()
    {

        if (collection == null) return;

	DebugStream.println("Saving collection " + collection.getName() + "...");

	// Change cursor to hourglass
	Gatherer.g_man.wait(true);

	// Create a backup of the collection file, just in case anything goes wrong
	File collection_file = new File(getLoadedCollectionColFilePath());
	if (collection_file.exists()) {
	    File collection_file_backup = new File(collection_file.getAbsolutePath() + "~");
	    if (!collection_file.renameTo(collection_file_backup)) {
		DebugStream.println("Error in CollectionManager.saveCollection(): could not create backup file.");
	    }
	    collection_file_backup.deleteOnExit();
	}

	// Write out the collection file
	collection.save();

	// Write out the collection configuration file
	collection.cdm.save();

	// Change cursor back to normal
	Gatherer.g_man.wait(false);
    }


    /** I started giving the user the choice of using an existing meta set or creating a new one. The second option being so that they didn't have to add/merge/ignore each element, they could all be added automatically. However, I am not sure where the merge prompt gets called from, and it is not essential, so I am leaving it for now - it should be added back in and finished. [kjdon] */
  // now add in greenstone metadata set too.
    private void addDefaultMetadataSets()
    {
	// Add dublin core which is the default metadata set. The user
	// can change this later
	File dc_file = new File(Gatherer.getGLIMetadataDirectoryPath()+"dublin.mds");
	if (dc_file.exists()) {
	    importMetadataSet(new MetadataSet(dc_file));
	}
	File gs_file = new File(Gatherer.getGLIMetadataDirectoryPath()+"greenstone.mds");
	if (gs_file.exists()) {
	    importMetadataSet(new MetadataSet(gs_file));
	}
    }


    private void addRequiredMetadataSets()
    {
	// Always import the extracted metadata set
	File extracted_metadata_set_file = new File(Gatherer.getGLIMetadataDirectoryPath() + MetadataSetManager.EXTRACTED_METADATA_NAMESPACE + StaticStrings.METADATA_SET_EXTENSION);
	importMetadataSet(new MetadataSet(extracted_metadata_set_file));
    }

    private String getDefaultCollectDirectory() {
	String collect_dir = Gatherer.getCollectDirectoryPath();
	// Remove erroneous file windows file separator as it causes problems when running import.pl
	if(collect_dir.length() > 2 && collect_dir.endsWith("\\")) {
	    collect_dir = collect_dir.substring(0, collect_dir.length() - 1);
	}
	return collect_dir;
    }

    // used as arg in the perl scripts
    private String getCollectDirectory() {
	String collect_dir = Gatherer.getCollectDirectoryPath();	
	return collect_dir.substring(0, collect_dir.length()-1); // remove trailing slash
	
	// the following will stick any colgroup at the end of the collect directory, making it no longer
	// possible to get the real collect dir in a general manner if this were located outside greenstone
	//String collect_dir = collection.getCollectionDirectory().getParentFile().getPath();
	//return collect_dir;
    }

    public static String getBuildType() {
	String buildType = (new CollectionMeta( CollectionDesignManager.collect_config.getBuildType() )).getValue(CollectionMeta.TEXT);
	return buildType;
    }

    public static boolean isSolrCollection() {
	return (Gatherer.GS3 && CollectionManager.getBuildType().equals("solr"));
    }


    /** Install collection by moving its files from building to index after a successful build.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.util.Utility
     */
    private boolean installCollection()
    {
	if (Configuration.fedora_info.isActive()) {
	    DebugStream.println("Fedora build complete. No need to move files.");
	    return true;
	}

	DebugStream.println("Build complete. Moving files.");

	try {
	    // Ensure that the local library has released this collection so we can delete the index directory
	    if (LocalLibraryServer.isRunning() == true) {
		LocalLibraryServer.releaseCollection(getLoadedCollectionName(true)); // URL style slash //collection.getName());
	    }
	    // deactivate it in tomcat so that windows will release the index files
	    if (Gatherer.GS3 && !Gatherer.isGsdlRemote) {
		Gatherer.configGS3Server(Configuration.site_name, ServletConfiguration.DEACTIVATE_COMMAND + collection.getName());
	    }
	    /*
	    File index_dir = new File(getLoadedCollectionIndexDirectoryPath());
	    DebugStream.println("Index = " + index_dir.getAbsolutePath());

	    File building_dir = new File(getLoadedCollectionBuildingDirectoryPath());
	    DebugStream.println("Building = " + building_dir.getAbsolutePath());

	    // Get the build mode from the build options
	    String build_mode = collection.build_options.getValue("mode");

	    // Special case for build mode "all": replace index dir with building dir
	    if (build_mode == null || build_mode.equals(Dictionary.get("CreatePane.Mode_All"))) {
		// Remove the old index directory
		if (index_dir.exists()) {
		    Utility.delete(index_dir);

		    // Wait for a couple of seconds, just for luck
		    wait(2000);

		    // Check the delete worked
		    if (index_dir.exists()) {
			throw new Exception(Dictionary.get("CollectionManager.Index_Not_Deleted"));
		    }
		}

		if (Gatherer.isGsdlRemote) {
		    Gatherer.remoteGreenstoneServer.deleteCollectionFile(
                             collection.getGroupQualifiedName(false), new File(getLoadedCollectionIndexDirectoryPath()));
		    Gatherer.remoteGreenstoneServer.moveCollectionFile(collection.getGroupQualifiedName(false), 
                    new File(getLoadedCollectionBuildingDirectoryPath()), new File(getLoadedCollectionIndexDirectoryPath()));
		}

		// Move the building directory to become the new index directory
		if (building_dir.renameTo(index_dir) == false) {
		    throw new Exception(Dictionary.get("CollectionManager.Build_Not_Moved"));
		}
	    }

	    // Otherwise copy everything in the building dir into the index dir
	    else {
		moveContentsInto(building_dir, index_dir);
	    }

	    // move oai tmpdb to oai livedb
	    */
	    
	}
	catch (Exception exception) {
	    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Install_Exception", exception.getMessage()), "Error", JOptionPane.ERROR_MESSAGE);
	    return false;
	}
	return true;
    }


    /** Moves all the files in one directory into another, overwriting existing files */
    private void moveContentsInto(File source_directory, File target_directory)
    {
	File[] source_files = source_directory.listFiles();
	for (int i = 0; i < source_files.length; i++) {
	    File source_file = source_files[i];
	    File target_file = new File(target_directory, source_file.getName());

	    if (source_file.isDirectory()) {
		moveContentsInto(source_file, target_file);
		source_file.delete();
	    }
	    else {
		if (target_file.exists()) {
		    target_file.delete();
		}

		source_file.renameTo(target_file);
	    }
	}
    }

    private void updateCollectionConfigXML(File base_cfg, File new_cfg) {
	//In this method, the files base_cfg and new_cfg are all xml files.

	Document base_cfg_doc = XMLTools.parseXMLFile(base_cfg);
	XMLTools.writeXMLFile(new_cfg, base_cfg_doc);
	Document new_cfg_doc = XMLTools.parseXMLFile(new_cfg);
	Element collection_config = new_cfg_doc.getDocumentElement();
	
	Node browseNode = XMLTools.getChildByTagNameIndexed(collection_config, StaticStrings.BROWSE_STR, 0);
	NodeList classifier_children = ((Element)browseNode).getElementsByTagName(StaticStrings.CLASSIFIER_STR);
	int num_nodes = classifier_children.getLength();
	
	if (num_nodes < 1) {
	    return;
	}

		// Read in the classifier command watching for hfile, metadata and sort arguments.
		String buttonname = null;
		String hfile = null;
		String metadata = null;
		String sort = null;
		
	for (int i=0; i<num_nodes; i++) {
	    Element classifier_element = (Element)classifier_children.item(i);
	    NodeList option_children = classifier_element.getElementsByTagName(StaticStrings.OPTION_STR);
	    for (int j=0; j<option_children.getLength(); j++) {
		Element option_element = (Element)option_children.item(j);
	        String name_str = option_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
 		String value_str = option_element.getAttribute(StaticStrings.VALUE_ATTRIBUTE);
		    
		if (name_str == null || name_str.equals("")) {
		    continue;
		}
		if (name_str != null && value_str == null ) {
		    value_str = "";
		}
		if (name_str.equals("hfile")) {
		    hfile = value_str;
		} 
		else if (name_str.equals("metadata") && value_str != null) { 
		    String replacement = ProfileXMLFileManager.getMetadataElementFor(value_str);
		    if (replacement != null && !replacement.equals("")) {
			metadata = replacement;
		    }
		}
		else if (name_str.equals("sort") && value_str != null) { 
		    String replacement = ProfileXMLFileManager.getMetadataElementFor(value_str);
		    if (replacement != null && !replacement.equals("")) {
			sort = replacement;
		    }
		}
		else if(name_str.equals("buttonname") && value_str != null) {
		    buttonname = value_str;
		}
	    }
	}
	for (int i=0; i<num_nodes; i++) {
	    Element classifier_element = (Element)classifier_children.item(i);
	    NodeList option_children = classifier_element.getElementsByTagName(StaticStrings.OPTION_STR);
	    for (int j=0; j<option_children.getLength(); j++) {
		Element option_element = (Element)option_children.item(j);
	        String name_str = option_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
		
		if (name_str.equals("metadata") && metadata != null) { 
		    option_element.setAttribute(StaticStrings.VALUE_ATTRIBUTE, metadata);
		}
		else if (name_str.equals("hfile") && hfile != null) {
		    option_element.setAttribute(StaticStrings.VALUE_ATTRIBUTE, metadata + ".txt");
		}
		else if (name_str.equals("sort") && sort != null) { 
		    option_element.setAttribute(StaticStrings.VALUE_ATTRIBUTE, sort);
		}
		else if(name_str.equals("buttonname") && (buttonname == "" || buttonname == null)) {
		    // No buttonname has been specified. Lets create one using the metadata as its value
		    Element option = new_cfg_doc.createElement(StaticStrings.OPTION_STR);
		    option.setAttribute(StaticStrings.NAME_ATTRIBUTE, "buttonname");
		    option_element.setAttribute(StaticStrings.VALUE_ATTRIBUTE, metadata);	
		    classifier_element.appendChild(option);
		}
	    }
	}
    }
	
    private void updateCollectionCFG(File base_cfg, File new_cfg, String description, String email, String title)
    {
	boolean first_name = true;
	boolean first_extra = true;
	
	// Now read in base_cfg line by line, parsing important onces and/or replacing them with information pertinent to our collection. Each line is then written back out to the new collect.cfg file.
	try {
	    BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(base_cfg), "UTF-8"));
	    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new_cfg), "UTF-8"));
	    String command = null;
	    while((command = in.readLine()) != null) {
		if (command.length()==0) {
		    // output a new line
		    out.newLine();
		    continue;
		}
		// 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("\\")) {
		    command = command.substring(0, command.lastIndexOf("\\"));
		    String next_line = in.readLine();
		    if(next_line != null) {
			command = command + next_line;
		    }
		}
		// commands can extend over more than one line so use the CommandTokenizer which takes care of that
		CommandTokenizer tokenizer = new CommandTokenizer(command, in, false);
		String command_type_str = tokenizer.nextToken().toLowerCase();

		if (command_type_str.equals(StaticStrings.COLLECTIONMETADATA_STR)) {
		    // read the whole thing in, but for collectionname, collectionextra, iconcollection, iconcollectionsmall we will ignore them
		    StringBuffer new_command = new StringBuffer(command_type_str);
		    String meta_name = tokenizer.nextToken();
		    new_command.append(' ');
		    new_command.append(meta_name);
		    while (tokenizer.hasMoreTokens()) {
			new_command.append(' ');
			new_command.append(tokenizer.nextToken());
		    }
		    if (meta_name.equals(StaticStrings.COLLECTIONMETADATA_COLLECTIONNAME_STR) || meta_name.equals(StaticStrings.COLLECTIONMETADATA_COLLECTIONEXTRA_STR) || meta_name.equals(StaticStrings.COLLECTIONMETADATA_ICONCOLLECTION_STR) || meta_name.equals(StaticStrings.COLLECTIONMETADATA_ICONCOLLECTIONSMALL_STR)) {
			// dont save
		    } else {
			write(out, new_command.toString());
		    }
		    new_command = null;
		    continue;
		} // if collectionmeta

		if (command_type_str.equals("classify")) {
		    StringBuffer text = new StringBuffer(command_type_str);
		    // Read in the classifier command watching for hfile, metadata and sort arguments.
		    String buttonname = null;
		    String hfile = null;
		    String new_metadata = null;
		    String old_metadata = null;
		
		    while(tokenizer.hasMoreTokens()) {
			String token = tokenizer.nextToken();
			if (token.equals("-hfile")) {
			    if(tokenizer.hasMoreTokens()) {
				text.append(" ");
				text.append(token);
				token = tokenizer.nextToken();
				hfile = token;
			    }
			}
			else if (token.equals("-metadata")) {
			    if(tokenizer.hasMoreTokens()) {
				text.append(" ");
				text.append(token);
				String temp_metadata = tokenizer.nextToken();
				String replacement = ProfileXMLFileManager.getMetadataElementFor(temp_metadata);
				if (replacement != null && !replacement.equals("")) {
				    token = replacement;
				    old_metadata = temp_metadata;
				    new_metadata = replacement;
				}
				else {
				    token = temp_metadata;
				}
				temp_metadata = null;
				replacement = null;
			    }
			}
			else if (token.equals("-sort")) {
			    if(tokenizer.hasMoreTokens()) {
				text.append(" ");
				text.append(token);
				String temp_metadata = tokenizer.nextToken();
				String replacement = ProfileXMLFileManager.getMetadataElementFor(temp_metadata);
				if (replacement != null && !replacement.equals("")) {
				    token = replacement;
				}
				else {
				    token = temp_metadata;
				}
				temp_metadata = null;
				replacement = null;
			    }
			}
			else if(token.equals("-buttonname")) {
			    buttonname = token;
			}
			text.append(' ');
			text.append(token);
			token = null;
		    }
		   
		    // If we replaced the metadata argument and didn't encounter a buttonname, then add one now pointing back to the old metadata name in order to accomodate macro files which required such names (buttonname is metadata name by default)!
		    if(old_metadata != null && new_metadata != null && buttonname == null) {
			text.append(' ');
			text.append("-buttonname");
			text.append(' ');
			text.append(old_metadata);
		    }
		    command = text.toString();
		    // Replace the hfile if we found it
		    if(hfile != null && new_metadata != null) {
			command = command.replaceAll(hfile, new_metadata + ".txt");
		    }
		
		    buttonname = null;
		    hfile = null;
		    new_metadata = null;
		    old_metadata = null;
		    write(out, command);
		} else {
		    // the rest of the commands just want a string - we read in all the tokens from the tokeniser and get rid of it.
		    StringBuffer new_command = new StringBuffer(command_type_str);
		    while (tokenizer.hasMoreTokens()) {
			new_command.append(' ');
			new_command.append(tokenizer.nextToken());
		    }

		    command = new_command.toString();
		    
		    // There is still one special case, that of the format command. In such a command we have to search for [<target>] to ensure we don't change parts of the format which have nothing to do with the metadata elements.
		    // we really want to build up the whole command here
		    boolean format_command = command_type_str.equals("format");
		    HashMap metadata_mapping = ProfileXMLFileManager.getMetadataMapping();
		    if (metadata_mapping != null) {
			Iterator keys = metadata_mapping.keySet().iterator();
			while (keys.hasNext()) {
			    String target = (String) keys.next();
			    String replacement = (String) metadata_mapping.get(target);
			    if (replacement != null && !replacement.equals("")) {
				if (format_command) {
				    target = "\\[" + target + "\\]";
				    replacement = "{Or}{[" + replacement + "]," + target + "}";
				}
				command = command.replaceAll(target, replacement);
			    }
			}
		    }

		    write(out, command);
		}
		tokenizer = null;
	    }
	    in.close();
	    in = null;
	    out.flush();
	    out.close();
	    out = null;
	}
	catch(Exception error) {
	    DebugStream.printStackTrace(error);
	}
	// All done, I hope.
    }
    
    private void write(BufferedWriter out, String message)
	throws Exception {
	out.write(message, 0, message.length());
	out.newLine();
    }


    /** The CollectionManager class is getting too confusing by half so I'll implement this TreeModelListener in a private class to make responsibility clear. */
    private class FMTreeModelListener
	implements TreeModelListener {
	/** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
	 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
	 */
	public void treeNodesChanged(TreeModelEvent event) {
	    if(collection != null) {
		collection.setSaved(false);
		collection.setFilesChanged(true);
	    }
	}
	/** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
	 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
	 */
	public void treeNodesInserted(TreeModelEvent event) {
	    if(collection != null) {
		collection.setSaved(false);
		collection.setFilesChanged(true);
	    }
	}
	/** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
	 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
	 */
	public void treeNodesRemoved(TreeModelEvent event) {
	    if(collection != null) {
		collection.setSaved(false);
		collection.setFilesChanged(true);

	    }
	}
	/** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
	 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
	 */
	public void treeStructureChanged(TreeModelEvent event) {
	    if(collection != null) {
		collection.setSaved(false);
	    }
	}
    }
}
