Mentions légales du service

Skip to content
Snippets Groups Projects
BasicAlignment.java 25.88 KiB
/*
 * $Id$
 *
 * Copyright (C) INRIA, 2003-2009
 * Copyright (C) CNR Pisa, 2005
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

package fr.inrialpes.exmo.align.impl;

import java.lang.Iterable;
import java.util.Hashtable;
import java.util.HashSet;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
import java.net.URI;

import org.xml.sax.ContentHandler;

import org.semanticweb.owl.align.Alignment;
import org.semanticweb.owl.align.AlignmentException;
import org.semanticweb.owl.align.AlignmentVisitor;
import org.semanticweb.owl.align.AlignmentProcess;
import org.semanticweb.owl.align.Cell;
import org.semanticweb.owl.align.Relation;
import org.semanticweb.owl.align.Parameters;

import fr.inrialpes.exmo.align.onto.Ontology;
import fr.inrialpes.exmo.align.onto.BasicOntology;

/**
 * Represents a basic ontology alignment, i.e., a fully functionnal alignment
 * for wich the type of aligned objects is not known.
 *
 * NOTE(JE): hashtabale are indexed by URI.
 * This is strange, but there might be a reason
 * NOTE(JE): I do not think that this is the case anymore
 *
 * In version 3.0 this class is virtually abstract.
 * But it cannot be declared abstract because it uses its own constructor.
 *
 * @author Jrme Euzenat, David Loup, Raphal Troncy
 * @version $Id$
 */

public class BasicAlignment implements Alignment {
    public void accept(AlignmentVisitor visitor) throws AlignmentException {
	visitor.visit(this);
    }

    protected Ontology<Object> onto1 = null;
    protected Ontology<Object> onto2 = null;
    protected int debug = 0;

    protected String level = "0";

    protected String type = "**";

    protected Hashtable<Object,Set<Cell>> hash1 = null;

    protected Hashtable<Object,Set<Cell>> hash2 = null;

    protected long time = 0;

    protected Parameters extensions = null;

    protected Parameters namespaces = null;

    public BasicAlignment() {
	hash1 = new Hashtable<Object,Set<Cell>>();
	hash2 = new Hashtable<Object,Set<Cell>>();
	extensions = new BasicParameters();
	namespaces = new BasicParameters();
	if ( this instanceof AlignmentProcess ) setExtension( Annotations.ALIGNNS, Annotations.METHOD, getClass().getName() );
	onto1 = new BasicOntology<Object>();
	onto2 = new BasicOntology<Object>();
    }

    /**
     * Initialises the Alignment object with two ontologies.
     * These two ontologies can be either an instance of fr.inrialpes.exmo.align.onto.Ontology
     *        which will then replaced the one that was there at creation time
     * or a "concrete" ontology which will be inserted in the fr.inrialpes.exmo.align.onto.Ontology
     * object.
     */
    public void init( Object onto1, Object onto2, Object cache ) throws AlignmentException {
	init( onto1, onto2 );
    }

    public void init( Object onto1, Object onto2 ) throws AlignmentException {
	if ( onto1 instanceof Ontology ) {
	    this.onto1 = (Ontology<Object>)onto1; // [W:unchecked]
	    this.onto2 = (Ontology<Object>)onto2; // [W:unchecked]
	} else {
	    this.onto1.setOntology( onto1 );
	    this.onto2.setOntology( onto2 );
	}
    }

    public static Parameters getParameters() {
	return (Parameters)null;
    }

    public int nbCells() {
	int sum = 0;
	for ( Enumeration e = hash1.elements(); e.hasMoreElements(); ) {
	    sum += ((Set)e.nextElement()).size();
	}
	return sum;
    }

    /** Alignment methods * */
    public Object getOntology1() {
	return onto1.getOntology();
    };

    public Object getOntology2() {
	return onto2.getOntology();
    };

