/*
 * AlignmentModel.java
 *
 * ...
 * ...
 * @author Oystein Reigem
 */

package aksis.alignment;

import java.awt.Color;
import java.util.*;
import java.io.*;
//import java.util.regex.*;
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.lang.reflect.*;
import java.awt.Toolkit;   // beep
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.*;
import java.nio.charset.*;
import java.text.*;
import java.lang.Thread;

//import javax.swing.event.ListDataEvent;
import java.awt.Rectangle;

//////////////////////////////////////////////////////////////////////////////////////////
// apparatus for structure and flow of alignable elements through the alignment process //
//////////////////////////////////////////////////////////////////////////////////////////

/*
class MyException extends Exception {

	public MyException() {
	}

	public MyException(String gripe) {
		super(gripe);
	}

}
*/

class Aligned {

	/**
	 * lists of aligned elements.
	 * one list for each text.
	 * shown in the gui's 'aligned' JList components.
	 * each element is an AElement object.
	 */
	protected DefaultListModel[] elements;

	/**
	 * the finished alignments.
	 * each alignment is a Link object.
	 */
	List alignments = new ArrayList();   // package access. list of Link objects

	Aligned() {
		////System.out.println("Aligned constructor");
		elements = new DefaultListModel[Alignment.NUM_FILES];
        ////System.out.println(elements.getClass().getName());
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elements[t] = new DefaultListModel();
	        ////System.out.println(elements[t].getClass().getName());
		}
        ////System.out.println(elements.getClass().getName());
		////System.out.println("Aligned constructor calls hello()");
        ////this.hello();
		////System.out.println("After Aligned constructor called hello()");
	}

	////public void hello() {
	////	System.out.println("hello from method hello() of class " + this.getClass().getName());
	////	for (int t=0; t<Alignment.NUM_FILES; t++) {
	////        System.out.println(elements[t].getClass().getName());
	////	}
	////}

	public void purge() {
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elements[t].clear();
		}
		alignments.clear();
	}

	// €€€flyttes til en util-fil???
	public boolean hasHoles(Set[] integerSet) {

		// €€€ få kommentarer hjemmefra
		//System.out.println("ankommer hasHoles");
		// check if the n latest finished alignments
		// (the n bottom alignments)
		// have any holes.
		// for each text see if there are holes
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			//System.out.println("t=" + t);
			if (((TreeSet)(integerSet[t])).size() > 0) {
				// there are elements in this text covered by the n latest alignments.
				// find the number of the highest element aligned for this text.
				// note that there might be crossing alignments,
				// so this element might not be covered by the n latest ones
				int high = ((AElement)(elements[t].get(elements[t].size()-1))).elementNumber;
				//System.out.println("high=" + high);
				// find the number of the lowest element covered by the n latest alignments
				int low = ((Integer)(((TreeSet)(integerSet[t])).last())).intValue();
				//System.out.println("low=" + low);
				// is there a hole?
				if (((TreeSet)(integerSet[t])).size() < (high-low+1)) {
					// yes
					//System.out.println("her er et hull");
					return true;
				}
			}
		}
		// no holes found
		//System.out.println("fant ingen hull");
		return false;

	}

	//AlignmentsEtc drop() {
	AlignmentsEtc drop(AlignGui gui) {
		//System.out.println("drop()");
		// can't just go ahead and drop bottom alignment.
		// must make sure there are no holes due to crossing alignments.
		// scan alignments from bottom upwards until there are no holes.€€€
		if (alignments.size() == 0) {
			//
			Toolkit.getDefaultToolkit().beep();
			//System.out.println("BEEEEEEEEEEEEEEEEP Aligned drop");
			System.out.println("Nothing to drop");
			return null;
		} else {
			Set[] testElementNumbers = new TreeSet[Alignment.NUM_FILES];   // TreeSet is a sorted Set
			for (int t=0; t<Alignment.NUM_FILES; t++) {
				Set s = new TreeSet();   // TreeSet is a sorted Set
				testElementNumbers[t] = s;
				//System.out.println("testElementNumbers["+t+"] = " + testElementNumbers[t]);
			}
			boolean holes = true;   // pessimistic €€€
			////System.out.println("alignments.size() = " + alignments.size());
			// find the first alignment to drop
			//System.out.println("skal gå i løkke og ta én alignment om gangen. starter med ingen");
			int numberOfFirstAlignmentToDrop = alignments.size() - 1;
			while (holes) {
				//System.out.println("løkkegjennomløp for å se om vi har hull hvis vi tar med en alignment til");
				if (numberOfFirstAlignmentToDrop < 0) {
					//System.err.println("*** Program error 1");
					ErrorMessage.programError("AlignmentsEtc drop(). numberOfFirstAlignmentToDrop < 0");   // 2006-08-10
					return null;
				}
				for (int t=0; t<Alignment.NUM_FILES; t++) {
					testElementNumbers[t].addAll(((Link)(alignments.get(numberOfFirstAlignmentToDrop))).getElementNumbers(t));
					//System.out.println("testElementNumbers["+t+"] = " + testElementNumbers[t]);
				}
				if (!hasHoles(testElementNumbers)) {   // €€€var ikke bra nok. høyeste element-nummer må også være med. ellers oppdages ikke kryssing likevel
					holes = false;
				} else {
					numberOfFirstAlignmentToDrop--;
				}
			}
			//
			AlignmentsEtc returnValue = new AlignmentsEtc();
			// elements belonging to alignments starting with number 'numberOfFirstAlignmentToDrop' are to be dropped
			//System.out.println("skal gå i dobbel løkke og ta ett element om gangen over til returnValue.....");
			for (int t=0; t<Alignment.NUM_FILES; t++) {
				//System.out.println("t=" + t);
				//System.out.println("numberOfFirstAlignmentToDrop=" + numberOfFirstAlignmentToDrop);
				if (((TreeSet)(testElementNumbers[t])).size() > 0) {
					// there are elements to drop
					int numberOfFirstElementToDrop = ((Integer)(((TreeSet)(testElementNumbers[t])).first())).intValue();
					//System.out.println("numberOfFirstElementToDrop=" + numberOfFirstElementToDrop);
					int numberOfLastElementToDrop = ((Integer)(((TreeSet)(testElementNumbers[t])).last())).intValue();
					//System.out.println("numberOfLastElementToDrop=" + numberOfLastElementToDrop);
					for (int j=numberOfFirstElementToDrop; j<=numberOfLastElementToDrop; j++) {
						//System.out.println("drops element");
						//AElement elementToDrop = (AElement)elements[t].remove(elements[t].size()-1);
						((DefaultListModel)(returnValue.elements[t])).addElement((AElement)(elements[t].remove(numberOfFirstElementToDrop)));   // ###
					}
				}
			}
			// alignments starting with number 'numberOfFirstAlignmentToDrop' are to be dropped
			//System.out.println("skal gå i løkke og ta én alignment om gangen over til returnValue.alignments");
			while (alignments.size() > numberOfFirstAlignmentToDrop) {
				//System.out.println("drops alignment");
				returnValue.alignments.add(returnValue.alignments.size(), alignments.remove(numberOfFirstAlignmentToDrop));
			}
			// update aligned/total ratio in status line
			gui.model.setMemoryUsage(gui);   // 2006-10-03
			gui.model.updateAlignedTotalRatio(gui);
			//
			return returnValue;
		}
	}

	void pickUp(AlignGui gui, AlignmentsEtc valueGot, boolean scroll) {
		//System.out.println("pickUp A");
		//System.out.println("pickUp()");
		//MemTest.print("Tenured Gen", "");
		if (valueGot != null) {
			// alignments
			//System.out.println("pickUp B");
			//MemTest.print("Tenured Gen", "");
			//######hvorfor tom løkke?
			for (int j=0; j<valueGot.alignments.size(); j++) {
				//System.out.println("skal legge til alignment nr " + ((Link)(valueGot.alignments.get(j))).alignmentNumber);
			}
			//System.out.println("pickUp C");
			//MemTest.print("Tenured Gen", "");
			alignments.addAll(alignments.size(), valueGot.alignments);
			//System.out.println("pickUp D");
			//MemTest.print("Tenured Gen", "");
			// elements
			for (int t=0; t<Alignment.NUM_FILES; t++) {
				//System.out.println("pickUp");
				//System.out.println("size of no " + t + " = " + ((DefaultListModel)(valueGot.elements[t])).size());
				//System.out.println("pickUp E");
				//MemTest.print("Tenured Gen", "");
				for (int i=0; i<((DefaultListModel)(valueGot.elements[t])).size(); i++) {
					//elements[t].addElement((AElement)(valueGot.elements[t]));
					//System.out.println("picks up " + (AElement)(((DefaultListModel)(valueGot.elements[t])).get(i)));
					elements[t].addElement(((AElement)((DefaultListModel)(valueGot.elements[t])).get(i)));   // ###
					//System.out.println("pickUp F");
					//MemTest.print("Tenured Gen", "");
				}
				// scroll the list box so the bottom shows
				//System.out.println("pickUp F2");
				//MemTest.print("Tenured Gen", "");
				//if (scroll) { // ### saboterer denne og ser hvordan det går
				if (true) {
					//System.out.println("scroll");
					gui.alignedListBox[t].ensureIndexIsVisible(elements[t].getSize()-1);
				}
				//System.out.println("pickUp G");
				//MemTest.print("Tenured Gen", "");
				//System.out.println("x");
				//gui.alignedListBox[t].repaint();   // ### forsøk på å få aligned til å oppdatere seg etterhver enkeltaligning
				//gui.alignedListBox[t].paintImmediately(new Rectangle(100, 100));
				//gui.alignedListBox[t].paintImmediately(gui.alignedListBox[t].getBounds()); funker til en viss grad. men ruller ikke

				//!!// ### forsøk på å lytte på events
				//!!//ListDataEvent listDataEvent;
				//!!//gui.alignedListBox[t].AccessibleJList.contentsChanged(listDataEvent);
				//!!//System.out.println("listDataEvent = " + listDataEvent);
			}
			// update aligned/total ratio in status line
			//System.out.println("pickUp() kaller updateAlignedTotalRatio()");
			gui.model.setMemoryUsage(gui);   // 2006-10-03
			gui.model.updateAlignedTotalRatio(gui);
		}
		//System.out.println("pickUp H");
		//MemTest.print("Tenured Gen", "");
	}

}

class ToAlign {

	/**
	 * lists of elements under consideration for alignment.
	 * one list for each text.
	 * shown in the gui's 'toAlign' JList components.
	 * each element is an AElement object.
	 */
	protected DefaultListModel[] elements;

	/**
	 * 0 or more alignments.
	 * each alignment is a Link object.
	 */
	private List pending = new ArrayList();

	///**
	// * the unaligned elements.
	// * also represented by a Link object.
	// */
	//private Link unused = new Link();

	/**
	 * the number of the first pending alignment.
	 * if no alignments pending, the number to use when establishing a pending alignment,
	 * i.e, one higher than the highest number of the finished alignments.
	 */
	private int firstAlignmentNumber = 0;

	ToAlign() {
		elements = new DefaultListModel[Alignment.NUM_FILES];
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elements[t] = new DefaultListModel();
		}
	}

	public void purge() {
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elements[t].clear();
		}
		pending.clear();
		firstAlignmentNumber = 0;
	}

	/**
	 * is all (both) to-align boxes empty?
	 */
	boolean empty() {
		//System.out.println("ToAlign.empty() kalt");
		//System.out.println("pending.size() = " + pending.size());
		//System.out.println("unused.empty() = " + unused.empty());
		//return (pending.size() == 0 && unused.empty());
		return (pending.size() == 0);
	}

	/**
	 * is to-align box number t empty?
	 */
	// ###needed for correction of bug 2006-03-28
	// ###kanskje bedre å kikke i boksens liste...
	boolean empty(int t) {
		// search through pending alignments for one that has element(s) in text t
		for (int ii=0; ii < pending.size(); ii++) {
			Link link = (Link)(pending.get(ii));
			if (link.elementNumbers[t].size() > 0) {
				// found one
				return false;
			}
		}
		// searched through all but found none
		return true;
	}

	// #######bør sikkert brukes mer

	int pendingCount() {
		return pending.size();
	}

	// €€€ 2004-10-31. brukes ikke - i hvert fall ikke for øyeblikket
	void link(AlignGui gui, int t, int indexClicked, int elementNumberClicked) {   // package access
		// link by default to uppermost pending alignment
		//System.out.println("firstAlignmentNumber = " + firstAlignmentNumber);
		link(gui, t, indexClicked, elementNumberClicked, firstAlignmentNumber);
	}

	// €€€ 2004-10-31. bare denne brukes.
	// €€€ denne programmeringen trenger sikkert rydding.
	// for mye som ikke er oop?
	void link(AlignGui gui, int t, int indexClicked, int elementNumberClicked, int alignmentNumberToLinkTo) {   // package access
		//System.out.println("ToAlign sin link()");
		//System.out.println("pending før at link() har gjort sin jobb:");
		for (int ii=0; ii < pending.size(); ii++) {
			//System.out.println("pending alignment nr " + ii + " er " + (Link)(pending.get(ii)));
		}
		// €€€foreløpig
		// redundans: indexClicked (nr på item i JList) og elementNumberClicked
		//System.out.println("indexClicked = " + indexClicked);
		//System.out.println("elementNumberClicked = " + elementNumberClicked);
		//System.out.println("alignmentNumberToLinkTo = " + alignmentNumberToLinkTo);
		//System.out.println("t = " + t);
		// clicked on unused or pending? €€€ skal ikke være noen unused lenger. i.g.n.m
		int alignmentNumberClicked = ((AElement)(elements[t].get(indexClicked))).alignmentNumber;
		//System.out.println("alignmentNumberClicked = " + alignmentNumberClicked);
		// ...
		int lastAlignmentNumber = firstAlignmentNumber + pending.size() - 1;
		//System.out.println("lastAlignmentNumber = " + lastAlignmentNumber);
		// ...
		//System.out.println("håndtering av alignmentNumberToLinkTo == -2");
		if (alignmentNumberToLinkTo == -2) {
			// -2 is a signal to link to the next available alignment
			if (   (alignmentNumberClicked == lastAlignmentNumber)
			    && (((Link)(pending.get(lastAlignmentNumber - firstAlignmentNumber))).countElements() <= 1)) {   // == 1
			    //System.out.println("has run out of alignments. back to the first one");
			    // has run out of alignments.
			    // back to the first one
			    //alignmentNumberToLinkTo = 0; ###
			    alignmentNumberToLinkTo = firstAlignmentNumber;
			} else {
			    //System.out.println("not run out of alignments");
				alignmentNumberToLinkTo = alignmentNumberClicked + 1;
				//System.out.println("alignmentNumberToLinkTo = " + alignmentNumberToLinkTo);
			}
		}
		//System.out.println("alignmentNumberToLinkTo = " + alignmentNumberToLinkTo);
		// might have to create new pending
		if (alignmentNumberToLinkTo > pending.size() - 1) {
			pending.add(new Link());
		}
		// change alignment
		//System.out.println("change alignment. alignment før = " + (Link)(pending.get(alignmentNumberClicked - firstAlignmentNumber)));
		//System.out.println("t = " + t);
		//System.out.println("alignmentNumberClicked = " + alignmentNumberClicked);
		//System.out.println("elementNumberClicked = " + elementNumberClicked);
		//System.out.println("firstAlignmentNumber = " + firstAlignmentNumber);
		//System.out.println("alignmentNumberToLinkTo = " + alignmentNumberToLinkTo);
		((Link)(pending.get(alignmentNumberClicked - firstAlignmentNumber))).elementNumbers[t].remove(new Integer(elementNumberClicked));
		//((Link)(pending.get(alignmentNumberClicked - firstAlignmentNumber))).elementNumbers[t].remove(new Integer(indexClicked));   // endret 2005-05-31. nei, det er tull. vi jobber på et Set, der nøklene er elementnummer, ikke indeks
		((Link)(pending.get(alignmentNumberToLinkTo - firstAlignmentNumber))).elementNumbers[t].add(new Integer(elementNumberClicked));
		//((Link)(pending.get(alignmentNumberToLinkTo - firstAlignmentNumber))).elementNumbers[t].add(new Integer(indexClicked));   // endret 2005-05-31. nei, det er tull. vi jobber på et Set, der nøklene er elementnummer, ikke indeks
		//((Link)(pending.get(alignmentNumberToLinkTo - firstAlignmentNumber))).alignmentNumber = alignmentNumberToLinkTo;
		//System.out.println("change alignment. alignment etter = " + (Link)(pending.get(alignmentNumberClicked - firstAlignmentNumber)));
 		// check if the change creates a hole in the list of pending alignments,
		// i.e, a pending alignment with no elements.
		// if so, remove hole
		//System.out.println("sjekk om hull");
		for (int ii=0; ii < pending.size(); ) {
			if (((Link)(pending.get(ii))).countElements() == 0) {
				// hole. remove
				// ### 2006-03-30.
				// ### oppdaget at jeg et _annet_ sted tok pending.remove(...) med fatale konsekvenser.
				// ### det ble nemlig hull i nummereringen av alignments.
				// ### (man droppet et element som ikke var linket mot noe annet element
				// ### (det inngikk altså i en 1-0- eller 0-1-alignment),
				// ### og da forsvant hele alignmenten,
				// ### men jeg hadde ikke tenkt på at jeg da måtte renummerere
				// ### de etterfølgende alignments i pending.
				// ### men her på _dette_ stedet rydder jeg vel grundig opp?
				pending.remove(ii);
				// (repeat with same ii)
			} else {
				ii++;
			}
		}
		// try to maintain a natural order to the pending alignments.
		//
		// there might not exist a canonical natural order
		// for alignments, though.
		// say two texts contain elements that are cross-aligned like this:
		//   A     B
		//   B     A
		// what is the natural order of the alignments?
		//
		// is it A before B?:
		//   A--A  B
		//       \/
		//       /\
		//   B--B  A
		//
		// or is it B before A?:
		//   A  B--B
		//    \/
		//    /\
		//   B  A--A
		//
		// the programming here will let the order be decided by the first text,
		// i.e, let the order of alignments be decided by the order
		// of their elements in the first text:
		//   A--A  B
		//       \/
		//       /\
		//   B--B  A
		//
		// a more precise rule is needed when alignments
		// have more than one element in each text:
		// then the order is decided by each alignment's
		// *first* element in the first text.
		//
		// alignments without any elements in the first text
		// might be more problematic.
		// example:
		// A      B
		// B      C
		//        A
		//
		// the programming here will put all alignments
		// with elements in the first text
		// before alignments without any elements in the first text:
		//   A--A  B
		//       \/
		//       /\
		//   B--B  A
		//
		//      C--C
		//
		// the latter alignments' order is decided by the order
		// of the elements in the second text.
/*
skitt
A B
A A
her havner B sist
her er det ikke noe som krysser.
det kan gjøre det lettere
eller overlate fullstendig til brukeren?
*/
		//System.out.println("prøv å bevare naturlig rekkefølge");
		List toSort = new ArrayList();
		for (int ii=0; ii < pending.size(); ii++) {
			// next pending alignment
			//System.out.println("next pending alignment. ii=" + ii);
			Link link = (Link)(pending.get(ii));
			// find this alignment's first text
			for (int tt=0; tt<Alignment.NUM_FILES; tt++) {
				if (link.elementNumbers[tt].size() > 0) {
					//System.out.println("make sort criteria for the alignment");
					// make sort criteria for the alignment
					int firstElementNumber = ((Integer)(((TreeSet)(link.elementNumbers[tt])).first())).intValue();
					//System.out.println("firstElementNumber = " + firstElementNumber);
					// €€€ right just zero fill €€€€€€€€€€€€€€€€€€€€€€€€€
					String firstElementNumberString = Integer.toString(1000000 + firstElementNumber).substring(1);
					//System.out.println("firstElementNumberString = " + firstElementNumberString);
					toSort.add(Integer.toString(tt) + "-" + firstElementNumberString + "-" + ii);
					break;
				}
			}
		}
		// sort
		List sorted = new ArrayList();
		Collections.sort(toSort);
		sorted = toSort;
		// reorder the pending alignments
		List newPending = new ArrayList();
		for (int ii=0; ii < sorted.size(); ii++) {
			//System.out.println("neste sorterte. ii=" + ii);
			String tempStr = (String)(sorted.get(ii));
			//System.out.println("tempStr = " + tempStr);
			String[] tempArr = tempStr.split("-");
			int tempInt = Integer.parseInt(tempArr[2]);
			//System.out.println("tempInt = " + tempInt);
			newPending.add(pending.get(tempInt));
			//((Link)(newPending.get(ii))).alignmentNumber = ii;
			((Link)(newPending.get(ii))).alignmentNumber = ii + gui.model.aligned.alignments.size();   // endret 2005-05-31. bug som har overlevd lenge. har visst ikke prøvd toAlign-klikking når det står noe i aligned. alignmentnummer i toAlign starter på # aligned alignments, ikke nødvendigvis på 0
		}
		//System.out.println("pending før sort =");
		for (int ii=0; ii < pending.size(); ii++) {
			//System.out.println("pending.get(" + ii + ") =" + (Link)(pending.get(ii)));
		}
		pending = newPending;
		//System.out.println("pending etter sort =");
		for (int ii=0; ii < pending.size(); ii++) {
			//System.out.println("pending.get(" + ii + ") =" + (Link)(pending.get(ii)));
		}
		// update and refresh elements
		for (int ii=0; ii < pending.size(); ii++) {
			//System.out.println("neste refresh elements");
			for (int tt=0; tt<Alignment.NUM_FILES; tt++) {
				//System.out.println("tt=" + tt);
				Iterator e = ((TreeSet)(((Link)(pending.get(ii))).getElementNumbers(tt))).iterator();
				//System.out.println("skaffet iterator");
				while (e.hasNext()) {
					//System.out.println("har en neste");
					int elementNumber = ((Integer)(e.next())).intValue();
					//System.out.println("elementNumber=" + elementNumber);
					int index = elementNumber - gui.model.aligned.elements[tt].size();   // endret 2005-05-31. bug som har overlevd lenge. har visst ikke prøvd toAlign-klikking når det står noe i aligned
					//((AElement)(elements[tt].get(elementNumber))).alignmentNumber = ((Link)(pending.get(ii))).alignmentNumber;
					((AElement)(elements[tt].get(index))).alignmentNumber = ((Link)(pending.get(ii))).alignmentNumber;   // endret 2005-05-31. bug som har overlevd lenge. har visst ikke prøvd toAlign-klikking når det står noe i aligned
					// shake it so the colour changes (remove + add)
					//elements[tt].add(elementNumber ,elements[tt].remove(elementNumber));
					elements[tt].add(index, elements[tt].remove(index));   // endret 2005-05-31. bug som har overlevd lenge. har visst ikke prøvd toAlign-klikking når det står noe i aligned
				}
			}
		}
		//System.out.println("pending etter at link() har gjort sin jobb:");
		for (int ii=0; ii < pending.size(); ii++) {
			//System.out.println("pending alignment nr " + ii + " er " + (Link)(pending.get(ii)));
		}

		// €€€ kuttet programmering som har med unused å gjøre.
		// får vi bruk for det igjen?
		/*
		((AElement)(elements[t].get(indexClicked))).alignmentNumber = alignmentNumberToLinkTo;   // €€€foreløpig. // €€€ og mangler det også noe som trigger gjenoppfrisking?
		System.out.println("alignmentNumberClicked = " + alignmentNumberClicked);
		if (alignmentNumberClicked == -1) {
			// unused
			System.out.println("unused 1");
			System.out.println("unused 2");
			unused.elementNumbers[t].remove(new Integer(elementNumberClicked));   // €€€foreløpig
			System.out.println("unused 3");
			if (pending.size() == 0) {   // €€€foreløpig
				pending.add(new Link());   // €€€foreløpig
			}   // €€€foreløpig
			// €€€ foreløpig har pending aldri mer enn én Link. jo, det kan være flere!
			((Link)(pending.get(0))).elementNumbers[t].add(new Integer(elementNumberClicked));
			((Link)(pending.get(0))).alignmentNumber = alignmentNumberToLinkTo;
			System.out.println("unused 4");
			System.out.println("unused 5");
		} else {
			// pending
			System.out.println("pending");
			System.out.println("pending.size()=" + pending.size());
			for (int k = 0; k < pending.size(); k++) { System.out.println("pending.get(" + k + ")=" + pending.get(k)); }
			System.out.println("alignmentNumberClicked=" + alignmentNumberClicked);
			System.out.println("firstAlignmentNumber=" + firstAlignmentNumber);
			System.out.println("elementNumberClicked=" + elementNumberClicked);
			if (alignmentNumberToLinkTo > lastAlignmentNumber) {
				pending.add(new Link());
			}
			((Link)(pending.get(alignmentNumberClicked - firstAlignmentNumber))).elementNumbers[t].remove(new Integer(elementNumberClicked));   // €€€foreløpig
			System.out.println("pending 2");
			((Link)(pending.get(0))).elementNumbers[t].add(new Integer(elementNumberClicked));   // €€€foreløpig
			System.out.println("pending 3");
			((Link)(pending.get(0))).alignmentNumber = alignmentNumberToLinkTo;   // €€€foreløpig
			System.out.println("pending 4");
			if (((Link)(pending.get(alignmentNumberClicked - firstAlignmentNumber))).empty()) {
				// a pending Link collapses
				System.out.println("a pending Link collapses");
				pending.remove(alignmentNumberClicked - firstAlignmentNumber);£££££££££££££££££
				// renumber the following Link's
				for (int i=alignmentNumberClicked - alignmentNumberToLinkTo; i<pending.size(); i++) {
					((Link)(pending.get(i))).alignmentNumber--;
					for (int tt=0; tt<Alignment.NUM_FILES; tt++) {
						Iterator e = ((TreeSet)(((Link)(pending.get(i))).getElementNumbers(tt))).iterator();
						while (e.hasNext()) {
							int elementNumber = ((Integer)(e.next())).intValue();
							((AElement)(elements[tt].get(elementNumber))).alignmentNumber = ((Link)(pending.get(i))).alignmentNumber;
							// shake it so the colour changes (remove + add)
							elements[tt].add(elementNumber ,elements[tt].remove(elementNumber));
						}
					}
				}
			}
		}
		//
		*/
	}

	//AElement drop(int t) {
	AElement drop(AlignGui gui, int t) {   // ### 2006-03-30
		//System.out.println("AElement.drop(). t = " + t);
		//if (empty()) {
		if (empty(t)) {   // ###bug corrected 2006-03-28
			Toolkit.getDefaultToolkit().beep();
			//System.out.println("BEEEEEEEEEEEEEEEEP ToAlign drop");
			//System.out.println("Nothing to drop");
			return null;
		} else {
			//System.out.println("elements[t].size() = " + elements[t].size());
			AElement elementToDrop = (AElement)elements[t].remove(elements[t].size()-1);
			//System.out.println("elementToDrop = " + elementToDrop);
			//System.out.println("elements[t].size() = " + elements[t].size());
			if (elementToDrop.alignmentNumber == -1) {
				// the element to drop is unused
				//unused.elementNumbers[t].remove(new Integer(elementToDrop.elementNumber));
				System.out.println("*** Program error ***");
			} else {
				// the element to drop is pending.
				// where in the 'pending' list is its Link?
				//System.out.println("elementToDrop.alignmentNumber = " + elementToDrop.alignmentNumber);
				//System.out.println("((Link)(pending.get(0))).alignmentNumber = " + ((Link)(pending.get(0))).alignmentNumber);
				int index = elementToDrop.alignmentNumber - ((Link)(pending.get(0))).alignmentNumber;
				//System.out.println("index = " + index);
				// remove element from its pending alignment
				((Link)(pending.get(index))).getElementNumbers(t).remove(new Integer(elementToDrop.elementNumber));
				// check if there is anything left of the pending alignment
				//System.out.println("check if there is anything left of the pending alignment");
				if (((Link)(pending.get(index))).empty()) {
					// nothing. remove it
					//System.out.println("nothing. remove it");
					pending.remove(index);
					// ### 2006-03-30.
					// renumber the pending alignments that follow!
					for (int ii=index; ii < pending.size(); ii++) {
						((Link)(pending.get(ii))).alignmentNumber--;
						// don't forget to update the alignment numbers
						// in the elements that belong to this alignment
						for (int tt=0; tt<Alignment.NUM_FILES; tt++) {
							Iterator e = ((TreeSet)(((Link)(pending.get(ii))).getElementNumbers(tt))).iterator();
							while (e.hasNext()) {
								int elementNumber = ((Integer)(e.next())).intValue();
								int indexx = elementNumber - gui.model.aligned.elements[tt].size();
								((AElement)(elements[tt].get(indexx))).alignmentNumber = ((Link)(pending.get(ii))).alignmentNumber;
								// shake the element so the colour changes (remove + add)
								elements[tt].add(indexx, elements[tt].remove(indexx));
							}
						}
					}
				}
			}
			return elementToDrop;
		}
	}

	//void pickUp(AlignGui gui, int t, AElement element) {
	void pickUp(int t, AElement element) {
		if (element != null) {
			//System.out.println("ToAlign sin pickUp");
			elements[t].add(elements[t].size(), element);
			//System.out.println("ToAlign sin pickUp 2");
			//unused.elementNumbers[t].add(new Integer(element.elementNumber));
			//System.out.println("ToAlign sin pickUp 3");
			// include the element in the last pending alignment.
			// create a pending alignment if none exists
			if (pending.size() == 0) {
				//System.out.println("create a pending alignment");
				pending.add(new Link());
				((Link)(pending.get(0))).alignmentNumber = firstAlignmentNumber;
			}
			//System.out.println("ToAlign sin pickUp 4");
			((Link)(pending.get(pending.size() - 1))).elementNumbers[t].add(new Integer(element.elementNumber));
			//System.out.println("ToAlign sin pickUp 5");
			int lastAlignmentNumber = firstAlignmentNumber + pending.size() - 1;
			//System.out.println("ToAlign sin pickUp 6");
			element.alignmentNumber = lastAlignmentNumber;
			//System.out.println("ToAlign sin pickUp 7");
			//gui.model.link(gui, t, elements[t].size() - 1, element.elementNumber);
			//System.out.println("t=" + t);
			//System.out.println("elements[t].size()=" + elements[t].size());
			//System.out.println("element.elementNumber=" + element.elementNumber);
		}
	}

	// ###ment for å ta imot fra aligned, men vil bruke det for someAligned også?
	void catch_(AlignmentsEtc valueGot) {
		if (valueGot != null) {
			// alignments
			//System.out.println("valueGot.alignments.size() = " + valueGot.alignments.size());
			//System.out.println("tar pending.addAll(...)");
			pending.addAll(0, valueGot.alignments);
			// elements
			for (int t=0; t<Alignment.NUM_FILES; t++) {
				//System.out.println("((DefaultListModel)(valueGot.elements[" + t + "])).size() = " + ((DefaultListModel)(valueGot.elements[t])).size());
				for (int i=0; i<((DefaultListModel)(valueGot.elements[t])).size(); i++) {
					//System.out.println("skal ta elements[t].add(" + i + ", ...)");
					//System.out.println("testDefaultListModel");
					DefaultListModel testDefaultListModel = (DefaultListModel)(valueGot.elements[t]);
					//System.out.println("tester testObject");
					Object testObject = ((DefaultListModel)(valueGot.elements[t])).get(i);
					//System.out.println("tester testAElement");
					AElement testAElement = (AElement)(((DefaultListModel)(valueGot.elements[t])).get(i));
					//System.out.println("har testet testAElement");
					elements[t].add(i, (AElement)(((DefaultListModel)(valueGot.elements[t])).get(i)));   // ###
				}
			}
			firstAlignmentNumber = ((Link)(pending.get(0))).alignmentNumber;
			//System.out.println("nå ble firstAlignmentNumber = " + firstAlignmentNumber);
		}
	}

	/**
	 * pops everything,
	 */
	AlignmentsEtc flush() {
		//System.out.println("flush A");
		//MemTest.print("Tenured Gen", "");
		//if (!unused.empty()) {
		//	// ...
		//	//System.out.println("unused ikke tom");
		//	Toolkit.getDefaultToolkit().beep();
		//	System.out.println("Program error. 'unused' not empty");
		//	//System.out.println("BEEEEEEEEEEEEEEEEP ToAlign flush 1");
		//	return null;
		//} else {
			//System.out.println("unused tom");
			//System.out.println("flush B");
			//MemTest.print("Tenured Gen", "");
			if (pending.size() == 0) {
				//System.out.println("pending tom");
				Toolkit.getDefaultToolkit().beep();
				System.out.println("Nothing to flush");
				//System.out.println("BEEEEEEEEEEEEEEEEP ToAlign flush 2");
				return null;
			} else {
				//System.out.println("skal flushe");
				// update firstAlignmentNumber
				firstAlignmentNumber = ((Link)(pending.get(pending.size() - 1))).alignmentNumber + 1;
				//System.out.println("flush. nå ble firstAlignmentNumber = " + firstAlignmentNumber);
				// update ...
				//System.out.println("flush C");
				//MemTest.print("Tenured Gen", "");
				AlignmentsEtc returnValue = new AlignmentsEtc();
				//System.out.println("flush D");
				//MemTest.print("Tenured Gen", "");
				while (pending.size() > 0) {
					returnValue.alignments.add(pending.remove(0));
					//System.out.println("flush E");
					//MemTest.print("Tenured Gen", "");
				}
				//System.out.println("nå er pending.size() = " + pending.size());
				for (int t=0; t<Alignment.NUM_FILES; t++) {
					//((DefaultListModel)(returnValue.elements[t])).addAll((DefaultListModel)(elements[t]));
					while (((DefaultListModel)(elements[t])).size() > 0) {
						((DefaultListModel)(returnValue.elements[t])).addElement((AElement)(elements[t].remove(0)));
						//System.out.println("flush F");
						//MemTest.print("Tenured Gen", "");
					}
					//System.out.println("nå er elements[" + t + "].size() = " + elements[t].size());
				}
				//System.out.println("flush G");
				//MemTest.print("Tenured Gen", "");
				return returnValue;
			}
		//}
	}

}

