Index: main/trunk/gli/src/org/greenstone/gatherer/Gatherer.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/Gatherer.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/Gatherer.java	(revision 23433)
@@ -56,4 +56,5 @@
 import org.greenstone.gatherer.gui.WarningDialog;
 import org.greenstone.gatherer.gui.FedoraLogin;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
 import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
 import org.greenstone.gatherer.util.JarTools;
@@ -471,4 +472,21 @@
 	    }
 
+		// Check that the local can support multiple filename encodings
+			//System.err.println("#### Java identifies current Locale as (file.encoding): "
+			//	+ System.getProperty("file.encoding"));
+	    if(System.getProperty("file.encoding").equals("UTF-8")){
+			// If the locale is UTF-8, Java will interpret all filename bytes as UTF-8, 
+			// which is a destructive process as it will convert characters not recognised
+			// by UTF-8 into the invalid character, rather than preserving the bytecodes.
+			// This has the effect that non-UTF8 encoded filenames on a system set to a 
+			// UTF-8 locale are not 'seen' by Java (if they contain non-ASCII characters).
+			multipleFilenameEncodingsNotSupported();		
+			FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED = false; 
+			FilenameEncoding.URL_FILE_SEPARATOR = File.separator;
+	    } else {
+			FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED = true;
+			FilenameEncoding.URL_FILE_SEPARATOR = "/"; // URL file separator is always "/" 
+	    }
+		
 	    // Set the default font for all Swing components.
 	    FontUIResource default_font = Configuration.getFont("general.font", true);
@@ -852,5 +870,5 @@
 				+ File.separator + "cgi" + File.separator + "gsdl3site.cfg";
 		} else { // cgi-bin/gsdlsite.cfg
-			return Configuration.gsdl_path + File.separator 
+			return Configuration.gsdl_path /* + File.separator */
 				+ "cgi-bin" + File.separator + "gsdlsite.cfg";
 		}
@@ -1258,4 +1276,13 @@
     }
 
+	/** Prints a warning message about the OS not supporting multiple filename encodings.  */
+    static private void multipleFilenameEncodingsNotSupported() {	
+		WarningDialog dialog = new WarningDialog("warning.NoEncodingSupport", 
+			Dictionary.get("General.MultipleFileNamesNotSupported.Title"), 
+			Dictionary.get("General.MultipleFileNamesNotSupported.Message"), null, false);
+		dialog.display();
+		dialog.dispose();
+		dialog = null;
+    }
 
     /** Sets up the proxy connection by setting JVM Environment flags and creating a new Authenticator.
Index: main/trunk/gli/src/org/greenstone/gatherer/collection/CollectionManager.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/collection/CollectionManager.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/collection/CollectionManager.java	(revision 23433)
@@ -62,4 +62,5 @@
 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;
@@ -1552,4 +1553,21 @@
 	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);		
+	    }
 	}
     }
Index: main/trunk/gli/src/org/greenstone/gatherer/collection/CollectionTreeNode.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/collection/CollectionTreeNode.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/collection/CollectionTreeNode.java	(revision 23433)
@@ -32,8 +32,8 @@
 import org.greenstone.gatherer.cdm.CollectionDesignManager;
 import org.greenstone.gatherer.file.FileNode;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
 import org.greenstone.gatherer.util.JarTools;
 import java.util.Set;
 import java.util.Iterator;
-import java.nio.charset.Charset;
 
 
@@ -54,4 +54,6 @@
     {
 	super(file);
+		// the super call will additionally call calcDisplayString() which will get any
+		// applicable the filename encoding and apply it to the file's name for display.
 
 	this.is_explodable = CollectionDesignManager.plugin_manager.isFileExplodable(file);
@@ -81,3 +83,142 @@
 	return is_srcreplaceable;
     }
+
+	/** This method returns a string representation of the filenodes in the 
+	* <i>Collection</i> Tree, that can then be displayed in the tree. If the 
+	* filename encoding is specified as metadata with the file, then this
+	* method will apply that to the filename's bytes to get the displayName.
+	*/
+    protected String calcDisplayString() {		
+		// metadata.xml files in collections don't get displayed anyway
+		if(file.getName().equals("metadata.xml")) { 
+		    return super.calcDisplayString();
+		}
+		
+		if(!FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED) {
+		    return super.calcDisplayString();
+		}
+
+		// Else: try to encode the display string from the 
+		// file_to_encoding map. If that fails, just use the locale	
+		String displayName = null;
+		String urlEncodedPath = getURLEncodedFilePath();
+		String encoding = FilenameEncoding.findFilenameEncoding(file, urlEncodedPath, false);
+
+		// if it isn't the same as the current encoding already applied, reapply
+		// it which will set the filenameEncoding again as well
+		if(!encoding.equals(getFilenameEncoding())) {
+		    displayName = reencodeDisplayName(encoding); // may return null
+		}
+		
+		if(displayName == null) {
+			if(FilenameEncoding.DEBUGGING) {
+				displayName = getURLEncodedFileName(); 
+			} else {
+				displayName = super.calcDisplayString();
+			}
+		}
+		
+		return displayName;
+    }
+
+	/** Call this if the filename encoding has changed and needs to be 
+	* recalculated and re-applied to the filename. This is only for display,
+	* so it only gets applied to the urlEncodedFileName (not urlEncodedFilePath). 
+	* Note that the filenameEncoding may not be the same as the given encoding
+	* at the end of this method (in case it was a charset alias of the encoding). 
+	* This method both sets and returns the displayFileName member variable. 
+	* @return the reencoded display name, if the encoding was a recognised alias
+	* in which case the filenameEncoding will store the canonical name.  */
+    public String reencodeDisplayName(String encoding) {
+		if(!FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED) {
+		    return displayFileName; // may still be null
+		}
+
+		filenameEncoding = encoding; // clear previous value
+
+		if(filenameEncoding.equals("")) {
+		    displayFileName = super.calcDisplayString(); // *FileNode* calcDisplayString
+		    return displayFileName;
+		}
+
+		try{
+		    displayFileName = new String(file.getName().getBytes(), filenameEncoding);
+		} catch(Exception e) { 
+			// IllegalCharsetName-, UnsupportedCharset- or UnsupportedEncoding-Exception
+		    // Store the unsupported encoding, but display with filesystem (or URL) encoding
+		    filenameEncoding = encoding;
+			
+			if(FilenameEncoding.DEBUGGING) {
+				displayFileName = getURLEncodedFileName();
+			} else {
+				displayFileName = super.calcDisplayString(); 
+			}
+		} 
+		return displayFileName;
+    }
+		
+	/** Can call this upon refreshing a FileNode (this CollectionTreeNode). It makes
+	* sure the display names of the visible nodes in the CollectionTree aren't stale */
+    public void refreshDescendantEncodings() {
+		// now recalculate encodings for all visible children
+		
+		// Don't bother with the encoding stuff when multiple filename encodings 
+		// are not supported because the system is UTF-8 (not Native-Latin-1) and 
+		// Java interprets all filename bytes as UTF-8, which destructively 
+		// converts unrecognised characters (for UTF-8) into the invalid character.	
+
+		if(!FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED 
+				|| child_nodes == null) 
+		{
+		    return;
+		}		
+		
+		// get and apply the filename encoding, if any
+		for(int i = 0; i < child_nodes.size(); i++) {
+		    CollectionTreeNode child_node = (CollectionTreeNode) child_nodes.get(i);			
+		    String urlEncodedPath = child_node.getURLEncodedFilePath();		
+		    
+		    String encoding = FilenameEncoding.findFilenameEncoding(
+					child_node.getFile(), urlEncodedPath, false);
+		    
+		    // if current encoding is different from the existing one, re-apply encoding
+		    if(!child_node.getFilenameEncoding().equals(encoding)) {
+			child_node.reencodeDisplayName(encoding);
+		    }
+		}
+    }
+	
+	// Unused at present
+	/** Call when the filename encoding of this folder level fileNode has changed
+	* and therefore the filename encodings of the descendant fileNodes have to be
+	* reset (their entries in the file_to_encoding hashmap cleared), so that we know
+	* to recalculate these later.  Call when deleting, moving or renaming col nodes */
+    public void resetDescendantEncodings() {
+		if(FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED) {
+		    resetDescendantEncodings(this.file, this.getURLEncodedFilePath());
+		    
+		    // for an actual file/folder delete, want to remove all corresponding 
+			// entries from the hashtable without recalculating any encodings 
+			// (files have been deleted, so no there will be no filenames to display).
+			// So don't do this here: refreshDescendantEncodings();
+			// It's done near the end of FileNode.map().
+		}
+    }
+	
+	// Together with the above, unused at present
+    private static void resetDescendantEncodings(File f, String urlEncodedPath) 
+    {
+		// remove this file f's urlencoded path name from the file_to_encoding Map
+		FilenameEncoding.map.remove(urlEncodedPath);
+			
+		if(f.isDirectory()) {
+		    File[] children = f.listFiles();	    
+		    for(int i = 0; i < children.length; i++) {
+				urlEncodedPath = FilenameEncoding.fileToURLEncoding(children[i]);
+				resetDescendantEncodings(children[i], urlEncodedPath); 
+						// sets filenameEncoding var
+	 	    }
+		}
+    }
+
 }