    public Ontology<Object> getOntologyObject1() {
	return onto1;
    };

    public Ontology<Object> getOntologyObject2() {
	return onto2;
    };

    public URI getOntology1URI() {
	return onto1.getURI();
    };

    public URI getOntology2URI() {
	return onto2.getURI();
    };

    public void setOntology1(Object ontology) throws AlignmentException {
	onto1.setOntology( ontology );
    };

    public void setOntology2(Object ontology) throws AlignmentException {
	onto2.setOntology( ontology );
    };

    public void setType(String type) { this.type = type; };

    public String getType() { return type; };

    public void setLevel(String level) { this.level = level; };

    public String getLevel() { return level; };

    public URI getFile1() { return onto1.getFile(); };

    public void setFile1(URI u) { onto1.setFile( u ); };

    public URI getFile2() { return onto2.getFile(); };

    public void setFile2(URI u) { onto2.setFile( u ); };

    public Parameters getExtensions(){ return extensions; }

    public void setExtension( String uri, String label, String value ) {
	final String[] ext = { uri, label, value };
	extensions.setParameter( uri+label, ext);
    };

    public String getExtension( String uri, String label ) {
	String [] ext = (String [])extensions.getParameter( uri+label );
	return (ext==null)?(String)null:ext[2];
    };

    public Parameters getXNamespaces(){ return namespaces; }

    public void setXNamespace( String label, String uri ) {
	namespaces.setParameter( label, uri );
    };

    public String getXNamespace( String label ) {
	return (String)namespaces.getParameter( label );
    };

    public Enumeration<Cell> getElements() {
	return new MEnumeration<Cell>( hash1 );
    }

    public Iterator<Cell> iterator() {
	return new MIterator<Cell>( hash1 );
    }

    public ArrayList<Cell> getArrayElements() {
	ArrayList<Cell> array = new ArrayList<Cell>();
	for (Enumeration e = getElements(); e.hasMoreElements(); ) {
	    array.add( (BasicCell)e.nextElement() );
	}
	return array;
    }

    /** Cell methods **/
    public Cell addAlignCell( String id, Object ob1, Object ob2, Relation relation, double measure, Parameters extensions ) throws AlignmentException {
	Cell cell = createCell( id, ob1, ob2, relation, measure);
	cell.setExtensions( extensions );
	addCell( cell );
	return cell;
    }
    public Cell addAlignCell( String id, Object ob1, Object ob2, Relation relation, double measure ) throws AlignmentException {
	Cell cell = createCell( id, ob1, ob2, relation, measure);
	addCell( cell );
	return cell;
    }

    // JE: Why does this not allow to create cells with ids preserved?
    // This would be useful when the Alignements are cloned to preserve them
    public Cell addAlignCell(Object ob1, Object ob2, String relation, double measure) throws AlignmentException {
	return addAlignCell( (String)null, ob1, ob2, BasicRelation.createRelation(relation), measure );
    };

    public Cell addAlignCell(Object ob1, Object ob2) throws AlignmentException {
	return addAlignCell( (String)null, ob1, ob2, BasicRelation.createRelation("="), 1. );
    }

    public Cell createCell( String id, Object ob1, Object ob2, Relation relation, double measure) throws AlignmentException {
	return (Cell)new BasicCell( id, ob1, ob2, relation, measure);
    }

    protected void addCell( Cell c ) throws AlignmentException {
	boolean found = false;
	Set<Cell> s1 = (Set<Cell>)hash1.get(c.getObject1());
	if ( s1 != null ) {
	    // I must check that there is no one here
	    for (Iterator i = s1.iterator(); !found && i.hasNext(); ) {
		if ( c.equals((BasicCell)i.next()) ) found = true;
	    }
	    if (!found) s1.add( c );
	} else {
	    s1 = new HashSet<Cell>();
	    s1.add( c );
	    hash1.put(c.getObject1(),s1);
	}
	found = false;
	Set<Cell> s2 = (Set<Cell>)hash2.get(c.getObject2());
	if( s2 != null ){
	    // I must check that there is no one here
	    for (Iterator i=s2.iterator(); !found && i.hasNext(); ) {
		if ( c.equals((BasicCell)i.next()) ) found = true;
	    }
	    if (!found)	s2.add( c );
	} else {
	    s2 = new HashSet<Cell>();
	    s2.add( c );
	    hash2.put(c.getObject2(),s2);
	}
    }