class Unaligned {

	/**
	 * lists of unaligned elements.
	 * one list for each text.
	 * shown in the gui's 'unaligned' JList components.
	 * each element is an AElement object.
	 */
	protected DefaultListModel[] elements;   // ########## private + get-metode er bedre

	Unaligned() {
		elements = new DefaultListModel[Alignment.NUM_FILES];
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elements[t] = new DefaultListModel();
		}
	}

	public void purge() {
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elements[t].clear();
		}
	}

	/**
	  * pop the top unaligned AElement of the t-th text
	  */
	AElement pop(int t) {

		if (elements[t].size() == 0) {
			Toolkit.getDefaultToolkit().beep();
			//System.out.println("BEEEEEEEEEEEEEEEEP Unaligned pop");
			System.out.println("Nothing to pop");
			return null;
		} else {
			return (AElement)elements[t].remove(0);
		}

	}

	/**
	  * insert an AElement at the top of the t-th text.
	  * set the element's state to unused,
	  * no matter what its previous state was.
	  */
	void catch_(int t, AElement element) {
		if (element != null) {
			element.alignmentNumber = -1;   // €€€bruke static konstant?
			elements[t].add(0, element);   // ### æsj. her trengs ingen (AElement)
		}
	}

	/**
	  * ...
	  */
	void add(int t, AElement element) {
		elements[t].addElement((Object)element);
	}

	///**
	//  * get the i'th AElement in total
	//  */
	//AElement get(AlignmentModel model, int t, int i) {
	//	... + model.toAlign.elements[0].getSize() ...
	//}

	public AElement get(int t, Node element) {
		// searches unaligned of text t
		// for an AElement containing a certain alignable element
		////Iterator it = elements[t].iterator();
		for (Enumeration en = elements[t].elements(); en.hasMoreElements();) {
			AElement test = (AElement)en.nextElement();
			if (test.element == element) {
				// success
				return test;
			}
		}
		// failure
		return null;
	}

}

/**
 * a class that bundles alignments with their AElement elements.
 * used for movement of data between aligned and to-align (alignments under manual consideration).
 * ###also used for movement of data from unaligned to toAlign when skipping half-aligned file
 * ###ikke god. redundans og mulighet for inkonsistens i alignments vs elements.
 */
class AlignmentsEtc {

	List alignments = new ArrayList();
	Object[] elements;

	AlignmentsEtc() {
		elements = new DefaultListModel[Alignment.NUM_FILES];
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elements[t] = new DefaultListModel();
		}
	}

	// ###brukes ikke overalt

	public void add(Link link) {
		alignments.add(link);
	}

	// ###brukes ikke overalt
	//public void add(int t, Element element) {
	public void add(int t, AElement element) {
		if (!((DefaultListModel)elements[t]).contains(element)) {
			((DefaultListModel)elements[t]).addElement(element);
		}
	}

	public boolean hasHoles() {

		TreeSet[] testElementNumbers = new TreeSet[Alignment.NUM_FILES];   // TreeSet is a sorted Set
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			testElementNumbers[t] = new TreeSet();
		}

		// loop through the alignments
		Iterator it = alignments.iterator();
		while(it.hasNext()) {
			Link link = (Link)it.next();
			for (int t=0; t<Alignment.NUM_FILES; t++) {
				Iterator it2 = link.getElementNumbers(t).iterator();
				while(it2.hasNext()) {
					Integer n = (Integer)it2.next();
					testElementNumbers[t].add(n);
				}
			}
		}

		// ###the hasHoles method should have been a utility thing,
		// not belong to the Aligned object.
		//###uh. Aligned's hasHoles is more complicated
		//return Aligned.hasHoles(testElementNumbers);
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			if (testElementNumbers[t].size() > 0) {
				int last = ((Integer)(testElementNumbers[t].last())).intValue();
				int first = ((Integer)(testElementNumbers[t].first())).intValue();
				if ((last - first + 1) != testElementNumbers[t].size()) {
					// found hole for text t
					return true;
				}
			}
		}
		// found no hole for any text
		return false;

	}

	public boolean empty() {
		return (alignments.size() == 0);
	}

	// for debugging purposes
	public void print() {
		// loop through the alignments
		Iterator it = alignments.iterator();
		System.out.println("<<<link");
		while(it.hasNext()) {
			Link link = (Link)it.next();
			System.out.println(link);
		}
		System.out.println("link>>>");
		// loop ... elements
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			System.out.println("<<<text " + t);
			DefaultListModel l = (DefaultListModel)elements[t];
			for (int i=0; i<l.size(); i++) {
				System.out.println(l.get(i));
			}
			System.out.println("text " + t + ">>>");
		}
	}

}

/**
 * the program works with elements from xml files, e.g sentences.
 * each element is a node in a DOM tree.
 * but the program also needs to know which alignment each element is involved in, if any.
 * for this purpose the AElement object knows not only the element
 * but also the element's sequence number and the number of the alignment.
 */
class AElement {

	public static final int NUM_COLORS = 10;   // foreløpig €€€€€€€€€€€€€€€

	/**
	 * the element itself.
	 * a node in a DOM tree for the current text.
	 */
	Node element;

	/**
	 * the sequence number of the element.
	 * the elements of a text are numbered 0, 1, 2, 3, ...
	 */
	int elementNumber;

	/**
	 * the number of the alignment the element is involved in.
	 * alignments have a global numbering 0, 1, 2, 3, ...
	 * #####################################################unused elements under consideration have a special value -1.
	 */
	int alignmentNumber;

	/**
	 * the length in characters of the text content of the element. €€€€€€€€€€€€€€€€€€ burde normalisert whitespace
	 */
	int length;

	AElement(Node o, int en) {
		element = o;
		elementNumber = en;
		alignmentNumber = -1;   // €€€ not used yet
		//length = XmlTools.getText(element).length();
		length = element.getTextContent().length();
	}

	public Color getColor() {
		//System.out.println("getColor. alignmentNumber = " + alignmentNumber);
		if (alignmentNumber == -1) {
			//return Color.white;
			return Color.getHSBColor((float)0.00, (float)0.00, (float)0.97);
		} else {
			return Color.getHSBColor((float)((float)alignmentNumber / NUM_COLORS), (float)0.13, (float)1.00);
		}
	}

	/**
	 * makes a value that keeps - but normalizes - the division into lines of the element.
	 * €€€because some files can have odd line endings.
	 * if this value is rendered in a list box as a one-line thing it will not wrap.
	 * if this value is rendered in a list box as a multi-line thing it will wrap at line endings.
	 * €€€ €€€ €€€ suddenly wrap works after all! and this method isn't used!
	 * €€€ fixed
	 */
	public String toString() {

		//System.out.println("AElement sin toString");
		// pattern = [\n\r]+ , i.e, matches all kinds of line endings, also multiple endings
		// 2006-09-19 Pattern pattern = Pattern.compile("[\\n\\r]+");
		//Matcher matcher = pattern.matcher(element.toString());   // since 1.5 Node.toString() yields e.g '[s: null]' and not '<s attr="stuff">Blah blah blah</s>'
		//Matcher matcher = pattern.matcher(element.getTextContent());   // €€€just the text, e.g, 'Blah blah blah'
		//System.out.println("kaller getXmlContent. resultat: " + XmlTools.getXmlContent(element));
		// 2006-09-19 Matcher matcher = pattern.matcher(XmlTools.getXmlContent(element));   // €€€just the text, e.g, 'Blah blah blah'
		//Matcher matcher = pattern.matcher(element.getNodeValue());   // €€€leads to Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
		//return "element nummer " + elementNumber + ": " + matcher.replaceAll("\n");   // replaces all kinds of line endings with a standard one
		//return matcher.replaceAll("\n");   // replaces all kinds of line endings with a standard one
		// 2006-09-19 return matcher.replaceAll(" ");   // §§§
		return XmlTools.getXmlContent(element);   // 2006-09-19
		//return "test";   // |||---|||
		// €€€ merkelig. dersom vi standardiserer til \n,
		// får vi ordentlig wrap + wrap der det er \n.
		// dersom vi setter blank, får vi ikke wrap.
		// €€€ hmm det er noe tull med \n-måten.
		// når linjer wrapper, ser vi ikke slutten.
		// det er noe feil i utregningen av hvor mye plass som trengs.
		// €€€ har ikke noe med scroll bar å gjøre. ser det i aligned også.
		//return matcher.replaceAll(" ") + "\n";   // €€€ yeah! word wrap works! €€€ øh/ alle elementer blir to linjer høye!
		//return matcher.replaceAll("\n") + "\n";

	}

	// ###some users might like parent info prepended to the elements
	// in their newline format output files.
	// this method makes a suitable version for that purpose
	public String toNewString(AncestorFilter filter) {

		//Pattern pattern = Pattern.compile("[\\n\\r]+");   // pattern = [\n\r]+ , i.e, matches all kinds of line endings, also multiple endings
		//Matcher matcher = pattern.matcher(XmlTools.getXmlContent(element));   // €€€just the text, e.g, 'Blah blah blah'

		//#### skal dette ut i XmlTools?

		Node current = element;
		//short test2 = element.getNodeType();   //###debug
		String pathText = "";

		if (!filter.denyAll()) {

			String ancestorInfo;
			NamedNodeMap attrs;
			String elementName;
			Attr attribute;
			boolean done = false;
			//short test = Node.ELEMENT_NODE;   //###debug
			while (!done) {
				// next parent?
				try {
					current = current.getParentNode();
				} catch (DOMException e) {
					done = true;
				}
				// ???
				if (current == null) {
					done = true;
				} else {
					// but stop before root element is reached
					try {
						Node test = current.getParentNode();
						if (test.getNodeName() == "#document") {
							done = true;
						}
					} catch (DOMException e) {
						done = true;
					}
				}
				if (!done) {
					if (current.getNodeType() == Node.ELEMENT_NODE) {
						//
						elementName = current.getNodeName();
						if (filter.allowElement(elementName)) {
							ancestorInfo = "<" + elementName;
							attrs = current.getAttributes();
							for (int i = 0; i < attrs.getLength(); i++) {
								attribute = (Attr)attrs.item(i);
								if (filter.allowAttribute(elementName, attribute.getName())) {
									ancestorInfo += " " + attribute.getName() + "='" + attribute.getValue() + "'";
								}
							}
							ancestorInfo += ">";
							pathText = ancestorInfo + " " + pathText;
						}
					}
				}
			}

		}

		//return pathText + matcher.replaceAll(" ");   // §§§
		return pathText + XmlTools.getXmlContent(element);   // 2006-09-19 (nå skal elementet inneholde tekst uten (særlig) unødig whitespace)

	}

}

/**
 * each Link object represents an alignment - a finished one or pending one.
 * ##########################################################in addition a Link object is used for unused elements under consideration.
 */
class Link {

	/**
	 * alignments are numbered 0, 1, 2, 3, ...
	 * the numbering is global, so the numbering of pending alignments
	 * continues the numbering of finished alignments.
	 * #################################################unused elements under consideration have a special number -1. €€€UNUSED
	 */
	int alignmentNumber;   // ########################skulle hatt set-metode. m.fl.

	/**
	 * the numbers of the elements involved in the alignment.
	 * one set for each text.
	 */
	TreeSet[] elementNumbers;

	Link() {
		alignmentNumber = -1;   // €€€
		elementNumbers = new TreeSet[Alignment.NUM_FILES];
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elementNumbers[t] = new TreeSet();   // TreeSet is a SortedSet
		}
	}

	TreeSet getElementNumbers(int t) {
		return elementNumbers[t];
	}

	boolean empty() {
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			if (elementNumbers[t].size() > 0) {
				return false;
			}
		}
		return true;
	}

	int countElements() {
		int count = 0;
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			count += elementNumbers[t].size();
		}
		return count;
	}

	public String toString() {
		String str = "(";
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			if (t > 0) { str += ";"; }
			str += "size=" + elementNumbers[t].size();
			Iterator e = ((TreeSet)(elementNumbers[t])).iterator();
			while (e.hasNext()) {
				str += ",el=";
				str += e.next();
			}
		}
		str += ")";
		str += " alignment nummer " + alignmentNumber;
		return str;
	}

}