Index: main/trunk/gli/src/org/greenstone/gatherer/file/FileNode.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/file/FileNode.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/file/FileNode.java	(revision 23433)
@@ -8,4 +8,5 @@
 import javax.swing.tree.*;
 import org.greenstone.gatherer.DebugStream;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
 import org.greenstone.gatherer.util.ArrayTools;
 
@@ -19,58 +20,50 @@
     protected File file = null;
     protected FileSystemModel model = null;
-    protected MutableTreeNode parent = null;
+    protected MutableTreeNode parent = null;	
+
+	protected String urlEncodedFileName = "";
+    protected String urlEncodedFilePath = "";
+	protected String filenameEncoding = "";
     /** The string that is displayed as the filename. Attempts to be in the correct encoding. */
     protected String displayFileName = null;
-
+	
 
     public FileNode(File file)
     {
-	this.file = file;
-
-	// Files cannot have children
-	if (file != null && !file.isDirectory()) { //file.isFile()) {
-	    // Cache this result to prevent unceasing missing disk messages being thrown if the
-	    // removable media was, um, removed after directory mapped
-	    this.allows_children = false;
-	    displayFileName = calcDisplayString();
-	}	
-    }
-
-
-     /** This method returns a string representation of the filenodes in the Collection
-     * Tree, that can then be displayed in the tree. 
-     * We'll initially assume that the filenames are utf8 encoded and so convert the
-     * filename into utf8 for proper presentation in the Collection tree pane.
-     * If the filenames are not utf8, then the conversion would have introduced funny
-     * characters. Therefore, when converting to utf8, if the converted filename 
-     * contains the special character '\ufffd', then we know the conversion did not work
-     * and we return the original string which may or may not be properly presented by
-     * default.
-     * See http://java.sun.com/j2se/1.4.2/docs/api/java/nio/charset/CharsetDecoder.html
-     * which says "How a decoding error is handled depends upon the action requested for 
-     * that type of error, which is described by an instance of the CodingErrorAction class.
-     * The possible error actions are to ignore the erroneous input, report the error to
-     * the invoker via the returned CoderResult object, or replace the erroneous input with
-     * the current value of the replacement string. The replacement has the initial value 
-     * "\uFFFD"; its value may be changed via the replaceWith method."
-     * The following made me think that String(byte[], String charsetName) constructor may
-     * use the replacement value \uFFFD.
-     * http://www.experts-exchange.com/Programming/Programming_Languages/Java/Q_20512969.html
-     * mentions the following which made me think of this: 
-     * convertedStr = convertedStr.replace('\ufffd', ' ');
-     */
-    protected String calcDisplayString() {
-	String filename = file.getName();
-	try{
-	    String utf8filename = new String(filename.getBytes(), "UTF8");
-	    if(utf8filename.indexOf('\ufffd') == -1) {
-		return utf8filename;
-	    } else { // contains the character indicating that it's invalid utf8
-		// return the original string
-		return filename;
-	    }
-	} catch(java.io.UnsupportedEncodingException e) {
-	    return filename;
-	}
+		this.file = file;
+
+		if (file != null) {
+			// Files cannot have children
+		    if(file.isFile()) {
+				// Cache this result to prevent unceasing missing disk messages being thrown if the
+				// removable media was, um, removed after directory mapped
+				this.allows_children = false;
+		    }
+		    filenameEncoding = "";
+		    urlEncodedFilePath = FilenameEncoding.calcURLEncodedFilePath(file);
+		    urlEncodedFileName = FilenameEncoding.calcURLEncodedFileName(urlEncodedFilePath);
+
+		    // work out the display string (extra special processing for CollectionTreeNodes)
+		    displayFileName = calcDisplayString();	    
+		}
+    }
+
+    public String getURLEncodedFileName() { return urlEncodedFileName; }
+
+    public String getURLEncodedFilePath() { return urlEncodedFilePath; }
+
+    public String getFilenameEncoding() { return filenameEncoding; }
+
+
+	/** This method returns a string representation of the filenodes in the tree, 
+	* that can then be displayed in the tree. Overridden in subclass CollectionTreeNode.
+	* Turn FilenameEncoding.DEBUGGING on to see URLEncoded filenames.
+	*/
+    protected String calcDisplayString() {	
+		if(FilenameEncoding.DEBUGGING) {
+			return getURLEncodedFileName();
+		} else {
+			return file.getName();
+		}
     }
 
@@ -314,4 +307,9 @@
 		    }
 		}
+		
+			// in case any filename encodings had gone stale, 
+			// (recalculate these and) refresh the display name
+			refreshDescendantEncodings();			
+					
 	    }
 
@@ -327,5 +325,9 @@
     }
 