    public Set<Cell> getAlignCells1(Object ob) throws AlignmentException {
	return hash1.get( ob );
    }
    public Set<Cell> getAlignCells2(Object ob) throws AlignmentException {
	return hash2.get( ob );
    }

    /*
     * @deprecated implemented as the one retrieving the highest strength correspondence
     */
    public Cell getAlignCell1(Object ob) throws AlignmentException {
	if ( Annotations.STRICT_IMPLEMENTATION == true ){
	    throw new AlignmentException("getAlignCell1: deprecated (use getAlignCells1 instead)");
	} else {
	    Set s2 = (Set)hash1.get(ob);
	    Cell bestCell = null;
	    double bestStrength = 0.;
	    if ( s2 != null ) {
		for( Iterator it2 = s2.iterator(); it2.hasNext(); ){
		    Cell c = (Cell)it2.next();
		    double val = c.getStrength();
		    if ( val > bestStrength ) {
			bestStrength = val;
			bestCell = c;
		    }
		}
	    }
	    return bestCell;
	}
    }

    public Cell getAlignCell2(Object ob) throws AlignmentException {
	if ( Annotations.STRICT_IMPLEMENTATION == true ){
	    throw new AlignmentException("getAlignCell2: deprecated (use getAlignCells2 instead)");
	} else {
	    Set s1 = (Set)hash2.get(ob);
	    Cell bestCell = null;
	    double bestStrength = 0.;
	    if ( s1 != null ){
		for( Iterator it1 = s1.iterator(); it1.hasNext(); ){
		    Cell c = (Cell)it1.next();
		    double val = c.getStrength();
		    if ( val > bestStrength ) {
			bestStrength = val;
			bestCell = c;
		    }
		}
	    }
	    return bestCell;
	}
    }

    /*
     * @deprecated
     */
    public Object getAlignedObject1(Object ob) throws AlignmentException {
	Cell c = getAlignCell1(ob);
	if (c != null) return c.getObject2();
	else return null;
    };

    /*
     * @deprecated
     */
    public Object getAlignedObject2(Object ob) throws AlignmentException {
	Cell c = getAlignCell2(ob);
	if (c != null) return c.getObject1();
	else return null;
    };

    /*
     * @deprecated
     */
    public Relation getAlignedRelation1(Object ob) throws AlignmentException {
	Cell c = getAlignCell1(ob);
	if (c != null) return c.getRelation();
	else return (Relation) null;
    };

    /*
     * @deprecated
     */
    public Relation getAlignedRelation2(Object ob) throws AlignmentException {
	Cell c = getAlignCell2(ob);
	if (c != null) return c.getRelation();
	else return (Relation) null;
    };

    /*
     * @deprecated
     */
    public double getAlignedStrength1(Object ob) throws AlignmentException {
	Cell c = getAlignCell1(ob);
	if (c != null) return c.getStrength();
	else return 0;
    };

    /*
     * @deprecated
     */
    public double getAlignedStrength2(Object ob) throws AlignmentException {
	Cell c = getAlignCell2(ob);
	if (c != null) return c.getStrength();
	else return 0;
    };

    // JE: beware this does only remove the exact equal cell
    // not those with same value
    public void removeAlignCell(Cell c) throws AlignmentException {
	Set<Cell> s1 = hash1.get(c.getObject1());
	Set<Cell> s2 = hash2.get(c.getObject2());
	s1.remove(c);
	s2.remove(c);
	if (s1.isEmpty())
	    hash1.remove(c.getObject1());
	if (s2.isEmpty())
	    hash2.remove(c.getObject2());
    }