/////////////////////////////////////////////

/**
 * separate thread for loading files.
 * to be more precise it's not for the process of reading a file into a DOM tree
 * but the processing of the elements we do afterwards.
 * but anyway it's a process we want to show progress for in gui components,
 * so we need to have it in a separate thread.
 */
class LoadFileThread extends Thread {

	AlignGui gui;

	NodeList[] nodes;
	int t;

	int percentDone = 0;
	int prevPercentDisplayed = 0;
	int elementNumber;


	// do GUI updates

	void doUpdate(Runnable r) {
		try {
			SwingUtilities.invokeAndWait(r);
		} catch (InvocationTargetException e1) {
			//System.err.println(e1);
			ErrorMessage.error(e1.toString());   // 2006-08-10
		} catch (InterruptedException e2) {
			//System.err.println(e2);
			ErrorMessage.error(e2.toString());   // 2006-08-10
		}
	}

	// (we need a constructor with some arguments
	// to get references to the stuff the thread is working with) €€€

	public LoadFileThread(AlignGui gui, NodeList[] nodes, int t) {
		this.gui = gui;
		this.nodes = nodes;
		this.t = t;
	}

	// do €€€

	public void run() {

		// clear €€€

		doUpdate(new Runnable() {
			public void run() {
				gui.statusLine.setText("");
				gui.statusLine.setProgress(percentDone);
			}
		});

		int numElements = nodes[t].getLength();
		// ### i use Math.log(x)/Math.log(10) instead of Math.log10(x) until i've got java 1.5 installed
		int step = Math.round((float)(Math.pow(10, Math.sqrt((((Math.log((double)numElements / 100) / Math.log(10))) + 1)))));
		step = Math.min(step, 100);
		step = Math.max(step, 10);

		for (elementNumber = 0; elementNumber < numElements; elementNumber++) {

			AElement element = new AElement(nodes[t].item(elementNumber), elementNumber);
			gui.model.unaligned.add(t, element);

			percentDone = Math.round((float)((float)(elementNumber+1) / numElements * 100.0));

			//if ((elementNumber + 1) % 100 == 0) {
			if (percentDone >= prevPercentDisplayed + step) {

				doUpdate(new Runnable() {
					public void run() {
						gui.statusLine.setText(Integer.toString(elementNumber+1));
						gui.statusLine.setProgress(percentDone);
					}
				});

				prevPercentDisplayed = percentDone;

			}

		}

		// €€€

		doUpdate(new Runnable() {
			public void run() {
				//gui.statusLine.setText("Finished");
				gui.statusLine.setText("Text parsed");
				gui.statusLine.setProgress(100);
			}
		});

		// €€€problem: sometimes at this point the content of the unaligned area doesn't show.
		// why?
		// shake it by removing and adding first element.
		// €€€doesn't always help!?
		// 2006-09-19: worse with JTextArea than JLabel?
		gui.model.unaligned.elements[t].add(0 ,gui.model.unaligned.elements[t].remove(0));

	}

}

/////////////////////////////////////////////

/**
 * information about how the current elements under alignment match
 * with respect to anchor words, proper names, dice, length, etc.
 * displayable version.
 * formatted into a list of lines
 * ######### to ulike steder som beregner skåre
 */
//class MatchInfoDisplayable {
class MatchInfo {

	AlignmentModel model;
	protected DefaultListModel displayableList;

	//MatchInfoDisplayable(AlignmentModel model) {
	MatchInfo(AlignmentModel model) {

		this.model = model;
		displayableList = new DefaultListModel();

	}

	//public void compute() {
	//
	//	//...;
	//
	//}

	public void clear() {   // §§§§§§§§§§§§§§§§§§§§§§§

		//...;

	}

	public void purge() {
		displayableList.clear();
		// (keep model)
	}

	//public String toString() {
	public void computeDisplayableList() {

		int t;
		int n;

		//System.out.println("computeDisplayableList()");

		ElementInfoToBeCompared elementInfoToBeCompared = new ElementInfoToBeCompared(model);

		// collect necessary info in an ElementInfoToBeCompared object

		for (t=0; t<Alignment.NUM_FILES; t++) {
			for (Enumeration en = model.toAlign.elements[t].elements(); en.hasMoreElements();) {
				n = ((AElement)en.nextElement()).elementNumber;
				try {
					ElementInfo info = model.compare.elementsInfo[t].getElementInfo(model, n, t);
					elementInfoToBeCompared.add(info, t);
				} catch (EndOfTextException e) {   // skal ikke forekomme her?
					System.out.println("*** program error.unexpected EndOfTextException ***");
					//return "*** program error. unexpected EndOfTextException ***";
					displayableList.add(0, (Object)"*** Program error. Unexpected EndOfTextException ***");
				}
			}
		}

		// get displayable info about how they match

		//return elementInfoToBeCompared.toString();
		List list = elementInfoToBeCompared.toList();
		Iterator it = list.iterator();
		displayableList.clear();
		while (it.hasNext()) {
			displayableList.addElement(it.next());
		}

	}

}

/**
 * some elements are to be compared,
 * either the elements in a step to be tried out,
 * or the elements under alignment, visible in the gui.
 * this class contains (refers to) the elements to be compared.
 * to be more precise the elements refered to
 * are ElementInfo objects in the Compare object.
 * methods that need to know the score of the element comparison,
 * or details about how they match,
 * must establish an ElementInfoToBeCompared object,
 * and call the object's getScore or toString method
 */
class ElementInfoToBeCompared {

	private AlignmentModel model;

	public static final String INDENT = "  ";

	List[] info = new ArrayList[Alignment.NUM_FILES];   // lists of ElementInfo, one for each text

	// score and details. calculated on demand, and only once

	//// -1 = not calculated yet
	//private float score = -1.0f;   // set by toString(). ### i mean toList(). can be got by getScore(). see getScore() §§§§§§§§§§§§§§§§§§§§
	// not calculated yet // 2006-09-20
	private float score = AlignmentModel.ELEMENTINFO_SCORE_NOT_CALCULATED;   // set by toList(). can be got by getScore(). see getScore() §§§§§§§§§§§§§§§§§§§§ // 2006-09-20
	//StringBuffer str = new StringBuffer();
	List ret = new ArrayList();

	public ElementInfoToBeCompared(AlignmentModel model) {

		this.model = model;

		for (int t=0; t<Alignment.NUM_FILES; t++) {
			info[t] = new ArrayList();
		}

	}

	public void add(ElementInfo elementInfo, int t) {

		//££££SKAL DENNE METODEN SØRGE FOR AT INFORMASJONEN OM ORD-POSISJON BLIR "GLOBAL",
		//OG IKKE LOKAL FOR HVERT ELEMENT?
		//nei, det går vel ikke, for denne klassen eier ikke elementene

		//System.out.println("&&& enter ElementInfoToBeCompared.add(...)");
		info[t].add(elementInfo);
		//System.out.println("t=" + t + ", after add: info[t].size()=" + info[t].size());

	}

