Index: /trunk/src/org/expeditee/agents/ScaleFrameset.java
===================================================================
--- /trunk/src/org/expeditee/agents/ScaleFrameset.java	(revision 312)
+++ /trunk/src/org/expeditee/agents/ScaleFrameset.java	(revision 312)
@@ -0,0 +1,85 @@
+package org.expeditee.agents;
+
+import java.util.Collection;
+
+import org.expeditee.actions.Misc;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.gui.FrameIO;
+import org.expeditee.gui.FrameKeyboardActions;
+import org.expeditee.gui.Vector;
+import org.expeditee.items.Item;
+import org.expeditee.items.Line;
+import org.expeditee.items.Permission;
+
+public class ScaleFrameset extends DefaultAgent {
+	private int _firstFrame = 1;
+
+	private float _scale = 1F;
+
+	private int _maxFrame = Integer.MAX_VALUE;
+
+	public ScaleFrameset(float scale, int firstFrame, int maxFrame) {
+		super();
+		_firstFrame = firstFrame;
+		_maxFrame = maxFrame;
+		_scale = scale;
+	}
+
+	public ScaleFrameset(float scale) {
+		this(scale, 1, Integer.MAX_VALUE);
+	}
+
+	@Override
+	protected Frame process(Frame frame) {
+		String framesetName = frame.getFramesetName();
+
+		_maxFrame = Math.min(FrameIO.getLastNumber(framesetName), _maxFrame);
+
+		// Scale each of the frames
+		for (int i = _firstFrame; i <= _maxFrame; i++) {
+			Frame next = FrameIO.LoadFrame(framesetName + i);
+			if (next == null)
+				continue;
+
+			for (Vector v : next.getVectors()) {
+				v.Source.scale(_scale, 0, 0);
+			}
+
+			Collection<Item> items = next.getVisibleItems();
+
+			for (Item item : items) {
+				// This line is only needed for circles!!
+				// Need to really fix up the way this works!!
+				if (item.hasEnclosures())
+					continue;
+				if (!item.hasPermission(Permission.full))
+					continue;
+				item.invalidateAll();
+				if (!(item instanceof Line)) {
+					item.scale(_scale, 0, 0);
+				}
+			}
+
+			for (Item item : items) {
+				if (!item.hasPermission(Permission.full))
+					continue;
+				// if (!(item instanceof Line))
+				item.updatePolygon();
+
+				if (item instanceof Line) {
+					((Line) item).refreshStroke(item.getThickness());
+				}
+
+				item.invalidateAll();
+			}
+		}
+		// TODO fix this so it actually works properly!!
+		// TODO figure out why it doesnt repaint correctly sometimes
+
+		// TODO make this thread safe!
+		FrameKeyboardActions.Refresh();
+
+		return null;
+	}
+}
Index: /trunk/src/org/expeditee/agents/SearchGreenstone.java
===================================================================
--- /trunk/src/org/expeditee/agents/SearchGreenstone.java	(revision 312)
+++ /trunk/src/org/expeditee/agents/SearchGreenstone.java	(revision 312)
@@ -0,0 +1,254 @@
+package org.expeditee.agents;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Vector;
+
+import org.expeditee.greenstone.Greenstone3Connection;
+import org.expeditee.greenstone.Query;
+import org.expeditee.greenstone.QueryOutcome;
+import org.expeditee.greenstone.Result;
+import org.expeditee.greenstone.ResultDocument;
+import org.expeditee.gui.AttributeValuePair;
+import org.expeditee.gui.Frame;
+import org.expeditee.gui.FrameCreator;
+import org.expeditee.gui.FrameGraphics;
+import org.expeditee.items.Text;
+
+public class SearchGreenstone extends SearchAgent {
+
+	private static boolean _doCasefolding = true;
+
+	private static boolean _doStemming = false;
+
+	protected static Greenstone3Connection _gsdl = null;
+
+	private static String _maxResults = "10";
+
+	private int _indexChoice = 1;
+
+	private static boolean _getAllMetadata = true;
+
+	private static int _locationChoice = 1;
+
+	private static String[] _indexKeys = { "TX", "TI", "JO", "BO", "CR", "KE" };
+
+	protected static Vector<Result> _currentResultSet;
+
+	public SearchGreenstone(String searchText) {
+		super(searchText);
+	}
+
+	public SearchGreenstone(int resultsCount, String searchText) {
+		this(searchText);
+		_maxResults = resultsCount + "";
+	}
+
+	public static void init(Frame settings) {
+		if (settings == null)
+			return;
+
+		// Set the settings
+		for (Text item : settings.getBodyTextItems(false)) {
+
+			AttributeValuePair avp = new AttributeValuePair(item.getText());
+			if (avp.isAnnotation())
+				continue;
+
+			String attribute = avp.getAttributeOrValue().toLowerCase();
+
+			if (attribute.equals("campus"))
+				_locationChoice = 0;
+			if (attribute.equals("autoconnect"))
+				connect();
+
+			if (attribute.equals("maxresults")) {
+				try {
+					_maxResults = avp.getValue();
+				} catch (Exception e) {
+				}
+			}
+
+			if (attribute.equals("dostemming"))
+				_doStemming = true;
+		}
+	}
+
+	public static void connect() {
+		if (_gsdl == null)
+			_gsdl = new Greenstone3Connection(_locationChoice);
+	}
+
+	@Override
+	protected Frame process(Frame frame) {
+		_results.setTitle("SearchGreenstone [" + _pattern + "]");
+
+		connect();
+
+		doQuery(_pattern);
+
+		createResults();
+		
+		_results.save();
+
+		String resultFrameName = _results.getName();
+		if (_clicked != null)
+			_clicked.setLink(resultFrameName);
+
+		return _results.getFirstFrame();
+	}
+
+	protected void createResults() {
+		viewByScore(_currentResultSet, _results);
+	}
+
+	protected void doQuery(String queryText) {
+		Query query = createQuery(queryText);
+		QueryOutcome queryOutcome = _gsdl.issueQueryToServer(query);
+		_currentResultSet = getResultSetMetadata(queryOutcome);
+	}
+
+	private Query createQuery(String queryText) {
+
+		Query query = new Query();
+
+		// set the query options
+		query.setQueryText(queryText);
+
+		query.setIndex(_indexKeys[_indexChoice]);
+		query.setMaxDocsToReturn(_maxResults);
+
+		if (_doStemming) {
+			query.setStemming("1");
+		} else {
+			query.setStemming("0");
+		}
+
+		if (_doCasefolding) {
+			query.setCasefolding("1");
+		} else {
+			query.setCasefolding("0");
+		}
+
+		return query;
+	}
+
+	public Vector<Result> getResultSetMetadata(QueryOutcome queryOutcome) {
+
+		Vector<Result> results = queryOutcome.getResults();
+		for (Enumeration<Result> e = results.elements(); e.hasMoreElements();) {
+			Result result = e.nextElement();
+			getResultMetadata(result);
+		}
+		return results;
+	}
+
+	private void getResultMetadata(Result result) {
+		String docID = result.getDocID();
+
+		_gsdl.getDocumentMetadataFromServer(docID, "Title");
+		_gsdl.getDocumentMetadataFromServer(docID, "Date");
+
+		if (_getAllMetadata) {
+			_gsdl.getDocumentMetadataFromServer(docID, "Date");
+			_gsdl.getDocumentMetadataFromServer(docID, "Booktitle");
+			_gsdl.getDocumentMetadataFromServer(docID, "Journal");
+			_gsdl.getDocumentMetadataFromServer(docID, "Creator");
+			_gsdl.getDocumentMetadataFromServer(docID, "Keywords");
+			_gsdl.getDocumentMetadataFromServer(docID, "Publisher");
+			_gsdl.getDocumentMetadataFromServer(docID, "Abstract");
+			_gsdl.getDocumentMetadataFromServer(docID, "Pages");
+			_gsdl.getDocumentMetadataFromServer(docID, "Number");
+			_gsdl.getDocumentMetadataFromServer(docID, "Volume");
+		}
+
+	}
+
+	/*
+	 * given the Vector of result items (ordered by descending relevance to the
+	 * query) this method iterates through them and constructs an HTML document
+	 * and has it rendered in the result window
+	 * 
+	 * This is the default presentation for results
+	 * 
+	 * You can modify this method as you wish to change the HTML for the default
+	 * presentation
+	 * 
+	 * Some useful method calls are illustrated here - if you have the ID of a
+	 * document you can get all data stored for it with ResultDocument rd =
+	 * gsdl.getDocument(docID); - a ResultDocument object has a set of methods
+	 * for getting metadata values for that document most metadata is a single
+	 * value, but authors and keywords can have multiple values and these are
+	 * stored in a Vector
+	 * 
+	 * The IF condition for 'Title' shows how to construct a link that can be
+	 * clicked on and then dealt with by the handleLinkClick() method
+	 * 
+	 */
+	public void viewByScore(Vector<Result> results, FrameCreator resultsCreator) {
+
+		for (Result result : results) {
+			String docID = result.getDocID();
+			ResultDocument rd = _gsdl.getDocument(docID);
+			int docRank = result.getRank();
+			// Put the details on a separate frame
+			FrameCreator details = new FrameCreator(rd.getTitle());
+			details.addText(getDetails(rd), null, null, null, true);
+
+			resultsCreator.addText((docRank + 1) + ". " + rd.getTitle(), null,
+					details.getName(), "getTextFromChildFrame", false);
+
+			FrameGraphics.requestRefresh(true);
+		}
+	}
+
+	protected String getDetails(ResultDocument rd) {
+		StringBuffer resultText = new StringBuffer("");
+
+		if (rd.metadataExists("Title")) {
+			resultText.append("title = " + rd.getTitle()).append('\n');
+		}
+		if (rd.metadataExists("Date")) {
+			resultText.append("date = " + rd.getDate()).append('\n');
+		}
+		if (rd.metadataExists("Booktitle")) {
+			resultText.append("booktitle = " + rd.getBooktitle()).append('\n');
+		}
+		if (rd.metadataExists("Pages")) {
+			resultText.append("pages = " + rd.getPages()).append('\n');
+		}
+		if (rd.metadataExists("Journal")) {
+			resultText.append("journal = " + rd.getJournal()).append('\n');
+		}
+		if (rd.metadataExists("Volume")) {
+			resultText.append("volume = " + rd.getVolume()).append('\n');
+		}
+		if (rd.metadataExists("Number")) {
+			resultText.append("number = " + rd.getNumber()).append('\n');
+		}
+		if (rd.metadataExists("Editor")) {
+			resultText.append("editor = " + rd.getEditor()).append('\n');
+		}
+		if (rd.metadataExists("Publisher")) {
+			resultText.append("publisher = " + rd.getPublisher()).append('\n');
+		}
+		if (rd.metadataExists("Abstract")) {
+			resultText.append("abstract = " + rd.getAbstract()).append('\n');
+		}
+		Vector<String> authors = rd.getAuthors();
+		for (Enumeration a = authors.elements(); a.hasMoreElements();) {
+			String author = (String) a.nextElement();
+			resultText.append("author = " + author).append('\n');
+		}
+		Vector<String> keywords = rd.getKeywords();
+		for (Enumeration k = keywords.elements(); k.hasMoreElements();) {
+			String keyword = (String) k.nextElement();
+			resultText.append("keyword = " + keyword).append('\n');
+		}
+		resultText.deleteCharAt(resultText.length() - 1);
+
+		return resultText.toString();
+	}
+
+}
Index: /trunk/src/org/expeditee/agents/SearchGreenstoneByDate.java
===================================================================
--- /trunk/src/org/expeditee/agents/SearchGreenstoneByDate.java	(revision 312)
+++ /trunk/src/org/expeditee/agents/SearchGreenstoneByDate.java	(revision 312)
@@ -0,0 +1,110 @@
+package org.expeditee.agents;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Vector;
+
+import org.expeditee.greenstone.Result;
+import org.expeditee.greenstone.ResultDocument;
+import org.expeditee.gui.FrameCreator;
+import org.expeditee.gui.FrameGraphics;
+
+public class SearchGreenstoneByDate extends SearchGreenstone {
+
+	public SearchGreenstoneByDate(String searchText) {
+		super(searchText);
+	}
+
+	public SearchGreenstoneByDate(int resultsCount, String searchText) {
+		super(resultsCount, searchText);
+	}
+
+	/**
+	 * dateMap is a hash table. The keys are year values. the data associated
+	 * with each key is a Vector of document IDs therefore, for the current
+	 * result set you can get the set of years in which the results were
+	 * published, and for each year you can get the set of documents published
+	 * in that year
+	 * 
+	 * If you want to introduce additional mappings (eg document written by
+	 * authors) you should introduce additional structures here (HashMap used in
+	 * the same way as dateMap will probably suffice
+	 * 
+	 */
+	private static HashMap<String, Vector<String>> _dateMap = new HashMap<String, Vector<String>>();
+
+	protected void doQuery(String queryText) {
+		_dateMap.clear();
+
+		super.doQuery(queryText);
+
+		initialiseDateMap(_currentResultSet);
+	}
+
+	protected void createResults() {
+		viewByDate(_dateMap, _results);
+	}
+
+	public void initialiseDateMap(Vector<Result> results) {
+		for (Result result : results) {
+			String docID = result.getDocID();
+
+			ResultDocument rd = _gsdl.getDocument(docID);
+
+			if (rd.metadataExists("Date")) {
+				addToDateMap(docID, rd);
+			}
+		}
+	}
+
+	public void addToDateMap(String docID, ResultDocument rd) {
+		if (_dateMap.containsKey(rd.getDate())) {
+			Vector<String> dateVector = _dateMap.get(rd.getDate());
+			if (!dateVector.contains(docID)) {
+				dateVector.addElement(docID);
+			}
+			_dateMap.put(rd.getDate(), dateVector);
+		} else {
+			Vector<String> dateVector = new Vector<String>();
+			dateVector.addElement(docID);
+			_dateMap.put(rd.getDate(), dateVector);
+		}
+	}
+
+	/**
+	 * This method provides an alternative view of the result set It uses the
+	 * dateMap which organises results according to the year in which they were
+	 * published
+	 * 
+	 * You will need to write a similar method for any additional view that you
+	 * implement
+	 * 
+	 * You may wish to modify this method to provide a better layout of the
+	 * output
+	 */
+	private void viewByDate(HashMap<String, Vector<String>> dateMap,
+			FrameCreator results) {
+		Vector<String> theDates = new Vector<String>(dateMap.keySet());
+		Collections.sort(theDates);
+
+		for (String date : theDates) {
+			Vector<String> ids = dateMap.get(date);
+
+			// Text dateText = results.addText(date, null, null, null, false);
+			// dateText.setFontStyle("bold");
+
+			for (String docID : ids) {
+				ResultDocument rd = _gsdl.getDocument(docID);
+
+				// Put the details on a separate frame
+				FrameCreator details = new FrameCreator(rd.getTitle());
+				details.addText(getDetails(rd), null, null, null, true);
+
+				results.addText(date + "- " + rd.getTitle(), null, details
+						.getName(), "getTextFromChildFrame", false);
+
+				FrameGraphics.requestRefresh(true);
+			}
+		}
+	}
+}
Index: /trunk/src/org/expeditee/greenstone/CharUtility.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/CharUtility.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/CharUtility.java	(revision 312)
@@ -0,0 +1,20 @@
+package org.expeditee.greenstone;
+
+public class CharUtility {
+
+	/** Returns an int value that is negative if cCompare comes before cRef in the alphabet, zero if
+	*   the two are equal, and positive if cCompare comes after cRef in the alphabet.
+	*/
+	public static int compareCharsAlphabetically(char cCompare, char cRef) {
+		return (alphabetizeChar(cCompare) - alphabetizeChar(cRef));
+	}
+	
+	private static int alphabetizeChar(char c) {
+		if(c < 65) return c;
+		if(c < 89) return (2 * c) - 65;
+		if(c < 97) return c + 24;
+		if(c < 121) return (2 * c) - 128;
+		return c;
+	}
+	
+}
Index: /trunk/src/org/expeditee/greenstone/DoublyLinkedList.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/DoublyLinkedList.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/DoublyLinkedList.java	(revision 312)
@@ -0,0 +1,234 @@
+package org.expeditee.greenstone;
+
+import java.util.NoSuchElementException;
+
+public class DoublyLinkedList {
+	
+	private DLLNode head, last;
+	private int size = 0;
+	
+	public void addFirst(Object data) {
+		DLLNode newNode = new DLLNode();
+		newNode.data = data;
+		if(size==0) {
+			head = newNode;
+			last = head;
+		} else {
+			newNode.nextNode = head;
+			head.previousNode = newNode;
+			head = newNode;
+		}
+		size++;
+	}
+	
+	public void addLast(Object data) {
+		DLLNode newNode = new DLLNode();
+		newNode.data = data;
+		if(size==0) {
+			head = newNode;
+		} else {
+			last.nextNode = newNode;
+			newNode.previousNode = last;
+		}
+		last = newNode;
+		size++;
+	}
+	
+	public void removeFirst() {
+		if(size <= 1) {
+			head = null;
+			last = null;
+		} else {
+			DLLNode oldHead = head;
+			head = oldHead.nextNode;
+			oldHead.nextNode = null;
+			head.previousNode = null;
+		}
+		size--;
+	}
+	
+	public void removeLast() {
+		if(size <= 1) {
+			head = null;
+			last = null;
+		} else {
+			last = last.previousNode;
+			last.nextNode.previousNode = null;
+			last.nextNode = null;
+		}
+		size--;
+	}
+	
+	public int size() {
+		return size;
+	}
+	
+	public void clear() {
+		DLLNode currentNode = last;
+		DLLNode tempNode;
+		while(currentNode != null) {
+			tempNode = currentNode.previousNode;
+			currentNode.nextNode = null;
+			currentNode.previousNode = null;
+			currentNode.data = null;
+			currentNode = tempNode;
+		}
+		last = null;
+		head = null;
+		size = 0;
+	}
+	
+	protected class DLLNode {
+		protected DLLNode nextNode, previousNode;
+		protected Object data;
+	}
+	
+	public DLLIterator iterator() {
+		return new DLLIterator();
+	}
+	
+	public class DLLIterator {
+		
+		private DLLNode currentPreviousNode = null;
+		private DLLNode currentNextNode = head;
+		
+		public boolean hasNext() {
+			if(currentNextNode == null) {
+				return false;
+			} else {
+				return(currentNextNode != null);
+			}
+		}
+		
+		public boolean hasPrevious() {
+			if(currentPreviousNode == null) {
+				return false;
+			} else {
+				return (currentPreviousNode != null);
+			}
+		}
+		
+		public Object next() {
+			if(currentNextNode == null) throw new NoSuchElementException("Attempt to retrieve next value from " +
+				"DoublyLinkedList after all values have already been retrieved. Verify hasNext method returns true " +
+				"before calling next method.");
+			Object data = currentNextNode.data;
+			DLLNode tempNode = currentNextNode;
+			currentNextNode = currentNextNode.nextNode;
+			currentPreviousNode = tempNode;
+			return data;
+		}
+		
+		public Object previous() {
+			if(currentPreviousNode == null) throw new NoSuchElementException("Attempt to retrieve previous value from " +
+				"head node of DoublyLinkedList. Verify hasPrevious method returns true " +
+				"before calling previous method.");
+			Object data = currentPreviousNode.data;
+			DLLNode tempNode = currentPreviousNode;
+			currentPreviousNode = currentPreviousNode.previousNode;
+			currentNextNode = tempNode;
+			return data;
+		}
+		
+		public void resetToBeginning() {
+			currentNextNode = head;
+			currentPreviousNode = null;
+		}
+
+		public void resetToEnd() {
+			currentNextNode = null;
+			currentPreviousNode = last;
+		}
+	}
+	
+	// ******************************************************************************************************************************
+	// *****************************************      from here on down is test code      *******************************************
+	// ******************************************************************************************************************************
+	
+	public static class Test {
+		public static void main(String[] args) {
+			DoublyLinkedList testListOne = new DoublyLinkedList();
+			String testObjectOne = "test object one";
+			testListOne.addFirst(testObjectOne);
+			System.out.println("Size after adding one object by calling addFirst: " + testListOne.size);
+			testListOne.removeFirst();
+			System.out.println("Then called removeFirst and size is: " + testListOne.size);
+			
+			testListOne.addLast(testObjectOne);
+			System.out.println("Size after adding one object by calling addLast: " + testListOne.size);
+			testListOne.removeLast();
+			System.out.println("Then called removeLast and size is: " + testListOne.size);
+			testListOne.clear();
+			testListOne.clear();
+			
+			testListOne.addFirst(testObjectOne);
+			DLLIterator iterator = testListOne.iterator();
+			System.out.println("hasNext method of iterator after adding one object by calling addFirst: " + iterator.hasNext());
+			System.out.println("hasPrevious method of iterator after adding one object by calling addFirst: " + iterator.hasPrevious());
+			String resultString = (String)iterator.next();
+			System.out.println("result string pulled out by calling next: " + resultString);
+			System.out.println("hasNext method of iterator after calling next: " + iterator.hasNext());
+			System.out.println("hasPrevious method of iterator after calling next: " + iterator.hasPrevious());
+			resultString = (String)iterator.previous();
+			System.out.println("result string pulled out by calling previous: " + resultString);
+			testListOne.clear();
+			
+			System.out.println("");
+			
+			String testObjectTwo = "test object two";
+			String testObjectThree = "test object three";
+
+			testListOne.addFirst(testObjectTwo);
+			testListOne.addFirst(testObjectOne);
+			iterator.resetToBeginning();
+			while(iterator.hasNext()) System.out.println((String)iterator.next());
+			testListOne.clear();
+			
+			System.out.println("");
+
+			testListOne.addLast(testObjectOne);
+			testListOne.addLast(testObjectTwo);
+			iterator.resetToBeginning();
+			while(iterator.hasNext()) System.out.println((String)iterator.next());
+			testListOne.clear();
+			
+			System.out.println("");
+
+			testListOne.addFirst(testObjectThree);
+			testListOne.addFirst(testObjectTwo);
+			testListOne.addFirst(testObjectOne);
+			iterator.resetToBeginning();
+			while(iterator.hasNext()) System.out.println((String)iterator.next());
+			testListOne.clear();
+			
+			System.out.println("");
+
+			testListOne.addLast(testObjectOne);
+			testListOne.addLast(testObjectTwo);
+			testListOne.addLast(testObjectThree);
+			iterator.resetToBeginning();
+			while(iterator.hasNext()) System.out.println((String)iterator.next());
+			testListOne.clear();
+			
+			System.out.println("");
+			
+			testListOne.addFirst(testObjectTwo);
+			testListOne.addFirst(testObjectOne);
+			iterator.resetToEnd();
+			while(iterator.hasPrevious()) System.out.println((String)iterator.previous());
+			testListOne.clear();
+			
+			System.out.println("");
+
+			testListOne.addFirst(testObjectThree);
+			testListOne.addFirst(testObjectTwo);
+			testListOne.addFirst(testObjectOne);
+			System.out.println("size after adding three objects: " + testListOne.size());
+			iterator.resetToEnd();
+			while(iterator.hasPrevious()) System.out.println((String)iterator.previous());
+			testListOne.clear();
+			
+			System.out.println("");
+		}
+	}
+}
Index: /trunk/src/org/expeditee/greenstone/Greenstone3Connection.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/Greenstone3Connection.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/Greenstone3Connection.java	(revision 312)
@@ -0,0 +1,772 @@
+package org.expeditee.greenstone;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.xerces.parsers.DOMParser;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * This class provides a simple API for communicating with a Greenstone 3 server
+ * using SOAP.
+ * <p>
+ * Greenstone 3 does not yet 'properly' implement SOAP-based web services. We
+ * would like to use a Greenstone WSDL (Web Services Definition Language) file
+ * and a higher level SOAP Client interface. But we can't. To get around this,
+ * this API uses a simple socket connection to the Greenstone 3 server, and
+ * sends SOAP requests as strings (XML documents). This works but isn't elegant.
+ * The server responds with a string representing an XML document.
+ * <p>
+ * The server's hostname and port are hard-coded. <b>Do not modify them.</b>
+ * <p>
+ * The Greenstone collection to use is <i>hcibib</i>, and this is also
+ * hard-coded. <b>Do not modify this.</b>
+ * <p>
+ * This collection can be accessed from a web browser at <a
+ * href="http://delaware.resnet.scms.waikato.ac.nz:8111/greenstone3/library?a=p&sa=about&c=hcibib">
+ * this location</a>.
+ */
+public class Greenstone3Connection {
+	/** an ordered list of {@link Query} objects */
+	private List queryList;
+
+	/**
+	 * a HashMap of {@link ResultDocument} objects with document IDs as the
+	 * keys. All the results returned in this session.
+	 */
+	private Map allResults;
+
+	/**
+	 * a HashMap keyed on the keywords found for all documents returned in this
+	 * session. Each item in the map is itself a HashMap, keyed on document IDs
+	 * with each item being NULL.
+	 */
+	private Map allKeywords;
+
+	/**
+	 * a HashMap keyed on the author names found for all documents returned in
+	 * this session. Each item in the map is itself a HashMap, keyed on document
+	 * IDs with each item being NULL.
+	 */
+	private Map allAuthors;
+
+	/**
+	 * a HashMap keyed on the publication dates found for all documents returned
+	 * in this session. Each item in the map is itself a HashMap, keyed on
+	 * document IDs with each item being NULL.
+	 */
+	private Map allDates;
+
+	/**
+	 * a HashMap keyed on the journal names found for all documents returned in
+	 * this session. Each item in the map is itself a HashMap, keyed on document
+	 * IDs with each item being NULL.
+	 */
+	private Map allJournals;
+
+	/**
+	 * a HashMap keyed on the book titles found for all documents returned in
+	 * this session. Each item in the map is itself a HashMap, keyed on document
+	 * IDs with each item being NULL.
+	 */
+	private Map allBooktitles;
+
+	/** the <i>hostname</i> where the Greenstone 3 server is running */
+	private String hostname;
+
+	/** the <i>port</i> on which the Greenstone 3 server is running */
+	private int port;
+
+	/** for communication with the server */
+	private Socket socket = null;
+
+	/** for writing the SOAP request strings to the server socket */
+	private PrintWriter toGSDL = null;
+
+	/** for reading the SOAP response strings from the server socket */
+	private BufferedReader fromGSDL = null;
+
+	/** string that starts every SOAP request */
+	private String SOAPrequestHeader;
+
+	/** acts as a template for every SOAP request string */
+	private String SOAPrequestMessage = "<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><soapenv:Body><message><request lang='en' to='hcibib/PROCESSNAME' type='PROCESSTYPE'>REQUESTBODY</request></message></soapenv:Body></soapenv:Envelope>";
+
+	/**
+	 * A client application using this API will normally only create one
+	 * instance of this class.
+	 * <p>
+	 * Create an instance with something like this
+	 * 
+	 * <pre>
+	 * Greenstone3Connection gsdl = new Greenstone3Connection();
+	 * </pre>
+	 * 
+	 * The constructor initialises the following <b>private</b> variables...
+	 * <ul>
+	 * <li>the <i>hostname</i> where the Greenstone 3 server is running</li>
+	 * <li>the <i>port</i> on which the Greenstone 3 server is running</li>
+	 * <li><i>queryList</i> an ordered list of {@link Query} objects</li>
+	 * <li><i>allResults</i> a HashMap of {@link ResultDocument} objects with
+	 * document IDs as the keys. All the results returned in this session.</li>
+	 * <li><i>allKeywords</i> a HashMap keyed on the keywords found for all
+	 * documents returned in this session. Each item in the map is itself a
+	 * HashMap, keyed on document IDs with each item being NULL.</li>
+	 * <li><i>allAuthors</i> a HashMap keyed on the author names found for all
+	 * documents returned in this session. Each item in the map is itself a
+	 * HashMap, keyed on document IDs with each item being NULL.</li>
+	 * <li><i>allDates</i> a HashMap keyed on the publication dates found for
+	 * all documents returned in this session. Each item in the map is itself a
+	 * HashMap, keyed on document IDs with each item being NULL.</li>
+	 * <li><i>allJournals</i> a HashMap keyed on the journal names found for
+	 * all documents returned in this session. Each item in the map is itself a
+	 * HashMap, keyed on document IDs with each item being NULL.</li>
+	 * <li><i>allBooktitles</i> a HashMap keyed on the book titles found for
+	 * all documents returned in this session. Each item in the map is itself a
+	 * HashMap, keyed on document IDs with each item being NULL.</li>
+	 * </ul>
+	 */
+	public Greenstone3Connection(int location) {
+		if (location == 0) {
+			this.hostname = "comp537.cs.waikato.ac.nz";
+			this.port = 80;
+			this.SOAPrequestHeader = "POST /greenstone3/services/localsite HTTP/1.1\nHost: comp537.cs.waikato.ac.nz:80\nSOAPAction: hcibib/PROCESSNAME\nContent-Type: text/xml;charset=utf-8\nContent-Length: ";
+		} else {
+			this.hostname = "130.217.220.10";
+			this.port = 8111;
+			this.SOAPrequestHeader = "POST /greenstone3/services/localsite HTTP/1.1\nHost: 130.217.220.10:8111\nSOAPAction: hcibib/PROCESSNAME\nContent-Type: text/xml;charset=utf-8\nContent-Length: ";
+		}
+		this.queryList = Collections.synchronizedList(new ArrayList());
+		this.allResults = Collections.synchronizedMap(new HashMap());
+		this.allKeywords = Collections.synchronizedMap(new HashMap());
+		this.allAuthors = Collections.synchronizedMap(new HashMap());
+		this.allDates = Collections.synchronizedMap(new HashMap());
+		this.allJournals = Collections.synchronizedMap(new HashMap());
+		this.allBooktitles = Collections.synchronizedMap(new HashMap());
+	}
+
+	public Map getSessionResults() {
+		return this.allResults;
+	};
+
+	/**
+	 * Print a string representation of the list of queries issued in this
+	 * session.
+	 */
+	public void dumpQueryList() {
+		ListIterator iter = queryList.listIterator();
+		while (iter.hasNext()) {
+			Query query = (Query) iter.next();
+			System.out.println(query.toString());
+		}
+	}
+
+	/**
+	 * Print a string representation of the Booktitles occuring for all query
+	 * results in this session. For each booktitle print the IDs of the
+	 * documents with that booktitle.
+	 */
+	public void dumpAllBooktitles() {
+		Set keys = allBooktitles.keySet();
+		Iterator iter = keys.iterator();
+		while (iter.hasNext()) {
+			String booktitle = (String) iter.next();
+			HashMap docMap = (HashMap) allBooktitles.get(booktitle);
+			System.out.println(booktitle);
+			System.out.println(docMap.keySet().toString());
+		}
+	}
+
+	/**
+	 * Print a string representation of the Journals occuring for all query
+	 * results in this session. For each journal print the IDs of the documents
+	 * with that journal.
+	 */
+	public void dumpAllJournals() {
+		Set keys = allJournals.keySet();
+		Iterator iter = keys.iterator();
+		while (iter.hasNext()) {
+			String journal = (String) iter.next();
+			HashMap docMap = (HashMap) allJournals.get(journal);
+			System.out.println(journal);
+			System.out.println(docMap.keySet().toString());
+		}
+	}
+
+	/**
+	 * Print a string representation of the Dates occuring for all query results
+	 * in this session. For each date print the IDs of the documents with that
+	 * date.
+	 */
+	public void dumpAllDates() {
+		Set keys = allDates.keySet();
+		Iterator iter = keys.iterator();
+		while (iter.hasNext()) {
+			String date = (String) iter.next();
+			HashMap docMap = (HashMap) allDates.get(date);
+			System.out.println(date);
+			System.out.println(docMap.keySet().toString());
+		}
+	}
+
+	/**
+	 * Print a string representation of the Authors occuring for all query
+	 * results in this session. For each author print the IDs of the documents
+	 * with that author.
+	 */
+	public void dumpAllAuthors() {
+		Set keys = allAuthors.keySet();
+		Iterator iter = keys.iterator();
+		while (iter.hasNext()) {
+			String author = (String) iter.next();
+			HashMap docMap = (HashMap) allAuthors.get(author);
+			System.out.println(author);
+			System.out.println(docMap.keySet().toString());
+		}
+	}
+
+	/**
+	 * Print a string representation of the Keywords occuring for all query
+	 * results in this session. For each keyword print the IDs of the documents
+	 * with that keyword.
+	 */
+	public void dumpAllKeywords() {
+		Set keys = allKeywords.keySet();
+		Iterator iter = keys.iterator();
+		while (iter.hasNext()) {
+			String keyword = (String) iter.next();
+			HashMap docMap = (HashMap) allKeywords.get(keyword);
+			System.out.println(keyword);
+			System.out.println(docMap.keySet().toString());
+		}
+	}
+
+	/**
+	 * Print a string representation of all the result documents returned by
+	 * queries in this session.
+	 */
+	public void dumpAllResults() {
+		Set keys = allResults.keySet();
+		Iterator iter = keys.iterator();
+
+		while (iter.hasNext()) {
+			String docID = (String) iter.next();
+			ResultDocument resultDocument = (ResultDocument) allResults
+					.get(docID);
+			System.out.println("____________" + docID + " ___________");
+			System.out.println(resultDocument.toString());
+		}
+	}
+
+	/**
+	 * Print all the result documents IDs returned by queries in this session,
+	 * along with their titles.
+	 */
+	public void dumpAllTitles() {
+		Set keys = allResults.keySet();
+		Iterator iter = keys.iterator();
+		while (iter.hasNext()) {
+			String docID = (String) iter.next();
+			ResultDocument resultDocument = (ResultDocument) allResults
+					.get(docID);
+			System.out.println(docID + "\t" + resultDocument.getTitle());
+		}
+	}
+
+	/**
+	 * Provides the {@link ResultDocument} object for the document with the
+	 * given ID
+	 * 
+	 * @param docID
+	 *            is a document identifier, in the form returned by the server
+	 *            and available from a {@link QueryOutcome}
+	 * @return the {@link ResultDocument} object reflecting the state of the
+	 *         result document at the time that this method was called. The
+	 *         state can change as more metadata is retrieved for the document
+	 *         and the document is returned by further queries.
+	 */
+	public ResultDocument getDocument(String docID) {
+		return (ResultDocument) allResults.get(docID);
+	}
+
+	/**
+	 * Implements the actual communication with the server. <b>You can not call
+	 * this method directly from your client code.</b>
+	 * <p>
+	 * Throws an exception and exits if the hosthame is not known or the
+	 * connection can't be established.
+	 * <p>
+	 * 
+	 * @param request
+	 *            an already well formed string that contains the appropriate
+	 *            HTTP headers and a SOAP message (in XML form) that will ask
+	 *            the server for some information.
+	 * @return a string containing a SOAP message (an XML document) that the
+	 *         server returned in response to the request
+	 */
+	private String doRequest(String request) {
+		// System.err.println("Connecting to " + hostname + " on port " + port);
+		try {
+			try {
+				socket = new Socket(hostname, port);
+			} catch (SecurityException se) {
+				System.err.println("Security exception : " + se);
+				System.exit(1);
+			}
+			toGSDL = new PrintWriter(socket.getOutputStream(), true);
+			fromGSDL = new BufferedReader(new InputStreamReader(socket
+					.getInputStream()));
+		} catch (UnknownHostException e) {
+			System.err.println("Don't know about GSDL host: " + hostname);
+			System.exit(1);
+		} catch (IOException e) {
+			System.err.println("IO exception : " + e);
+			System.exit(1);
+		}
+
+		String result = null;
+		toGSDL.println(request);
+		// System.err.println("Issued request to " + hostname + " on port " +
+		// port);
+		try {
+			String terminator = "Envelope>";
+			String response = "";
+
+			char c;
+			do {
+				c = (char) fromGSDL.read();
+				response = response + c;
+			} while (!response.endsWith(terminator));
+			toGSDL.close();
+			fromGSDL.close();
+			socket.close();
+
+			int start = response.indexOf("<?xml");
+			result = response.substring(start);
+			// System.out.println(result);
+			int a = result.indexOf('\n');
+			int b = result.indexOf('\n', a + 1);
+			while (a != -1 && b != -1) {
+				// System.out.println(a + " " +b);
+				result = result.substring(0, a - 1) + result.substring(b + 1);
+				a = result.indexOf('\n');
+				b = result.indexOf('\n', a + 1);
+			}
+		} catch (IOException e) {
+			System.err.println(e);
+			System.exit(1);
+		}
+		return result;
+	}
+
+	/**
+	 * Produces a SOAP request string, sends it to the server, gets and
+	 * processes the response updating the appropriate data structures. Uses the
+	 * settings represented in the provided argument to produce a SOAP request
+	 * string. The string is sent to the server using the {@link doRequest}
+	 * method. The returned XML document is processed and the information
+	 * therein is used to store information about the returned documents and
+	 * this query.
+	 * <p>
+	 * This method updates the {@link queryList} and {@link allResults} data
+	 * <p>
+	 * 
+	 * @param query
+	 *            a {@link Query} object that must be constructed and passed to
+	 *            this method by the calling client application
+	 * @return a {@link QueryOutcome} object that stores information about the
+	 *         server's response
+	 * 
+	 */
+	public QueryOutcome issueQueryToServer(Query query) {
+		QueryOutcome queryOutcome = new QueryOutcome();
+		String result = null;
+		String requestBody = "<paramList><param name='maxDocs' value='MAXDOCS'/><param name='level' value='Sec'/><param name ='index' value='INDEX'/><param name='matchMode' value='MATCHMODE'/><param name='query' value='QUERY'/><param name='case' value='CASE'/><param name='sortBy' value='SORTBY'/><param name='stem' value='STEM'/><param name='firstDoc' value='FIRSTDOC'/><param name='lastDoc' value='LASTDOC'/></paramList>";
+		requestBody = requestBody.replaceFirst("MAXDOCS", query
+				.getMaxDocsToReturn());
+		requestBody = requestBody.replaceFirst("INDEX", query.getIndex());
+		requestBody = requestBody.replaceFirst("MATCHMODE", query
+				.getMatchMode());
+		requestBody = requestBody.replaceFirst("QUERY", query.getQueryText());
+		requestBody = requestBody.replaceFirst("CASE", query.getCasefolding());
+		requestBody = requestBody.replaceFirst("SORTBY", query.getSortBy());
+		requestBody = requestBody.replaceFirst("STEM", query.getStemming());
+		requestBody = requestBody.replaceFirst("FIRSTDOC", query.getFirstDoc());
+		requestBody = requestBody.replaceFirst("LASTDOC", query.getLastDoc());
+		String request = SOAPrequestMessage.replaceFirst("PROCESSNAME",
+				"TextQuery");
+		request = request.replaceFirst("PROCESSTYPE", "process");
+		request = request.replaceFirst("REQUESTBODY", requestBody);
+		request = SOAPrequestHeader.replaceFirst("PROCESSNAME", "TextQuery")
+				+ request.length() + "\n\n" + request;
+
+		int firstDoc = java.lang.Integer.parseInt(query.getFirstDoc());
+
+		result = doRequest(request);
+		// System.out.println("\n\n" + result + "\n");
+		StringReader sr = new StringReader(result);
+		InputSource is = new InputSource(sr);
+		DOMParser p = new DOMParser();
+		try {
+			p.parse(is);
+		} catch (SAXException se) {
+			System.err.println(se);
+		} catch (IOException ioe) {
+			System.err.println(ioe);
+		}
+		Document d = p.getDocument();
+		NodeList metadataList = d.getElementsByTagName("metadata");
+		for (int i = 0; i < metadataList.getLength(); i++) {
+			Node n = metadataList.item(i);
+			NamedNodeMap nnm = n.getAttributes();
+			Node att = nnm.getNamedItem("name");
+			if (att.getNodeValue().compareTo("numDocsMatched") == 0) {
+				queryOutcome.setHowManyDocsMatched(n.getFirstChild()
+						.getNodeValue());
+			} else if (att.getNodeValue().compareTo("numDocsReturned") == 0) {
+				queryOutcome.setHowManyDocsReturned(n.getFirstChild()
+						.getNodeValue());
+			}
+		}
+
+		NodeList documentList = d.getElementsByTagName("documentNode");
+		for (int i = 0; i < documentList.getLength(); i++) {
+			Node n = documentList.item(i);
+			NamedNodeMap nnm = n.getAttributes();
+			Node nid = nnm.getNamedItem("nodeID");
+			Node nscore = nnm.getNamedItem("rank");
+			String docID = nid.getFirstChild().getNodeValue();
+			queryOutcome.addResult(docID, firstDoc + i, nscore.getFirstChild()
+					.getNodeValue());
+		}
+		query.addQueryOutcome(queryOutcome);
+		Query q = (Query) query.clone();
+		queryList.add(q);
+
+		for (int i = 0; i < documentList.getLength(); i++) {
+			Node n = documentList.item(i);
+			NamedNodeMap nnm = n.getAttributes();
+			Node nid = nnm.getNamedItem("nodeID");
+			Node nscore = nnm.getNamedItem("rank");
+			String docID = nid.getFirstChild().getNodeValue();
+
+			QueryContext queryContext = new QueryContext(firstDoc + i, nscore
+					.getFirstChild().getNodeValue(), q);
+			if (allResults.containsKey(docID)) {
+				ResultDocument resultDocument = (ResultDocument) allResults
+						.get(docID);
+				resultDocument.incrementFrequencyReturned();
+				resultDocument.addQueryContext(queryContext);
+				allResults.put(docID, resultDocument);
+			} else {
+				ResultDocument resultDocument = new ResultDocument();
+				resultDocument.addQueryContext(queryContext);
+				allResults.put(docID, resultDocument);
+			}
+		}
+		return queryOutcome;
+	}
+
+	/**
+	 * Produces a SOAP request string, sends it to the server, gets and
+	 * processes the response updating the appropriate data structures. Given a
+	 * document identifier and the name of a metadata item, this method produces
+	 * a SOAP request string. The string is sent to the server using the
+	 * {@link doRequest} method.
+	 * <p>
+	 * The request is simply for the values of the given metadata item of the
+	 * given document. <b>If the metadata item for the given document has
+	 * already been retrieved from the server, the server is NOT contacted
+	 * again.</b>
+	 * <p>
+	 * The returned XML document is processed. The {@link ResultDocument} object
+	 * for the document in question is updated with the returned metadata
+	 * information, and the {@link allResults} data is consequently updated.
+	 * <p>
+	 * If the requested metadata is one of Keywords, Authors, Dates, Journals,
+	 * Booktitles then the appropriate data structure is updated.
+	 * <p>
+	 * The method does not return a value. Private data structures are updated
+	 * instead. The calling client application should proceed to access document
+	 * metadata using the provided methods.
+	 * <p>
+	 * 
+	 * @param docID
+	 *            is a document identifier, in the form returned by the server
+	 *            and available from a {@link QueryOutcome}
+	 * @param metadata
+	 *            is the metadata field whose value is to be retrieved. Valid
+	 *            values are
+	 *            <ul>
+	 *            <li>Title</li>
+	 *            <li>Creator (the authors)</li>
+	 *            <li>Journal</li>
+	 *            <li>Booktitle</li>
+	 *            <li>Volume</li>
+	 *            <li>Number</li>
+	 *            <li>Editor</li>
+	 *            <li>Pages</li>
+	 *            <li>Publisher</li>
+	 *            <li>Date</li>
+	 *            <li>Keywords</li>
+	 *            <li>Abstract</li>
+	 *            </ul>
+	 */
+	public void getDocumentMetadataFromServer(String docID, String metadata) {
+		ResultDocument resultDocument = (ResultDocument) allResults.get(docID);
+		if (resultDocument.metadataExists(metadata)) {
+			return;
+		}
+
+		String result = null;
+		String requestBody = "<paramList><param name='metadata' value='METADATAFIELD'/></paramList><documentNodeList><documentNode nodeID='DOCIDVALUE'/></documentNodeList>";
+		requestBody = requestBody.replaceFirst("METADATAFIELD", metadata);
+		requestBody = requestBody.replaceFirst("DOCIDVALUE", docID);
+		String request = SOAPrequestMessage.replaceFirst("PROCESSNAME",
+				"DocumentMetadataRetrieve");
+		request = request.replaceFirst("PROCESSTYPE", "process");
+		request = request.replaceFirst("REQUESTBODY", requestBody);
+
+		request = SOAPrequestHeader.replaceFirst("PROCESSNAME",
+				"DocumentMetadataRetrieve")
+				+ request.length() + "\n\n" + request;
+
+		result = doRequest(request);
+		StringReader sr = new StringReader(result);
+		InputSource is = new InputSource(sr);
+		DOMParser p = new DOMParser();
+		try {
+			p.parse(is);
+		} catch (SAXException se) {
+			System.err.println(se);
+		} catch (IOException ioe) {
+			System.err.println(ioe);
+		}
+		Document d = p.getDocument();
+		NodeList metadataList = d.getElementsByTagName("metadata");
+		String metadataval = null;
+		if (metadataList.getLength() > 0) {
+			Node n = metadataList.item(0);
+			metadataval = n.getFirstChild().getNodeValue();
+
+			if (metadata.compareTo("Keywords") == 0) {
+				String[] keywords = metadataval.split(",");
+				for (int i = 0; i < keywords.length; i++) {
+					String s = keywords[i].trim().toLowerCase();
+					resultDocument.addKeyword(s);
+					if (allKeywords.containsKey(s)) {
+						HashMap docMap = (HashMap) allKeywords.get(s);
+						docMap.put(docID, null);
+						allKeywords.put(s, docMap);
+					} else {
+						HashMap docMap = new HashMap();
+						docMap.put(docID, null);
+						allKeywords.put(s, docMap);
+					}
+				}
+			} else if (metadata.compareTo("Creator") == 0) {
+				String[] authors = metadataval.split("(,)|( and )");
+				// System.err.println(metadataval);
+				for (int i = 0; i < authors.length; i++) {
+					authors[i] = authors[i].trim().toLowerCase();
+				}
+
+				boolean containsExtraName = authors.length % 2 != 0;
+
+				for (int i = 0; i + 1 < authors.length; i = i + 2) {
+					String s = authors[i] + ", " + authors[i + 1];
+
+					//Handle names with jr. in them
+					if (containsExtraName) {
+						if (i + 2 < authors.length
+								&& authors[i + 2].contains("jr")) {
+							s += " " + authors[i + 2];
+							i++;
+						}
+					}
+
+					s = s.replaceAll("[.]", "");
+					// System.err.println(s);
+					resultDocument.addAuthor(s);
+					if (allAuthors.containsKey(s)) {
+						HashMap docMap = (HashMap) allAuthors.get(s);
+						docMap.put(docID, null);
+						allAuthors.put(s, docMap);
+					} else {
+						HashMap docMap = new HashMap();
+						docMap.put(docID, null);
+						allAuthors.put(s, docMap);
+					}
+				}
+			} else if (metadata.compareTo("Title") == 0) {
+				resultDocument.setTitle(metadataval);
+			} else if (metadata.compareTo("Booktitle") == 0) {
+				resultDocument.setBooktitle(metadataval);
+				if (allBooktitles.containsKey(metadataval)) {
+					HashMap docMap = (HashMap) allBooktitles.get(metadataval);
+					docMap.put(docID, null);
+					allBooktitles.put(metadataval, docMap);
+				} else {
+					HashMap docMap = new HashMap();
+					docMap.put(docID, null);
+					allBooktitles.put(metadataval, docMap);
+				}
+			} else if (metadata.compareTo("Date") == 0) {
+				resultDocument.setDate(metadataval.replaceAll("[^0-9]", ""));
+				if (allDates.containsKey(metadataval)) {
+					HashMap docMap = (HashMap) allDates.get(metadataval);
+					docMap.put(docID, null);
+					allDates.put(metadataval, docMap);
+				} else {
+					HashMap docMap = new HashMap();
+					docMap.put(docID, null);
+					allDates.put(metadataval, docMap);
+				}
+			} else if (metadata.compareTo("Pages") == 0) {
+				resultDocument.setPages(metadataval);
+			} else if (metadata.compareTo("Journal") == 0) {
+				resultDocument.setJournal(metadataval);
+				if (allJournals.containsKey(metadataval)) {
+					HashMap docMap = (HashMap) allJournals.get(metadataval);
+					docMap.put(docID, null);
+					allJournals.put(metadataval, docMap);
+				} else {
+					HashMap docMap = new HashMap();
+					docMap.put(docID, null);
+					allJournals.put(metadataval, docMap);
+				}
+			} else if (metadata.compareTo("Volume") == 0) {
+				resultDocument.setVolume(metadataval);
+			} else if (metadata.compareTo("Number") == 0) {
+				resultDocument.setNumber(metadataval);
+			} else if (metadata.compareTo("Abstract") == 0) {
+				resultDocument.setAbstract(metadataval);
+			} else if (metadata.compareTo("Editor") == 0) {
+				resultDocument.setEditor(metadataval);
+			} else if (metadata.compareTo("Publisher") == 0) {
+				resultDocument.setPublisher(metadataval);
+			}
+
+		}
+		allResults.put(docID, resultDocument);
+	}
+
+	public String getClassifierNodeName(String nodeID) {
+		String result = null;
+		String requestBody = "<paramList><param name='metadata' value='Title'/></paramList><classifierNodeList><classifierNode nodeID='NODEID'/></classifierNodeList>";
+		requestBody = requestBody.replaceFirst("NODEID", nodeID);
+		String request = SOAPrequestMessage.replaceFirst("PROCESSNAME",
+				"ClassifierBrowseMetadataRetrieve");
+		request = request.replaceFirst("PROCESSTYPE", "process");
+		request = request.replaceFirst("REQUESTBODY", requestBody);
+
+		request = SOAPrequestHeader.replaceFirst("PROCESSNAME",
+				"ClassifierBrowseMetadataRetrieve")
+				+ request.length() + "\n\n" + request;
+
+		// System.err.println(request);
+		result = doRequest(request);
+		// System.err.println(result);
+
+		StringReader sr = new StringReader(result);
+		InputSource is = new InputSource(sr);
+		DOMParser p = new DOMParser();
+		try {
+			p.parse(is);
+		} catch (SAXException se) {
+			System.err.println(se);
+		} catch (IOException ioe) {
+			System.err.println(ioe);
+		}
+
+		String returnName = null;
+
+		Document d = p.getDocument();
+		NodeList metadataList = d.getElementsByTagName("metadata");
+		for (int i = 0; i < metadataList.getLength(); i++) {
+			Node n = metadataList.item(i);
+			NamedNodeMap nnm = n.getAttributes();
+			Node att = nnm.getNamedItem("name");
+			if (att.getNodeValue().compareTo("Title") == 0) {
+				returnName = n.getFirstChild().getNodeValue();
+			}
+		}
+		return returnName;
+	}
+
+	public void getClassifierNodes(String rootNode) {
+		String result = null;
+		String requestBody = "<paramList><param name='structure' value='children'/></paramList><classifierNodeList><classifierNode nodeID='CLASSIFIER'/></classifierNodeList>";
+		requestBody = requestBody.replaceFirst("CLASSIFIER", rootNode);
+		String request = SOAPrequestMessage.replaceFirst("PROCESSNAME",
+				"ClassifierBrowse");
+		request = request.replaceFirst("PROCESSTYPE", "process");
+		request = request.replaceFirst("REQUESTBODY", requestBody);
+
+		request = SOAPrequestHeader.replaceFirst("PROCESSNAME",
+				"ClassifierBrowse")
+				+ request.length() + "\n\n" + request;
+
+		System.err.println(getClassifierNodeName(rootNode));
+		// System.err.print(rootNode + "#");
+
+		// System.err.println(request);
+		result = doRequest(request);
+		// System.err.println(result);
+
+		StringReader sr = new StringReader(result);
+		InputSource is = new InputSource(sr);
+		DOMParser p = new DOMParser();
+		try {
+			p.parse(is);
+		} catch (SAXException se) {
+			System.err.println(se);
+		} catch (IOException ioe) {
+			System.err.println(ioe);
+		}
+		Document d = p.getDocument();
+
+		NodeList childList = d.getElementsByTagName("classifierNode");
+		NodeList documentList = d.getElementsByTagName("documentNode");
+		// System.err.println("\td " + documentList.getLength());
+		// System.err.println("\tc " + childList.getLength());
+
+		if (childList.getLength() > 0) {
+			for (int i = 0; i < childList.getLength(); i++) {
+				Node n = childList.item(i);
+				NamedNodeMap nnm = n.getAttributes();
+				Node nid = nnm.getNamedItem("nodeID");
+				String nodeID = nid.getFirstChild().getNodeValue();
+
+				// System.err.println("\tchild : " + nodeID);
+
+				if (nodeID.compareTo(rootNode) != 0
+						&& nodeID.compareTo("2.6.22") != 0) {
+					// System.err.println("\t" + nodeID);
+					getClassifierNodes(nodeID);
+				}
+			}
+		}
+		if (documentList.getLength() > 0)
+			System.out.println(getClassifierNodeName(rootNode) + "#"
+					+ documentList.getLength());
+	}
+}
Index: /trunk/src/org/expeditee/greenstone/Query.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/Query.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/Query.java	(revision 312)
@@ -0,0 +1,136 @@
+package org.expeditee.greenstone;
+
+import java.util.Vector;
+
+public class Query implements Cloneable
+{
+        private String queryText;
+        private String stemming;
+        private String casefolding;
+        private String maxDocsToReturn;
+        private String sortBy;
+        private String matchMode;
+        private String index;
+        private String firstDoc;
+        private String lastDoc;
+
+	private QueryOutcome queryOutcome;
+
+        public Query()
+        {
+                queryText = "the";
+                stemming = "0";
+                casefolding = "1";
+                maxDocsToReturn = "5";
+                sortBy = "1";
+                matchMode = "all";
+                index = "TX";
+		firstDoc = "0";
+		lastDoc = "0";
+
+		queryOutcome = new QueryOutcome();
+        }
+
+        public Query clone() {
+            try {
+                return (Query)super.clone();
+            }
+            catch (CloneNotSupportedException e) {
+                throw new InternalError(e.toString());
+            }
+        }
+
+	public String toString() {
+		String dump = "\nqueryText\t" + this.queryText + "\nstemming\t" + stemming + "\ncasefolding\t" + casefolding + "\nmaxDocs\t\t" + maxDocsToReturn;
+		dump = dump + "\nsortBy\t\t" + sortBy + "\nmatchMode\t" + matchMode + "\nindex\t\t" + index;
+		dump = dump + "\nfirstDoc\t" + firstDoc + "\nlastDoc\t\t" + lastDoc + "\n";
+
+		return dump + queryOutcome.toString();
+	}
+
+	public void setQueryText(String queryText) {
+		this.queryText = new String(queryText);
+	}
+
+	public void setStemming(String stemming) {
+		this.stemming = new String(stemming);
+	}
+
+	public void setCasefolding(String casefolding) {
+		this.casefolding = new String(casefolding);
+	}
+
+	public void setMaxDocsToReturn(String maxDocsToReturn) {
+		this.maxDocsToReturn = new String(maxDocsToReturn);
+	}
+/*
+	public void setSortBy(String sortBy) {
+		this.sortBy = new String(sortBy);
+	}
+*/
+
+	public void setMatchMode(String matchMode) {
+		this.matchMode = new String(matchMode);
+	}
+
+	public void setIndex(String index) {
+		this.index = new String(index);
+	}
+
+	public void setFirstDoc(String firstDoc) {
+		this.firstDoc = new String(firstDoc);
+	}
+
+	public void setLastDoc(String lastDoc) {
+		this.lastDoc = new String(lastDoc);
+	}
+
+
+	public void addQueryOutcome(QueryOutcome queryOutcome) {
+		this.queryOutcome = queryOutcome;
+	}
+
+
+
+
+        public String getQueryText() {
+                return this.queryText;
+        }
+
+        public String getStemming() {
+                return this.stemming;
+        }
+
+        public String getCasefolding() {
+                return this.casefolding;
+        }
+
+        public String getMaxDocsToReturn() {
+                return this.maxDocsToReturn;
+        }
+
+        public String getSortBy() {
+                return this.sortBy;
+        }
+
+        public String getMatchMode() {
+                return this.matchMode;
+        }
+
+        public String getIndex() {
+                return this.index;
+        }
+
+	public String getFirstDoc() {
+		return this.firstDoc;
+	}
+
+	public String getLastDoc() {
+		return this.lastDoc;
+	}
+
+
+	public QueryOutcome getQueryOutcome() {
+		return this.queryOutcome;
+	}
+}
Index: /trunk/src/org/expeditee/greenstone/QueryContext.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/QueryContext.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/QueryContext.java	(revision 312)
@@ -0,0 +1,36 @@
+package org.expeditee.greenstone;
+
+public class QueryContext
+{
+        private int rank;
+        private String score;
+        private Query query;
+
+        public QueryContext(int rank, String score, Query query)
+        {
+		this.rank = rank;
+		this.score = score;
+		this.query = query;
+        }
+
+	public String toString() {
+		String dump = "\n\nQueryContext..\n";
+		dump = dump + "rank\t\t" + rank + "\n";
+		dump = dump + "score\t\t" + score ;
+		dump = dump + query.toString();
+
+		return dump;
+	}
+
+	public int getRank() {
+		return this.rank;
+	}
+
+	public String getScore() {
+		return this.score;
+	}
+
+	public Query getQuery() {
+		return this.query;
+	}
+}
Index: /trunk/src/org/expeditee/greenstone/QueryOutcome.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/QueryOutcome.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/QueryOutcome.java	(revision 312)
@@ -0,0 +1,57 @@
+package org.expeditee.greenstone;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+public class QueryOutcome
+{
+        private String docsMatched;
+        private String docsReturned;
+
+	private Vector<Result> results;
+
+        public QueryOutcome()
+        {
+		docsMatched = "0";
+		docsReturned = "0";
+		results = new Vector<Result>();
+        }
+
+	public String toString() {
+		String dump = this.docsMatched + " documents matched the query \n";
+		dump = dump + this.docsReturned + " documents were returned\n";
+
+
+        	for (Enumeration e = this.results.elements(); e.hasMoreElements();) {
+                	Result result = (Result) e.nextElement();
+			dump = dump + result.toString();
+		}
+		return dump;
+	}
+
+	public void setHowManyDocsMatched(String docsMatched) {
+		this.docsMatched = new String(docsMatched);
+	}
+
+	public void setHowManyDocsReturned(String docsReturned) {
+		this.docsReturned = new String(docsReturned);
+	}
+
+	public void addResult(String docID, int rank, String score) {
+		this.results.addElement(new Result(docID, rank, score));
+	}
+
+
+
+        public String getHowManyDocsMatched() {
+                return this.docsMatched;
+        }
+
+        public String getHowManyDocsReturned() {
+                return this.docsReturned;
+        }
+
+	public Vector<Result> getResults() {
+		return this.results;
+	}
+}
Index: /trunk/src/org/expeditee/greenstone/Result.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/Result.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/Result.java	(revision 312)
@@ -0,0 +1,30 @@
+package org.expeditee.greenstone;
+
+public class Result {
+	private String docID;
+	private int rank;
+	private String score;
+
+	Result(String docID, int rank, String score) {
+		this.docID = docID;
+		this.rank = rank;
+		this.score = score;
+	}
+
+	public String toString() {
+		String dump = this.rank + "\t" + this.score + "\t" + this.docID + "\n";
+		return dump;
+	}
+
+	public String getDocID() {
+		return this.docID;
+	}
+
+	public int getRank() {
+		return this.rank;
+	}
+
+	public String getScore() {
+		return this.score;
+	}
+}
Index: /trunk/src/org/expeditee/greenstone/ResultDocument.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/ResultDocument.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/ResultDocument.java	(revision 312)
@@ -0,0 +1,229 @@
+package org.expeditee.greenstone;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Vector;
+
+public class ResultDocument {
+	private Vector<String> authors;
+
+	private Vector<String> keywords;
+
+	private String title;
+
+	private String date;
+
+	private String booktitle;
+
+	private String pages;
+
+	private String journal;
+
+	private String volume;
+
+	private String number;
+
+	private String docabstract;
+
+	private String editor;
+
+	private String publisher;
+
+	private int frequencyReturned;
+
+	private Vector<QueryContext> queryContexts;
+
+	private Set<String> retrievedMetadata;
+
+	public ResultDocument() {
+		authors = new Vector<String>();
+		keywords = new Vector<String>();
+		title = null;
+		date = null;
+		booktitle = null;
+		pages = null;
+		journal = null;
+		volume = null;
+		number = null;
+		docabstract = null;
+		editor = null;
+		publisher = null;
+		frequencyReturned = 1;
+		queryContexts = new Vector<QueryContext>();
+		retrievedMetadata = new HashSet<String>();
+	}
+
+	public String toString() {
+		String dump = "Authors\t\t" + authors.toString() + "\n";
+		dump = dump + "Keywords\t" + keywords.toString() + "\n";
+		dump = dump + "Title\t\t" + title + "\n";
+		dump = dump + "Date\t\t" + date + "\n";
+		dump = dump + "Booktitle\t" + booktitle + "\n";
+		dump = dump + "Pages\t\t" + pages + "\n";
+		dump = dump + "Journal\t\t" + journal + "\n";
+		dump = dump + "Volume\t\t" + volume + "\n";
+		dump = dump + "Number\t\t" + number + "\n";
+		dump = dump + "Editor\t\t" + editor + "\n";
+		dump = dump + "Publisher\t" + publisher + "\n";
+		dump = dump + "Frequency\t" + frequencyReturned + "\n";
+		dump = dump + "Abstract\t" + docabstract;
+
+		for (QueryContext queryContext : queryContexts) {
+			dump = dump + queryContext.toString();
+		}
+		return dump;
+	}
+
+	public String toDelimitedString(String delimiter) {
+		String dump = "";
+		dump = dump + date + delimiter;
+		if (authors.size() > 0) {
+			dump = dump + authors.elementAt(0) + delimiter;
+		} else {
+			dump = dump + null + delimiter;
+		}
+		if (keywords.size() > 0) {
+			dump = dump + keywords.elementAt(0) + delimiter;
+		} else {
+			dump = dump + null + delimiter;
+		}
+		dump = dump + title + delimiter;
+		dump = dump + booktitle + delimiter;
+		dump = dump + journal + delimiter;
+		dump = dump + volume + delimiter;
+		dump = dump + number + delimiter;
+		dump = dump + editor + delimiter;
+		dump = dump + publisher + delimiter;
+		dump = dump + frequencyReturned;
+		return dump;
+	}
+
+	public void addAuthor(String author) {
+		this.authors.addElement(author);
+		this.retrievedMetadata.add("Creator");
+	}
+
+	public void addKeyword(String keyword) {
+		this.keywords.addElement(keyword);
+		this.retrievedMetadata.add("Keywords");
+	}
+
+	public void setTitle(String title) {
+		this.title = new String(title);
+		this.retrievedMetadata.add("Title");
+	}
+
+	public void setDate(String date) {
+		this.date = new String(date);
+		this.retrievedMetadata.add("Date");
+	}
+
+	public void setBooktitle(String booktitle) {
+		this.booktitle = new String(booktitle);
+		this.retrievedMetadata.add("Booktitle");
+	}
+
+	public void setPages(String pages) {
+		this.pages = new String(pages);
+		this.retrievedMetadata.add("Pages");
+	}
+
+	public void setJournal(String journal) {
+		this.journal = new String(journal);
+		this.retrievedMetadata.add("Journal");
+	}
+
+	public void setVolume(String volume) {
+		this.volume = new String(volume);
+		this.retrievedMetadata.add("Volume");
+	}
+
+	public void setNumber(String number) {
+		this.number = new String(number);
+		this.retrievedMetadata.add("Number");
+	}
+
+	public void setAbstract(String docabstract) {
+		this.docabstract = new String(docabstract);
+		this.retrievedMetadata.add("Abstract");
+	}
+
+	public void setEditor(String editor) {
+		this.editor = new String(editor);
+		this.retrievedMetadata.add("Editor");
+	}
+
+	public void setPublisher(String publisher) {
+		this.publisher = new String(publisher);
+		this.retrievedMetadata.add("Publisher");
+	}
+
+	public void incrementFrequencyReturned() {
+		this.frequencyReturned++;
+	}
+
+	public void addQueryContext(QueryContext queryContext) {
+		this.queryContexts.addElement(queryContext);
+	}
+
+	public Vector<String> getAuthors() {
+		return authors;
+	}
+
+	public Vector<String> getKeywords() {
+		return keywords;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public String getDate() {
+		return date;
+	}
+
+	public String getBooktitle() {
+		return booktitle;
+	}
+
+	public String getPages() {
+		return pages;
+	}
+
+	public String getJournal() {
+		return journal;
+	}
+
+	public String getVolume() {
+		return volume;
+	}
+
+	public String getNumber() {
+		return number;
+	}
+
+	public String getAbstract() {
+		return docabstract;
+	}
+
+	public String getEditor() {
+		return editor;
+	}
+
+	public String getPublisher() {
+		return publisher;
+	}
+
+	public int getFrequencyReturned() {
+		return frequencyReturned;
+	}
+
+	public Vector getQueryContexts() {
+		return this.queryContexts;
+	}
+
+	public boolean metadataExists(String metadataTag) {
+		return this.retrievedMetadata.contains(metadataTag);
+	}
+
+}
Index: /trunk/src/org/expeditee/greenstone/hcibibGUI.java
===================================================================
--- /trunk/src/org/expeditee/greenstone/hcibibGUI.java	(revision 312)
+++ /trunk/src/org/expeditee/greenstone/hcibibGUI.java	(revision 312)
@@ -0,0 +1,559 @@
+package org.expeditee.greenstone;
+
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+
+public class hcibibGUI implements ItemListener, ActionListener {
+
+    /*  You should not need to modify this set of global variables */
+    JFrame mainFrame;
+    Greenstone3Connection gsdl;
+    JCheckBox stemmingCheck;
+    JCheckBox casefoldingCheck;
+    JTextField queryField;
+    JTextField docsToReturnField;
+    JComboBox indexComboBox;
+    JRadioButton homeButton;
+    JRadioButton waikatoButton;
+    JEditorPane resultPane;
+    boolean doCasefolding = true;
+    boolean doStemming = false;
+    boolean getAllMetadata = true;
+    int indexChoice = 1;
+    int locationChoice = 1;
+    String[] indexKeys = {"TX", "TI", "JO", "BO", "CR", "KE"};
+    Vector currentResultSet;
+    /* If you need to, you can introduce further globals to store useful data  */
+    /** 
+     *  dateMap is a hash table. The keys are year values.
+     *  the data associated with each key is a Vector of document IDs
+     *  therefore, for the current result set you can get the set of years in which the
+     *  results were published, and for each year you can get the set of documents published in that year
+     *  
+     *  If you want to introduce additional mappings (eg document written by authors) you should
+     *  introduce additional structures here (HashMap used in the same way as dateMap will probably suffice
+     *  
+     */
+    HashMap dateMap = new HashMap();
+
+    /* creates the result window which contains a menu and HTML renderer
+     * you will only need to modify this code to add more items to the View menu
+     *  or add additional GUI components to the window (which isn't required for the assignment
+     *  To change the initial window size modify the arguments to the setSize()  call at end of this method
+     */
+    public void createAndShowResultGUI() {
+        JFrame resultFrame = new JFrame("Query results");
+        JPanel containerPanel = new JPanel();
+        JPanel controlPanel = new JPanel();
+        GridBagConstraints gbc = new GridBagConstraints();
+
+        containerPanel.setLayout(new GridBagLayout());
+        resultFrame.setContentPane(containerPanel);
+
+        containerPanel.setLayout(new GridBagLayout());
+        controlPanel.setLayout(new GridBagLayout());
+
+        JMenuBar menuBar = new JMenuBar();
+        JMenu viewMenu = new JMenu("View");
+        menuBar.add(viewMenu);
+
+
+
+
+        /* add items to the view menu. Insert code here to add more items */
+        JMenuItem viewByDate = new JMenuItem("View by Date");
+        viewByDate.addActionListener(this);
+        viewMenu.add(viewByDate);
+
+        JMenuItem viewByScore = new JMenuItem("View by Score");
+        viewByScore.addActionListener(this);
+        viewMenu.add(viewByScore);
+        /* end of menu item code */
+
+
+
+
+        resultFrame.setJMenuBar(menuBar);
+
+        resultPane = new JEditorPane();
+        resultPane.setContentType("text/html");
+        resultPane.setEditable(false);
+        resultPane.addHyperlinkListener(new HyperlinkListener() {
+
+            public void hyperlinkUpdate(HyperlinkEvent r) {
+                try {
+                    if (r.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+                        handleLinkClick(r);
+                    }
+                } catch (Exception e) {
+                }
+            }
+        });
+        resultPane.setText("Results will appear here");
+
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.weightx = 1.0;
+        gbc.weighty = 0.0;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        containerPanel.add(controlPanel, gbc);
+
+        gbc.gridy = 1;
+        gbc.weighty = 1.0;
+        gbc.fill = GridBagConstraints.BOTH;
+        containerPanel.add(new JScrollPane(resultPane), gbc);
+
+        resultFrame.pack();
+        resultFrame.setSize(400, 600);
+        resultFrame.setVisible(true);
+    }
+
+    /* the argument to this method is the label of an item selected in the View menu
+     * based on the item selected from the menu the appropriate view method is called
+     * to handle additional view options add further else-if clauses
+     * you will need to implement any further viewByXXXX methods that you call
+     */
+    public void changeView(String menuLabel) {
+        if (menuLabel.compareTo("View by Date") == 0) {
+            viewByDate(dateMap);
+        } else if (menuLabel.compareTo("View by Score") == 0) {
+            viewByScore(currentResultSet);
+        }
+    }
+
+    /* this method is called when any link in the result window is clicked
+     * It expects the link URL to be of the form    http://<metadata-tag>#<document-id>
+     * For example    http://title#HASH01f33a09ebd2421ae5155b37s55
+     * indicates that the user has clicked on the title link for document with ID HASH01f33a09ebd2421ae5155b37s55
+     * 
+     * This enables you to make the application respond to clicks within result items if you wish to modify
+     * the view based on such a user action
+     *  
+     * Note that you need to construct these links in code that generates the HTML to be rendered in the result window
+     */
+    public void handleLinkClick(HyperlinkEvent r) {
+        String linkText = r.getURL().toString();
+        linkText = linkText.replaceAll("http://", "");
+        String parts[] = linkText.split("#");
+
+        ResultDocument rd = gsdl.getDocument(parts[1]);
+
+        StringBuffer HTMLcontent = new StringBuffer("<html><body>");
+        HTMLcontent.append("You clicked on the " + parts[0] + " of document " + parts[1] + "<br>");
+        HTMLcontent.append("Document title = " + rd.getTitle());
+        HTMLcontent.append("</body></html>");
+
+        resultPane.setText(HTMLcontent.toString());
+    }
+
+    /* given the Vector of result items (ordered by descending relevance to the query) this method
+     * iterates through them and constructs an HTML document and has it rendered in the result window
+     * 
+     * This is the default presentation for results
+    
+     * You can modify this method as you wish to change the HTML for the default presentation
+     * 
+     * Some useful method calls are illustrated here
+     *       - if you have the ID of a document you can get all data stored for it with
+     *           ResultDocument rd = gsdl.getDocument(docID);
+     *       - a ResultDocument object has a set of methods for getting metadata values for that document
+     *           most metadata is a single value, but authors and keywords can have multiple values
+     *           and these are stored in a Vector
+     * 
+     * The IF condition for 'Title' shows how to construct a link that can be clicked on
+     * and then dealt with by the    handleLinkClick()   method
+     * 
+     */
+    public void viewByScore(Vector results) {
+        StringBuffer resultHTML = new StringBuffer("<html><head>Results</head><body>");
+
+        for (Enumeration e = results.elements(); e.hasMoreElements();) {
+            Result result = (Result) e.nextElement();
+
+            String docID = result.getDocID();
+            int docRank = result.getRank();
+            String docScore = result.getScore();
+
+            ResultDocument rd = gsdl.getDocument(docID);
+
+            resultHTML.append("<p>");
+            resultHTML.append("docID = " + docID + "<br>");
+            resultHTML.append("docRank = " + docRank + " (for this query)<br>");
+            resultHTML.append("docScore = " + docScore + " (for this query)<br>");
+
+            if (rd.metadataExists("Title")) {
+                resultHTML.append("title = <a  href='http://title#" + docID + "'>" + rd.getTitle() + "</a><br>");
+            }
+            if (rd.metadataExists("Date")) {
+                resultHTML.append("date = " + rd.getDate() + "<br>");
+            }
+            if (rd.metadataExists("Booktitle")) {
+                resultHTML.append("booktitle = " + rd.getBooktitle() + "<br>");
+            }
+            if (rd.metadataExists("Pages")) {
+                resultHTML.append("pages = " + rd.getPages() + "<br>");
+            }
+            if (rd.metadataExists("Journal")) {
+                resultHTML.append("journal = " + rd.getJournal() + "<br>");
+            }
+            if (rd.metadataExists("Volume")) {
+                resultHTML.append("volume = " + rd.getVolume() + "<br>");
+            }
+            if (rd.metadataExists("Number")) {
+                resultHTML.append("number = " + rd.getNumber() + "<br>");
+            }
+            if (rd.metadataExists("Editor")) {
+                resultHTML.append("editor = " + rd.getEditor() + "<br>");
+            }
+            if (rd.metadataExists("Publisher")) {
+                resultHTML.append("publisher = " + rd.getPublisher() + "<br>");
+            }
+            if (rd.metadataExists("Abstract")) {
+                resultHTML.append("abstract = " + rd.getAbstract() + "<br>");
+            }
+            Vector authors = rd.getAuthors();
+            for (Enumeration a = authors.elements(); a.hasMoreElements();) {
+                String author = (String) a.nextElement();
+                resultHTML.append("author = " + author + "<br>");
+            }
+            Vector keywords = rd.getKeywords();
+            for (Enumeration k = keywords.elements(); k.hasMoreElements();) {
+                String keyword = (String) k.nextElement();
+                resultHTML.append("keyword = " + keyword + "<br>");
+            }
+            resultHTML.append("</p>");
+        }
+        resultHTML.append("</body></html>");
+
+        resultPane.setText(resultHTML.toString());
+        resultPane.setCaretPosition(0);
+    }
+
+    /* This method provides an alternative view of the result set
+     * It uses the dateMap which organises results according to the year in which they were published
+     * 
+     * You will need to write a similar method for any additional view that you implement
+     * 
+     * You may wish to modify this method to provide a better layout of the output
+     */
+    public void viewByDate(HashMap dateMap) {
+        StringBuffer resultHTML = new StringBuffer("<html><head>Results</head><body>");
+
+        Vector theDates = new Vector(dateMap.keySet());
+        Collections.sort(theDates);
+
+
+        for (Enumeration e = theDates.elements(); e.hasMoreElements();) {
+            String date = (String) e.nextElement();
+            Vector ids = (Vector) dateMap.get(date);
+
+            resultHTML.append("<p>");
+            resultHTML.append("<b>" + date + "</b><br>");
+
+            for (Enumeration i = ids.elements(); i.hasMoreElements();) {
+                String docID = (String) i.nextElement();
+                ResultDocument rd = gsdl.getDocument(docID);
+                resultHTML.append(rd.getTitle() + "<br>");
+            }
+
+            resultHTML.append("</p>");
+        }
+
+        resultHTML.append("</body></html>");
+
+        resultPane.setText(resultHTML.toString());
+        resultPane.setCaretPosition(0);
+    }
+
+    /* this method is called whenever the user presses the RETURN key in the query box
+     * 
+     * it first deletes any date->document mappings from the previous query by clearing the dateMap hashtable
+     * if you introduce further mapping structures you will need to clear them here
+     * 
+     * a query object is created using the settings in the query window and the query request is
+     * sent to the Greenstone server, which responds with the outcome of the query
+     * the metadata for each result document is then retrieved from the server
+     * 
+     * The dateMap hashtable is then initialised
+     * 
+     * If you wished to group documents by author you would
+     *      - introduce a new HashMap (probably called   authorMap  )
+     *      - write a method called   initialiseAuthorMap    modelled on initialiseDateMap
+     *          which uses a method   addToAuthorMap  which would be modelled on   addToDateMap
+     *      - call   initialiseDateMap  in this method
+     *      - write a viewByAuthor method
+     * 
+     * the  viewByScore   method is called to display the default result output
+     * you can change the view method which is called if you wish to
+     */
+    public void doQuery(String queryText) {
+
+        dateMap.clear();
+
+        Query query = createQuery(queryText);
+        QueryOutcome queryOutcome = gsdl.issueQueryToServer(query);
+        currentResultSet = getResultSetMetadata(queryOutcome);
+
+        initialiseDateMap(currentResultSet);
+
+        viewByScore(currentResultSet);
+    }
+
+    /************** YOU SHOULD NOT NEED TO MODIFY ANYTHING PAST THIS POINT  *************/
+    public static void main(String[] args) {
+        hcibibGUI gui = new hcibibGUI();
+    }
+
+    public hcibibGUI() {
+        gsdl = new Greenstone3Connection(locationChoice);
+        createAndShowMainGUI();
+        createAndShowResultGUI();
+    }
+
+    public void createAndShowMainGUI() {
+        JPanel containerPanel = new JPanel();
+        JPanel locationPanel = new JPanel();
+        JPanel optionPanel = new JPanel();
+        JPanel queryPanel = new JPanel();
+        GridBagConstraints gbc = new GridBagConstraints();
+
+
+        mainFrame = new JFrame("GUI for Greenstone 3 HCI Bibliography Collection");
+        containerPanel.setLayout(new GridBagLayout());
+        mainFrame.setContentPane(containerPanel);
+
+        locationPanel.setLayout(new GridBagLayout());
+        optionPanel.setLayout(new GridBagLayout());
+        queryPanel.setLayout(new GridBagLayout());
+
+        TitledBorder title;
+        title = BorderFactory.createTitledBorder("Where am I?");
+        locationPanel.setBorder(title);
+        title = BorderFactory.createTitledBorder("Query options");
+        optionPanel.setBorder(title);
+        title = BorderFactory.createTitledBorder("Query");
+        queryPanel.setBorder(title);
+
+        ButtonGroup locationGroup = new ButtonGroup();
+        homeButton = new JRadioButton("home");
+        waikatoButton = new JRadioButton("CS labs");
+        homeButton.addActionListener(this);
+        waikatoButton.addActionListener(this);
+        homeButton.setSelected(true);
+        locationGroup.add(homeButton);
+        locationGroup.add(waikatoButton);
+
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        locationPanel.add(homeButton, gbc);
+        gbc.gridx = 1;
+        gbc.gridy = 0;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        locationPanel.add(waikatoButton, gbc);
+
+        stemmingCheck = new JCheckBox("Stemming");
+        stemmingCheck.setSelected(doStemming);
+        stemmingCheck.addItemListener(this);
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        optionPanel.add(stemmingCheck, gbc);
+
+        casefoldingCheck = new JCheckBox("Casefolding");
+        casefoldingCheck.setSelected(doCasefolding);
+        casefoldingCheck.addItemListener(this);
+        gbc.gridx = 0;
+        gbc.gridy = 1;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        optionPanel.add(casefoldingCheck, gbc);
+
+        JLabel docsToReturnLabel = new JLabel("Max results");
+        gbc.gridx = 1;
+        gbc.gridy = 0;
+        gbc.anchor = GridBagConstraints.LINE_END;
+        optionPanel.add(docsToReturnLabel, gbc);
+
+        docsToReturnField = new JTextField(3);
+        docsToReturnField.setText("10");
+        gbc.gridx = 2;
+        gbc.gridy = 0;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        optionPanel.add(docsToReturnField, gbc);
+
+        JLabel indexLabel = new JLabel("Search in...");
+        gbc.gridx = 0;
+        gbc.gridy = 2;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        optionPanel.add(indexLabel, gbc);
+
+        String[] indexStrings = {"all fields", "document titles", "journal names",
+            "book/conference names", "authors", "keywords"
+        };
+        indexComboBox = new JComboBox(indexStrings);
+        indexComboBox.setSelectedIndex(1);
+        indexComboBox.addActionListener(this);
+        gbc.gridx = 0;
+        gbc.gridy = 3;
+        optionPanel.add(indexComboBox, gbc);
+
+        queryField = new JTextField(30);
+        queryField.addActionListener(new MyActionListener());
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        queryPanel.add(queryField, gbc);
+
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.weightx = 1.0;
+        gbc.weighty = 0.0;
+        gbc.anchor = GridBagConstraints.LINE_START;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        containerPanel.add(locationPanel, gbc);
+        gbc.gridy = 1;
+        containerPanel.add(optionPanel, gbc);
+        gbc.gridy = 2;
+        containerPanel.add(queryPanel, gbc);
+
+        mainFrame.pack();
+        mainFrame.setVisible(true);
+    }
+
+    public Query createQuery(String queryText) {
+
+        Query query = new Query();
+
+        // set the query options
+        query.setQueryText(queryText);
+
+        query.setIndex(indexKeys[indexChoice]);
+
+        String docsToReturn = docsToReturnField.getText();
+        if (docsToReturn.compareTo("") == 0) {
+            docsToReturn = "10";
+        }
+        query.setMaxDocsToReturn(docsToReturn);
+
+        if (doStemming) {
+            query.setStemming("1");
+        } else {
+            query.setStemming("0");
+        }
+
+        if (doCasefolding) {
+            query.setCasefolding("1");
+        } else {
+            query.setCasefolding("0");
+        }
+
+        return query;
+    }
+
+    private void getResultMetadata(Result result) {
+        String docID = result.getDocID();
+
+        gsdl.getDocumentMetadataFromServer(docID, "Title");
+        gsdl.getDocumentMetadataFromServer(docID, "Date");
+
+        if (getAllMetadata) {
+            gsdl.getDocumentMetadataFromServer(docID, "Date");
+            gsdl.getDocumentMetadataFromServer(docID, "Booktitle");
+            gsdl.getDocumentMetadataFromServer(docID, "Journal");
+            gsdl.getDocumentMetadataFromServer(docID, "Creator");
+            gsdl.getDocumentMetadataFromServer(docID, "Keywords");
+            gsdl.getDocumentMetadataFromServer(docID, "Publisher");
+            gsdl.getDocumentMetadataFromServer(docID, "Abstract");
+            gsdl.getDocumentMetadataFromServer(docID, "Pages");
+            gsdl.getDocumentMetadataFromServer(docID, "Number");
+            gsdl.getDocumentMetadataFromServer(docID, "Volume");
+        }
+
+    }
+
+    public Vector getResultSetMetadata(QueryOutcome queryOutcome) {
+
+        Vector results = queryOutcome.getResults();
+        for (Enumeration e = results.elements(); e.hasMoreElements();) {
+            Result result = (Result) e.nextElement();
+            getResultMetadata(result);
+        }
+        return results;
+    }
+
+    public void initialiseDateMap(Vector results) {
+        for (Enumeration e = results.elements(); e.hasMoreElements();) {
+            Result result = (Result) e.nextElement();
+
+            String docID = result.getDocID();
+
+            ResultDocument rd = gsdl.getDocument(docID);
+
+            if (rd.metadataExists("Date")) {
+                addToDateMap(docID, rd);
+            }
+        }
+    }
+
+    public void addToDateMap(String docID, ResultDocument rd) {
+        if (dateMap.containsKey(rd.getDate())) {
+            Vector dateVector = (Vector) dateMap.get(rd.getDate());
+            if (!dateVector.contains(docID)) {
+                dateVector.addElement(docID);
+            }
+            dateMap.put(rd.getDate(), dateVector);
+        } else {
+            Vector dateVector = new Vector();
+            dateVector.addElement(docID);
+            dateMap.put(rd.getDate(), dateVector);
+        }
+    }
+
+    public void itemStateChanged(ItemEvent e) {
+        Object sourceItem = e.getItemSelectable();
+
+        if (sourceItem == stemmingCheck) {
+            doStemming = !doStemming;
+        } else if (sourceItem == casefoldingCheck) {
+            doCasefolding = !doCasefolding;
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Object sourceItem = e.getSource();
+
+        if (sourceItem == indexComboBox) {
+            indexChoice = indexComboBox.getSelectedIndex();
+        } else if (sourceItem == homeButton || sourceItem == waikatoButton) {
+            if (homeButton.isSelected()) {
+                locationChoice = 1;
+            } else {
+                locationChoice = 0;
+            }
+            gsdl = new Greenstone3Connection(locationChoice);
+        } else if (sourceItem.getClass().getName().compareTo("javax.swing.JMenuItem") == 0) {
+            String menuLabel = ((JMenuItem) sourceItem).getText();
+            changeView(menuLabel);
+        }
+    }
+
+    class MyActionListener implements ActionListener {
+
+        public void actionPerformed(ActionEvent evt) {
+            String queryText = queryField.getText();
+            doQuery(queryText);
+        }
+    }
+}
+
+