    /***************************************************************************
     * The cut function suppresses from an alignment all the cell over a
     * particulat threshold
     **************************************************************************/
    public void cut2(double threshold) throws AlignmentException {
	for (Enumeration e = getElements(); e.hasMoreElements(); ) {
	    Cell c = (Cell)e.nextElement();
	    if ( c.getStrength() < threshold )
		removeAlignCell( c );
	}
    }

    /***************************************************************************
     * Default cut implementation
     * For compatibility with API until version 1.1
     **************************************************************************/
    public void cut( double threshold ) throws AlignmentException {
	cut( "hard", threshold );
    }

    /***************************************************************************
     * Cut refinement :
     * - getting those cells with strength above n (hard)
     * - getting the n best cells (best)
     * - getting those cells with strength at worse n under the best (span)
     * - getting the n% best cells (perc)
     * - getting those cells with strength at worse n% of the best (prop)
     * - getting all cells until a gap of n (hardgap)
     * - getting all cells until a gap of n% of the last (propgap)
     * Rule:
     * threshold is betweew 1 and 0
     **************************************************************************/
    public void cut( String method, double threshold ) throws AlignmentException
    {
	// Check that threshold is a percent
	if ( threshold > 1. || threshold < 0. )
	    throw new AlignmentException( "Not a percentage or threshold : "+threshold );
	// Create a sorted list of cells
	// For sure with sorted lists, we could certainly do far better
	List<Cell> buffer = getArrayElements();
	Collections.sort( buffer );
	int size = buffer.size();
	boolean found = false;
	int i = 0; // the number of cells to keep
	// Depending on the method, find the limit
	if ( method.equals("perc") ){
	    i = (new Double(size*threshold)).intValue();
	} else if ( method.equals("best") ){
	    i = java.lang.Math.min( size, new Double(threshold*100).intValue() );
	} else if ( method.equals("hardgap") || method.equals("propgap") ){
	    double gap;
	    double last = ((Cell)buffer.get(0)).getStrength();
	    if ( method.equals("propgap") ) gap = last * threshold;
	    else gap = threshold;
	    for( i=1; i < size && !found ; i++) {
		if ( last - ((Cell)buffer.get(i)).getStrength() > gap ) found = true;
		else {
		    last = ((Cell)buffer.get(i)).getStrength();
		    if ( method.equals("propgap") ) gap = last * threshold;
		}
	    }
	} else {
	    double max;
	    if ( method.equals("hard") ) max = threshold;
	    else if ( method.equals("span") ) max = ((Cell)buffer.get(0)).getStrength() - threshold;
	    else if ( method.equals("prop") ) max = ((Cell)buffer.get(0)).getStrength() * threshold;
	    else throw new AlignmentException( "Not a cut specification : "+method );
	    for( i=0; i < size && !found ; i++) {
		if ( ((Cell)buffer.get(i)).getStrength() < max ) found = true;
	    }
	}
	// Introduce the result back in the structure
	size = i;
	hash1.clear();
	hash2.clear();
	for( i=0; i < size; i++ ) {
	    addCell( (Cell)buffer.get(i) );
	}
    };

    /**
     * Returns default exception for conversion to URIAlignments
     *
     */
    public URIAlignment toURIAlignment() throws AlignmentException {
	throw new AlignmentException("[BasicAlignment].toURIAlignment() cannot process");
    }