	public boolean empty() {

		//System.out.println("&&& enter ElementInfoToBeCompared.empty()");
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			//System.out.println("t=" + t + ", info[t].size()=" + info[t].size());
			if (info[t].size() == 0) {
				//System.out.println("return true");
				return true;
			}
		}
		//System.out.println("return false");
		return false;

	}

	public float getScore() {

		//System.out.println("&&& enter ElementInfoToBeCompared.getScore(). score = " + score);
		//if (score <= -0.9f) {   // i.e, == -1.0f, i.e, not calculated
		if (score == AlignmentModel.ELEMENTINFO_SCORE_NOT_CALCULATED) {   // 2006-09-20. this change corrects an error. earlier element-info with calculated score -99999.0 (menat to kill path) would get their score re-calculated
			//System.out.println("må beregne");
			// not been calculated yet.
			// use toString() to calculate it
			// and just throw away the String returned
			//toString();
			toList();
		} else {
			//System.out.println("ikke nødvendig å beregne");
		}
		//System.out.println("&&& leave ElementInfoToBeCompared.getScore(). score = " + score);
		return score;

	}

	//public String toString() {
	public List toList() {

		//System.out.print("entering toList(). score=" + score + ". ");

		DecimalFormat myFormatter = (DecimalFormat)NumberFormat.getInstance(Locale.ENGLISH);
		myFormatter.applyPattern("0.###");

		//System.out.println("enter ElementInfoToBeCompared.toList() ============================");
		//System.out.println("&&& enter ElementInfoToBeCompared.toString(). score = " + score);
		//if (score <= -0.9f) {   // i.e, == -1.0f, i.e, not calculated
		if (score == AlignmentModel.ELEMENTINFO_SCORE_NOT_CALCULATED) {   // 2006-09-20. this change corrects an error. earlier element-info with calculated score -99999.0 (menat to kill path) would get their score re-calculated
			// not been calculated yet.
			//System.out.print("score not calculated yet. ");

			score = 0.0f;
			//System.out.println("&&& ElementInfoToBeCompared.toString(). init score = " + score);

			if (empty()) {

				// keep score 0. keep str ""
				//System.out.println("&&& ElementInfoToBeCompared.toString(). empty. keep score 0. keep str null string");

			} else {

				int t;
				int tt;
				Iterator it;
				Iterator it1;
				Iterator it2;
				String retLine;   // to contain one line of info

				// 2006-04-05

				// 2006-09-20
				//////////////////
				// bad lengths? //
				//////////////////

				int[] length = new int[Alignment.NUM_FILES];   // length in chars of the relevant elements of each text
				int[] elementCount = new int[Alignment.NUM_FILES];   // number of relevant elements from each text

				for (t=0; t<Alignment.NUM_FILES; t++) {
					length[t] = 0;
					it = info[t].iterator();
					while (it.hasNext()) {
						ElementInfo info1 = (ElementInfo)it.next();
						length[t] += info1.length;
					}
					elementCount[t] = info[t].size();
				}

				if (SimilarityUtils.badLengthCorrelation(length[0], length[1], elementCount[0], elementCount[1], model.getLengthRatio())) {

					score = AlignmentModel.ELEMENTINFO_SCORE_HOPELESS;
					retLine = "Very poor length match";
					ret.add(retLine);

				} else {
				// end 2006-09-20

					// this methods produces detailed information about how the
					// elements under consideration match,
					// and collects that information in List ret

					// do reporting###

					// we don't know the common score for the word based methods yet.
					// we'll have to come back and insert the score later.
					retLine = INDENT + "Word based methods score: ";
					ret.add(retLine);
					// remember in which line to insert the score later
					int wordMethodsScoreLineNumber = ret.size() - 1;

					// end 2006-04-05

					//////////////////
					// anchor words //
					//////////////////

					Clusters anchorWordClusters = new Clusters();   // 2006-04-05

					//int anchorWordScore = 0;   // 2006-04-05

					AnchorWordHit hit;
					int index;
					//int high = 0;   // 2006-04-05
					//int highSum = 0;   // 2006-04-05
					//int low = 0;   // 2006-04-05
					//int lowSum = 0;   // 2006-04-05
					//int oneSum = 0;   // 2006-04-05
					int count;
					int smallest;
					int smallestCount;
					boolean presentInAllTexts;
					int matchType;
					float weight;
					String word;
					int pos;
					int len;
					int elementNumber ;
					int indentLevel;
					boolean includeMatchType;

					//System.out.println("&&& ElementInfoToBeCompared.toString(). not empty. go ahead and calculate");
					// for each text t make a list hits[t] of anchor word hits
					// for (from) the elements under consideration from text t.
					// a hit is an occurrence of an anchor word in a text,
					// but not yet a confirmed match with a word from the other text ###

					List[] hits = new ArrayList[Alignment.NUM_FILES];
					for (t=0; t<Alignment.NUM_FILES; t++) {
						hits[t] = new ArrayList();
						//System.out.println("før anchor words sin 'it = info[t].iterator();'");
						it = info[t].iterator();   //£££ER DETTE LØKKE OVER ELEMENTER?
						//System.out.println("etter anchor words sin 'it = info[t].iterator();'");
						///////////////@@@@@@@@@@int offset = 0;   // 2006-04-05
						while (it.hasNext()) {
							ElementInfo info1 = (ElementInfo)it.next();
							it2 = info1.anchorWordHits.hits.iterator();
							while (it2.hasNext()) {
								hit = (AnchorWordHit)it2.next();   //£££DA HAR hit HER EN NUMMERERING AV ORDPOSISJON SOM ER LOKAL FOR HVERT ELEMENT.
								//System.out.println("adder " + hit + " for tekst nr " + t);
								// change word position from local within each element
								// to global within all the elements under consideration for text t.
								// ####alternativ: operere med to-nivå nummerering i hit-ene etc:
								// 1 elementnummer, 2 lokalt ordnummer
								//System.out.println("hit.getPos() før   = " + hit.getPos());
								///////////////@@@@@@@@@@if (offset != 0) { hit.setPos(hit.getPos() + offset); }   // 2006-04-05
								//System.out.println("hit.getPos() etter = " + hit.getPos());
								hits[t].add(hit);
							}
							///////////////@@@@@@@@@@offset += info1.words.length;   // 2006-04-05
						}
						//System.out.println("hits[" + t + "] = " + hits[t]);
					}

					// see if any hits match up,
					// i.e, if any occurring anchor words in different texts
					// share the same anchor word list entry

					// sort these lists of hits on
					// (1) index (anchor word list entry number) and
					// (2) word
					for (t=0; t<Alignment.NUM_FILES; t++) {
						Collections.sort(hits[t], new AnchorWordHitComparator());
						//System.out.println("sortert hits[" + t + "] = " + hits[t]);
					}

					// match up hits.
					// first init pointers to current hit in each list
					int[] current = new int[Alignment.NUM_FILES];
					for (t=0; t<Alignment.NUM_FILES; t++) {
						current[t] = 0;
						//System.out.println("pointer to current hit in text " + t + " = " + current[t]);
					}

					// then do stuff.
					// one loop pass per anchor word list entry with hits.
					// do them in index order, smallest first
					// (we just had them sorted just for this reason)
					boolean done = false;
					while (!done) {
						//System.out.println("next pass while (!done)");
						// find smallest anchor word list index in remaining hits.
						// check if it is present inn all texts
						smallest = Integer.MAX_VALUE;
						smallestCount = 0;
						//System.out.println("smallest anchor word list index so far = " + smallest);
						for (t=0; t<Alignment.NUM_FILES; t++) {
							//System.out.println("next text - " + t);
							if (current[t] < hits[t].size()) {
								//System.out.println("there are remaining hits for text " + t);
								// there are remaining hits for text t
								hit = (AnchorWordHit)((List)hits[t]).get(current[t]);   // ### (AnchorWordHit)
								if (hit.getIndex().intValue() < smallest) {
									// found a new smallest
									smallest = hit.getIndex().intValue();
									// reset count
									smallestCount = 1;
									//System.out.println("found a smaller one: " + smallest);
								} else if (hit.getIndex().intValue() == smallest) {
									// same smallest. increment count
									smallestCount++;
								} // else not a smallest
							}
						}
						presentInAllTexts = (smallestCount == Alignment.NUM_FILES);
						/*if (presentInAllTexts) {
							//System.out.println("hit for index " + smallest + " present in all texts");
						} else {
							//System.out.println("hit for index " + smallest + " not present in all texts");
						}*/
						// in the following: collect data for output only if the hit### was present in all texts
						// ...
						if (smallest == Integer.MAX_VALUE) {
							//System.out.println("found no remaining hits, for any text");
							// no remaining hits, for any text
							done = true;
						} else {
							// there are remaining hits, at least for some of the texts.
							// find all hits with this smallest remaining anchor word list index.
							//// look through all texts and find the highest/lowest number of hits in any text.
							//// this highest/lowest number might be the contribution to the anchor word score
							//// for this anchor word list entry,
							//// provided the hit is in every text..
							//// example:
							//// anchor word entry: "xxx/yyy,yy"
							//// texts: "I saw a xxx going down the xxx" vs "Jeg så en yyy nede i yy yyy"
							//// (§§§§§§§§§idiotisk, ubrukelig eksempel)
							//// 2 hits in first text and 3 hits in second text makes a score of max(2,3) = 3
							//System.out.println("there are remaining hits, at least for some of the texts");
							//high = 0;
							//low = Integer.MAX_VALUE;
							//System.out.println("highest number of hits in any text so far = " + high);
							//System.out.println("lowest number of hits in any text so far = " + low);
							//retLine = "";   // init next line of info // 2006-04-05
							for (t=0; t<Alignment.NUM_FILES; t++) {
								//System.out.println("next text - " + t);
								/*if (presentInAllTexts) {
									if (t == 0) {
										// €€€ hvorfor brukes ikke toString-metode? duplisert programmering
										//str.append(INDENT + INDENT + (smallest + 1) + " ");   // +1 since we want anchor word entries numbered from 1 and upwards when they are displayed
										retLine += INDENT + INDENT + (smallest + 1) + " ";   // +1 since we want anchor word entries numbered from 1 and upwards when they are displayed
									} else {
										//str.append("/");
										retLine += "/";
									}
								}*/ // 2006-04-05
								count = 0;
								//System.out.println("number of hits in current text so far = " + count);
								boolean first = true;
								if (current[t] < hits[t].size()) {
									//System.out.println("there are remaining hits, at least for text " + t);
									// there are remaining hits for text t.
									// get all hits with smallest index, if any
									boolean done2 = false;
									for (int c = current[t]; !done2; c++) {
										//System.out.println("check hit " + c + " for text " + t);
										hit = (AnchorWordHit)hits[t].get(c);
										//System.out.println("hit = " + hit);
										index = hit.getIndex().intValue();
										if (index == smallest) {
											//System.out.println("this is a smallest hit");
											//// samle opp ordet i en ### liste for text t, såsant det er et nytt ord
											// add to cluster list   // 2006-04-05
											elementNumber = hit.getElementNumber();   // 2006-04-05
											pos = hit.getPos();   // 2006-04-05
											//System.out.println("t = " + t + ", elementNumber = " + elementNumber + ", pos = " + pos);
											word = hit.getWord();   // 2006-04-05
											len = Utils.countWords(word);   // 2006-04-07 ### hadde vært penere med egen member len i tillegg til pos, slik som Ref har
											matchType = index;   // each anchor word entry is its own match type, sort of   // 2006-04-05
											//weight = 1.0f;   // 2006-04-05
											if (len > 1) {   // (2006-04-10)
												weight = model.getAnchorPhraseMatchWeight();   // 2006-04-07
											} else {   // (2006-04-10)
												weight = model.getAnchorWordMatchWeight();   // 2006-04-07
											}   // (2006-04-10)
											//Ref ref = new Ref(matchType, weight, t, elementNumber, pos, word);   // 2006-04-05
											//System.out.println("legger ny ref i anchorWordClusters: " + ref);
											//anchorWordClusters.add(ref);   // ### heller en addRef-metode? handler om grenseoppgang mellom clustergreiene og utsiden   // 2006-04-05
											// ### 2006-04-06 her er det et problem. vi adder én ref om gangen, og de samler seg ikke i cluster.
											// må enten adde dem i par eller hele clustre, eller må vi ha en annen addemetode for anker-ref,
											// slik at ref-ene havner i samme cluster når de har samme ankerordentry.
											// eller den addemetoden som vi har må behandle anker-ref annerledes.
											// matchemetoden, mener jeg!
											// ja - gjør det siste. lar matchemetoden behandle anker-ref annerledes.
											// forresten. får da inn ref-er som ikke har med matching å gjøre,
											// f.eks to forekomster av samme ankerord i samme tekst.
											// og uansett får jeg jo også cluster med enslig ref, når jeg gir én ref om gangen.
											// aha. disse siste problemene har jo med hva jeg gjør her.
											// addingen skal behandle anker-ref annerledes,
											// men her i denne metoden må jeg adde ref bare når presentInAllTexts
											//anchorWordClusters.add(new Ref(matchType, weight, t, elementNumber, pos, word));   // ### heller en addRef-metode? handler om grenseoppgang mellom clustergreiene og utsiden   // 2006-04-05
											// ...
											/*if (first) {
												first = false;
											} else {
												if (presentInAllTexts) {
													//str.append(",");
													retLine += ",";
												}
											}*/
											if (presentInAllTexts) {
												//str.append(hit.getWord());
												//retLine += hit.getWord();
												//anchorWordClusters.add(new Ref(matchType, weight, t, elementNumber, pos, word));   // ### heller en addRef-metode? handler om grenseoppgang mellom clustergreiene og utsiden   // 2006-04-06
												anchorWordClusters.add(new Ref(matchType, weight, t, elementNumber, pos, len, word));   // ### heller en addRef-metode? handler om grenseoppgang mellom clustergreiene og utsiden   // 2006-04-07
											}
											count++;
											//System.out.println("number of hits in current text so far = " + count);
										} else {
											done2 = true;
										}
										if (c+1 >= hits[t].size()) {
											done2 = true;
										}
									}
									// ...
									current[t] += count;
									//System.out.println("updated pointer to current hit in text " + t + " to " + current[t]);
								}
								//if (count > high) { high = count; }
								//if (count < low) { low = count; }
							}
							/*if (presentInAllTexts) {
								if (model.getClusterScoreMethod() == 3) {
									//str.append(" (" + high + " points)");
									retLine += " (" + high + " points)";
								} if (model.getClusterScoreMethod() == 2) {
									//str.append(" (" + low + " points)");
									retLine += " (" + low + " points)";
								} else { // if (model.getClusterScoreMethod() == 1)
									//str.append(" (" + 1 + " points)");   // €€€ sløyfe?
									retLine += " (" + 1 + " points)";   // €€€ sløyfe?
								}
								//str.append("\n");
								//System.out.println("add'er retLine = " + retLine);
								ret.add(retLine);
								//System.out.println("nå er det " + ret.size() + " linjer i ret");
							}*/ // 2006-04-05
						}
						/*// add points for this anchor word list entry
						if (presentInAllTexts) {
							highSum += high;
							lowSum += low;
							oneSum += 1;
						}*/ // 2006-04-05
					}

					// ...
					/*if (model.getClusterScoreMethod() == 3) {
						anchorWordScore = highSum;
					} if (model.getClusterScoreMethod() == 2) {
						anchorWordScore = lowSum;
					} else { // if (model.getClusterScoreMethod() == 1)
						anchorWordScore = oneSum;
					}*/ // 2006-04-05

					//System.out.println(">>> anchorWordScore = " + anchorWordScore + "\n");

					// ...
					//if (str.length() > 0) {
					//	str.insert(0, INDENT + "Anchor word score: " + anchorWordScore + "\n");
					//} else {
					//	str.insert(0, INDENT + "No anchor word matches. Score: 0\n");
					//}

					//int anchorWordScore = anchorWordClusters.getScore(model.getClusterScoreMethod());   // 2006-04-05
					//float anchorWordScore = anchorWordClusters.getScore(model.getClusterScoreMethod());   // 2006-04-07
					float anchorWordScore = anchorWordClusters.getScore(model.getLargeClusterScorePercentage());

					// next line of info...
					//if (anchorWordScore > 0) {   // ### ryddigere med samme syntaks alltid
						retLine = INDENT + INDENT + "Anchor word score: " + myFormatter.format(anchorWordScore);   // 2006-04-05
					//} else {
					//	retLine = INDENT + "No anchor word matches. Score: 0";
					//}
					//// ...is header for anchor info. insert at top
					//ret.add(0, retLine);
					ret.add(retLine);   // 2006-04-05

					indentLevel = 3;   // 2006-04-05
					includeMatchType = true;   // i.e, include anchor word entry number. ### + 1 ### ugly
					ret.addAll(anchorWordClusters.getDetails(indentLevel, includeMatchType));   // getDetails() does its own indentation and endline. ### ikke helt bra?   // 2006-04-05

					//// ...
					//score += anchorWordScore;   // 2006-04-05

					///////////////////
					// proper names, //
					// dice,         //
					// and numbers   //
					///////////////////

					//int properNameScore = 0;   // 2006-04-05
					//int diceScore = 0;   // 2006-04-05

					// check all the words in one text against all the words in the other.
					// collect clusters of proper names.
					// collect clusters of dice-related words.
					// collect clusters of numbers.
					// (usually all the words in a cluster will be related to each other,
					// but not necessarily.)
					String word1;
					String word2;
					String nextWord1;   // 2006-04-07
					String nextWord2;   // 2006-04-07
					//String phrase1;   // 2006-04-07. words glued together without space between them
					//String phrase2;   // 2006-04-07. words glued together without space between them
					String showPhrase1;   // 2006-04-18. words with space between them
					String showPhrase2;   // 2006-04-18. words with space between them
					//Clusters properNameClusters = new Clusters(model.getClusterScoreMethod());
					Clusters properNameClusters = new Clusters();   // 2006-04-05
					//Clusters diceClusters = new Clusters(model.getClusterScoreMethod());
					Clusters diceClusters = new Clusters();   // 2006-04-05
					Clusters numberClusters = new Clusters();   // 2006-04-06
					//System.out.println("Skipper proper, dice, numbers");
					for (t=0; t<Alignment.NUM_FILES; t++) {
						for (tt=t+1; tt<Alignment.NUM_FILES; tt++) {
							//System.out.println("\nneste (eneste) gjennomløp. t = " + t + ". tt = " + tt);

							// check text t against text tt
							// (in practice text 0 (known to the user as text 1)
							// against text 1 (known to the user as text 2))

							// ... each word in relevant elements of text t

							it1 = info[t].iterator();
							while (it1.hasNext()) {
								ElementInfo info1 = (ElementInfo)it1.next();
								for (int x = 0; x < info1.words.length; x++) {

									word1 = info1.words[x];
									// 2006-04-07
									if (x < info1.words.length - 1) {
										nextWord1 = info1.words[x+1];
									} else {
										nextWord1 = "";
									}

									// end 2006-04-07
									// ... each word in relevant elements of text tt

									it2 = info[tt].iterator();
									while (it2.hasNext()) {
										ElementInfo info2 = (ElementInfo)it2.next();
										for (int y = 0; y < info2.words.length; y++) {

											word2 = info2.words[y];
											// 2006-04-07
											if (y < info2.words.length - 1) {
												nextWord2 = info2.words[y+1];
											} else {
												nextWord2 = "";
											}
											// end 2006-04-07

											// compare two words

											// proper names

											if (Character.isUpperCase(word1.charAt(0)) && Character.isUpperCase(word2.charAt(0)) && word1.equals(word2)) {

												// the words are capitalized and equal.
												// add to cluster list

												//System.out.println("\n" + word1 + " and " + word2 + " are capitalized and equal. add to cluster list");
												//properNameClusters.add(t, tt, x, y);
												//properNameClusters.add(t, tt, x, y, word1, word2);
												matchType = Match.PROPER;   // 2006-04-05
												//weight = 1.0f;   // 2006-04-05
												weight = model.getProperNameMatchWeight();   // 2006-04-07
												//properNameClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, word1, word2);   // 2006-04-05
												properNameClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 1, 1, word1, word2);   // 2006-04-07
												//System.out.println("%%% properNameClusters etter add = " + properNameClusters);

											}

											// dice

											// first check if the words are long enough to be considered
											if ((word1.length() >= model.getDiceMinWordLength()) && (word2.length() >= model.getDiceMinWordLength())) {

												//System.out.println("\nskal dice-sammenlikne " + word1 + " med " + word2);
												//if (SimilarityUtils.dice(word1, word2) >= model.getDiceMinCountingScore()) {
												if (SimilarityUtils.diceMatch(word1, word2, model.getDiceMinCountingScore())) {   // 2006-08-09

													// the words are related.
													// add to cluster list

													//System.out.println("\n" + word1 + " and " + word2 + " are dice-related. add to cluster list");
													//diceClusters.add(t, tt, x, y, word1, word2);
													matchType = Match.DICE;   // 2006-04-05
													//weight = 1.0f;   // 2006-04-05
													weight = model.getDiceMatchWeight();   // 2006-04-07
													//diceClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, word1, word2);   // 2006-04-05
													diceClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 1, 1, word1, word2);   // 2006-04-07

												}

											}

											// 2006-04-07

											// also try dice on 2 words against 1 word...

											if (nextWord1 != "") {

												//phrase1 = word1 + " " + nextWord1;
												//phrase1 = word1 + nextWord1;
												showPhrase1 = word1 + " " + nextWord1;   // 2006-04-18

												// first check if the phrases/words are long enough to be considered
												//if ((phrase1.length()-1 >= model.getDiceMinWordLength()) && (word2.length() >= model.getDiceMinWordLength())) {
												//if ((phrase1.length() >= model.getDiceMinWordLength()) && (word2.length() >= model.getDiceMinWordLength())) {
												if (   (word1.length()     >= model.getDiceMinWordLength())
												    && (nextWord1.length() >= model.getDiceMinWordLength())
												    && (word2.length()     >= model.getDiceMinWordLength())) {   // 2006-04-18

													//if (SimilarityUtils.dice(phrase1, word2) >= model.getDiceMinCountingScore()) {
													//if (SimilarityUtils.dice(phrase1, word2, "2-1") >= model.getDiceMinCountingScore()) {   // 2006-04-18
													if (SimilarityUtils.diceMatch(word1, nextWord1, word2, "2-1", model.getDiceMinCountingScore())) {   // 2006-08-09

														// the phrases/words are related.
														// add to cluster list

														//System.out.println("\n" + phrase1 + " and " + word2 + " are dice-related. add to cluster list");
														matchType = Match.DICE;   // 2006-04-05
														weight = model.getDicePhraseMatchWeight();   // 2006-04-07
														//diceClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 2, 1, phrase1, word2);
														diceClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 2, 1, showPhrase1, word2);   // 2006-04-18

													}

												}

											}

											// ...and 1 word against 2 words

											if (nextWord2 != "") {

												//phrase2 = word2 + " " + nextWord2;
												//phrase2 = word2 + nextWord2;
												showPhrase2 = word2 + " " + nextWord2;   // 2006-04-18

												// first check if the phrases/words are long enough to be considered
												//if ((word1.length() >= model.getDiceMinWordLength()) && (phrase2.length()-1 >= model.getDiceMinWordLength())) {
												//if ((word1.length() >= model.getDiceMinWordLength()) && (phrase2.length() >= model.getDiceMinWordLength())) {
												if (   (word1.length()     >= model.getDiceMinWordLength())
												    && (word2.length()     >= model.getDiceMinWordLength())
												    && (nextWord2.length() >= model.getDiceMinWordLength())) {   // 2006-04-18

													//if (SimilarityUtils.dice(word1, phrase2) >= model.getDiceMinCountingScore()) {
													//if (SimilarityUtils.dice(word1, phrase2, "1-2") >= model.getDiceMinCountingScore()) {   // 2006-04-18
													if (SimilarityUtils.diceMatch(word1, word2, nextWord2, "1-2", model.getDiceMinCountingScore())) {   // 2006-08-09

														// the phrases/words are related.
														// add to cluster list

														//System.out.println("\n" + word1 + " and " + phrase2 + " are dice-related. add to cluster list");
														matchType = Match.DICE;   // 2006-04-05
														weight = model.getDicePhraseMatchWeight();   // 2006-04-07
														//diceClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 1, 2, word1, phrase2);
														diceClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 1, 2, word1, showPhrase2);   // 2006-04-18

													}

												}

											}

											// end 2006-04-07

											// 2006-04-06

											// numbers

											float num1;
											float num2;
											try {
												num1 = Float.parseFloat(word1);
												num2 = Float.parseFloat(word2);
												if (num1 == num2) {

													// same number
													// add to cluster list

													matchType = Match.NUMBER;
													//weight = 1.0f;
													weight = model.getNumberMatchWeight();   // 2006-04-07
													//numberClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, word1, word2);
													numberClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 1, 1, word1, word2);   // 2006-04-07

												}

											} catch (NumberFormatException ne) {
											}

											// end 2006-04-06

										}
									}

								}

							}
						}
					}

					//System.out.println("%%% properNameClusters ferdig = " + properNameClusters);

					//int properNameScore = properNameClusters.getScore(model.getClusterScoreMethod());
					//float properNameScore = properNameClusters.getScore(model.getClusterScoreMethod());
					float properNameScore = properNameClusters.getScore(model.getLargeClusterScorePercentage());

					//int diceScore = diceClusters.getScore(model.getClusterScoreMethod());
					//float diceScore = diceClusters.getScore(model.getClusterScoreMethod());
					float diceScore = diceClusters.getScore(model.getLargeClusterScorePercentage());

					//int numberScore = numberClusters.getScore(model.getClusterScoreMethod());
					//float numberScore = numberClusters.getScore(model.getClusterScoreMethod());
					float numberScore = numberClusters.getScore(model.getLargeClusterScorePercentage());

					// ...

					//str.append(INDENT + "Proper name score: " + properNameScore + "\n");
					retLine = INDENT + INDENT + "Proper name score: " + myFormatter.format(properNameScore);   // 2006-04-05
					ret.add(retLine);

					//score += properNameScore;   // 2006-04-05

					//str.append(properNameClusters.getWords());   // getWords() does its own indentation and endline. ### ikke helt bra?
					//ret.addAll(properNameClusters.getDetails());   // getDetails() does its own indentation and endline. ### ikke helt bra?
					indentLevel = 3;   // 2006-04-05
					includeMatchType = false;
					ret.addAll(properNameClusters.getDetails(indentLevel, includeMatchType));   // getDetails() does its own indentation and endline. ### ikke helt bra?   // 2006-04-05

					//str.append(INDENT + "Dice score: " + diceScore + "\n");
					retLine = INDENT + INDENT + "Dice score: " + myFormatter.format(diceScore);   // 2006-04-05
					ret.add(retLine);

					//score += diceScore;   // 2006-04-05

					//str.append(diceClusters.getWords());   // getWords() does its own indentation and endline. ### ikke helt bra?
					//ret.addAll(diceClusters.getDetails());   // getDetails() does its own indentation and endline. ### ikke helt bra?
					indentLevel = 3;   // 2006-04-05
					includeMatchType = false;
					ret.addAll(diceClusters.getDetails(indentLevel, includeMatchType));   // getDetails() does its own indentation and endline. ### ikke helt bra?   // 2006-04-05

					// 2006-04-06

					retLine = INDENT + INDENT + "Number score: " + myFormatter.format(numberScore);
					ret.add(retLine);

					indentLevel = 3;
					includeMatchType = false;
					ret.addAll(numberClusters.getDetails(indentLevel, includeMatchType));

					// end 2006-04-06

					// 2006-04-05

					////////////////////////////////

					// common score for anchor words, proper names, dice and numbers

					Clusters commonClusters = new Clusters();
					commonClusters.add(anchorWordClusters);
					commonClusters.add(properNameClusters);
					commonClusters.add(diceClusters);
					commonClusters.add(numberClusters);   // 2006-04-06

					//int commonScore = commonClusters.getScore(model.getClusterScoreMethod());
					//float commonScore = commonClusters.getScore(model.getClusterScoreMethod());
					float commonScore = commonClusters.getScore(model.getLargeClusterScorePercentage());

					// go back and insert the common score for the word based methods
					ret.set(wordMethodsScoreLineNumber, (String)ret.get(wordMethodsScoreLineNumber) + myFormatter.format(commonScore));

					score += commonScore;

					// end 2006-04-05

					// debugging or testing
					String tempo = commonClusters.nonTrivialClusters_ToString();
					if (tempo != "") {
						System.out.println(tempo);
					}

					////////////////////////////////
					// scoring special characters //
					////////////////////////////////

					//int scoringCharacterScore = 0;   // 2006-04-05

					// check all the ... ... ...

					String char1;
					String char2;
					//Clusters scoringCharacterClusters = new Clusters(model.getClusterScoreMethod());
					Clusters scoringCharacterClusters = new Clusters();   // 2006-04-05
					for (t=0; t<Alignment.NUM_FILES; t++) {
						for (tt=t+1; tt<Alignment.NUM_FILES; tt++) {

							// check text t against text tt (in practice 0 (1) against 1 (2))

							// each scoring special character in relevant elements of text t

							it1 = info[t].iterator();
							while (it1.hasNext()) {
								ElementInfo info1 = (ElementInfo)it1.next();
								//System.out.println("info1.scoringCharacters = " + info1.scoringCharacters);
								for (int x = 0; x < info1.scoringCharacters.length(); x++) {
									char1 = info1.scoringCharacters.substring(x, x+1);

									// ... each scoring char in relevant elements of text tt

									it2 = info[tt].iterator();
									while (it2.hasNext()) {
										ElementInfo info2 = (ElementInfo)it2.next();
										//System.out.println("info2.scoringCharacters = " + info2.scoringCharacters);
										for (int y = 0; y < info2.scoringCharacters.length(); y++) {
											char2 = info2.scoringCharacters.substring(y, y+1);

											// compare two characters

											if (char1.equals(char2)) {

												// equal.
												// add to cluster list

												//scoringCharacterClusters.add(t, tt, x, y, char1, char2);
												matchType = Match.SCORING_CHARACTERS;   // ### irrelevant   // 2006-04-05
												//weight = 1.0f;   // 2006-04-05
												weight = model.getScoringCharacterMatchWeight();   // 2006-04-07
												//scoringCharacterClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, char1, char2);   // 2006-04-05
												scoringCharacterClusters.add(matchType, weight, t, tt, info1.elementNumber, info2.elementNumber, x, y, 1, 1, char1, char2);   // 2006-04-07

											}

										}
									}

								}

							}

						}
					}

					//int scoringCharacterScore = scoringCharacterClusters.getScore(model.getClusterScoreMethod());
					//float scoringCharacterScore = scoringCharacterClusters.getScore(model.getClusterScoreMethod());
					float scoringCharacterScore = scoringCharacterClusters.getScore(model.getLargeClusterScorePercentage());

					// ...

					//str.append(INDENT + "Scoring special characters score: " + scoringCharacterScore + "\n");
					//retLine = INDENT + "Scoring special characters score: " + scoringCharacterScore;
					retLine = INDENT + "Special characters score: " + myFormatter.format(scoringCharacterScore);
					ret.add(retLine);

					score += scoringCharacterScore;

					//str.append(scoringCharacterClusters.getWords());   // getWords() does its own indentation and endline. ### ikke helt bra?
					//ret.addAll(scoringCharacterClusters.getDetails());   // getDetails() does its own indentation and endline. ### ikke helt bra?
					indentLevel = 2;   // 2006-04-05
					includeMatchType = false;
					ret.addAll(scoringCharacterClusters.getDetails(indentLevel, includeMatchType));   // getDetails() does its own indentation and endline. ### ikke helt bra?   // 2006-04-05

					////////////
					// length //
					////////////

					/* 2006-09-20
					int[] length = new int[Alignment.NUM_FILES];   // length in chars of the relevant elements of each text
					int[] elementCount = new int[Alignment.NUM_FILES];   // number of relevant elements from each text

					for (t=0; t<Alignment.NUM_FILES; t++) {
						length[t] = 0;
						it = info[t].iterator();
						while (it.hasNext()) {
							ElementInfo info1 = (ElementInfo)it.next();
							length[t] += info1.length;
						}
						elementCount[t] = info[t].size();
					}
					*/

					// ...
					float scoreBefore = score;
					//score = SimilarityUtils.adjustForLengthCorrelation(score, length[0], length[1]);
					//score = SimilarityUtils.adjustForLengthCorrelation(score, length[0], length[1], model.getLengthRatio());
					score = SimilarityUtils.adjustForLengthCorrelation(score, length[0], length[1], elementCount[0], elementCount[1], model.getLengthRatio());
					//System.out.println(">>> score = " + score + "\n");

					retLine = "Lengths " + length[0] + " (" + myFormatter.format(length[0]*model.getLengthRatio()) + ") and " + length[1];
					if (score > scoreBefore) {
						//str.append("Lengths " + length[0] + " and " + length[1] + " match well,\n" + INDENT + "increasing score from " + scoreBefore + " to " + score + "\n");
						//retLine = "Lengths " + length[0] + " and " + length[1] + " match well,";
						retLine += " match well,";
						ret.add(retLine);
						retLine = INDENT + "increasing score from " + myFormatter.format(scoreBefore) + " to " + myFormatter.format(score);
						ret.add(retLine);
					} else if (score < scoreBefore) {
						//str.append("Lengths " + length[0] + " and " + length[1] + " don't match well,\n" + INDENT + "reducing score from " + scoreBefore + " to " + score + "\n");
						//retLine = "Lengths " + length[0] + " and " + length[1] + " don't match well,";
						retLine += " don't match well,";
						ret.add(retLine);
						retLine = INDENT + "reducing score from " + myFormatter.format(scoreBefore) + " to " + myFormatter.format(score);
						ret.add(retLine);
					} else {
						//str.append("Lengths " + length[0] + " and " + length[1] + " match so-so,\n" + INDENT + "making no change to the score " + score + "\n");
						//retLine = "Lengths " + length[0] + " and " + length[1] + " match so-so,";
						retLine += " match so-so,";
						ret.add(retLine);
						retLine = INDENT + "making no change to the score " + myFormatter.format(score);
						ret.add(retLine);
					}

					////////////////////////////////////
					// micro adjustment to break ties // 2005-11-03
					////////////////////////////////////

					// when otherwise scoring equal,
					// paths with 1-1's are to preferred
					// over paths with other alignments.
					// add (subtract) micro punishment if step is not 1-1
					boolean is11 = true;
					for (t=0; t<Alignment.NUM_FILES; t++) {
						if (info[t].size() != 1) {
							is11 = false;
						}
					}
					if (!is11) {
						score -= .001;
					}

					////////////////////////////////////

					//str.insert(0, "Total match score: " + score + "\n");
					retLine = "Total match score: " + myFormatter.format(score);
					// main header. insert at top
					ret.add(0, retLine);

				}   // 2006-09-20

			}
			//System.out.println("&&& soon leave ElementInfoToBeCompared.toString(). score = " + score);

		} else {
			//System.out.print("score already calculated. ");
		}

		//System.out.println("leaving toList(). score = " + score);

		//// return textual version §§§
		//return new String(str);
		// return textual version as a List
		//System.out.println("exit ElementInfoToBeCompared.toList() ============================");
		return ret;

	}

}

/**
 *
 */
class AlignmentModel {

	// when trying to align elements
	// the program will work forward in the text trying out many possible paths
	// before selecting the best one.
	// then it will suggest or select the first step from the best path.
	// the program will try paths of length maxPathLength.
	// Why the 'Max' in the variable name?
	// Well - maxPathLength is max in two senses:
	// - paths will unavoidably be shorter at the very end of the text
	// - paths are built step by step, so intermediate paths are shorter.
	// maxPathLength is settable by the user in the settings dialog.
	// it can be set as low as 1, but not higher than MAX__MAX_PATH_LENGTH.

	// 2006-09-20

	public static float ELEMENTINFO_SCORE_NOT_CALCULATED = -1.0f;
	public static float ELEMENTINFO_SCORE_HOPELESS = -99999.0f;
	// ###should these two be different values?
	public static float BEST_PATH_SCORE_NOT_CALCULATED = -1.0f;
	public static float BEST_PATH_SCORE_BAD = -1.0f;   // ###perhaps not used in real life
	// end 2006-09-20

	Document[] docs;   // package access
	// list of all relevant elements, e.g, <s> elements
	NodeList[] nodes;   // package access. 2004-11-09: flytter denne fra load...thread til hit i model. liste over alle relevante elementer
	// list of all elements, e.g, also <p> elements
	NodeList[] allNodes;   // package access. 2005-09-01. trenger denne fordi: søker etter node med bestemt id. noden kan være på høyere nivå, f.eks <p> i stedet for <s>. og får ikke til å bruke Document.getElementById()

	// ########## skulle vært Hashtable?

	// alignable elements and their ancestors###
	HashMap relevantElementNames = new HashMap();
	HashMap relevantAncestorElementNames = new HashMap();

	private DocumentBuilder builder;

	protected File currentOpenDirectory;
	protected File currentSaveDirectory;

	protected String[] inputFilepath = new String[Alignment.NUM_FILES];
	protected String[] outputFilepath = new String[Alignment.NUM_FILES];
	protected String[] inputFilename = new String[Alignment.NUM_FILES];

	protected String anchorFilename = "";

	protected String settingsFilename = "";   // 2006-09-21

	protected Charset[] charset = new Charset[Alignment.NUM_FILES];   // input files character set. output files character set = input files character set

	protected Aligned aligned;
	protected ToAlign toAlign;
	protected Unaligned unaligned;

	private String specialCharacters   = Alignment.DEFAULT__SPECIAL_CHARACTERS;
	private String scoringCharacters   = Alignment.DEFAULT__SCORING_CHARACTERS;
	private float lengthRatio          = Alignment.DEFAULT__LENGTH_RATIO;
	private int diceMinWordLength      = Alignment.DEFAULT__DICE_MIN_WORD_LENGTH;
	private float diceMinCountingScore = Alignment.DEFAULT__DICE_MIN_COUNTING_SCORE;

	//private int clusterScoreMethod     = Alignment.DEFAULT__CLUSTER_SCORE_METHOD;
	private int largeClusterScorePercentage = Alignment.DEFAULT__LARGE_CLUSTER_SCORE_PERCENTAGE;

	private int maxPathLength          = Alignment.DEFAULT__MAX_PATH_LENGTH;

	//