-
+	// overridden in subclass CollectionTreeNode to reset and reencode display strings
+    public void resetDescendantEncodings() {}
+    public void refreshDescendantEncodings() {}
+	
+	
     public void setModel(FileSystemModel model) {
 	this.model = model;
Index: main/trunk/gli/src/org/greenstone/gatherer/gui/EnrichPane.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/gui/EnrichPane.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/gui/EnrichPane.java	(revision 23433)
@@ -44,4 +44,5 @@
 import org.greenstone.gatherer.collection.CollectionTreeNode;
 import org.greenstone.gatherer.gui.tree.DragTree;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
 import org.greenstone.gatherer.metadata.MetadataElement;
 import org.greenstone.gatherer.metadata.MetadataValue;
@@ -287,4 +288,14 @@
   public void valueChanged(TreeSelectionEvent event)
     {
+	if(FilenameEncoding.isRefreshRequired()) { 
+		// The CollectionTree is in the process of being re-built to deal with filename-encodings, 
+		// so don't mess up the table during that time.
+		// (CollectionTreeNode.refreshDescendantEncodings() doesn't cope well if this method
+		// responds on Tree selection changes while the tree is updating its encodings, resulting in lost
+		// and misplaced metadata values in the table and thereby in the metadata.xml files themselves).
+		return;
+	}
+	
+	
 	// If we haven't got focus then it must have been a selection in the Gather pane, so don't bother rebuilding
 	if (has_focus == false) {
Index: main/trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/gui/GUIManager.java	(revision 23433)
@@ -59,4 +59,5 @@
 import org.greenstone.gatherer.gui.metaaudit.MetaAuditFrame;
 import org.greenstone.gatherer.gui.tree.DragTree;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
 import org.greenstone.gatherer.metadata.MetadataSet;
 import org.greenstone.gatherer.metadata.MetadataXMLFileManager;
@@ -295,4 +296,5 @@
 	tab_pane.setSelectedComponent(gather_pane);
 	Gatherer.c_man.closeCollection();
+	FilenameEncoding.closeCollection(); // clear filename-to-encodings map
     }
 
Index: main/trunk/gli/src/org/greenstone/gatherer/metadata/FilenameEncoding.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/metadata/FilenameEncoding.java	(revision 23433)
+++ main/trunk/gli/src/org/greenstone/gatherer/metadata/FilenameEncoding.java	(revision 23433)
@@ -0,0 +1,435 @@
+/**
+ *############################################################################
+ * A component of the Greenstone Librarian Interface, part of the Greenstone
+ * digital library suite from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * Author: Michael Dewsnip, NZDL Project, University of Waikato, NZ
+ *
+ * Copyright (C) 2010 Greenstone Digital Library Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *############################################################################
+ */
+ 
+package org.greenstone.gatherer.metadata;
+
+import java.io.File;
+import java.net.*;
+import java.nio.charset.*;
+import java.util.*;
+import org.greenstone.gatherer.collection.CollectionManager;
+
+
+/** Static access class that contains many of the methods used to work with filename encodings. 
+* Works closely with classes FileNode, CollectionTreeNode, MetadataXMLFile, MetadataXMLFileManager
+* to maintain a map of URLEncodedFilenames to their filename encodings.
+* The process of filename encoding further affects the CollectionManager which refreshes its CollectionTree, 
+* FileManager (move, delete, rename actions), MetadataValueTableModel, EnrichPane. */
+
+public class FilenameEncoding {
+	/** Display of filenames in the trees are in URL encoding, if debugging */
+	public static boolean DEBUGGING = false;
+
+	/** Set to false by Gatherer if the locale is UTF-8, as Java's handling is 
+	* such that non-UTF8 filename encodings on a UTF-8 locale are destructively 
+	* converted so that the bytecodes in the filename are not preserved. */
+    public static boolean MULTIPLE_FILENAME_ENCODINGS_SUPPORTED = false;
+
+	/** Also set by Gatherer. 
+	* If the OS supports multiple filename encodings, we will be working with URL strings
+	* and the applicable separators are always the forward slash ("/") not File.separator. 
+	* If  multiple filename encodings are not supported, we're dealing with File.separator. */
+    public static String URL_FILE_SEPARATOR = File.separator;
+    
+
+	/** gs.filenameEncoding is a special sort of metadata that is not merely to be stored along 
+	 * with a file, but is to be applied in real-time on the file's name in the CollectionTree 
+	 * display. Since FileNodes are constantly destroyed and reconstructed by that Tree when 
+	 * its nodes are expanded and contracted, storing the filename encodings of each file along
+	 * with the file in a FileNode doesn't help because it doesn't last. Instead of rediscovering
+	 * the encoding at every stage by querying the metadataXML file, we store the encodings for
+	 * fast access: in a map of (URLEncodedFilePath, filename-encoding) pairs. 
+	 * The current design of the map is to only store any active filename metadata assigned
+	 * directly at that file/folder's level, and if there is none discovered at that level, then
+	 * storing the empty string for it. Therefore, if the hashmap contains no entry for 
+	 * a file, it means this still needs to be retrieved. */
+    public static Map map = new HashMap();
+
+    /** Set to true if filename encoding metadata was changed. Called by the enter keyPress 
+	* event in gui.EnrichPane and when the gs.FilenameEncoding field loses focus. */
+    private static boolean refreshRequired = false;
+	
+//*********************** BUSY REFRESHING / REQUIRING  REFRESH *********************
+
+    public static boolean isRefreshRequired() { 
+		return refreshRequired; 
+	}
+	
+    public static void setRefreshRequired(boolean state) { 
+		if(MULTIPLE_FILENAME_ENCODINGS_SUPPORTED) {
+			refreshRequired = state;
+		} else {
+			refreshRequired = false; 
+		}		
+	}
+
+//************************** MAP RETRIEVAL METHODS ******************************	
+
+	/** Returns the cumulative gs.filenameEncoding metadata 
+	* assigned to a file inside the collection. */    
+	public static String findFilenameEncoding(
+			File file, String urlEncodedFilePath, boolean bruteForceLookup) 
+	{
+		//if(bruteForceLookup) {
+		//	return findFilenameEncodingBruteForce(file, urlEncodedFilePath, bruteForceLookup);
+		//}		
+	
+		String encoding = "";
+		
+		// Check any assigned encoding at this level, starting with the map first
+		// and else retrieving the filename encoding from the metadata file
+		if(!map.containsKey(urlEncodedFilePath)) {
+		
+			// Check for filename encoding metadata *directly* associated with the file
+			// Now don't need to get any inherited encoding metadata here, because of 
+			// the way we're storing and retrieving encoding information from the map.			
+			ArrayList list = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(file, true); // true: gets gs.filenameEncoding only
+			if(!list.isEmpty()) {
+			    MetadataValue metavalue = (MetadataValue)list.get(0); // get(list.size()-1); 
+			    encoding = metavalue.getValue();			    
+			} // else no filename encoding set yet at this level
+		
+			// Now we've done a lookup at this level cache the result in the map,
+			// including empty strings, to indicate that we've done a full lookup
+			map.put(urlEncodedFilePath, encoding);
+		} 
+		else { // an entry exists in the map, get it from there
+			encoding = (String)map.get(urlEncodedFilePath);
+		}	
+		
+		// if no meta was specified at at the file level, look for any inherited metadata
+		if(encoding.equals("")) {
+			encoding = getInheritedFilenameEncoding(urlEncodedFilePath, file);
+		}
+		
+		//System.err.println("\n@@@@Looked for: " + urlEncodedFilePath + " | found: " + encoding);
+		return encoding; // found something in map, may still be "", but it's what was stored
+	}
+	
+	/** Checks the file-to-encoding map for all the superfolders of the given 
+	 * filename in sequence for an applicable encoding. Note that the file/folder
+	 * at the level of urlFoldername (and dir) has already been inspected. */
+    static public String getInheritedFilenameEncoding(String urlFoldername, File dir) 
+	{
+		String encoding = "";
+		boolean done = false;
+		
+		// don't want to search past import folder which is as 
+		// far as we need to go to determine inherited encodings
+		File importDir = new File(CollectionManager.getLoadedCollectionImportDirectoryPath());
+		if(dir.equals(importDir)) { // if the top-level dir was already checked, we're done
+			done = true;
+		}
+
+		// For directories, first remove trailing file separator in order to start checking from higher level folders
+		int lastIndex = urlFoldername.length()-1;
+		char urlFileSeparatorChar = URL_FILE_SEPARATOR.charAt(0);
+		if(urlFoldername.charAt(lastIndex) == urlFileSeparatorChar) { 
+		    urlFoldername = urlFoldername.substring(0, lastIndex);
+		}
+
+		while(!done) {
+			// get the folder that's one level up
+			dir = dir.getParentFile();		
+			
+		    int index = urlFoldername.lastIndexOf(URL_FILE_SEPARATOR);		
+		    if(index == -1) { // no more slashes
+				done = true;
+		    } else { 
+				urlFoldername = urlFoldername.substring(0, index);
+		    }
+		    
+			// now look in the map to see whether there's an encoding for this folder
+		    String folder = urlFoldername + URL_FILE_SEPARATOR;
+		    if(map.containsKey(folder)) {
+				encoding = (String)map.get(folder); // may be ""				
+		    } else { // no entry in map, so look in the metadata.xml at this folder level
+				ArrayList list = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(
+						dir, true); // true: gets gs.filenameEncoding only
+				if(!list.isEmpty()) {
+				    MetadataValue metavalue = (MetadataValue)list.get(0); // get(list.size()-1); 
+				    encoding = metavalue.getValue();			    
+				}
+				map.put(folder, encoding); // may be ""
+			}
+			
+			if(!encoding.equals("")){
+			    done = true;
+			} // else if "", loop to check next folder up
+			else if(dir.equals(importDir)) { // don't iterate past the import folder, which we've now checked
+				done = true;
+			}
+		}
+
+		return encoding;
+    }
+
+	/** Called by GUIManager when a collection is closed. This then empties the 
+	* file-to-encoding map which is applicable only on a per-collection basis */
+    static public void closeCollection() {	
+		//printFilenameMap("Closing collection. Clearing file-to-encoding map of entries:");
+		map.clear();
+    }
+
+	// Useful for debugging: prints contents of file-to-encoding map
+    static public void printFilenameMap(String heading) {
+		System.err.println("\n********************************************");
+		System.err.println(heading.toUpperCase());
+		Iterator entries = map.entrySet().iterator();
+		while(entries.hasNext()) {
+		    Map.Entry entry = (Map.Entry)entries.next();
+		    System.err.println("+ " + (String)entry.getKey() + ": " + (String)entry.getValue());
+		}
+		System.err.println("********************************************\n");
+    }
+	
+	// UNUSED at present. Brute force version of the findFilenameEncoding() method
+	// Doesn't use the map, but gets *all* the metadata assigned to a file/folder to 
+	// work out the encoding applicable to a file/folder.
+    public static String findFilenameEncodingBruteForce(File file, String urlEncodedFilename, 
+					     boolean bruteForceLookup) 
+    {
+		System.err.println("\n***** BRUTE FORCE getFilenameEncoding() called\n");
+	
+	
+		String encoding = "";
+
+		// Check for filename encoding metadata *directly* associated with the file
+		// Now don't need to get any inherited encoding metadata here, because of 
+		// the way we're storing and retrieving encoding information from the map.
+
+		ArrayList list = MetadataXMLFileManager.getMetadataAssignedToFile(file, true); // true: gets gs.filenameEncoding only
+		if(!list.isEmpty()) {	   
+		    // try to get the filename encoding meta that was assigned last to this 
+		    // file, even though it makes no sense to have multiple values for it
+		    MetadataValue metavalue = (MetadataValue)list.get(list.size()-1); 
+		    encoding = metavalue.getValue();
+		    
+		    if(encoding == null) { // unlikely ???
+				System.err.println("**** ERROR: encoding for " 
+						+ urlEncodedFilename + " is NULL!");    
+				encoding = "";
+		    }
+		} // else no filename encoding set yet, perhaps
+		//System.err.println("**** Found encoding for " + urlEncodedFilename + " " + encoding);
+		return encoding;
+    }
+
+//****************************** APPLYING ENCODINGS TO FILENAMES *****************************
+	
+    /** URL encoded version of the byte codes of the given file's name */
+    public static String calcURLEncodedFilePath(File file) {	
+		if(!MULTIPLE_FILENAME_ENCODINGS_SUPPORTED) {
+		    return file.getAbsolutePath();
+		}
+		else {
+		    String filename = fileToURLEncoding(file);
+		    return filename;
+		}
+    }
+
+    /** URL encoded version of the byte codes of this file's name */
+    public static String calcURLEncodedFileName(String urlfilepath) {
+		String filename = urlfilepath;
+		if(filename.endsWith(URL_FILE_SEPARATOR)) { // directory, remove trailing slash
+		    filename = filename.substring(0, filename.length() - 1);
+		}	    
+		
+		// remove the directory prefix (if any) to get the filename
+		int index = filename.lastIndexOf(URL_FILE_SEPARATOR);
+		if(index != -1) {
+		    filename = filename.substring(index+1); // skip separator
+		}
+		
+		return filename;    
+    }
+
+	/** Given a string representing an alias to an official encoding (and unofficial ones 
+	* starting with "Latin-"), attempts to work out what the canonical encoding for that is. 
+	* If the given encoding is unrecognised, it is returned as is. */
+    public static String canonicalEncodingName(String encoding) {
+		String canonicalEncoding = encoding;
+		try {
+			// Latin-1 -> ISO-8859-1
+			String alias = canonicalEncoding.toLowerCase();
+			if(alias.startsWith("latin")){								
+				canonicalEncoding = "ISO-8859" + alias.substring("latin".length());
+			}
+
+			// canonical encoding for official aliases
+			canonicalEncoding = Charset.forName(canonicalEncoding).name();			
+			return canonicalEncoding;
+		} catch (Exception e) {
+			System.err.println("(Could not recognise encoding (alias): " 
+					+ encoding + ".)");
+		    return encoding; // no alias could be found, return the original parameter
+		}
+    }
+
+//************************* GETTING THE URL ENCODING OF FILENAMES *********************************
+	// Dr Bainbridge's methods
+	/* On Linux machines that are set to using an ISO-8859 (Latin) type encoding, 
+	* we can work with URL-encoded filenames in Java. Java works with whatever 
+	* encoding the filesystem uses. Unlike systems working with UTF-8, where Java 
+	* interprets filenames as UTF-8 (a destructive process since characters invalid
+	* for UTF-8 are replaced with the invalid character, which means the original
+	* character's byte codes can not be regained), working with an ISO-8859-1 
+	* system means the original byte codes of the characters are preserved, 
+	* regardless of whether the characters represent ISO-8859-1 or not. Such byte
+	* codes are converted by the following method to the correct URL versions of 
+	* the strings that the filenames represent (that is, the correct URL representations
+	* of the filenames in their original encodings). This is useful for interactions with
+	* Perl as Java and Perl can use URL-encoded filenames to talk about the same files
+	* on the file system, instead of having to work out what encoding they are in. */
+	
+    public static String fileToURLEncoding(File file) {
+		if(!MULTIPLE_FILENAME_ENCODINGS_SUPPORTED) {
+		    return file.getAbsolutePath();
+		}
+
+		String filename_url_encoded = "";
+		
+		// The following test for whether the file exists or not is a problem
+		// when a File object--whose actual file is in the process of being moved 
+		// and therefore temporarily does not 'exist' on the actual system--can't
+		// be URL encoded: the following would return "" when a file doesn't exist.
+		// So commenting out the test.
+		/* 
+		if(!file.getName().equals("recycle")) {
+		    if(!file.isFile() && !file.isDirectory()) {
+			System.err.println("*** ERROR. Java can't see file: " + file.getAbsolutePath());
+			return "";
+		    }
+		    
+		    if(!file.exists()) {
+			System.err.println("*** NOTE: File doesn't exist: " + file.getAbsolutePath());
+			return ""; //file.getName();
+		    }
+		}
+		*/
+
+		URI filename_uri = file.toURI();
+		try {
+		    // The trick:
+		    //  1. toASCIIString() will %xx encode values > 127
+		    //  2. Decode the result to "ISO-8859-1" 
+		    //  3. URL encode the bytes to string
+		    
+		    // Step 2 forces the string to be 8-bit values.  It
+		    // doesn't matter if the starting raw filename was *not*
+		    // in the ISO-8859-1 encoding, the effect is to ensure
+		    // we have an 8-bit byte string that (numerically)
+		    // captures the right value.  These numerical values are
+		    // then used to determine how to URL encode it
+		    
+		    String filename_ascii = filename_uri.toASCIIString();
+		    String filename_raw_bytes = URLDecoder.decode(filename_ascii,"ISO-8859-1");
+		    filename_url_encoded = iso_8859_1_filename_to_url_encoded(filename_raw_bytes);
+		}
+		catch (Exception e) {
+		    e.printStackTrace();
+		    // Give up trying to convert
+		    filename_url_encoded = file.getAbsolutePath(); 
+		}
+		return filename_url_encoded;
+    }
+
+    
+    // For unicode codepoints see:
+    // http://unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT for ISO8859-1 (Latin-1)
+    // where 0xE2 maps to codepoint 0x00E2 and is defined as "Latin small letter a with circumflex"
+    // http://unicode.org/Public/MAPPINGS/ISO8859/8859-7.TXT for ISO8859-7 (Greek)
+    // where 0xE2 maps to codepoint 0x03B2 and is defined as "Greek small letter beta"    
+    public static String iso_8859_1_filename_to_url_encoded(String raw_bytes_filename) 
+		throws Exception
+    {
+		String urlEncoded = "";
+
+		try {
+		    // By this point we have a UTF-8 encoded string that captures
+		    // what the ISO-8859-1 (Latin-1) character is that corresponded to the
+		    // 8-bit numeric value for that character in the filename
+		    // on the file system
+		    
+		    // For example:
+		    //   File system char:             <lower-case beta char in Latin-7> = %E2
+		    //   Equivalent Latin 1 char:      <lower-case a with circumflex>    = %E2
+		    //   Mapped to UTF-8:              <lower-case a with circumflex>    = <C3><A2>
+
+		    // Our task is to take the string the contains <C3><A2> and ensure that
+		    // we "see" it as <E2>
+
+		    byte [] raw_bytes = raw_bytes_filename.getBytes("ISO-8859-1");
+		    String unicode_filename = new String(raw_bytes,"UTF-8");
+		    
+		    for(int i = 0; i < unicode_filename.length(); i++) {
+			char charVal = unicode_filename.charAt(i);
+			if((int)charVal > 127) {
+			    urlEncoded += String.format("%%%02X", (int)charVal);
+			} else {
+			    urlEncoded += String.format("%c", (char)charVal);
+			}
+		    }
+		}
+		catch (Exception e) {
+		    //e.printStackTrace();
+		    throw(e);
+		}
+
+		return urlEncoded;
+    }
+
+    // unused for now
+    public static String raw_filename_to_url_encoded(String fileName)
+		throws Exception
+    {
+		String urlEncoded = "";
+		try {
+		    byte[] bytes = fileName.getBytes();
+		    
+		    for(int i = 0; i < bytes.length; i++) {
+			// mask each byte (by applying & 0xFF) to make the signed
+			// byte (in the range -128 to 127) unsigned (in the range
+			// 0 to 255).
+
+			int byteVal = (int)(bytes[i] & 0xFF);
+			
+			if(byteVal > 127) {
+			    urlEncoded += String.format("%%%02X", (int)byteVal);
+			} else {
+			    urlEncoded += String.format("%c",(char)byteVal);
+			}
+		    }
+		}
+		catch (Exception e) {
+		    //e.printStackTrace();
+		    throw(e);
+		}
+
+		return urlEncoded;
+    }
+   
+}
Index: main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataValueTableModel.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataValueTableModel.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataValueTableModel.java	(revision 23433)
@@ -139,4 +139,10 @@
 
 	MetadataValueTableEntry metadata_value_table_entry = (MetadataValueTableEntry) metadata_value_table_entries.get(row);
+	
+	if(metadata_value_table_entry == null) {
+		System.err.println("\n@@@@@ MetadataValueTableModel.getValueAt(): metadata_value_table_entry is unexpectedly null!\n");
+		return null;
+	}
+	
 	if (col == 0 && metadata_value_table_entry.isInheritedMetadata()) {
 	    return metadata_value_table_entry.getFolderMetadataInheritedFrom();
@@ -213,5 +219,9 @@
     {
 	MetadataValueTableEntry metadata_value_table_entry = getMetadataValueTableEntry(row);
-
+	if(metadata_value_table_entry == null) {
+		System.err.println("\n@@@@@ MetadataValueTableModel.setValueAt(): metadata_value_table_entry is unexpectedly null!\n");
+		return;
+	}
+	
 	// If nothing has changed no action is necessary
 	String old_metadata_value = metadata_value_table_entry.getFullValue();
@@ -252,5 +262,5 @@
 	    MetadataValueTreeNode metadata_value_tree_node = metadata_element.addMetadataValue((String) new_metadata_value);
 	    MetadataValue metadata_value = new MetadataValue(metadata_element, metadata_value_tree_node);
-	    metadata_value.setIsAccumulatingMetadata(true);
+	    metadata_value.setIsAccumulatingMetadata(true);		
 	    (new AppendMetadataTask(metadata_value)).run();
 	}
Index: main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataXMLFile.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataXMLFile.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataXMLFile.java	(revision 23433)
@@ -31,4 +31,5 @@
 import java.util.*;
 import org.greenstone.gatherer.DebugStream;
+import org.greenstone.gatherer.collection.CollectionTreeNode;
 import org.greenstone.gatherer.util.XMLTools;
 import org.w3c.dom.*;
@@ -45,4 +46,9 @@
     static final private String METADATA_ELEMENT = "Metadata";
 
+    /** Special metadata field: the filename encoding is a unique sort of metadata in
+     * that it is not just information stored with a collection file, but also needs to
+     * be applied in real-time to the collection file (to its filename) for display. */
+    static final public String FILENAME_ENCODING_METADATA = "gs.filenameEncoding";
+	
     // To speed things up a bit we keep the last accessed metadata.xml file in memory
     static private File loaded_file = null;
@@ -57,5 +63,5 @@
 
 
-    public void addMetadata(File file, ArrayList metadata_values)
+    public void addMetadata(CollectionTreeNode file_node, ArrayList metadata_values)
     {
 	// If this metadata.xml file isn't the one currently loaded, load it now
@@ -76,8 +82,8 @@
 
 	// Determine the file's path relative to the location of the metadata.xml file
-	String metadata_xml_file_directory_path = getParentFile().getAbsolutePath();
-	String file_relative_path = file.getAbsolutePath().substring(metadata_xml_file_directory_path.length());
-	if (file_relative_path.startsWith(File.separator)) {
-	    file_relative_path = file_relative_path.substring(File.separator.length());
+	String metadata_xml_file_directory_path = FilenameEncoding.fileToURLEncoding(getParentFile());
+	String file_relative_path = file_node.getURLEncodedFilePath().substring(metadata_xml_file_directory_path.length());
+	if (file_relative_path.startsWith(FilenameEncoding.URL_FILE_SEPARATOR)) {
+	    file_relative_path = file_relative_path.substring(FilenameEncoding.URL_FILE_SEPARATOR.length());
 	}
 
@@ -127,6 +133,9 @@
 	    appropriate_fileset_element.appendChild(new_description_element);
 
+		// add the fileset element for .* at the top: especially important for
+	    // non-accumulating (and override mode) meta. Other type fileset elements can be appended
 	    if(file_path_regexp.equals(DIRECTORY_FILENAME)) {
-	      loaded_file_document.getDocumentElement().insertBefore(appropriate_fileset_element, loaded_file_document.getDocumentElement().getFirstChild());
+			loaded_file_document.getDocumentElement().insertBefore(appropriate_fileset_element,
+					loaded_file_document.getDocumentElement().getFirstChild());
 	    } else {
 	      loaded_file_document.getDocumentElement().appendChild(appropriate_fileset_element);
@@ -147,4 +156,12 @@
 	    metadata_value_string = metadata_value_string.replaceAll("\\]", "&#093;");
 
+	    // the gs.filenameEncoding metadata is unique in that, when added, removed or
+	    // changed, it must be applied on the file(name) whose metadata has been adjusted
+	    if(metadata_element_name_full.equals(FILENAME_ENCODING_METADATA)) {
+			metadata_value_string = processFilenameEncoding(file_path_regexp, 
+										file_node, metadata_value_string, false);
+		                      // true only if removing meta
+	    }
+
 	    // Check if this piece of metadata has already been assigned to this FileSet element
 	    boolean metadata_already_assigned = false;
@@ -156,5 +173,5 @@
 		String current_metadata_element_name_full = current_metadata_element.getAttribute("name");
 		if (current_metadata_element_name_full.equals(metadata_element_name_full)) {
-		  // if the metadata must not accumulate, then edit teh current value
+		  // if the metadata must not accumulate, then edit the current value
 		  if (!metadata_value.isAccumulatingMetadata()) {
 		    XMLTools.setNodeText(current_metadata_element, metadata_value_string);
@@ -196,5 +213,5 @@
 
 
-    public ArrayList getMetadataAssignedToFile(File file)
+    public ArrayList getMetadataAssignedToFile(File file, boolean fileEncodingOnly)
     {
 	// If this metadata.xml file isn't the one currently loaded, load it now
@@ -214,9 +231,12 @@
 	}
 
-	// Determine the file's path relative to the location of the metadata.xml file
+	// Determine the file's path relative to the location of the metadata.xml file	
+	String file_relative_path = FilenameEncoding.fileToURLEncoding(file);
 	File metadata_xml_file_directory = getParentFile();
-	String file_relative_path = file.getAbsolutePath().substring(metadata_xml_file_directory.getAbsolutePath().length());
-	if (file_relative_path.startsWith(File.separator)) {
-	    file_relative_path = file_relative_path.substring(File.separator.length());
+	String metadata_xml_file_directory_path = FilenameEncoding.fileToURLEncoding(metadata_xml_file_directory);
+	file_relative_path = file_relative_path.substring(metadata_xml_file_directory_path.length());
+
+	if (file_relative_path.startsWith(FilenameEncoding.URL_FILE_SEPARATOR)) {
+	    file_relative_path = file_relative_path.substring(FilenameEncoding.URL_FILE_SEPARATOR.length());
 	}
 
@@ -255,5 +275,5 @@
 
 		// This fileset specifies metadata for the folder the file is in
-		if (file_relative_path.startsWith(current_filename_element_value + File.separator)) {
+		if (file_relative_path.startsWith(current_filename_element_value + FilenameEncoding.URL_FILE_SEPARATOR)) {
 		    current_fileset_matches = true;
 		    folder_metadata_inherited_from = new File(metadata_xml_file_directory, current_filename_element_value);
@@ -272,4 +292,8 @@
 		Element current_metadata_element = (Element) metadata_elements_nodelist.item(k);
 		String metadata_element_name_full = current_metadata_element.getAttribute("name");
+		// if we're only looking for fileEncoding metadata and this isn't it, skip to the next
+		if(fileEncodingOnly && !metadata_element_name_full.equals(FILENAME_ENCODING_METADATA)) {
+		    continue;
+		}		
 		String metadata_set_namespace = MetadataTools.getMetadataSetNamespace(metadata_element_name_full);
 
@@ -335,5 +359,5 @@
 
 
-    public void removeMetadata(File file, ArrayList metadata_values)
+    public void removeMetadata(CollectionTreeNode file_node, ArrayList metadata_values)
     {
 	// If this metadata.xml file isn't the one currently loaded, load it now
@@ -354,8 +378,8 @@
 
 	// Determine the file's path relative to the location of the metadata.xml file
-	String metadata_xml_file_directory_path = getParentFile().getAbsolutePath();
-	String file_relative_path = file.getAbsolutePath().substring(metadata_xml_file_directory_path.length());
-	if (file_relative_path.startsWith(File.separator)) {
-	    file_relative_path = file_relative_path.substring(File.separator.length());
+	String metadata_xml_file_directory_path = FilenameEncoding.fileToURLEncoding(getParentFile());
+	String file_relative_path = file_node.getURLEncodedFilePath().substring(metadata_xml_file_directory_path.length());
+	if (file_relative_path.startsWith(FilenameEncoding.URL_FILE_SEPARATOR)) {
+	    file_relative_path = file_relative_path.substring(FilenameEncoding.URL_FILE_SEPARATOR.length());
 	}
 
@@ -422,7 +446,18 @@
 		    String current_metadata_value_string = XMLTools.getElementTextValue(current_metadata_element);
 		    if (current_metadata_value_string.equals(metadata_value_string)) {
+			
 			// Remove this Metadata element
 			current_metadata_element.getParentNode().removeChild(current_metadata_element);
-
+			
+			// the gs.filenameEncoding metadata is unique in that, when added, removed or
+			// changed, it must be applied on the file(name) whose metadata has been adjusted
+			if(current_metadata_element_name_full.equals(FILENAME_ENCODING_METADATA)) {
+			
+				// metadata_value_string will hereafter be the inherited gs.FilenameEncoding 
+				// metadata (if any), now that the value at this level has been removed
+			    metadata_value_string = processFilenameEncoding(file_path_regexp,
+						file_node, "", true); // true only if *removing* this meta	
+			}
+			
 			// If there are no Metadata elements left now, remove the (empty) FileSet element
 			if (metadata_elements_nodelist.getLength() == 0) {
@@ -441,5 +476,5 @@
 
 
-    public void replaceMetadata(File file, MetadataValue old_metadata_value, MetadataValue new_metadata_value)
+    public void replaceMetadata(CollectionTreeNode file_node, MetadataValue old_metadata_value, MetadataValue new_metadata_value)
     {
 	// If this metadata.xml file isn't the one currently loaded, load it now
@@ -460,8 +495,8 @@
 
 	// Determine the file's path relative to the location of the metadata.xml file
-	String metadata_xml_file_directory_path = getParentFile().getAbsolutePath();
-	String file_relative_path = file.getAbsolutePath().substring(metadata_xml_file_directory_path.length());
-	if (file_relative_path.startsWith(File.separator)) {
-	    file_relative_path = file_relative_path.substring(File.separator.length());
+	String metadata_xml_file_directory_path = FilenameEncoding.fileToURLEncoding(getParentFile());
+	String file_relative_path = file_node.getURLEncodedFilePath().substring(metadata_xml_file_directory_path.length());
+	if (file_relative_path.startsWith(FilenameEncoding.URL_FILE_SEPARATOR)) {
+	    file_relative_path = file_relative_path.substring(FilenameEncoding.URL_FILE_SEPARATOR.length());
 	}
 
@@ -541,9 +576,20 @@
 	    // If the new metadata value already existed, remove the original value
 	    if (new_metadata_value_already_exists) {
-		metadata_element_to_edit.getParentNode().removeChild(metadata_element_to_edit);
+			if(metadata_element_to_edit != null) { //?????????
+			    metadata_element_to_edit.getParentNode().removeChild(metadata_element_to_edit);
+			} else {
+			    System.err.println("ERROR MetadataXMLFile: metadata_element_to_edit is null");
+			}
 	    }
 	    // Otherwise replace the old value with the new value
 	    // Ensure metadata_element_to_edit isn't null (may occur when multiple files are selected)
 	    else if (metadata_element_to_edit != null) {
+		
+		// the gs.filenameEncoding metadata is unique in that, when added, removed or
+		// changed, it must be applied on the file(name) whose metadata has been adjusted
+		if(metadata_element_name_full.equals(FILENAME_ENCODING_METADATA)) {
+		    new_metadata_value_string = processFilenameEncoding(file_path_regexp, file_node, new_metadata_value_string, false);
+		    // true only if removing meta		
+		}		
 		XMLTools.setElementTextValue(metadata_element_to_edit, new_metadata_value_string);
 	    }
@@ -649,3 +695,72 @@
 	}
     }
+
+	/** 
+	 * The gs.filenameEncoding metadata is unique in that, when added, removed or
+	 * replaced, it must be applied on the file(name) whose metadata has been 
+	 * adjusted. 
+	 * This method handles all that, given the regular expression or filepath name 
+	 * to match on (.* matches subdirectories), the affected fileNode, the new 
+	 * encoding value and whether a new encoding value has been added/an existing
+	 * one has been replaced or whether the encoding metadata has been removed.
+	 * The new adjusted value for the encoding metadata is returned.
+	 *
+	 * MetadataXMLFileManager maintains a hashmap of (URL-encoded filepaths, encoding) 
+	 * to allow fast access to previously assigned gs.filenameEncoding metadata (if
+	 * any) for each file. This hashmap also needs to be updated, but this update
+	 * is complicated by the fact that it concerns regular expressions that could
+	 * affect multiple filenames.
+	 */
+    public String processFilenameEncoding(String file_path_regexp, CollectionTreeNode file_node, 
+					String encoding_metadata_value, boolean removingMetadata) 
+    {
+		if(!FilenameEncoding.MULTIPLE_FILENAME_ENCODINGS_SUPPORTED) {
+		    return encoding_metadata_value;
+		}
+
+		// Work out this filenode's new encoding and apply it:
+		
+		if(removingMetadata) { // encoding_metadata_value = ""
+			// gs.filenameEncoding metadata being removed, work out 
+			// any inherited metadata to replace it with in the meta-table
+			encoding_metadata_value = FilenameEncoding.getInheritedFilenameEncoding(
+						file_node.getURLEncodedFilePath(), file_node.getFile());
+			// should be canonical encoding already
+		} 
+		else if(!encoding_metadata_value.equals("")) { 
+			// if adding or replacing filename encoding,
+			// get the canonical encoding name for this alias
+			encoding_metadata_value = FilenameEncoding.canonicalEncodingName(encoding_metadata_value);
+		}	
+		// Reencode the display of this filenode only as any affected 
+		// childnodes will be reencoded on FileNode.refreshDescendantEncodings()
+		file_node.reencodeDisplayName(encoding_metadata_value);
+			
+
+		// Whether removing or adding/replacing the file's gs.filename encoding meta,
+		// store this in the file-to-encoding map for fast access, since the map stores
+		// empty string values when no meta has been assigned at this file level.
+		// In the case of removingMetadata, the value stored will be the fallback value
+		
+		String urlpath = file_node.getURLEncodedFilePath();
+		if(removingMetadata) { 
+			// remove it from the map instead of inserting "", so that when folders in the collectiontree 
+			// are being deleted or shifted, the removemetada (and addmetadata) calls that get fired 
+			// for each affected filenodes does not cause the undesirable effect of multiple "" to be 
+			// entered into the filename-to-encoding map for filepaths that no longer exist .
+			FilenameEncoding.map.remove(urlpath); 
+		} else { // for adding and replacing, put the encoding into the map (also replaces any existing encoding for it)
+			FilenameEncoding.map.put(urlpath, encoding_metadata_value);			
+		}
+		
+		// If new folder-level metadata (or metadata for a set of files fitting a pattern) has been
+		//  assigned, the file_to_encodings map will be cleared for all descendant folders and files,
+		//  so that these can be re-calculated upon refreshing the visible parts of the CollectionTree. 
+		// Mark the state as requiring a refresh of the CollectionTree. 
+		// This next step also serves to prevent the MetadataValueTableModel from trying to update
+		// itself while a refresh (involving re-encoding of filenames of visible nodes) is in progress.
+		FilenameEncoding.setRefreshRequired(true);
+		
+		return encoding_metadata_value;
+    }
 }
Index: main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataXMLFileManager.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataXMLFileManager.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/metadata/MetadataXMLFileManager.java	(revision 23433)
@@ -90,5 +90,5 @@
 		if (current_file_directory_path.equals(metadata_xml_file.getParentFile().getAbsolutePath())) {
 		    applicable_metadata_xml_file_found = true;
-		    metadata_xml_file.addMetadata(current_file, metadata_values);
+		    metadata_xml_file.addMetadata(file_nodes[i], metadata_values);
 		    if (!modified_metadata_xml_files.contains(metadata_xml_file)) {
 			modified_metadata_xml_files.add(metadata_xml_file);
@@ -107,5 +107,5 @@
 
 		// ...and add the metadata
-		new_metadata_xml_file.addMetadata(current_file, metadata_values);
+		new_metadata_xml_file.addMetadata(file_nodes[i], metadata_values);
 		if (!modified_metadata_xml_files.contains(new_metadata_xml_file)) {
 		    modified_metadata_xml_files.add(new_metadata_xml_file);
@@ -160,5 +160,5 @@
 
 	// Get the metadata assigned to the specified file from the applicable metadata.xml files
-	ArrayList assigned_metadata = getMetadataAssignedToFile(file, applicable_metadata_xml_files);
+	ArrayList assigned_metadata = getMetadataAssignedToFile(file, applicable_metadata_xml_files, false);
 
 	// Remove any folder-level metadata
@@ -173,23 +173,63 @@
 
 
+	/** Returns the metadata assigned to a file inside the collection, excluding folder-level/inherited metadata. */
+    static public ArrayList getMetadataAssignedDirectlyToFile(File file)
+    {
+		return getMetadataAssignedDirectlyToFile(file, false);
+    }
+	
     /** Returns the metadata assigned to a file inside the collection, excluding folder-level/inherited metadata. */
-    static public ArrayList getMetadataAssignedDirectlyToFile(File file)
-    {
-	// Get all the metadata assigned to the specified file...
-	ArrayList assigned_metadata = getMetadataAssignedToFile(file);
-
-	// ...then remove any folder-level metadata
-	for (int i = assigned_metadata.size() - 1; i >= 0; i--) {
-	    if (((MetadataValue) assigned_metadata.get(i)).isInheritedMetadata()) {
-		assigned_metadata.remove(i);
-	    }
-	}
-
-	return assigned_metadata;
-    }
-
+    static public ArrayList getMetadataAssignedDirectlyToFile(File file, boolean filenameEncodingMetaOnly)
+    {
+		
+		// Get all the metadata assigned to the specified file...
+		ArrayList assigned_metadata = getMetadataAssignedToFile(file, filenameEncodingMetaOnly);
+
+		// ...then remove any folder-level metadata
+		for (int i = assigned_metadata.size() - 1; i >= 0; i--) {
+		    if (((MetadataValue) assigned_metadata.get(i)).isInheritedMetadata()) {
+			assigned_metadata.remove(i);
+		    }
+		}
+
+		return assigned_metadata;
+		/*
+		// Get all the metadata assigned to the specified file...
+		// Build up a list of applicable metadata.xml files - which in this case 
+		// is exclusively the metadata file at this file/folder's own level.
+		ArrayList applicable_metadata_xml_files = new ArrayList();
+
+		// Find the metadata.xml file (if any) that is at the same level as the file
+		String file_directory_path = (file.isDirectory() ? file : file.getParentFile()).getAbsolutePath() + File.separator;
+		for (int i = 0; i < metadata_xml_files.size(); i++) {
+		    MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(i);
+		    
+		    if (file_directory_path.equals(metadata_xml_file.getParentFile().getAbsolutePath() + File.separator)) {
+				//System.err.println("Found metadata_xml_file: " + metadata_xml_file);
+				applicable_metadata_xml_files.add(metadata_xml_file);
+		    }
+		}
+		
+		if(applicable_metadata_xml_files.size() == 0) {
+		    return new ArrayList(0);
+		}
+
+		// Return the metadata assigned to the specified file from the applicable metadata.xml files
+		return getMetadataAssignedToFile(file, applicable_metadata_xml_files, filenameEncodingMetaOnly);
+		*/
+    }
+	
 
     /** Returns all the metadata assigned to a file inside the collection. */
     static public ArrayList getMetadataAssignedToFile(File file)
+    {
+	return getMetadataAssignedToFile(file, false);
+    }
+	
+	/** Returns all the metadata assigned to a file inside the collection (or 
+	 *	just gs.filenameEncoding if parameter filenameEncodingMetaOnly is true),
+	 * 	including folder-level/inherited metadata.
+	 */    
+    static public ArrayList getMetadataAssignedToFile(File file, boolean filenameEncodingMetaOnly)
     {
 	// Build up a list of applicable metadata.xml files
@@ -207,13 +247,14 @@
 	}
 
-	// sort the metadataxml files in order starting from those in the 
+	// Sort the metadataxml files in order starting from those in the 
 	// topmost folders down to the one in the lowest level folder.
 	Collections.sort(applicable_metadata_xml_files, metadataXMLFileComparator);
 	// Return the metadata assigned to the specified file from the applicable metadata.xml files
-	return getMetadataAssignedToFile(file, applicable_metadata_xml_files);
-    }
-
-
-    static private ArrayList getMetadataAssignedToFile(File file, ArrayList applicable_metadata_xml_files)
+	return getMetadataAssignedToFile(file, applicable_metadata_xml_files, filenameEncodingMetaOnly);
+    }
+
+	// package access method
+    static ArrayList getMetadataAssignedToFile(File file, ArrayList applicable_metadata_xml_files,
+						       boolean filenameEncodingMetaOnly)	
     {
 	// Build up a list of metadata values assigned to this file
@@ -225,5 +266,5 @@
 	    DebugStream.println("Applicable metadata.xml file: " + metadata_xml_file);
 
-	    ArrayList metadata_values = metadata_xml_file.getMetadataAssignedToFile(file);
+	    ArrayList metadata_values = metadata_xml_file.getMetadataAssignedToFile(file, filenameEncodingMetaOnly);
 	    for (int j = 0; j < metadata_values.size(); j++) {
 		MetadataValue metadata_value = (MetadataValue) metadata_values.get(j);
@@ -316,5 +357,5 @@
 		// This metadata.xml file is only potentially applicable if it is above or at the same level as the file
 		if (current_file_directory_path.startsWith(metadata_xml_file.getParentFile().getAbsolutePath())) {
-		    metadata_xml_file.removeMetadata(current_file, metadata_values);
+		    metadata_xml_file.removeMetadata(file_nodes[i], metadata_values);
 		    if (!modified_metadata_xml_files.contains(metadata_xml_file)) {
 			modified_metadata_xml_files.add(metadata_xml_file);
@@ -350,5 +391,5 @@
 		// This metadata.xml file is only applicable if it is at the same level as the file
 		if (current_file_directory_path.equals(metadata_xml_file.getParentFile().getAbsolutePath())) {
-		    metadata_xml_file.replaceMetadata(current_file, old_metadata_value, new_metadata_value);
+		    metadata_xml_file.replaceMetadata(file_nodes[i], old_metadata_value, new_metadata_value);
 		    if (!modified_metadata_xml_files.contains(metadata_xml_file)) {
 			modified_metadata_xml_files.add(metadata_xml_file);
@@ -399,50 +440,50 @@
 
   
-  /**
-  * Comparator to order MetadataXMLFiles in ascending order from
-  * those in a higher level folder to those in a lower level folder
-  * It is based on the assumption that all MetadataXMLFiles sent to
-  * it to compare will be linear descendants of one toplevel folder
-  * E.g. /A/metadata.xml, /A/B/metadata.xml, /A/B/C/D/metadata.xml.
-  * In other words, that each is a substring of one other until we
-  * get to the toplevel folder.
-  */
-  private static class MetadataXMLFileComparator implements Comparator {
-    
-    public int compare(Object o1, Object o2) {
-      if(!(o1 instanceof MetadataXMLFile)) {
-	return -1;
-      } else if (!(o2 instanceof MetadataXMLFile)) {
-	return 1;
-      } 
-
-      // Both are MetadataXMLFiles objects. Remove the terminating
-      // "metadata.xml" from their filenames to get their containing folder
-      String filename1 = ((MetadataXMLFile)o1).getParentFile().getAbsolutePath();
-      String filename2 = ((MetadataXMLFile)o2).getParentFile().getAbsolutePath();
-
-      // if 1 is a prefix 2, then 1 < 2 in the ordering (1 comes before 2)
-      if(filename2.startsWith(filename1)) {
-	return -1;
-      } else if(filename1.startsWith(filename2)) {
-	return 1;
-      } else { 
-	// unlikely that the metadata.xml files will be the same
-	// or that neither is a prefix of the other
-	return filename1.compareTo(filename2); // sorts in ascending order
-      }      
-      
-    }
-
-    public boolean equals(Object obj) {
-      if(!(obj instanceof MetadataXMLFileComparator)) {
-	return false;
-      } 
-     
-      // else it is the same sort of comparator
-      return true;
-    }
-
-  }
+	/**
+	  * Comparator to order MetadataXMLFiles in ascending order from
+	  * those in a higher level folder to those in a lower level folder
+	  * It is based on the assumption that all MetadataXMLFiles sent to
+	  * it to compare will be linear descendants of one toplevel folder
+	  * E.g. /A/metadata.xml, /A/B/metadata.xml, /A/B/C/D/metadata.xml.
+	  * In other words, that each is a substring of one of the others until we
+	  * the toplevel folder is reached.
+	*/
+	private static class MetadataXMLFileComparator implements Comparator {
+	    
+	    public int compare(Object o1, Object o2) {
+			if(!(o1 instanceof MetadataXMLFile)) {
+				return -1;
+			} else if (!(o2 instanceof MetadataXMLFile)) {
+				return 1;
+			} 
+
+			// Both are MetadataXMLFiles objects. Remove the terminating
+			// "metadata.xml" from their filenames to get their containing folder
+			String filename1 = ((MetadataXMLFile)o1).getParentFile().getAbsolutePath();
+			String filename2 = ((MetadataXMLFile)o2).getParentFile().getAbsolutePath();
+
+			// if 1 is a prefix of 2, then 1 < 2 in the ordering (1 comes before 2)
+			if(filename2.startsWith(filename1)) {
+				return -1;
+			} else if(filename1.startsWith(filename2)) {
+				return 1;
+			} else { 
+				// unlikely that the metadata.xml files will be the same
+				// or that neither is a prefix of the other
+				return filename1.compareTo(filename2); // sorts in ascending order
+			}      
+
+		}
+
+		public boolean equals(Object obj) {
+			if(!(obj instanceof MetadataXMLFileComparator)) {
+				return false;
+			} 
+
+			// else it is the same sort of comparator
+			return true;
+		}
+
+	}
 
 }
Index: main/trunk/gli/src/org/greenstone/gatherer/util/SynchronizedTreeModelTools.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/util/SynchronizedTreeModelTools.java	(revision 23432)
+++ main/trunk/gli/src/org/greenstone/gatherer/util/SynchronizedTreeModelTools.java	(revision 23433)
@@ -31,4 +31,6 @@
 import javax.swing.tree.*;
 import org.greenstone.gatherer.DebugStream;
+import org.greenstone.gatherer.metadata.FilenameEncoding;
+import org.greenstone.gatherer.collection.CollectionTreeNode;
 
 /** Due to the TreeModel objects not having any synchronization, certain assumptions, such as the model state remaining constant during a repaint, don't always hold - especially given that I'm changing the tree model on a different thread. In order to get around this I will use the latest swing paradigm wherein you flag a section of code to be executed by the AWT GUI Event queue, as soon as other gui tasks have finished. This way I shouldn't have tree redraws throwing NPEs because the array size of the children of a certain node has changed -while- the repaint call was made, i.e. repaint() calls getChildCount() = 13, removeNodeFromParent() called, repaint calls getChildAt(12) = ArrayIndexOutOfBoundsException. 
@@ -104,5 +106,17 @@
 	final Runnable doRemoveNodeFromParent = new Runnable() {
 		public void run() {
-		    model.removeNodeFromParent(target_node);
+			// If we're dealing with a collection tree node, it may have 
+			// gs.FilenameEncoding assigned, so we remove its entry from the map. 
+			// Needs to be done here because the tree is constantly changing 
+			// when nodes are being removed, renamed and deleted, and this 
+			// affects lookup queries sent to the map. 
+			// Don't need to do a recursive reset on this coltreenode, because 
+			// Delete/Move/Rename FileJobs were created for *each* node			
+			if(target_node instanceof CollectionTreeNode) {
+				CollectionTreeNode colNode = (CollectionTreeNode)target_node;
+				FilenameEncoding.map.remove(colNode.getURLEncodedFilePath());
+			}
+			
+		    model.removeNodeFromParent(target_node);			
 		}
 	    };