    /**
     * The harden function acts like threshold but put all weights to 1.
     */
    public void harden(double threshold) throws AlignmentException {
	for (Enumeration e = getElements(); e.hasMoreElements();) {
	    Cell c = (Cell)e.nextElement();
	    if (c.getStrength() < threshold) removeAlignCell( c );
	    else c.setStrength(1.);
	}
    }
    /**
     * Algebraic part
     * This is to be improved by (TODO):
     * - improving cell equivalence (maybe not dependent on the confidence... and
     *     grounding it on abstract data types)
     * - using algebraic meet and join for relations and confidences
     *     (the type of relation used can be declared in the alignment)
     * - check compatibility and setup for type and level
     * - conserve extensions if necessary
     */
    /*
     * This method is used by the algebraic operators
     * It has to be overriden by implementations.
     */
    public BasicAlignment createNewAlignment( Object onto1, Object onto2 ) throws AlignmentException {
	BasicAlignment align = new BasicAlignment();
	align.init( onto1, onto2 );
	return align;
    }

   /**
     * The second alignment is suppresed from the first one meaning that for
     * any pair (o, o', n, r) in O and (o, o', n', r) in O' the resulting
     * alignment will contain:
     * ( o, o', diff(n,n'), r)
     * any pair which is only in the first alignment is preserved.
     */
    public Alignment diff(Alignment align) throws AlignmentException {
	// Could also test: onto1 == getOntologyObject1();
	if ( onto1.getURI() != align.getOntology1URI() )
	    throw new AlignmentException("Can only diff alignments with same ontologies");
	if ( onto2.getURI() != align.getOntology2URI() )
	    throw new AlignmentException("Can only diff alignments with same ontologies");
	BasicAlignment result = createNewAlignment( onto1, onto2 );
	for ( Enumeration e = getElements(); e.hasMoreElements(); ) {
	    Cell c1 = (Cell)e.nextElement();
	    //URI uri1 = c1.getObject2AsURI();
	    Set<Cell> s2 = (Set<Cell>)align.getAlignCells1( c1.getObject1() );
	    boolean found = false;
	    if ( s2 != null ){
		for ( Cell c2 : s2 ){
		    //if ( uri1.toString().equals(c2.getObject2AsURI().toString()) ) {
		    if ( c1.equals( c2 ) ) {
			found = true;
		    }
		}
	    }
	    if ( !found ) result.addCell( c1 );
	}
	return result;
    }

   /**
     * The second alignment is meet with the first one meaning that for
     * any pair (o, o', n, r) in O and (o, o', n', r) in O' the resulting
     * alignment will contain:
     * ( o, o', meet(n,n'), r)
     * any pair which is in only one alignment is preserved.
     */
    public Alignment meet(Alignment align) throws AlignmentException {
	// Could also test: onto1 == getOntologyObject1();
	if ( onto1.getURI() != align.getOntology1URI() )
	    throw new AlignmentException("Can only meet alignments with same ontologies");
	if ( onto2.getURI() != align.getOntology2URI() )
	    throw new AlignmentException("Can only meet alignments with same ontologies");
	BasicAlignment result = createNewAlignment( onto1, onto2 );
	for ( Enumeration e = getElements(); e.hasMoreElements(); ) {
	    Cell c1 = (Cell)e.nextElement();
	    Set<Cell> s2 = (Set<Cell>)align.getAlignCells1( c1.getObject1() );
	    boolean found = false;
	    if ( s2 != null ){
		for ( Cell c2 : s2 ){
		    if ( c1.equals( c2 ) ) {
			found = true;
		    }
		}
	    }
	    // again, no new cell
	    if ( found ) result.addCell( c1 );
	}
	return result;
    }