	private float anchorWordMatchWeight       = Alignment.DEFAULT__ANCHORWORD_MATCH_WEIGHT;
	private float anchorPhraseMatchWeight     = Alignment.DEFAULT__ANCHORPHRASE_MATCH_WEIGHT;
	private float properNameMatchWeight       = Alignment.DEFAULT__PROPERNAME_MATCH_WEIGHT;
	private float diceMatchWeight             = Alignment.DEFAULT__DICE_MATCH_WEIGHT;
	private float dicePhraseMatchWeight       = Alignment.DEFAULT__DICEPHRASE_MATCH_WEIGHT;
	private float numberMatchWeight           = Alignment.DEFAULT__NUMBER_MATCH_WEIGHT;
	private float scoringCharacterMatchWeight = Alignment.DEFAULT__SCORINGCHARACTER_MATCH_WEIGHT;

	/*
	private int outputFileNamingMethod = Alignment.DEFAULT__FILE_NAMING_METHOD;
	private String fileNamingCorrespExtension = Alignment.DEFAULT__CORRESP_EXTENSION;
	private String fileNamingNewlineExtension = Alignment.DEFAULT__NEWLINE_EXTENSION;
	private String fileNamingCorrespSuffix = Alignment.DEFAULT__CORRESP_SUFFIX;
	private String fileNamingNewlineSuffix = Alignment.DEFAULT__NEWLINE_SUFFIX;
	*/

	// filter for newline format ancestor info
	AncestorFilter ancestorFilter = new AncestorFilter(AncestorFilter.MODE_ALLOW, "", "");  // default = allow none = deny all

	// 2006-02-23 match info log file
	//protected String logFilename = Alignment.DEFAULT__LOG_FILENAME;
	protected String logFilename = "";
	protected OutputStreamWriter logFileOut;
	boolean logging = false;   // logging on/off (true/false)

	protected AnchorWordList anchorWordList;
	protected Compare compare;

	//protected AnchorWordMatches anchorWordMatches;   // ### computed at suggest(), but not at unalign()
	//protected MatchInfoDisplayable matchInfoDisplayable;   // ### computed at suggest(), but not at unalign()
	protected MatchInfo matchInfo;   // ### computed at suggest(), but not at unalign()

	public AlignmentModel() {   // package access €€€ nei dette er jo public

		////System.out.println("går i gang med å lage model");

		// ###hvorfor står disse her? skal de ikke opp blant members?
		setRelevantElementNames(Alignment.DEFAULT__RELEVANT_ELEMENT_NAMES);
		setRelevantAncestorElementNames(Alignment.DEFAULT__RELEVANT_ANCESTOR_ELEMENT_NAMES);

		////System.out.println("skal be om å få laget aligned");
		aligned = new Aligned();
		////System.out.println("skal be om å få laget toAlign");
		toAlign = new ToAlign();
		////System.out.println("skal be om å få laget unaligned");
		unaligned = new Unaligned();
		////System.out.println("har fått laget unaligned");

		docs = new Document[Alignment.NUM_FILES];
		nodes = new NodeList[Alignment.NUM_FILES];
		allNodes = new NodeList[Alignment.NUM_FILES];

		// set up the parser here.
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setValidating(false);   // #### være et brukervalg???
		//factory.setValidating(true);
		//factory.setNamespaceAware(true);

		try {
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException pce) {
			// parser with specified options can't be built
			pce.printStackTrace();
		}

		compare = new Compare();

		//anchorWordList = new AnchorWordList();
		anchorWordList = new AnchorWordList(AlignmentModel.this);

		//anchorWordMatches = new AnchorWordMatches();
		//matchInfoDisplayable = new MatchInfoDisplayable(AlignmentModel.this);
		matchInfo = new MatchInfo(AlignmentModel.this);

		/*
		//The plugin that calculates alignment
		//2004-02-19: When I have other plugins, there must be a mechanism
		// to choose different plugins.
		plugin = new ExistingCorrespPlugin();
		*/

	}

	public void purge(AlignGui gui) {

		// ###dupl kode. se konstruktor.
		// men gir det mening å skille dette ut i en metode,
		// f.eks la konstruktor bruke purge()?
		// #########ikke dupl likevel...

		aligned.purge();
		toAlign.purge();
		unaligned.purge();

		for (int t=0; t<Alignment.NUM_FILES; t++) {
			docs[t]     = null;
			nodes[t]    = null;
			allNodes[t] = null;
		}

		compare.purge();

		matchInfo.purge();

		gui.statusLine.setText("Cleared");

	}

	// input = output files character set.

	// (first and second files may have different character sets,

	// but output character set = input character set)

	public Charset getCharset(int t) {
		return charset[t];
	}

	public void setCharset(int t, Charset cs) {
		charset[t] = cs;
	}

	public void setRelevantElementNames(String string) {

		String[] array = string.split(" ");
		relevantElementNames.clear();   // 2006-02-28. denne manglet
		for (int i=0; i<array.length; i++) {
			String name = array[i];
			if (name != "") {
				relevantElementNames.put(name, true);
			}
		}

	}

	public HashMap getRelevantElementNames() {
		return relevantElementNames;
	}

	public String getRelevantElementNamesAsString() {
		String string = "";
		boolean first = true;
		Iterator it = relevantElementNames.keySet().iterator();
		while (it.hasNext()) {
			if (first) {
				first = false;
			} else {
				string += " ";
			}
			string += (String)it.next();
		}
		return string;
	}

	public void setRelevantAncestorElementNames(String string) {

		String[] array = string.split(" ");
		relevantAncestorElementNames.clear();   // 2006-02-28. denne manglet
		for (int i=0; i<array.length; i++) {
			String name = array[i];
			if (name != "") {
				relevantAncestorElementNames.put(name, true);
			}
		}

	}

	public HashMap getRelevantAncestorElementNames() {
		return relevantAncestorElementNames;
	}

	public String getRelevantAncestorElementNamesAsString() {
		String string = "";
		boolean first = true;
		Iterator it = relevantAncestorElementNames.keySet().iterator();
		while (it.hasNext()) {
			if (first) {
				first = false;
			} else {
				string += " ";
			}
			string += (String)it.next();
		}
		return string;
	}

	public boolean getLogging() {
		//System.out.println("getLogging() kalt. logging = " + logging);
		return logging;
	}

	public void setLogging(boolean logging) {
		this.logging = logging;
	}

	public String getSpecialCharacters() {
		return specialCharacters;
	}

	public void setSpecialCharacters(String specialCharacters) {
		this.specialCharacters = specialCharacters;
	}

	// ### to metoder med samme navn ###
	public String getScoringCharacters() {
		return scoringCharacters;
	}

	public void setScoringCharacters(String scoringCharacters) {
		this.scoringCharacters = scoringCharacters;
	}

	public float getLengthRatio() {
		return lengthRatio;
	}

	public void setLengthRatio(float lengthRatio) {
		this.lengthRatio = lengthRatio;
	}

	public int getDiceMinWordLength() {
		return diceMinWordLength;
	}

	public void setDiceMinWordLength(int diceMinWordLength) {
		this.diceMinWordLength = diceMinWordLength;
	}

	public float getDiceMinCountingScore() {
		return diceMinCountingScore;
	}

	public void setDiceMinCountingScore(float diceMinCountingScore) {
		this.diceMinCountingScore = diceMinCountingScore;
	}

	/*
	public int getClusterScoreMethod() {
		return clusterScoreMethod;
	}

	public void setClusterScoreMethod(int clusterScoreMethod) {
		this.clusterScoreMethod = clusterScoreMethod;
	}
	*/

	public int getLargeClusterScorePercentage() {
		return largeClusterScorePercentage;
	}

	public void setLargeClusterScorePercentage(int largeClusterScorePercentage) {
		this.largeClusterScorePercentage = largeClusterScorePercentage;
	}

	//

	public float getAnchorWordMatchWeight() {
		return anchorWordMatchWeight;
	}

	public float getAnchorPhraseMatchWeight() {
		return anchorPhraseMatchWeight;
	}

	public float getProperNameMatchWeight() {
		return properNameMatchWeight;
	}

	public float getDiceMatchWeight() {
		return diceMatchWeight;
	}

	public float getDicePhraseMatchWeight() {
		return dicePhraseMatchWeight;
	}

	public float getNumberMatchWeight() {
		return numberMatchWeight;
	}

	public float getScoringCharacterMatchWeight() {
		return scoringCharacterMatchWeight;
	}

	public void setMatchWeights(
		float anchorWordMatchWeight,
		float anchorPhraseMatchWeight,
		float properNameMatchWeight,
		float diceMatchWeight,
		float dicePhraseMatchWeight,
		float numberMatchWeight,
		float scoringCharacterMatchWeight
	) {
		this.anchorWordMatchWeight       = anchorWordMatchWeight;
		this.anchorPhraseMatchWeight     = anchorPhraseMatchWeight;
		this.properNameMatchWeight       = properNameMatchWeight;
		this.diceMatchWeight             = diceMatchWeight;
		this.dicePhraseMatchWeight       = dicePhraseMatchWeight;
		this.numberMatchWeight           = numberMatchWeight;
		this.scoringCharacterMatchWeight = scoringCharacterMatchWeight;

	}

	// 2006-09-21. ###duplisert, men lager disse for å gjøre det lett for meg selv når jeg nå skal implementere innlesing fra settingsfil ved oppstart

	public void setAnchorWordMatchWeight(float anchorWordMatchWeight) {
		this.anchorWordMatchWeight       = anchorWordMatchWeight;
	}

	public void setAnchorPhraseMatchWeight(float anchorPhraseMatchWeight) {
		this.anchorPhraseMatchWeight     = anchorPhraseMatchWeight;
	}

	public void setProperNameMatchWeight(float properNameMatchWeight) {
		this.properNameMatchWeight       = properNameMatchWeight;
	}

	public void setDiceMatchWeight(float diceMatchWeight) {
		this.diceMatchWeight             = diceMatchWeight;
	}

	public void setDicePhraseMatchWeight(float dicePhraseMatchWeight) {
		this.dicePhraseMatchWeight       = dicePhraseMatchWeight;
	}

	public void setNumberMatchWeight(float numberMatchWeight) {
		this.numberMatchWeight           = numberMatchWeight;
	}

	public void setScoringCharacterMatchWeight(float scoringCharacterMatchWeight) {
		this.scoringCharacterMatchWeight = scoringCharacterMatchWeight;
	}
	// end 2006-09-21

	//

	public int getMaxPathLength() {
		return maxPathLength;
	}

	public void setMaxPathLength(int maxPathLength) {
		this.maxPathLength = maxPathLength;
	}

	/*
	public int getOutputFileNamingMethod() {
		return outputFileNamingMethod;
	}

	public void setOutputFileNamingMethod(int outputFileNamingMethod) {
		this.outputFileNamingMethod = outputFileNamingMethod;
	}

	public String getFileNamingCorrespExtension() {
		return fileNamingCorrespExtension;
	}

	public void setFileNamingCorrespExtension(String fileNamingCorrespExtension) {
		this.fileNamingCorrespExtension = fileNamingCorrespExtension;
	}

	public String getFileNamingNewlineExtension() {
		return fileNamingNewlineExtension;
	}

	public void setFileNamingNewlineExtension(String fileNamingNewlineExtension) {
		this.fileNamingNewlineExtension = fileNamingNewlineExtension;
	}

	public String getFileNamingCorrespSuffix() {
		return fileNamingCorrespSuffix;
	}

	public void setFileNamingCorrespSuffix(String fileNamingCorrespSuffix) {
		this.fileNamingCorrespSuffix = fileNamingCorrespSuffix;
	}

	public String getFileNamingNewlineSuffix() {
		return fileNamingNewlineSuffix;
	}

	public void setFileNamingNewlineSuffix(String fileNamingNewlineSuffix) {
		this.fileNamingNewlineSuffix = fileNamingNewlineSuffix;
	}
	*/

	public AncestorFilter getAncestorFilter() {
		//System.out.println("getAncestorFilter()");
		return ancestorFilter;
	}

	//public void setAncestorInfoMode(int mode) {
	//	ancestorInfoMode = mode;
	//}

	public void setAncestorInfo(int mode, String elementNames, String attributeNames) {
		//System.out.println("setAncestorInfo(). mode = " + mode);
		setAncestorInfoElementNames(elementNames);
		setAncestorInfoAttributeNames(attributeNames);
		// mode parameter is radio button choice 0-3
		if (mode == AncestorInfoRadioButtonPanel.NONE) {
			ancestorFilter.setMode(AncestorFilter.MODE_ALLOW);
			clearAncestorInfoElementNames();
			clearAncestorInfoAttributeNames();
		} else if (mode == AncestorInfoRadioButtonPanel.ALL) {
			ancestorFilter.setMode(AncestorFilter.MODE_DENY);
			clearAncestorInfoElementNames();
			clearAncestorInfoAttributeNames();
		} else if (mode == AncestorInfoRadioButtonPanel.ALLOW) {
			ancestorFilter.setMode(AncestorFilter.MODE_ALLOW);
		} else if (mode == AncestorInfoRadioButtonPanel.DENY) {
			ancestorFilter.setMode(AncestorFilter.MODE_DENY);
		} else {
			// ### program error ###
			ancestorFilter.setMode(AncestorFilter.MODE_ALLOW);   // ###dodgy??
		}
	}

	// 2006-09-21
	// ###for enkelhets skyld
	public void setAncestorInfoChoice(int mode) {
		// mode parameter is radio button choice 0-3
		if (mode == AncestorInfoRadioButtonPanel.NONE) {
			ancestorFilter.setMode(AncestorFilter.MODE_ALLOW);
			////clearAncestorInfoElementNames();
			////clearAncestorInfoAttributeNames();
		} else if (mode == AncestorInfoRadioButtonPanel.ALL) {
			ancestorFilter.setMode(AncestorFilter.MODE_DENY);
			////clearAncestorInfoElementNames();
			////clearAncestorInfoAttributeNames();
		} else if (mode == AncestorInfoRadioButtonPanel.ALLOW) {
			ancestorFilter.setMode(AncestorFilter.MODE_ALLOW);
		} else if (mode == AncestorInfoRadioButtonPanel.DENY) {
			ancestorFilter.setMode(AncestorFilter.MODE_DENY);
		} else {
			// ### program error ###
			ancestorFilter.setMode(AncestorFilter.MODE_ALLOW);   // ###dodgy??
		}
	}
	// end 2006-09-21

	public int getAncestorInfoChoice() {   // 0 =
		//System.out.println("getAncestorInfoChoice(). ancestorFilter.mode = " + ancestorFilter.mode + ", ancestorFilter.noElements() = " + ancestorFilter.noElements());
		if (ancestorFilter.mode == AncestorFilter.MODE_ALLOW) {
			if (ancestorFilter.noElements()) {
				return AncestorInfoRadioButtonPanel.NONE;   // ###ugly?
			} else {
				return AncestorInfoRadioButtonPanel.ALLOW;   // ###ugly?
			}
		} else {
			if (ancestorFilter.noElements()) {
				return AncestorInfoRadioButtonPanel.ALL;   // ###ugly?
			} else {
				return AncestorInfoRadioButtonPanel.DENY;   // ###ugly?
			}
		}
	}

	public String getAncestorInfoElementNamesAsString() {
		//System.out.println("getAncestorInfoElementNamesAsString()");
		return ancestorFilter.getElementNamesAsString();
	}

	public String getAncestorInfoAttributeNamesAsString() {
		//System.out.println("getAncestorInfoAttributeNamesAsString()");
		return ancestorFilter.getAttributeNamesAsString();
	}

	//private void setAncestorInfoElementNames(String names) {
	public void setAncestorInfoElementNames(String names) {
		//System.out.println("setAncestorInfoElementNames(String names). names = " + names);
		ancestorFilter.setElementNames(names);
	}

	private void clearAncestorInfoElementNames() {

		ancestorFilter.setElementNames("");
	}

	//private void setAncestorInfoAttributeNames(String names) {
	public void setAncestorInfoAttributeNames(String names) {
		//System.out.println("setAncestorInfoAttributeNames(String names). names = " + names);
		ancestorFilter.setAttributeNames(names);
	}

	private void clearAncestorInfoAttributeNames() {
		ancestorFilter.setAttributeNames("");
	}

	//public void setAncestorInfoElementNames(String names) {
 	//	String[] array = names.split(" ");
	//	ancestorInfoElementNames.clear();
	//	for (int i=0; i<array.length; i++) {
	//		String name = array[i];
	//		if (name != "") {
	//			ancestorInfoElementNames.put(name, true);
	//		}
	//	}
	//}

	//public HashMap getAncestorInfoElementNames() {
	//	return ancestorInfoElementNames;
	//}

	//public void setAncestorInfoAttributeNames(String names) {
 	//	String[] array = names.split(" ");
	//	ancestorInfoAttributeNames.clear();
	//	for (int i=0; i<array.length; i++) {
	//		String name = array[i];
	//		if (name != "") {
	//			ancestorInfoAttributeNames.put(name, true);
	//		}
	//	}
	//}

	//public HashMap getAncestorInfoAttributeNames() {
	//	return ancestorInfoAttributeNames;
	//}

	// 2006-02-23 match info log file
	public String getLogFilename() {
		return logFilename;
	}

	public void setLogFilename(String logFilename) {
		this.logFilename = logFilename;
	}

	public void setAnchorFilename(String anchorFilename) {
		this.anchorFilename = anchorFilename;
	}

	// 2006-09-21
	public void setSettingsFilename(String settingsFilename) {
		this.settingsFilename = settingsFilename;
	}
	// end 2006-09-21

	/*
	// 2006-09-22
	public void loadSettingsFile() {
		// try to load the settings file with the name in variable settingsFilename.
		// the file might not exist
		Toolkit.getDefaultToolkit().beep();
		System.out.println("loadSettingsFile() ikke implementert. settingsFilename=" + settingsFilename);
	}
	// end 2006-09-22
	*/

	//

	// 2006-10-03
	void setMemoryUsage(AlignGui gui) {
		//System.out.println("model sin setMemoryUsage()");
		gui.statusLine.setMemoryUsage();
	}
	// end 2006-10-03