   /**
     * The second alignment is join with the first one meaning that for
     * any pair (o, o', n, r) in O and (o, o', n', r) in O' the resulting
     * alignment will contain:
     * ( o, o", join(n,n'), r)
     * any pair which is in only one alignment is discarded.
     */
    public Alignment join(Alignment align) throws AlignmentException {
	// Could also test: onto1 == getOntologyObject1();
	if ( onto1.getURI() != align.getOntology1URI() )
	    throw new AlignmentException("Can only join alignments with same ontologies");
	if ( onto2.getURI() != align.getOntology2URI() )
	    throw new AlignmentException("Can only join alignments with same ontologies");
	BasicAlignment result = createNewAlignment( onto1, onto2 );
	result.ingest( align );
	//for ( Cell e : getElements() ){//I should do an iterator!
	for ( Enumeration e = getElements(); e.hasMoreElements(); ) {
	    Cell c1 = (Cell)e.nextElement();
	    Set<Cell> s2 = (Set<Cell>)align.getAlignCells1( c1.getObject1() );
	    boolean found = false;
	    if ( s2 != null ){
		for ( Cell c2 : s2 ){
		    if ( c1.equals( c2 ) ) {
			found = true;
		    }
		}
	    }
	    // again, no new cell
	    if ( !found ) result.addCell( c1 );
	}
	return result;
    }

    /**
     * The second alignment is composed with the first one meaning that for
     * any pair (o, o', n, r) in O and (o',o", n', r') in O' the resulting
     * alignment will contain:
     * ( o, o", join(n,n'), compose(r, r')) iff compose(r,r') exists.
     */
    public Alignment compose(Alignment align) throws AlignmentException {
	if ( onto2.getURI() != align.getOntology1URI() )
	    throw new AlignmentException("Can only compose alignments with a common ontologies");
	BasicAlignment result = createNewAlignment( onto1, ((BasicAlignment)align).getOntologyObject2() );
	for ( Enumeration e = getElements() ; e.hasMoreElements(); ){
		Cell c1 = (Cell)e.nextElement();
		Set<Cell> cells2 = align.getAlignCells1(c1.getObject2());
		if (cells2 !=null) {
			for (Cell c2 : cells2) {
				Cell newCell = c1.compose(c2);
				if (newCell != null) {
				    result.addCell(newCell);
				}
			}
		}
	}
	return result;
    }

    /**
     * A new alignment is created such that for
     * any pair (o, o', n, r) in O the resulting alignment will contain:
     * ( o', o, n, inverse(r)) iff compose(r) exists.
     */

    public Alignment inverse() throws AlignmentException {
	BasicAlignment result = createNewAlignment( onto2, onto1 );
	result.setFile1( getFile2() );
	result.setFile2( getFile1() );
	// We must inverse getType
	result.setType( getType() );
	result.setLevel( getLevel() );
	// Must add an inverse to the method extension
	for ( Object ext : ((BasicParameters)extensions).getValues() ){
	    result.setExtension( ((String[])ext)[0], ((String[])ext)[1], ((String[])ext)[2] );
	}
	String oldid = result.getExtension( Annotations.ALIGNNS, "id" );
	if ( oldid != null && !oldid.equals("") ) {
	    result.setExtension( Annotations.ALIGNNS, "derivedFrom", oldid );
	    result.getExtensions().unsetParameter( Annotations.ALIGNNS+"id" );
	}
	result.setExtension( Annotations.ALIGNNS, "method", "http://exmo.inrialpes.fr/align/impl/BasicAlignment#inverse" );
	for ( Enumeration e = namespaces.getNames() ; e.hasMoreElements(); ){
	    String label = (String)e.nextElement();
	    result.setXNamespace( label, getXNamespace( label ) );
	}
	for ( Enumeration e = getElements() ; e.hasMoreElements(); ){
	    result.addCell(((Cell)e.nextElement()).inverse());
	}
	return (Alignment)result;
    };

    /** Housekeeping **/
    public void dump(ContentHandler h) {
    };

    /**
     * Incorporate the cells of the alignment into its own alignment. Note: for
     * the moment, this does not copy but really incorporates. So, if hardening
     * is applied, then the ingested alignmment will be modified as well.
     * JE: May be a "force" boolean for really ingesting or copying may be
     *     useful
     */
    public void ingest(Alignment alignment) throws AlignmentException {
	if ( alignment != null ) {
	    for (Enumeration e = alignment.getElements(); e.hasMoreElements();) {
		addCell((Cell)e.nextElement());
	    }
	}
    }