	void updateAlignedTotalRatio(AlignGui gui) {

		//System.out.println("updateAlignedTotalRatio()");
		String text = "Aligned: ";
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			if (t > 0) { text += " - "; }
			text += Integer.toString(getLowestUnalignedElementNumber(t) + 1) + "/" + nodes[t].getLength();
		}
		gui.statusLine.setText(text);
		// 2006-10-03. ###disse funker ikke. må jeg yielde på en eller annen måte????
		//gui.statusLine.invalidate();   // 2006-08-14
		//gui.statusLine.validate();   // 2006-08-14
		System.out.println(text);   // 2006-10-03

	}

	int getLowestUnalignedElementNumber(int t) {

		// lowest unaligned or under consideration

		if (toAlign.elements[t].size() > 0) {
			return ((AElement)(toAlign.elements[t].get(0))).elementNumber;
		} else if (unaligned.elements[t].size() > 0) {
			return ((AElement)(unaligned.elements[t].get(0))).elementNumber;
		} else {
			return nodes[t].getLength() - 1;
		}
		// ### AlignGui gui,
		// ### gui.model.

	}

	//

    /**
     * €€€€€€€€€€€€€€€€€€€€€Loads an xml file.
     * @return true if loading was successful, false if there was an error.
     *         (most likely a parsing error)
     */
	//void loadFile(AlignGui gui, File f, int t) {   // package access
	//void loadFile(AlignGui gui, File f, int t) throws EmptyElementException {   // package access // 2006-09-19
	void loadFile(AlignGui gui, File f, int t) throws Exception {   // package access   // 2006-09-22

	//void loadFile(File f, int t) {   // package access

		////System.out.println("f = " + f);
		// ...
        Document result = null;

        try {

			// make DOM tree from xml file
			////gui.counterDoc.insertString(0, "file -> DOM", null);
			//gui.counter.setText("file -> DOM");
			gui.statusLine.setText("File -> DOM");
            result = builder.parse(f);
            //System.out.println("File " + f.getName() + " loaded as text " + t+1);
            //System.out.println("File " + f.getName() + " loaded as text " + (t+1));

			//€€€2006-02-28. for å kunne lagre utfil med samme encoding som innfil
			Charset cs = Charset.forName(result.getXmlEncoding());
            setCharset(t, cs);

            //// ### gjør dette kun for å fortelle hvor mange elementer det er?
            //// ### men det er jo ikke direkte child nodes vi er interessert i. disse kan jo være <p> f.eks
            //NodeList childNodes = result.getChildNodes();
            //System.out.println("Child node count: " + childNodes.getLength());
            //childNodes = null;

            docs[t] = result;

        // 2006-09-19
        } catch (Exception e) {   // €€€€€€€€€€€€€€€

            ErrorMessage.error("Exception (1) when loading text " + (t+1) + " " + f.getName() + ":\n" + e.toString());   // 2006-08-10

        }
        // end 2006-09-19

		// get a list of alignable elements from the DOM tree
        try {   // 2006-09-19
            nodes[t] = getElements(t);
		//} catch (EmptyElementException e) {   // 2006-09-19
		} catch (Exception e) {   // 2006-09-22
			throw e;   // ###   // 2006-09-19
		}   // 2006-09-19

        try {   // 2006-09-19

			// get a list of all elements
			allNodes[t] = docs[t].getElementsByTagName("*");

			// €€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€dette er ikke skikkelig

			// clear ...
			//System.out.println("*** do clear stuff here? ***");

			// fill the unaligned list boxes with a suitable version of the elements
			//System.out.println("Element count: " + nodes[t].getLength());

			// process element list.
			// update GUI while processing element list.
			// do processing in separate thread so GUI elements can be updated.
			Thread load = new LoadFileThread(gui, nodes, t);
			load.start();

			//// init aligned/total ratio in status line
			// ### funker visst ikke, men skitt i det
			//updateAlignedTotalRatio(gui);
			// ### aha. metoden vil jo ikke funke når bare én fil er lest inn.
			// ### og når fil nr to er lest inn, vil vi vel at det skal stå "Parsed",
			// og ikke overskrive dette med "0/9999 - 0/9999" - ?

			// remember name and full pathname of input file
			gui.model.inputFilepath[t] = f.getCanonicalPath();
			gui.model.inputFilename[t] = f.getName();

		} catch (Exception e) {   // €€€€€€€€€€€€€€€

			//System.err.println("Exception when loading " + t + " " + f.getName() + ": ");
			//System.err.println(e.toString());
			//ErrorMessage.error("Exception when loading " + t + " " + f.getName() + ":\n" + e.toString());   // 2006-08-10
			ErrorMessage.error("Exception (2) when loading text " + (t+1) + " " + f.getName() + ":\n" + e.toString());   // 2006-08-10
			//e.printStackTrace();

			//return false;

		}

	}

    //private NodeList getElements(int t) {
    //private NodeList getElements(int t) throws EmptyElementException {   // 2006-09-19
    private NodeList getElements(int t) throws Exception {   // 2006-09-22

        //return docs[t].getElementsByTagName("s");
        //return docs[t].getElementsByTagName("p"); funker
        // ### klønete
        String[] relevantElementNamesArray = new String[relevantElementNames.size()];
        Iterator it = relevantElementNames.keySet().iterator();
        int count = 0;
        while (it.hasNext()) {
			String name = (String)it.next();
			relevantElementNamesArray[count] = name;
			count++;
		}
        try {   // 2006-09-19
        	//return XmlTools.getElementsByTagNames(docs[t], relevantElementNamesArray);
        	return XmlTools.getElementsByTagNames(docs[t], relevantElementNamesArray, getSpecialCharacters());   // 2006-10-03
		//} catch (EmptyElementException e) {   // 2006-09-19
		} catch (Exception e) {   // 2006-09-22
			throw e;   // ###   // 2006-09-19
		}   // 2006-09-19

	}

    /**
     * establishes corresp attributes in dom for text t
     */
	void setCorrespAttributes(int t) {

    	Iterator it;
    	Iterator eIt;

    	// clean dom of corresp attributes that may have been in the input file

    	for (int i=0; i<((NodeList)(nodes[t])).getLength(); i++) {
			Element el = (Element)(((NodeList)(nodes[t])).item(i));
			el.removeAttribute("corresp");
		}

    	// set new corresp attributes in dom.
    	// loop through all finished alignments

    	String newAttribute;
    	it = aligned.alignments.iterator();
    	while (it.hasNext()) {

			// next alignment
			Link link = (Link)(it.next());
			// get the corresp attribute values from all the other texts.
			// loop through all the other texts
	    	newAttribute = "";
			for (int tt=0; tt<Alignment.NUM_FILES; tt++) {
				if (tt != t) {

					// other text.

					// inspect the elements the alignment has got in this other text
					// and find id's for the corresp attribute to refer to

					if (link.elementNumbers[tt].size() == 0) {

						//System.out.println("t = " + t + ". tt = " + tt + ". the alignment has no elements in this other text");
						// the alignment has no elements in this other text.
						// try to find something else for the corresp attribute to refer to -
						// a node one level up.
						// e.g, let an <s> refer to a <p>.
						// challenge: find the correct <p> (or similar)

						// check with previous siblings -
						// e.g, previous <s>'s in the same <p> -
						// what they refer to in the other text

						// which element to start with?
						//System.out.println("which element to start with?");
						// there might be more than one element from this text in the alignment.
						// find the first one

						int smallestElementNumber = Integer.MAX_VALUE;
						eIt = link.elementNumbers[t].iterator();
						while (eIt.hasNext()) {
							int elementNumber = ((Integer)(eIt.next())).intValue();
							if (elementNumber < smallestElementNumber) {
								smallestElementNumber = elementNumber;
							}
						}

						Node el = nodes[t].item(smallestElementNumber);
						//System.out.println("el. name = " + el.getNodeName() + ". type = " + el.getNodeType() + ". id = " + ((Element)el).getAttribute("id"));

						//System.out.println("look for previous");
						Node prevEl = XmlTools.getPreviousRelevantSiblingElement(el, relevantElementNames);
						String otherId = "";
						while (prevEl != null) {
							//System.out.println("prevEl. name = " + prevEl.getNodeName() + ". type = " + prevEl.getNodeType());
							if (((Element)prevEl).getAttribute("corresp") != "") {

								// found a sibling which refers to #####this other text.
								// get its last corresp attribute value (if more than one)

								String[] values = ((Element)prevEl).getAttribute("corresp").split(" ");
								otherId = values[values.length-1];
								//System.out.println("t = " + t + ". tt = " + tt + ". otherId = " + otherId);
								break;

							} else {

								prevEl = XmlTools.getPreviousRelevantSiblingElement(prevEl, relevantElementNames);

							}
						}
						//System.out.println("otherId = " + otherId);

						if (otherId == "") {

							// no previous sibling refers to the other text.
							//System.out.println("no previous sibling refers to the other text");
							// must try to consult elements in the previous parent element
							//System.out.println("must try to consult elements in the previous parent element");

							// first up one level
							//System.out.println("first up one level");

							Node parent = XmlTools.getRelevantAncestorElement(el, relevantAncestorElementNames);
							if (parent == null) {

								// no higher level.
								//System.out.println("no higher level");
								// no reason to believe there's a higher level in the other text either.
								// refer to nothing

								newAttribute = "";

							} else {

								// then to previous sibling (previous parent)
								//System.out.println("then to previous sibling (previous parent)");

								Node prevParent = XmlTools.getPreviousRelevantSiblingElement(parent, relevantAncestorElementNames);
								if (prevParent == null) {

									// no sibling. first parent.
									//System.out.println("no sibling. first parent");
									// then it's the first parent in the other text we want.
									// first get the first element in the other text

									Node otherElement = nodes[tt].item(0);

									// then get its parent

									Node otherParent = XmlTools.getRelevantAncestorElement(otherElement, relevantAncestorElementNames);
									if (otherParent == null) {

										// no parent.
										// refer to nothing

										newAttribute = "";

									} else {

										// refer to that parent

										newAttribute = ((Element)otherParent).getAttribute("id");
										//System.out.println("refer to that otherParent. t = " + t + ". tt = " + tt + ". newAttribute = " + newAttribute);

									}

								} else {

									// found previous parent.
									//System.out.println("found previous parent");
									// try its children (###which hopefully are on the right level,
									// and not e.g on a level between <p> and <s>).
									// work backwards from last child
									//System.out.println("try its children. work backwards from last child");

									prevEl = XmlTools.getRelevantLastDescendantElement(prevParent, relevantElementNames);
									if (prevEl == null) {

										// no children.
										//System.out.println("no children");
										// could be e.g empty <p>,
										// or e.g some irrelevant element between <p>'s.
										// give up.
										// refer to nothing

										newAttribute = "";

									} else {

										// ...
										//System.out.println("there are children");

										otherId = "";
										while (prevEl != null) {
											//System.out.println("look for child with corresp");
											if (((Element)prevEl).getAttribute("corresp") != "") {

												// found a sibling which refers to #####this other text.
												//System.out.println("found a sibling which refers to #####this other text");
												// get its last corresp attribute value (if more than one)
												//System.out.println("get its last corresp attribute value (if more than one)");

												String[] values = ((Element)prevEl).getAttribute("corresp").split(" ");
												//System.out.println("values.length() = " + values.length() + ", values[0] = " + values[0]);   // ###
												otherId = values[values.length-1];
												//System.out.println("t = " + t + ". tt = " + tt + ". otherId = " + otherId);
												break;

											} else {

												prevEl = XmlTools.getPreviousRelevantSiblingElement(prevEl, relevantElementNames);

											}
										}

										if (otherId == "") {

											// no children of previous parent refer to the other text.
											// ...
											// give up.
											// refer to nothing

											newAttribute = "";

										} else {

											// found reference to the other text
											//System.out.println("found reference to the other text");
											// get element in the other text
											// reference to which level?
											//System.out.println("which level?");

											//Node otherEl = docs[tt].getElementById(otherId);   // ### funker ikke????!!!!
											Node otherEl = XmlTools.getElementByIdInNodeList(allNodes[tt], otherId);  // ### gjør dette isteden

											if (relevantElementNames.containsKey(otherEl.getNodeName())) {

												// the "relevant" level
												//System.out.println("the 'relevant' level");

												// get its parent.
												//System.out.println("get its parent");
												// up one level in the other text

												Node otherParent = XmlTools.getRelevantAncestorElement(otherEl, relevantAncestorElementNames);
												//System.out.println("parent has id = " + ((Element)otherParent).getAttribute("id"));
												if (otherParent == null) {

													// no higher level.
													//System.out.println("no higher level");
													// give up.
													// refer to nothing

													newAttribute = "";

												} else {

													// then to next sibling (next parent)
													//System.out.println("then to next sibling (next parent)");

													Node nextOtherParent = XmlTools.getNextRelevantSiblingElement(otherParent, relevantAncestorElementNames);
													if (nextOtherParent == null) {

														// no next sibling (next parent).
														//System.out.println("no next sibling (next parent)");
														// give up
														// refer to nothing

														newAttribute = "";

													} else {

														// refer to that next sibling (next parent),
														// which hopefully is "in synch"
														// with the current element and its parent

														newAttribute = ((Element)nextOtherParent).getAttribute("id");
														//System.out.println("refer to that next sibling (next parent). t = " + t + ". tt = " + tt + ". newAttribute = " + newAttribute);

													}

												}

											} else {

												// something else, i.e, parent level.
												//System.out.println("something else, i.e, parent level");
												// refer to that parent

												newAttribute = otherId;

											}

										}

									}

								}

							}


						} else {

							// found a previous sibling with a reference to the other text.
							// reference to which level?

							//System.out.println("found a previous sibling with a reference to the other text. otherId = " + otherId);
							//System.out.println("t = " + t);
							//System.out.println("tt = " + tt);
							//System.out.println("docs[tt].getXmlVersion() = " + docs[tt].getXmlVersion());
							//Node otherEl = docs[tt].getElementById(otherId);   // ### funker ikke????!!!!
							Node otherEl = XmlTools.getElementByIdInNodeList(allNodes[tt], otherId);  // ### gjør dette isteden
							//System.out.println("otherEl = " + otherEl);
							//System.out.println("otherEl.getNodeName() = " + otherEl.getNodeName());

							if (relevantElementNames.containsKey(otherEl.getNodeName())) {

								// the "relevant"level

								// get its parent

								Node otherParent = XmlTools.getRelevantAncestorElement(otherEl, relevantAncestorElementNames);

								if (otherParent == null) {

									// has no parent.
									// refer to nothing

									newAttribute = "";

								} else {

									// refer to that parent

									newAttribute = ((Element)otherParent).getAttribute("id");

								}

							} else {

								// something else, i.e, parent level.
								// refer to that parent

								newAttribute = otherId;

							}

						}


					} else {

						// loop through the elements the alignment has got in this other text

						eIt = link.elementNumbers[tt].iterator();
						while (eIt.hasNext()) {
							int elementNumber = ((Integer)(eIt.next())).intValue();
							String id = ((Element)(((AElement)(aligned.elements[tt].get(elementNumber))).element)).getAttribute("id");
							if (newAttribute != "") {
								newAttribute += " ";
							}
							newAttribute += id;
						}

					}

				}
			}

			// set the corresp attribute values in all the elements
			// the alignment has got in the current text
			eIt = link.elementNumbers[t].iterator();
			while (eIt.hasNext()) {
				int elementNumber = ((Integer)(eIt.next())).intValue();
				((Element)(((AElement)(aligned.elements[t].get(elementNumber))).element)).setAttribute("corresp", newAttribute);
			}

		}

	}

    /**
     * Saves an xml file with corresp attributes
     */
    //void saveFile(AlignGui gui, File f, int t) {   // package access
    //void saveFile(File f, int t) {   // package access
    //void saveCorrespFormatFile(File f, int t) {   // package access
    void saveCorrespFormatFile(File f, int t, Charset cs) {   // package access

    	// €€€ burde vært advarsel hvis pending alignments?
    	// hvis unaligned?
    	// komme spørsmål om prog skal sette et merke?

    	//// establish corresp attributes in dom for text t
    	// ### nei, gjør det på forhånd
    	//setCorrespAttributes(t);

    	// write dom to file

        //XmlOutput.writeXml(docs[t], f);
        XmlOutput.writeXml(docs[t], f, cs);

    }

    /**
     * Saves file in newline format
     */
    //void saveNewlineFormatFile(File f, int t) {   // package access
    //void saveNewlineFormatFile(File f, int t, Charset cs) {   // package access
    void saveNewlineFormatFile(File f, int t, Charset cs, AncestorFilter filter) {   // package access

    	//System.out.println("filter = " + filter);

    	// €€€ burde vært advarsel hvis pending alignments?
    	// hvis unaligned?
    	// komme spørsmål om prog skal sette et merke?

    	Iterator it;
    	Iterator eIt;

    	// clean dom of corresp attributes

    	for (int i=0; i<((NodeList)(nodes[t])).getLength(); i++) {
			Element el = (Element)(((NodeList)(nodes[t])).item(i));
			el.removeAttribute("corresp");
		}

    	// ...

    	//FileWriter out;
    	OutputStreamWriter out;
    	try {

			//out = new FileWriter(f);
			//€€€endringer 2006-02-20 for å kunne skrive utf-8, o.a
			OutputStream fOut= new FileOutputStream(f);
			OutputStream bOut= new BufferedOutputStream(fOut);
			out = new OutputStreamWriter(bOut, cs);

		} catch (IOException e1) {

			// ### ### ### ### ### ### ### ### ### ### ### ### ###
			Toolkit.getDefaultToolkit().beep();
			System.out.println("Program error? Can't create new FileWriter");
			return;

		}

    	// loop through all finished alignments and write to file

    	it = aligned.alignments.iterator();
    	while (it.hasNext()) {
			// next alignment
			Link link = (Link)(it.next());
			// loop through the alignment's elements
			String line = "";
			boolean first = true;
			eIt = link.elementNumbers[t].iterator();
			while (eIt.hasNext()) {
				int elementNumber = ((Integer)(eIt.next())).intValue();
				//Element element = (Element)(((AElement)(aligned.elements[t].get(elementNumber))).element);
				//String elementText = XmlTools.getText(element);   ### heller bruke .getTextContent()
				AElement aElement = (AElement)(aligned.elements[t].get(elementNumber));
				//String elementText = aElement.toString();
				// ###toNewString(): some users might like parent info prepended to the elements
				// in their newline format output files
				String elementText = aElement.toNewString(filter);
				if (first) {
					first = false;
				} else {
					line += " ";
				}
				line += elementText;
			}
			try {
				out.write(line + "\n");
			} catch (IOException e2) {
				// ### ### ### ### ### ### ### ### ### ### ### ### ###
				Toolkit.getDefaultToolkit().beep();
				System.out.println("Program error? Can't do out.write");
				try {
					out.close();
				} catch (IOException e3) {
					// ### ### ### ### ### ### ### ### ### ### ### ### ###
					Toolkit.getDefaultToolkit().beep();
					System.out.println("Program error? Can't do out.close");
					return;
				}
				return;
			}

		}

		try {
			out.close();
		} catch (IOException e4) {
			// ### ### ### ### ### ### ### ### ### ### ### ### ###
			Toolkit.getDefaultToolkit().beep();
			System.out.println("Program error? Can't do out.close");
			return;
		}

    	// what if there are unfinished ones?

    	//...

    }

	/**
	 * Saves file in "external" format
     */
    void saveExternalFormatFile(File f) {

		// €€€ samme spm som for de andre formatene

		// establish corresp attributes in dom for all texts.
		// ### no - not necessary if already saved in "corresp" format

		//£££££££££££££££
		//for (int t=0; t<Alignment.NUM_FILES; t++) {
		//	setCorrespAttributes(t);
		//}

    	// ...

        OutputStreamWriter out;

        try {

			// output is always utf-8
			OutputStream fOut = new FileOutputStream(f);
			OutputStream bOut = new BufferedOutputStream(fOut);
			Charset cs = Charset.forName("UTF-8");
			out = new OutputStreamWriter(bOut, cs);

		} catch (IOException e) {

			// €€€ PLAIN_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE?
			JOptionPane.showMessageDialog(
				null,
				"Can't save file " + f.getName(),
				//"€€€Title",
				"Error",   // 2006-09-21
				JOptionPane.ERROR_MESSAGE
			);
            //System.err.println("Exception when saving " + f.getName() + ": ");
            //System.err.println(e.toString());
            ErrorMessage.error("Exception when saving " + f.getName() + ":\n" + e.toString());   // 2006-08-10
            //e.printStackTrace();

            return;

        }

    	//////////bollocks int p = 0;   // current position in output file

    	// create and output header

    	String data = "<?xml version='1.0' encoding='utf-8'?>\n";
    	// #######mye dupl kode try/except/feilhåndtering
    	try {
			out.write(data, 0, data.length());
		} catch (IOException e) {
			JOptionPane.showMessageDialog(
				null,
				"Can't write to file " + f.getName(),
				//"€€€Title",
				"Error",   // 2006-09-21
				JOptionPane.ERROR_MESSAGE
			);
            //System.err.println("Exception when writing to " + f.getName() + ": ");
            //System.err.println(e.toString());
            ErrorMessage.error("Exception when writing to " + f.getName() + ":\n" + e.toString());   // 2006-08-10
            return;
        }

		// create and output root start element
		data = "<linkGrp targType='" + "..." + "' toDoc='" + inputFilename[0] + "' fromDoc='" + inputFilename[1] + "'>\n";
    	try {
			out.write(data, 0, data.length());
		} catch (IOException e) {
			JOptionPane.showMessageDialog(
				null,
				"Can't write to file " + f.getName(),
				//"€€€Title",
				"Error",   // 2006-09-21
				JOptionPane.ERROR_MESSAGE
			);
            //System.err.println("Exception when writing to " + f.getName() + ": ");
            //System.err.println(e.toString());
            ErrorMessage.error("Exception when writing to " + f.getName() + ":\n" + e.toString());   // 2006-08-10
            return;
        }

    	// loop through all finished alignments and write to file

    	Iterator it = aligned.alignments.iterator();
    	while (it.hasNext()) {

			// next alignment.
			// get all the id's to link
			Link link = (Link)(it.next());
			String xtargetsValue = "";
			// loop through the texts
			for (int t=0; t<Alignment.NUM_FILES; t++) {
				// to get the relevant id's from text t
				// look at the corresp attribute in the alignment's first element in the _other_ text.
				// why do it via the _other_ text?
				// because we may pick up links to elements on a higher level
				int tt = 1 - t;   // ##############
				String ids = "";
				if (link.elementNumbers[tt].size() > 0) {
					int firstElementNumber = ((Integer)(((TreeSet)(link.elementNumbers[tt])).first())).intValue();
					// get the corresp attribute
					//System.out.println((AElement)(aligned.elements[tt].get(firstElementNumber)));
					ids = ((Element)(((AElement)(aligned.elements[tt].get(firstElementNumber))).element)).getAttribute("corresp");
				} else {
					// the alignment has no element in the other text.
					// get the id's from the alignment's elements in _this_ text
					Iterator eIt = link.elementNumbers[t].iterator();
					while (eIt.hasNext()) {
						int elementNumber = ((Integer)(eIt.next())).intValue();
						String id = ((Element)(((AElement)(aligned.elements[t].get(elementNumber))).element)).getAttribute("id");
						if (ids != "") {
							ids += " ";
						}
						ids += id;
					}
				}
				// ...
				if (t > 0) {
					xtargetsValue += ";";
				}
				xtargetsValue += ids;
			}
			// create link (alignment) info
			data = "<link id='...' xtargets='" + xtargetsValue + "'>\n";
			// output info
	    	try {
				out.write(data, 0, data.length());
			} catch (IOException e) {
				JOptionPane.showMessageDialog(
					null,
					"Can't write to file " + f.getName(),
					//"€€€Title",
					"Error",   // 2006-09-21
					JOptionPane.ERROR_MESSAGE
				);
				//System.err.println("Exception when writing to " + f.getName() + ": ");
				//System.err.println(e.toString());
				ErrorMessage.error("Exception when writing to " + f.getName() + ":\n" + e.toString());   // 2006-08-10
				return;
			}

		}

		// create and output root end element

		data = "</linkGrp>\n";
    	try {
			out.write(data, 0, data.length());
		} catch (IOException e) {
			JOptionPane.showMessageDialog(
				null,
				"Can't write to file " + f.getName(),
				//"€€€Title",
				"Error",   // 2006-09-21
				JOptionPane.ERROR_MESSAGE
			);
            //System.err.println("Exception when writing to " + f.getName() + ": ");
            //System.err.println(e.toString());
            ErrorMessage.error("Exception when writing to " + f.getName() + ":\n" + e.toString());   // 2006-08-10
            return;
        }

		// close output file

    	try {
			out.close();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(
				null,
				"Can't close file " + f.getName(),
				//"€€€Title",
				"Error",   // 2006-09-21
				JOptionPane.ERROR_MESSAGE
			);
            //System.err.println("Exception when closing " + f.getName() + ": ");
            //System.err.println(e.toString());
            ErrorMessage.error("Exception when closing " + f.getName() + ":\n" + e.toString());   // 2006-08-10
            return;
        }

	}

    // compute and display info about the current anchor word matches
    // and other matches §§§
    void computeMatches(AlignGui gui) {   // ### compute and display
    //void computeMatches() {
    	//System.out.println("model sin computeMatches(). gui = " + gui);
		//gui.setMatchInfoTextArea(matchInfoDisplayable.toString());
		matchInfo.computeDisplayableList();
		gui.matchInfoList.setVisible(true);
	}

    // clear info about the current anchor word matches
    // and other matches §§§
    void clearMatches(AlignGui gui) {
		//matchInfoDisplayable.clear();
		//gui.setMatchInfoTextArea("");
		// ### earlier the info box was a JTextArea.
		// now it is a JList referring to a List.
		// it feels wrong to null the List.
		// instead we hide the box
		gui.matchInfoList.setVisible(false);
	}

    // 2006-02-23. log displayed info about the current anchor word matches and other matches §§§
    void logMatches(AlignGui gui) {
		//
		//System.out.println("Skal jeg skrive alignete elementer og match-info til loggfil?");

        if (gui.model.getLogging()) {   // 2006-04-18

			//System.out.println("Ja, jeg skal det.");

			try {

				// ###logMatches() er misvisende navn hvis også skal logge selve elementene

				String text = "";
				for (int t=0; t<Alignment.NUM_FILES; t++) {
					text += "Text " + t + "\n";
					for (Enumeration en = gui.model.toAlign.elements[t].elements(); en.hasMoreElements();) {
						AElement ae = (AElement)en.nextElement();
						int n = ae.elementNumber;
						text += "Element " + n + "\n";
						text += ae.toString() + "\n";
					}
				}
				text += "\n";

				//String text = "log - info:\n" + gui.model.matchInfo.displayableList.toString() + "\n";
				//String text = "";
				for (Enumeration en = gui.model.matchInfo.displayableList.elements(); en.hasMoreElements();) {
					text += (String)en.nextElement() + "\n";
				}
				text += "\n";
				//System.out.println(text);
				logFileOut.write(text, 0, text.length());

			} catch (Exception e) {

				//System.err.println("Exception when writing info to log file");   // ###(vet ikke om jeg har navnet på loggfilen)
				//System.err.println(e.toString());
				ErrorMessage.error("Exception when writing info to log file:\n" + e.toString());   // ###(vet ikke om jeg har navnet på loggfilen)   // 2006-08-10

			}

		}

	}

    // 2006-02-23. put warning in match info log
    //void logMatchesHeader(String header) {   // header or warning
    void logHeader(AlignGui gui, String header) {   // header or warning   // 2006-04-18
		//
		//System.out.println("Skal jeg skrive header eller advarsel til loggfil?");

		if (gui.model.getLogging()) {   // 2006-04-18

			//
			System.out.println("Ja, jeg skal det.");

			try {

				//String text = "log - warning: " + warning + "\n";
				String text = header + "\n\n";
				//System.out.println(text);
				logFileOut.write(text, 0, text.length());

			} catch (Exception e) {

				//System.err.println("Exception when writing header or warning to log file");   // ###(vet ikke om jeg har navnet på loggfilen)
				//System.err.println(e.toString());
				ErrorMessage.error("Exception when writing header or warning to log file:\n" + e.toString());   // 2006-08-10

			}

		}

	}

	/*
	void unalign(AlignGui gui) {   // package access
		toAlign.catch_(gui, aligned.drop(gui));
	}

	void align(AlignGui gui) {   // package access
		aligned.pickUp(gui, toAlign.flush(gui));
	}

	void less(AlignGui gui, int t) {   // package access
		unaligned.catch_(gui, t, toAlign.drop(gui, t));
	}

	void more(AlignGui gui, int t) {   // package access
		toAlign.pickUp(gui, t, unaligned.pop(gui, t));
	}
	*/

	void unalign(AlignGui gui) {   // package access

		// 2006-02-23. put warning in match info log
		//System.out.println("unalign skal kalle logHeader() for å skrive advarsel til loggfil");
		//logMatchesHeader("*** Unalign operation - info above should have been erased ***");
		logHeader(gui, "*** Unalign operation - info above should have been erased ***");   // 2006-04-18

		//toAlign.catch_(aligned.drop());
		toAlign.catch_(aligned.drop(gui));

		computeMatches(gui);   // ### compute and display
		//ShowCompare.clear(gui);
		gui.compareInfoPanel.off();
		gui.compareInfoPanel.repaint();
	}

	void align(AlignGui gui, boolean scroll) {   // package access
		// needs to know gui to be able to scroll list boxes for finished alignments

		// 2006-02-23. log displayed info about the current anchor word matches and other matches §§§
		// 2006-02-23. put warning in match info log if more than one alignment
		if (gui.model.toAlign.pendingCount() > 1) {
			//logMatchesHeader("*** More than one alignment - info below is misleading ***");
			logHeader(gui, "*** More than one alignment - info below is misleading ***");   // 2006-04-18
		} else {
			//logMatchesHeader("*** Next alignment ***");
			logHeader(gui, "*** Next alignment ***");   // 2006-04-18
		}

		logMatches(gui);

		aligned.pickUp(gui, toAlign.flush(), scroll);

		computeMatches(gui);   // ### compute and display
		//ShowCompare.clear(gui);
		gui.compareInfoPanel.off();
		gui.compareInfoPanel.repaint();
		// garbage collect.
		// for each text find number of first element not yet aligned.
		// (if all are aligned the number will be one larger than the highest element number.)
		// (€€€perhaps the code here should check the element numbers themselves and not just rely on size())
		int[] ix = new int[Alignment.NUM_FILES];
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			ix[t] = gui.model.aligned.elements[t].size();
		}
		//System.out.println("align() - before garbage collect");
		gui.model.compare.garbageCollect(gui.model, ix);
	}

	// €€€ 2004-10-31. brukes ikke - i hvert fall ikke for øyeblikket
	void link(AlignGui gui, int t, int index, int elementNumber) {   // package access
		// €€€foreløpig
		//System.out.println("AlignmentModel sin link() med 4 param");
		toAlign.link(gui, t, index, elementNumber);
	}

	// €€€ 2004-10-31. bare denne brukes.
	// model sin link() kjøres kun når bruker klikker på et element i to-align
	void link(AlignGui gui, int t, int index, int elementNumber, int alignmentNumberToLinkTo) {   // package access
		// €€€foreløpig
		//System.out.println("AlignmentModel sin link() med 5 param");
		toAlign.link(gui, t, index, elementNumber, alignmentNumberToLinkTo);
	}

	// ### ikke godt navn? i skip-1-1-modus og automatisk gjør den mer enn bare å foreslå
	void suggest(AlignGui gui) {   // package access
	//void suggest() {   // package access

		// debug
		//int debugCount = 0;

		boolean outOfMemory = false;   // 2006-10-03

		//System.out.println("model sin suggest(). gui = " + gui);
		//System.out.println("model sin suggest()");
		if (!toAlign.empty()) {
			// automatically align before suggesting
			//System.out.println("automatically align before suggesting");
			gui.model.align(gui, false);   // false = don't scroll aligned yet (because of a memory leak - ?)
		}
		if (!toAlign.empty()) {
			Toolkit.getDefaultToolkit().beep();
			//System.out.println("BEEEEEEEEEEEEEEEEP AlignmentModel suggest");
			//System.out.println("Program error? Alignment of pending elements failed?");
		} else {

			//System.out.println("else");
			//boolean skip11 = gui.skip11CheckBox.isSelected();
			int mode = gui.getMode();   // ######### heller hete runMode? for ikke å få sammenblanding med newline ancestor-mode?
			//##############må forbedres
			//int skip11Limit;
			int runLimit;
			try {
				//skip11Limit = Integer.parseInt(gui.skip11LimitTextField.getText());
				runLimit = Integer.parseInt(gui.runLimitTextField.getText());
			} catch (NumberFormatException e) {
				//skip11Limit = 999999;   // ###
				runLimit = 999999;   // ###
			}
			//int skip11count = 0;
			int runCount = 0;
			boolean doneAligning = false;
			// loop. do one alignment, or perhaps several alignments (if skip 1-1)
			//System.out.println("before while (!doneAligning)");
			while (!doneAligning) {

				//System.out.println("in while (!doneAligning)");
				//AlignmentModel.this.compare.testPrint(AlignmentModel.this);

				AlignmentModel.this.compare.resetBestPathScores();   // €€€€€€€€€€€ nå eller etterpå?

				/*
				// €€€ foreløpig.
				// foreslår ett element fra hver tekst
				for (int t=0; t<Alignment.NUM_FILES; t++) {
					toAlign.pickUp(t, unaligned.pop(t));
				}
				*/

				/*
				// €€€ foreløpig.
				int opt_n = -1;   // €€€€€€€€€€€€€€€€€€€€
				OperationNode test = GaleChurch.testGaleChurch(0, 1, unaligned.elements[0], unaligned.elements[1], opt_n);
				//System.out.println("G&C har bestemt operation " + test.operation);
				*/

				//System.out.println("!!! suggest");
				// start 1 before first unaligned elements €€€
				int[] position = new int[Alignment.NUM_FILES];
				for (int t=0; t<Alignment.NUM_FILES; t++) {
					//System.out.println("t = " + t);
					if (((DefaultListModel)(unaligned.elements[t])).size() > 0) {
						//System.out.println("DefaultListModel)(unaligned.elements[t])).size() > 0");
						position[t] = ((AElement)(((DefaultListModel)(unaligned.elements[t])).get(0))).elementNumber - 1;   // #############
					} else {
						// no more unaligned elements in text t.
						// ### er dette suspekt? kan det hende at ikke alle elementene fra DOM er med???
						//System.out.println("no more unaligned elements in text t");
						position[t] = AlignmentModel.this.nodes[t].getLength() - 1;
					}
				}
				//System.out.println("!!! skal lage ny QueueList. position=" + position[0] + "," + position[1] + ". altså - dette er den siste cellen som vi har alignet, ikke den første vi skal aligne");
				// will investigate "all" possible paths with a certain number of steps.
				// will loop once per step, each time building "all" paths
				// that are one step longer than in the previous loop.
				// collect the paths in the queue list.
				// init queue list (queueList)
				QueueList queueList = new QueueList(AlignmentModel.this, position);
				// the paths that are one step longer will, while they are being created,
				// reside in nextQueueList
				QueueList nextQueueList;
				// variable for each of all the possible steps to try when lengthening a path: 0-0, 0-1, etc
				PathStep step;
				// init counter for the lengthening loop
				int stepCount = 0;
				// the lengthening loop
				boolean doneLengthening = false;
				//System.out.println("before do ... while (!doneLengthening)");
				do {
					/*
					debugCount++;
					if (debugCount <= 3) {
						System.out.println("\n>>>>>>>>>>>>>> queueList =\n" + queueList + "\n");
					}
					*/
					//System.out.println("\nstepCount=" + stepCount + "\n");
					//System.out.println("\nqueueList before lengthening = " + queueList + "\n");
					Iterator qIt = queueList.entry.iterator();
					nextQueueList = new QueueList();
					// loop over each entry in the queue list. each entry is a path
					//System.out.println("before while (qIt.hasNext())");
					while (qIt.hasNext()) {
						//System.out.println("in while (qIt.hasNext())");
						//System.out.println("inner while");
						Object temp = qIt.next();
						////System.out.println("crocodile");
						QueueEntry queueEntry = (QueueEntry)(temp);
						//System.out.println("before if (!queueEntry.removed)");
						if (!queueEntry.removed) {   // ### 2005-11-02. hmmm. denne var det ikke så mye vits så lenge jeg ikke merket for fjerning i queueList, bare i nextQueueList
							if (queueEntry.end) {
								// path goes to the end of all texts.
								// use as it is
								QueueEntry newQueueEntry = (QueueEntry)queueEntry.clone();   // denne har allerede newQueueEntry.end = true;
							} else {
								//System.out.println("in if (!queueEntry.removed)");
								//System.out.println("queueEntry = " + queueEntry);
								////System.out.println("AlignmentModel.this.compare.incrementsList.size() = " + AlignmentModel.this.compare.incrementsList.size());
								// loop through all the possible steps to lengthen the current path with.
								// note. some or all of these steps will not be possible after all
								// at the end of the texts
								Iterator iIt = AlignmentModel.this.compare.stepList.iterator();
								//System.out.println("before while (iIt.hasNext())");
								while (iIt.hasNext()) {
									//System.out.println("in while (iIt.hasNext())");
									step = (PathStep)iIt.next();
									//System.out.println("*** neste steg å prøve å forlenge med er " + step);
									//nextQueueList.entry.add(new QueueEntry(AlignmentModel.this, queueEntry, step));
									//QueueEntry newQueueEntry = new QueueEntry(AlignmentModel.this, queueEntry, step);
									try {
										//System.out.println("before makeLongerPath(...");
										QueueEntry newQueueEntry = queueEntry.makeLongerPath(AlignmentModel.this, step);
										//System.out.println("after makeLongerPath(...");
										//System.out.println("after makeLongerPath(... newQueueEntry = " + newQueueEntry);
										if (newQueueEntry.path != null) {   // €€€ .path = null er min krøkkete måte å fortelle at det nye forslaget til path ikke er bedre enn andre paths til samme position, og at forslaget skal kastes
											//System.out.println("forlenget path beste hittil");
											// this new path might be a better (better-scoring) path than
											// some other paths in the new list. remove those other paths, if any
											int[] pos = newQueueEntry.path.position;
											//if ((pos[0] == 2) && (pos[1] == 3)) {
											//	debugCount++;
											//	if (debugCount == 2) {
											//		System.out.println("\n>> >> >> 1 queueList = " + queueList + "\n");
											//	}
											//}
											nextQueueList.remove(pos);   // doesn't remove them for real. just marks them for removal later
											//if ((pos[0] == 2) && (pos[1] == 3)) {
											//	if (debugCount == 2) {
											//		System.out.println("\n>> >> >> 2 queueList = " + queueList + "\n");
											//	}
											//}
											queueList.remove(pos);   // must do the same thing in the source. (###dodgy?) see comments for the QueueList remove() method
											//if ((pos[0] == 2) && (pos[1] == 3)) {
											//	if (debugCount == 2) {
											//		System.out.println("\n>> >> >> 3 queueList = " + queueList + "\n");
											//	}
											//}
											// insert new path in the new list
											nextQueueList.add(newQueueEntry);
										} else {
											//System.out.println("forlenget path skåret ikke høyt nok");
										}
									} catch (EndOfAllTextsException e) {
										//System.out.println("suggest() catches EndOfAllTextsException");
										// end of all texts.
										// use path as it is, but mark it properly
										QueueEntry newQueueEntry = (QueueEntry)queueEntry.clone();
										newQueueEntry.end = true;
										//System.out.println("suggest() made newQueueEntry = " + newQueueEntry);
										// insert new path in the new list unless already there.
										if (!nextQueueList.contains(newQueueEntry)) {
											nextQueueList.add(newQueueEntry);
										}
									} catch (EndOfTextException e) {
										//System.out.println("suggest() catches EndOfTextException");
										// end of at least one text but not all of them.
										// forget
										//System.out.println("EndOfTextException");
									} catch (BlockedException e) {
										//...
									}
								}
							}
						}
					}
					//System.out.println("\n>>>>>>>>>>>nextQueueList før removeForReal = " + nextQueueList);
					nextQueueList.removeForReal();   // remove for real. see above
					//System.out.println("\n>>>>>>>>>>>nextQueueList etter removeForReal = " + nextQueueList);
					if (nextQueueList.empty()) {
						// not possible to lengthen path. must have reached the end of all the texts
						doneLengthening = true;
					} else {
						queueList = nextQueueList;
						//System.out.println("queueList after lengthening = " + queueList);
						stepCount++;
						doneLengthening = (stepCount >= AlignmentModel.this.getMaxPathLength());
					}
				} while (!doneLengthening);
				//System.out.println("!!! har laget ny QueueList med alle stier som har <= " + stepCount + " steg. queueList = " + queueList + "\n");
				//System.out.println("!!! Skal finne den beste stien av disse");

				// ...

				if (   (queueList.entry.size() == 0)   // ### will not happen?
				    || (   (queueList.entry.size() == 1)
				        && (((QueueEntry)(queueList.entry.get(0))).path.steps.size() == 0)
				       )
				   )
				{

					// must be end of all texts

					doneAligning = true;

				} else {

					Iterator qIt2 = queueList.entry.iterator();
					//float bestScore = -1.f;   // ###
					// normalized = diveded by number of sentences.
					// done because: the paths compared may well have the same number of steps,
					// but they often have a different number of sentences.
					// if not normalized a path with e.g 2-1 + 1-2 can win over a correct 1-1 + 1-1 + 1-1
					// because it gains extra points from the extra sentences the former path has at its end
					//float normalizedBestScore = -1.f;   // ###
					float normalizedBestScore = AlignmentModel.BEST_PATH_SCORE_NOT_CALCULATED;   // 2006-09-20
					Path bestPath = null;
					//String report = ""; //%%%
					while (qIt2.hasNext()) {
						QueueEntry candidate = ((QueueEntry)qIt2.next());
						//System.out.println("!!! candidate.score = " + candidate.score);
						//report += "---------------------" + "\n"; //%%%
						//report += "path: " + candidate.path + "\n"; //%%%
						//report += "score: " + candidate.score + "\n"; //%%%
						//report += "length in sentences: " + candidate.path.getLengthInSentences() + "\n"; //%%%
						float normalizedCandidateScore = candidate.score / candidate.path.getLengthInSentences();
						//report += "normalized score: " + normalizedCandidateScore + "\n"; //%%%
						//if (candidate.score > bestScore) {
						if (normalizedCandidateScore > normalizedBestScore) {
							//System.out.println("!!! bedre enn bestScore = " + bestScore);
							//bestScore = candidate.score;
							normalizedBestScore = normalizedCandidateScore;
							bestPath = candidate.path;
						}
					}
					//System.out.println(">>>=================>>> bestScore = " + bestScore);
					//System.out.println(">>>=================>>> best path = " + bestPath);

					// ...
					if (bestPath.steps.size() > 0) {

						//System.out.print("A ");
						//MemTest.print("Tenured Gen", "");

						PathStep stepSuggestion = (PathStep)bestPath.steps.get(0);
						//System.out.println(">>>=================>>> suggested step = " + stepSuggestion);

						//System.out.print("B ");
						//MemTest.print("Tenured Gen", "");

						// ...
						for (int t=0; t<Alignment.NUM_FILES; t++) {
							for (int i=0; i<stepSuggestion.increment[t]; i++) {
								more(gui, t);
							}
						}

						//System.out.print("C ");
						//MemTest.print("Tenured Gen", "");

						// ... present anchor match info in gui ??????????????????????????

						// ...
						//skip11count++;
						runCount++;
						//doneAligning = !(skip11 && (skip11count < skip11Limit) && stepSuggestion.is11());
						if (mode == Alignment.MODE_ONE) {
							doneAligning = true;
						} else if (mode == Alignment.MODE_AUTO) {
							if (runCount < runLimit) {
								doneAligning = false;
							} else {
								doneAligning = true;
							}
						} else if (mode == Alignment.MODE_SKIP11) {
							if ((runCount < runLimit) && stepSuggestion.is11()) {
								doneAligning = false;
							} else {
								doneAligning = true;
							}
						}
						if (!doneAligning) {
							// skip mode. automatically align what was suggested
							// ### er det mulig å kjøre en action uten å gå via en komponent?
							////gui.alignButton.processMouseMotionEvent((MouseEvent)(java.awt.event.MouseEvent.MOUSE_CLICKED));   // ?????????????????????
							/////gui.AlignAction.actionPerformed(new ActionEvent(...));
							//### gui.model kunstig? heller AlignmentModel.this??
							gui.model.align(gui, false);   // false = don't scroll aligned yet (because of a memory leak - ?)
						}

						/*
						//debug
						if (doneAligning) {
							System.out.println("we are doneAligning. compare er nå\n" + gui.model.compare);
							System.out.println("rapport fra utvelgelsesprosessen:\n" + report);
						}
						*/

						//System.out.print("D ");
						//MemTest.print("Tenured Gen", "");

					} else {

						// no best path. reason: no more unaligned text

						Toolkit.getDefaultToolkit().beep();
						System.out.println("No more unaligned text");
						doneAligning = true;

					}

				}

				if (!doneAligning) {
					// force painting if doing more than one alignment
					// (in skip 1-1 or automatic mode)

					//System.out.print("E ");
					//MemTest.print("Tenured Gen", "");

					for (int t=0; t<Alignment.NUM_FILES; t++) {

						// paintImmediately
						// public void paintImmediately(int x, int y, int w, int h)
						// Paints the specified region in this component
						// and all of its descendants that overlap the region, immediately.
						// It's rarely necessary to call this method.
						// In most cases it's more efficient to call repaint,
						// which defers the actual painting
						// and can collapse redundant requests into a single paint call.
						// This method is useful if one needs to update the display
						// while the current event is being dispatched

						// #### dette funker, med forbehold:
						// aligned ruller ikke
						// og toAlign står tom.
						// og så tar det tid mellom tekst 1 og 2
						//gui.alignedListBox[t].paintImmediately(gui.alignedListBox[t].getBounds());
						//gui.toAlignListBox[t].paintImmediately(gui.toAlignListBox[t].getBounds());
						//gui.unalignedListBox[t].paintImmediately(gui.unalignedListBox[t].getBounds());

						// ### med dette så friskes bare en av rutene opp - første align.
						// og oppfrisk starter litt langt nede. må være noe med koord.
						//gui.alignedScrollPane[t].paintImmediately(gui.alignedScrollPane[t].getBounds());
						//gui.toAlignScrollPane[t].paintImmediately(gui.toAlignScrollPane[t].getBounds());
						//gui.unalignedScrollPane[t].paintImmediately(gui.unalignedScrollPane[t].getBounds());

						// ### aha. getBounds er feil. det skal være getSize el. likn

						//System.out.print("F. t=" + t + ". ");
						//MemTest.print("Tenured Gen", "");

						// ### aha. mangler scroll. har utsatt scroll pga mulig memory leak.
						// se andre steder i koden. aktiverer scroll igjen, og ser hvordan det går
						// paint
						// ###vis først at element forsvant fra unaligned
						// 2006-10-03. PRØVER UTEN DENNE
						//gui.unalignedScrollPane[t].paintImmediately(0, 0, gui.unalignedScrollPane[t].getWidth(), gui.unalignedScrollPane[t].getHeight());
						// ###ikke vits i å vise toAlign? kanskje for å sikre at den blir blanket ut i det tilfellet at det står noe der fra før.
						// dersom forslaget skal vise, må det skje et annet sted i programmet

						//System.out.print("G ");
						//MemTest.print("Tenured Gen", "");

						// 2006-10-03. PRØVER UTEN DENNE
						//gui.alignedScrollPane[t].paintImmediately(0, 0, gui.alignedScrollPane[t].getWidth(), gui.alignedScrollPane[t].getHeight());
						// ###så at det dukker opp i aligned

						//System.out.print("H ");
						//MemTest.print("Tenured Gen", "");

						// 2006-10-03. PRØVER UTEN DENNE
						//gui.toAlignScrollPane[t].paintImmediately(0, 0, gui.toAlignScrollPane[t].getWidth(), gui.toAlignScrollPane[t].getHeight());

						//System.out.print("I ");
						//MemTest.print("Tenured Gen", "");

						// hjelpe på memoryproblem? ????????????????????? næh
						//Thread.yield();

					}
				}

				// 2006-10-03
				//System.out.println("MemTest.getRemainingHeap()=" + MemTest.getRemainingHeap));
				if (MemTest.getRemainingHeap() < 10000000) {
					// less than 10MB heap space remains.
					// not safe to continue?
					doneAligning = true;
					outOfMemory = true;
				}
				// end 2006-10-03

			}



			// 2006-10-03. painter BARE NÅ TIL SLUTT. dette hjalp mot vanvittig minnebruk når man kjørte automatisk

			for (int t=0; t<Alignment.NUM_FILES; t++) {
				gui.unalignedScrollPane[t].paintImmediately(0, 0, gui.unalignedScrollPane[t].getWidth(), gui.unalignedScrollPane[t].getHeight());
				gui.alignedScrollPane[t].paintImmediately(0, 0, gui.alignedScrollPane[t].getWidth(), gui.alignedScrollPane[t].getHeight());
				gui.toAlignScrollPane[t].paintImmediately(0, 0, gui.toAlignScrollPane[t].getWidth(), gui.toAlignScrollPane[t].getHeight());
			}








			// ... present anchor match info in gui
			//clearAnchorWordMatches();   // ############################# andre steder
			computeMatches(gui);   // ### compute and display

			//System.out.println("J");
			//MemTest.print("Tenured Gen", "");

			//ShowCompare.show(gui);
			gui.compareInfoPanel.on();

			//System.out.println("K");
			//MemTest.print("Tenured Gen", "");

			gui.compareInfoPanel.repaint();

			//System.out.println("L");
			//MemTest.print("Tenured Gen", "");

		}

		// scroll aligned. (waited until now because of a memory leak - ?)
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			gui.alignedListBox[t].ensureIndexIsVisible(aligned.elements[t].getSize()-1);
		}

		// 2006-10-05
		// try to get java to do garbage collection.
		// ###System.runFinalization() eller System.gc() hjalp kanskje litt.
		// minnebruk gikk slik:
		//           /
		//        /\/
		//     /\/
		//  /\/
		// /
		// i stedet for slik:
		//     /
		//    /
		//   /
		//  /
		// /
		// man bare inntil programmet slapp opp for minne første gang!
		//System.runFinalization();
		System.gc();
		// end 2006-10-05

		if (outOfMemory) {
			JOptionPane.showMessageDialog(
				null,
				"Running out of memory.\n",
				"Running out of memory",
				JOptionPane.ERROR_MESSAGE
			);
		}

	}

	void skipCorresp(AlignGui gui) {

		// the texts may contain some alignments already - in the form of corresp attributes.
		// if so, link them together, according to the corresp values,
		// and get them up into aligned area,
		// until we run out of matching corresp values.
		// ### to-align must be empty and the starts of unaligned must be in synch

		// ##### hadde vært tøft om de viste med farger allerede i unaligned????????????

		System.out.println("enter skipCorresp()");

		// is to-align empty?
		if (!toAlign.empty()) {
			// no. ######### trenger bedre advarsel/forklaring for brukeren enn kun et pip
			Toolkit.getDefaultToolkit().beep();
			System.out.println("Can't do this while there are pending alignments");
		} else {

			System.out.println("to-align is empty");
			AElement aEl;
			Element element;
			AlignmentsEtc someAligned;
			int alignmentNumber = 0;
			int t, tt, t2;
			Link link;

			int holeyCount = 0;

			// for each text continue as long as there are elements,
			// and the elements have ''corresp'' attributes.
			// initialize the thing that keeps track of which texts
			// have run out of relevant elements.
			boolean[] stop = new boolean[Alignment.NUM_FILES];
			for (t=0; t<Alignment.NUM_FILES; t++) {
				stop[t] = false;
			}

			// for each

			// make a new empty someAligned.
			// will be filled and emptied and reused. ###thrown away
			someAligned = new AlignmentsEtc();
			//System.out.println("make a new empty someAligned");

			// ...

			boolean done = false;
			do {

				System.out.println("find the elements belonging to the next alignment, i.e, find the corresponding elements in each text, ...");
				// find the elements belonging to the next alignment,
				// i.e, find the corresponding elements in each text,
				// from the unaligned areas.
				// what about loners?
				// all right. they might need special treatment.
				// what about crossing relations?
				// oh yes. if relations cross we have to continue and find more alignments,
				// until they make a continuous glob without any holes

				System.out.println("first skim off all loners lying topmost in the unaligned.");
				// first skim off all loners lying topmost in the unaligned.
				// take them from the left

				for (t=0; t<Alignment.NUM_FILES; t++) {

					if (!stop[t]) {

						tt = 1 - t;   // ###
						System.out.println("text " + t + ". other text is " + tt);

						System.out.println("loop just in case there are several loners in a row in the same text");
						// loop just in case there are several loners in a row in the same text
						boolean finishedLonersInThisText = false;
						do {

							System.out.println("try to get loner from text " + t);
							// try to get loner from text t.
							// stop text t if no element or no corresp

							if (unaligned.elements[t].size() == 0) {

								System.out.println("no more unaligned elements in text " + t + ". stop text " + t);
								stop[t] = true;
								finishedLonersInThisText = true;

							} else {

								System.out.println("1 get next available element in text " + t);
								// get next available element in text t #########
								//element = (Element)((AElement)(unaligned.elements[t].get(0))).element;
								aEl = Skip.getNextAvailableUnalignedElement(unaligned, someAligned, t);
								System.out.println("1");
								element = (Element)aEl.element;
								System.out.println("2");
								System.out.println(element.getAttributes());
								System.out.println(element.getAttributes().getNamedItem("corresp"));
								// 2006-09-15 ###dupl. kode
								String correspValue;   // 2006-09-15
								if (element.getAttributes().getNamedItem("corresp") == null) {   // 2006-09-15
									correspValue = "";   // 2006-09-15
								} else {   // 2006-09-15
									//String correspValue = element.getAttributes().getNamedItem("corresp").getNodeValue();
									correspValue = element.getAttributes().getNamedItem("corresp").getNodeValue();   // 2006-09-15
								}   // 2006-09-15
								System.out.println("topmost unaligned element 1. correspValue = " + correspValue);
								if (correspValue == "") {

									System.out.println("(I) element with no corresp attribute. stop text " + t);
									// element with no corresp attribute
									stop[t] = true;
									finishedLonersInThisText = true;

								} else {

									System.out.println("there are id(s) in the corresp attribute of the element");
									// there are id(s) in the corresp attribute of the element
									String[] correspIds = correspValue.split(" ");
									if (correspIds.length > 1) {
										System.out.println("more than one id. this element can't be a loner.");
										// more than one id. this element can't be a loner.
										// loners refer to one element only, on a "parent" level
										finishedLonersInThisText = true;
									} else {
										System.out.println("one id. check alignable elements in the other text to see if the id belongs to one of them");
										// one id. check alignable elements in the other text
										// to see if the id belongs to one of them
										if (XmlTools.getElementByIdInNodeList(nodes[tt], correspValue) != null) {
											System.out.println("belongs to alignable element in other text. => not loner");
											// belongs to alignable element in other text.
											// => not loner
											// check further
											if (XmlTools.getElementByIdInDefaultListModel(unaligned.elements[tt], correspValue) != null) {
												// the element in the other text is an unaligned element
												// ok
												finishedLonersInThisText = true;
											} else {
												System.out.println("error in corresp. treat as loner");
												// the element in the other text is not an unaligned element
												// error in corresp
												//###############
												// treat as loner
											}
										} else if(XmlTools.getElementByIdInNodeList(allNodes[tt], correspValue) != null) {
											System.out.println("belongs to other element in other text, presumably one on a 'parent' level. => loner");
											// belongs to other element in other text,
											// presumably one on a "parent" level.
											// (€€€but we don't check that element further,.
											// neither its "level" nor its location)
											// => loner
										} else {
											System.out.println("error in file. treat as loner");
											// error in file.
											//###############
											// treat as loner
										}
									}

									// ...
									System.out.println("xxx");

									if (!finishedLonersInThisText) {
										System.out.println("found loner. pop it from unaligned and make an alignment out of it");
										// found loner.
										//// pop it from unaligned and make an alignment out of it
										// make an alignment out of it
										//AElement aEl = (AElement)(AlignmentModel.this.unaligned.pop(t));
										System.out.println("1.5 get next available element in text " + t);
										// get next available element in text t #########
										//AElement aEl = (AElement)(unaligned.elements[t].get(0));
										aEl = Skip.getNextAvailableUnalignedElement(unaligned, someAligned, t);
										link = new Link();
										link.alignmentNumber = alignmentNumber;
										aEl.alignmentNumber = link.alignmentNumber;
										alignmentNumber++;
										link.elementNumbers[t] = new TreeSet();
										link.elementNumbers[t].add(aEl.elementNumber);
										link.elementNumbers[tt] = new TreeSet();
										// add it to our collection of ... alignments (the someAligned thing)
										someAligned.add(link);
										//someAligned.print();
										//if (someAligned.alignments.size() > 3) {
										//	System.out.println("kill this process"); stop[0] = true; stop[1] = true; finishedLonersInThisText = true;
										//}
										// ###also the element. kunne ikke Link også holdt rede på disse?
										//someAligned.add(t, element);
										someAligned.add(t, aEl);
										//someAligned.print();
										if (!someAligned.hasHoles()) {
											// got one or more alignments, with no holes.
											// pop the relevant elements out of unaligned.
											// we don't need their content.
											// we got all the data we need already.
											// just throw them away
											System.out.println("pop and throw them away 1");
											for (t2=0; t2<Alignment.NUM_FILES; t2++) {
												for (int i=0; i<((DefaultListModel)someAligned.elements[t2]).size(); i++) {
													System.out.println("pops from text " + t2);
													AElement aDum = (AElement)(AlignmentModel.this.unaligned.pop(t2));
													String id = aDum.element.getAttributes().getNamedItem("id").getNodeValue();
													System.out.println("popped element with id " + id + " from text " + t2);
													///////////MemTest.print("Heap memory");
													//System.out.println("A");
													//MemTest.print("Tenured Gen", "");
													//System.out.println("unaligned.elements[" + t2+ "].size() = " + unaligned.elements[t2].size());
												}
											}
											// process the someAligned and empty it
											System.out.println("process the someAligned and empty it 1");
											toAlign.catch_(someAligned);
											someAligned = new AlignmentsEtc();
											aligned.pickUp(gui, toAlign.flush(), false);   // false = don't scroll aligned yet (because of a memory leak - ?)
											holeyCount = 0;
										} else {
											holeyCount++;
											System.out.println("holes 1");
										}
									}

								}
							}
						} while (!finishedLonersInThisText);

					}

				} // for

				/// then ...
				System.out.println("finished skimming loners");

				if (!(stop[0] && stop[1])) {   // ###

					// not run out of elements with corresp

					System.out.println("grab the leftmost topmost element");
					// grab the leftmost topmost element
					// of a more substantial alignment (i.e, not loner)

					link = new Link();
					link.alignmentNumber = alignmentNumber;
					alignmentNumber++;
					t = 0;
					tt = 1;
					System.out.println("get top element in text " + t);
					// get top element in text t.
					// stop text t if no element or no corresp
					if (unaligned.elements[t].size() == 0) {

						System.out.println("no more unaligned elements in text " + t + ". stop text " + t);

						stop[t] = true;

					} else {

						System.out.println("2 get next available element in text " + t);
						// get next available element in text t #########
						//AElement aEl = (AElement)(unaligned.elements[t].get(0));
						aEl = Skip.getNextAvailableUnalignedElement(unaligned, someAligned, t);
						element = (Element)aEl.element;
						// 2006-09-15 ###dupl. kode
						String correspValue;   // 2006-09-15
						if (element.getAttributes().getNamedItem("corresp") == null) {   // 2006-09-15
							correspValue = "";   // 2006-09-15
						} else {   // 2006-09-15
							//String correspValue = element.getAttributes().getNamedItem("corresp").getNodeValue();
							correspValue = element.getAttributes().getNamedItem("corresp").getNodeValue();   // 2006-09-15
						}   // 2006-09-15
						System.out.println("topmost unaligned element 2. correspValue = " + correspValue);
						if (correspValue == "") {

							System.out.println("(II) element with no corresp attribute. stop text " + t);
							// element with no corresp attribute
							stop[t] = true;

						} else {

							System.out.println("there are id(s) in the corresp attribute of the element");
							// there are id(s) in the corresp attribute of the element
							//link.alignmentNumber = alignmentNumber;
							link.elementNumbers[t] = new TreeSet();
							link.elementNumbers[t].add(aEl.elementNumber);
							link.elementNumbers[tt] = new TreeSet();
							// ###also ...
							//someAligned.add(t, element);
							aEl.alignmentNumber = link.alignmentNumber;
							someAligned.add(t, aEl);
							//someAligned.print();
							System.out.println("get all the corresponding elements in the other text.");
							// get all the corresponding elements in the other text.
							String[] correspIds = correspValue.split(" ");
							AElement otherAEl = null;
							for (int i = 0; i < correspIds.length; i++) {
								System.out.println("get Node otherEl");
								Node otherEl = XmlTools.getElementByIdInNodeList(nodes[tt], correspIds[i]);
								if (otherEl != null) {
									System.out.println("the corresp id makes sense insofar as it is an id for an alignable element in the other text.");
									// the corresp id makes sense insofar as
									// it is an id for an alignable element in the other text.
									// check further
									otherAEl = AlignmentModel.this.unaligned.get(tt, otherEl);
									if (otherAEl != null) {
										System.out.println("it's an unaligned element all right");
										// it's an unaligned element all right
										link.elementNumbers[tt].add(otherAEl.elementNumber);
										// ###also ...
										//someAligned.add(tt, (Element)otherAEl.element);
										otherAEl.alignmentNumber = link.alignmentNumber;
										someAligned.add(tt, otherAEl);
										//someAligned.print();
									} else {
										System.out.println("error");
										// error
										// ######### trenger feilmelding - ikke kun et pip?
										Toolkit.getDefaultToolkit().beep();
										System.out.println("Hit a case the program can't handle");
										// ####grisete
										stop[0] = true;
										stop[1] = true;
										break;
									}
								}
							}
							if (!(stop[0] && stop[1])) {   // ###
								System.out.println("take one of these elements in the other text and get all the corresponding elements in the first text");
								System.out.println("otherAEl = " + otherAEl);
								// take one of these elements in the other text and get
								// all the corresponding elements in the first text
								String backCorrespValue = otherAEl.element.getAttributes().getNamedItem("corresp").getNodeValue();
								System.out.println("backCorrespValue = " + backCorrespValue);
								String[] backCorrespIds = backCorrespValue.split(" ");
								for (int i = 0; i < backCorrespIds.length; i++) {
									System.out.println("get Node backEl");
									Node backEl = XmlTools.getElementByIdInNodeList(nodes[t], backCorrespIds[i]);
									if ( backEl != null) {
										System.out.println("the ... id makes sense insofar as it is an id for an alignable element in the first text.");
										// the .. id makes sense insofar as
										// it is an id for an alignable element in this text.
										// check further
										AElement backAEl = AlignmentModel.this.unaligned.get(t, backEl);
										if (backAEl != null) {
											System.out.println("it's an unaligned element all right");
											// it's an unaligned element all right
											link.elementNumbers[t].add(backAEl.elementNumber);
											// ###also ...
											//someAligned.add(t, (Element)backAEl.element);
											backAEl.alignmentNumber = link.alignmentNumber;
											someAligned.add(t, backAEl);
											//someAligned.print();
										} else {
											System.out.println("error");
											// error
											// ######### trenger feilmelding - ikke kun et pip?
											Toolkit.getDefaultToolkit().beep();
											System.out.println("Hit a case the program can't handle");
											// ####grisete
											stop[0] = true;
											stop[1] = true;
											break;
										}
									}
								}
							}
							if (!(stop[0] && stop[1])) {   // ###
								// ??? stop text t if no element or no corresp
								// check both sides to see if all the corresp''s agree
								//if link.consistentCorresp() { ##################################i.g.n.m. I.G.N.M.
									// we have made Link out of them.
									// put it in a new someAligned
									someAligned.add(link);
									//someAligned.print();
									//if (someAligned.alignments.size() > 3) {
									//	System.out.println("kill this process"); stop[0] = true; stop[1] = true;
									//}
									if (!someAligned.hasHoles()) {
										// got one or more alignments, with no holes.
										// pop the relevant elements out of unaligned.
										// we don't need their content.
										// we got all the data we need already.
										// just throw them away
										System.out.println("pop and throw them away 2");
										for (t2=0; t2<Alignment.NUM_FILES; t2++) {
											for (int i=0; i<((DefaultListModel)someAligned.elements[t2]).size(); i++) {
												System.out.println("pops from text " + t2);
												AElement aDum = (AElement)(AlignmentModel.this.unaligned.pop(t2));
												String id = aDum.element.getAttributes().getNamedItem("id").getNodeValue();
												System.out.println("popped element with id " + id + " from text " + t2);
												///////////MemTest.print("Heap memory");
												//System.out.println("B");
												//MemTest.print("Tenured Gen", "");
												//System.out.println("unaligned.elements[" + t2+ "].size() = " + unaligned.elements[t2].size());
											}
										}
										//System.out.println("B2");
										//MemTest.print("Tenured Gen", "");
										// process the someAligned and empty it
										System.out.println("process the someAligned and empty it 2");
										toAlign.catch_(someAligned);
										//System.out.println("B3");
										//MemTest.print("Tenured Gen", "");
										someAligned = new AlignmentsEtc();
										//System.out.println("B5");
										//MemTest.print("Tenured Gen", "");
										aligned.pickUp(gui, toAlign.flush(), false);   // false = don't scroll aligned yet (because of a memory leak - ?)
										//System.out.println("B6");
										//MemTest.print("Tenured Gen", "");
										holeyCount = 0;
										//System.out.println("B7");
										//MemTest.print("Tenured Gen", "");
									} else {
										holeyCount++;
										System.out.println("holes 2");
									}
									System.out.println("B8");
									//MemTest.print("Tenured Gen", "");
								//} else {
								//	error
								//}
							}
							System.out.println("C");
							//MemTest.print("Tenured Gen", "");
						}
						System.out.println("D");
						//MemTest.print("Tenured Gen", "");
					}
					System.out.println("E");
					//MemTest.print("Tenured Gen", "");

				}
				System.out.println("F");
				//MemTest.print("Tenured Gen", "");

				if (holeyCount > 10) {
					// we have done ... alignments, and someAligned is still holey.
					// we suspect something is wrong
					System.out.println("kill this process"); stop[0] = true; stop[1] = true;
				}

				System.out.println("før while. stop[0]=" + stop[0] +", stop[1]=" + stop[1]);

			} while(!(stop[0] && stop[1]));
			System.out.println("G");
			//MemTest.print("Tenured Gen", "");

			if (!someAligned.empty()) {
				// error
				// ######### trenger feilmelding - ikke kun et pip?
				//dette blir ikke bra hvis someAligned har hull!!! ########################
				Toolkit.getDefaultToolkit().beep();
				System.out.println("!someAligned.empty()");
				System.out.println("Dodgy case??????????????");
				toAlign.catch_(someAligned);
				someAligned = new AlignmentsEtc();
			}

		}

		// scroll aligned. (waited until now because of a memory leak - ?)
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			gui.alignedListBox[t].ensureIndexIsVisible(aligned.elements[t].getSize()-1);
		}

	}

	// 2006-08-15

	void break_(AlignGui gui) {
		Toolkit.getDefaultToolkit().beep();
	}
	// end 2006-08-15





/*
				// the elements we pick from unaligned
				// will refer to elements in the other text(s).
				// we put these other elements (or to be precise, their id''s)
				// on a wanted list (or to be precise, one list for each text).
				// at the same time we strike found elements from the wanted list.
				// when the wanted list is empty we have picked
				// the elements for _one_ complete alignment,
				// or _several_ alignments in the case of crossed relations,
				// or _no_ alignments in the case we run out of elements with corresp attributes,
				// or run out of text altogether

				// initialize wanted list.
				// and initialize pointers to next available elements in unaligned
				// we don''t pick elements as in actually removing the elements from unaligned -
				// not until we have found complete alignments.
				// we just have pointers (indexes) to keep track of how far we have come.
				// the pointers are kept in array ''next''

				Wanted wanted = new Wanted();   // array with one list of id''s per text
				Picked picked = new Picked();   // array with one (or no) id per text
				int[] next = new int(Alignment.NUM_FILES);
				for (int t=0; t<Alignment.NUM_FILES; t++) {
					next[t] = 0;
				}

				// ...

				boolean complete = false;
				do {

					int countPicked = 0;   // counts the elements picked in ####this round
					for (int t=0; t<Alignment.NUM_FILES; t++) {

						// ####### number of the other text
						if (t == 0) {
							tt = 1;
						} else {
							tt = 0;
						}

						while (wanted.wants(t)) {   // for each text t the first call to method wants() is a special case which returns true

							// there are elements from text t on the wanted list.
							// pick next element from text t

							 if (next[t] <= unaligned.elements[t].size() - 1) {

								AElement aElement = (AElement)unaligned.elements[t].get(next[t]);
								Element node = aElement.element;
								String id = node.getAttribute("id");
								picked.add(t, id);

								// get the references this element has to elements in other text,
								// and put the other elements on the wanted list

								String[] refs = node.getAttribute("corresp").split(" ");
								for (int i=0; i<refs.length; i++) {
									String ref = refs[i];
									// is this ref a reference to a "relevant" element, e.g, an <s>?
									// if not, it must be the element in a 1-0 or 0-1 alignment,
									// with a reference to an ancestor element, e.g, a <p>
									if (XmlTools.getElementByIdInNodeList(nodes[tt], ref) {
										// "relevant"
										...
										wanted.add(tt, ref);
									} else {
										// ancestor
										...make sure to keep the exact id?...
									}
								}

								// remove the picked ones from the wanted list

								wanted.remove(picked);

								// update pointer to next element to pick

								next[t]++;

							} else {

								// error. no more elements in this text

								...

							}

						}

					}

					// ...
					...

				} while (!(wanted.empty() || picked.empty()));

				if (...) {
					// found a set of corresponding elements.
					// ### or perhaps several ones
					// make an alignment out of them?
					// or 'more' them up from unaligned to to-align?
					// ### and do something extra if there are crossed relations?
					...
					if (...) {
						// something wrong with corresp attrs
						...
						done = true;
						// ### or throw exception?
					}
					// then 'align' them
					...
				} else {
					done = true;
				}

			} while(...);

		}

	}




	getIndexOfElementByIdInNodeList(nodes[...], id);

class Link {
	int alignmentNumber;
	Set[] elementNumbers;

*/












	void less(AlignGui gui, int t) {   // package access

		////System.out.println("at model.less()");
		//unaligned.catch_(t, toAlign.drop(t));
		unaligned.catch_(t, toAlign.drop(gui, t));   // ### 2006-03-30
		computeMatches(gui);   // ### compute and display
		//ShowCompare.clear(gui);
		gui.compareInfoPanel.off();
		gui.compareInfoPanel.repaint();

		// 2006-10-03
		// update aligned/total ratio in status line
		gui.model.setMemoryUsage(gui);   // 2006-10-03
		gui.model.updateAlignedTotalRatio(gui);

	}

	void more(AlignGui gui, int t) {   // package access

		//System.out.println("model sin more(). gui = " + gui);
		//System.out.println("\nmodel sin more(). t = " + t);
		//////////MemTest.print("Heap memory", "");
		//MemTest.print("Tenured Gen", "");
		//toAlign.pickUp(gui, t, unaligned.pop(t));
		toAlign.pickUp(t, unaligned.pop(t));
		computeMatches(gui);   // ### compute and display);
		//ShowCompare.clear(gui);
		gui.compareInfoPanel.off();
		gui.compareInfoPanel.repaint();

		// 2006-10-03
		// update aligned/total ratio in status line
		gui.model.setMemoryUsage(gui);   // 2006-10-03
		gui.model.updateAlignedTotalRatio(gui);

	}

}