    /**
     * Generate a copy of this alignment object
     * It has the same content but a different id (no id indeed)
     */
    public Object clone() {
	BasicAlignment align;
	try { align = createNewAlignment( onto1, onto2 ); }
	catch (AlignmentException ae) { ae.printStackTrace(); return null; }
	align.setType( getType() );
	align.setLevel( getLevel() );
	align.setFile1( getFile1() );
	align.setFile2( getFile2() );
	for ( Object ext : ((BasicParameters)extensions).getValues() ){
	    align.setExtension( ((String[])ext)[0], ((String[])ext)[1], ((String[])ext)[2] );
	}
	String oldid = align.getExtension( Annotations.ALIGNNS, "id" );
	if ( oldid != null && !oldid.equals("") ) {
	    align.setExtension( Annotations.ALIGNNS, "derivedFrom", oldid );
	    align.getExtensions().unsetParameter( Annotations.ALIGNNS+"id" );
	}
	align.setExtension( Annotations.ALIGNNS, "method", this.getClass().getName()+"#clone" );
	for ( Enumeration e = namespaces.getNames() ; e.hasMoreElements(); ){
	    String label = (String)e.nextElement();
	    align.setXNamespace( label, getXNamespace( label ) );
	}
	try { align.ingest( this ); }
	catch (AlignmentException ex) { ex.printStackTrace(); }
	return align;
    }

    /**
     * This should be rewritten in order to generate the axiom ontology instead
     * of printing it! And then use ontology serialization for getting it
     * printed.
     */
    public void render( AlignmentVisitor renderer ) throws AlignmentException {
	accept(renderer);
    }

    /**
     * Can be used for reducing the amount of memory taken by an alignment
     * Does nothing in BasicAlignment.
     */
    public void cleanUp() {}
}


class MEnumeration<T> implements Enumeration<T> {
    private Enumeration<Set<T>> set = null; // The enumeration of sets
    private Iterator<T> current = null; // The current set's enumeration

    MEnumeration( Hashtable<Object,Set<T>> s ){
	set = s.elements();
	while( set.hasMoreElements() && current == null ){
	    current = set.nextElement().iterator();
	    if( !current.hasNext() ) current = null;
	}
    }
    public boolean hasMoreElements(){
	return ( current != null);
    }
    public T nextElement(){
	T val = current.next();
	if( !current.hasNext() ){
	    current = null;
	    while( set.hasMoreElements() && current == null ){
		current = set.nextElement().iterator();
		if( !current.hasNext() ) current = null;
	    }
	}
	return val;
    }
}

class MIterator<T> implements Iterator<T> {
    // Because of the remove, the implentation should be different
    // Keeping the last element at hand
    private Enumeration<Set<T>> set = null; // The enumeration of sets
    private Iterator<T> current = null; // The current set's enumeration
    private Iterator<T> next = null; // The next set enumeration

    MIterator( Hashtable<Object,Set<T>> s ){
	set = s.elements();
	if ( set.hasMoreElements() ) {
	    current = set.nextElement().iterator();
	    if ( current.hasNext() ) {
		next = current;
	    } else {
		while( set.hasMoreElements() && next == null ){
		    next = set.nextElement().iterator();
		    if( !next.hasNext() ) next = null;
		}
	    }
	}
    }
    public boolean hasNext(){
	return ( next != null && next.hasNext() );
    }
    public T next(){
	current = next;
	T val = current.next();
	if( !current.hasNext() ){
	    next = null;
	    while( set.hasMoreElements() && next == null ){
		next = set.nextElement().iterator();
		if( !next.hasNext() ) next = null;
	    }
	}
	return val;
    }
    public void remove(){
	if ( current != null ) current.remove();
    }
}