/*



            gui.statusLine.setText("");
            int percentDone = 0;
            gui.statusLine.setProgress(percentDone);
            gui.statusLine.repaint_();
            int numElements = nodes[t].getLength();
            for (int i=0; i<numElements; ++i) {
	            AElement element = new AElement(nodes[t].item(i), i);
                unaligned.add(t, element);
                percentDone = Math.round((float)((float)i / numElements * 100.0));
                ////gui.counterDoc.insertString(0, Integer.toString(i+1), null);   // €€€ problem. blir ikke frisket opp
                //gui.counter.setText(Integer.toString(i+1));
                gui.statusLine.setText(Integer.toString(i+1));
                ////Thread.sleep(100);
                gui.statusLine.setProgress(percentDone);
                gui.statusLine.repaint_();
            }
            ////gui.counterDoc.insertString(0, "", null);
            //gui.counter.setText("");
            gui.statusLine.setText("");
            gui.statusLine.repaint_();

            //return true;



*/
/*
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			elementsInfo[t].setFirst(model, ix[t], t);
*/

/*
		for (int t=0; t<Alignment.NUM_FILES; t++) {
			if (position[t] < elementsInfo[t].getFirst(model, t)) {   skal vel være .getFirst()
				// outside (before) matrix.
				// reason: path is e.g 0-1 step
				return Integer.MIN_VALUE;   (float)(Integer.MIN_VALUE);   // #####
			}
		}
*/
