package de.uka.ipd.consensus.impl;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.swing.event.EventListenerList;

import org.tm4j.net.Locator;
import org.tm4j.net.LocatorFactory;
import org.tm4j.net.LocatorResolutionException;
import org.tm4j.topicmap.DuplicateObjectIDException;
import org.tm4j.topicmap.DuplicateResourceLocatorException;
import org.tm4j.topicmap.ProviderTransaction;
import org.tm4j.topicmap.ProviderTransactionException;
import org.tm4j.topicmap.TopicMap;
import org.tm4j.topicmap.TopicMapProvider;
import org.tm4j.topicmap.TopicMapProviderFactory;
import org.tm4j.topicmap.XTMPSI;

import de.uka.ipd.consensus.foundation.AbstractConsensusFoundationModule;
import de.uka.ipd.consensus.foundation.ConsensusFoundation;
import de.uka.ipd.consensus.foundation.ConsensusFoundationManager;
import de.uka.ipd.consensus.foundation.logging.LoggingManager;
import de.uka.ipd.consensus.foundation.ontology.ExportHandler;
import de.uka.ipd.consensus.foundation.ontology.ImportHandler;
import de.uka.ipd.consensus.foundation.ontology.OntologyEvent;
import de.uka.ipd.consensus.foundation.ontology.OntologyListener;
import de.uka.ipd.consensus.foundation.ontology.OntologyManager;
import de.uka.ipd.consensus.foundation.ontology.OntologyManagerClassException;
import de.uka.ipd.consensus.foundation.ontology.OntologyManagerDuplicateIdException;
import de.uka.ipd.consensus.foundation.ontology.OntologyManagerException;
import de.uka.ipd.consensus.foundation.ontology.OntologyManagerTransactionException;
import de.uka.ipd.consensus.foundation.query.QueryEngine;
import de.uka.ipd.consensus.foundation.rating.RatingManager;
import de.uka.ipd.consensus.foundation.rights.DynamicRightsManager;
import de.uka.ipd.consensus.foundation.rights.DynamicRightsManagerException;
import de.uka.ipd.consensus.foundation.schema.Association;
import de.uka.ipd.consensus.foundation.schema.Attribute;
import de.uka.ipd.consensus.foundation.schema.Attributeable;
import de.uka.ipd.consensus.foundation.schema.Concept;
import de.uka.ipd.consensus.foundation.schema.Occurrence;
import de.uka.ipd.consensus.foundation.schema.Rateable;
import de.uka.ipd.consensus.foundation.schema.Synonym;
import de.uka.ipd.consensus.foundation.schema.Topic;
import de.uka.ipd.consensus.foundation.schema.User;
import de.uka.ipd.consensus.foundation.user.UserManager;

/**
 * 
 * TODO
 * @author Thomas Much
 * @version 2006-06-14
 */
public class OntologyManagerImpl extends AbstractConsensusFoundationModule implements OntologyManager {

	public static final String INTERNAL_TYPE_PREFIX = "__CF__";

	private static final String LOCATOR_DEFAULT_BASE = "http://uka.de/ipd/dbis/consensus/default.xtm";
	
	private static final String PSI_SYNONYMOUS_TERMS = "http://www.techquila.com/psi/thesaurus/#synonymous-terms";
	private static final String PSI_SYNONYM          = "http://www.techquila.com/psi/thesaurus/#synonym";
	
	private EventListenerList    listenerList   = new EventListenerList();
	private HashMap              exporters      = new HashMap();
	private HashMap              importers      = new HashMap();
	private HashMap              mergers        = new HashMap();
	private List                 internalTopics = new ArrayList();
	private QueryEngine          queryEngine;
	private DynamicRightsManager drm;

	private TopicMapProvider provider;
	private TopicMap         tm;
	private Locator          base;
	private Locator          psiSuperclassSublass;
	private Locator          psiSuperclass;
	private Locator          psiSublass;
	private Locator          psiClassInstance;
	private Locator          psiClass;
	private Locator          psiInstance;
	private Locator          psiSynonymousTerms;
	private Locator          psiSynonym;

	private org.tm4j.topicmap.Topic internalTopicType;
	private org.tm4j.topicmap.Topic userTopicType;
	private org.tm4j.topicmap.Topic commentTopicType;
	private org.tm4j.topicmap.Topic createdbyTopicType;
	private org.tm4j.topicmap.Topic nameTopicType;
	private org.tm4j.topicmap.Topic attributeTopicType;
	private org.tm4j.topicmap.Topic valueTopicType;
	private org.tm4j.topicmap.Topic versionTopicType;
	private org.tm4j.topicmap.Topic synonymoustermsTopicType;
	private org.tm4j.topicmap.Topic synonymTopicType;
	private org.tm4j.topicmap.Topic superclasssubclassTopicType;
	private org.tm4j.topicmap.Topic superclassTopicType;
	private org.tm4j.topicmap.Topic subclassTopicType;
	private org.tm4j.topicmap.Topic classInstanceTopicType;
	private org.tm4j.topicmap.Topic classTopicType;
	private org.tm4j.topicmap.Topic instanceTopicType;

	private boolean running    = false;
	private boolean useclasses = false;

	
	public void init(Properties config, LoggingManager logging, UserManager users) throws Exception {

		running = true;

		String usecls = config.getProperty("cfimpl.schema.usesclasses");

		this.useclasses = Boolean.valueOf(usecls).booleanValue();

		// create & register "standard" im-/export handlers:
		this.addExportHandler( new XTMExportHandler(logging,this) );
		this.addImportHandler( new XTMImportHandler(logging,this) );

		// load declared handlers dynamically:
		
		String exportHandler = config.getProperty( ConsensusFoundation.CF_EXPORT_KEY );
		String importHandler = config.getProperty( ConsensusFoundation.CF_IMPORT_KEY );

		if (exportHandler != null) {

			StringTokenizer st = new StringTokenizer(exportHandler,",");

			while (st.hasMoreTokens()) {

				String handlerClassName = st.nextToken().trim();
				
				if (handlerClassName.length() > 0) {
					this.addExportHandler( (ExportHandler)getHandler("Export handler",config,logging,handlerClassName) );
				}
			}
		}

		if (importHandler != null) {

			StringTokenizer st = new StringTokenizer(importHandler,",");

			while (st.hasMoreTokens()) {

				String handlerClassName = st.nextToken().trim();
				
				if (handlerClassName.length() > 0) {					
					this.addImportHandler( (ImportHandler)getHandler("Import handler",config,logging,handlerClassName) );
				}
			}
		}

		// don't merge topics with the same base name:
		config.setProperty("tm4j.name.based.merge","false");
		// use static merging (two merged topics actually become one topic):
		config.setProperty("tm4j.static.merge","true");

		TopicMapProviderFactory tmpf;

		String backend = config.getProperty("cfimpl.ontology.backend");
		
		if ("hibernate".equalsIgnoreCase( backend )) {
			logging.debug("Using HIBERNATE backend.");
			tmpf = new org.tm4j.topicmap.hibernate.TopicMapProviderFactoryImpl();
		}
		else {
			logging.debug("Using MEMORY backend.");
			tmpf = new org.tm4j.topicmap.memory.TopicMapProviderFactoryImpl();
		}
		
		provider = tmpf.newTopicMapProvider( config );
		
		String locatorBase = config.getProperty("cfimpl.ontology.baseuri");
		
		if ((locatorBase == null) || (locatorBase.length() == 0)) {
			locatorBase = LOCATOR_DEFAULT_BASE;
		}
		
		LocatorFactory lf = provider.getLocatorFactory();

		base = lf.createLocator( "URI", locatorBase );

		if (lf instanceof org.tm4j.topicmap.hibernate.LocatorFactoryImpl) {
			// ugly trick, but TM4J does not offer a public way to create Locators that can be used with Hibernate queries...
			// (TM4J uses the same trick internally)
			org.tm4j.topicmap.hibernate.LocatorFactoryImpl psilf = (org.tm4j.topicmap.hibernate.LocatorFactoryImpl)lf;
			psiSuperclassSublass = psilf.createPersistentLocator( "URI", XTMPSI.SUPERCLASS_SUBCLASS );
			psiSuperclass        = psilf.createPersistentLocator( "URI", XTMPSI.SUPERCLASS );
			psiSublass           = psilf.createPersistentLocator( "URI", XTMPSI.SUBCLASS );
			psiClassInstance     = psilf.createPersistentLocator( "URI", XTMPSI.CLASS_INSTANCE );
			psiClass             = psilf.createPersistentLocator( "URI", XTMPSI.CLASS );
			psiInstance          = psilf.createPersistentLocator( "URI", XTMPSI.INSTANCE );
			psiSynonymousTerms   = psilf.createPersistentLocator( "URI", PSI_SYNONYMOUS_TERMS );
			psiSynonym           = psilf.createPersistentLocator( "URI", PSI_SYNONYM );
		}
		else {
			psiSuperclassSublass = lf.createLocator( "URI", XTMPSI.SUPERCLASS_SUBCLASS );
			psiSuperclass        = lf.createLocator( "URI", XTMPSI.SUPERCLASS );
			psiSublass           = lf.createLocator( "URI", XTMPSI.SUBCLASS );
			psiClassInstance     = lf.createLocator( "URI", XTMPSI.CLASS_INSTANCE );
			psiClass             = lf.createLocator( "URI", XTMPSI.CLASS );
			psiInstance          = lf.createLocator( "URI", XTMPSI.INSTANCE );
			psiSynonymousTerms   = lf.createLocator( "URI", PSI_SYNONYMOUS_TERMS );
			psiSynonym           = lf.createLocator( "URI", PSI_SYNONYM );
		}

		tm = provider.getTopicMap( base );

		if (tm == null) {
			tm = provider.createTopicMap( base );
			logging.debug("TopicMap created: " + tm );
		}
		else {
			logging.debug("Found existing TopicMap " + tm);
		}

		this.createTopicTypes();

		// try to import existing Topic Map:

		String tmFile = config.getProperty("cfimpl.ontology.file");

		if ((tmFile != null) && (tmFile.length() > 0)) {

			ImportHandler handler = this.getImportHandler(tmFile);
		
			if (handler != null) {
				InputStream in = this.getClass().getClassLoader().getResourceAsStream(tmFile);

				if (in == null) {
					logging.debug("TopicMap file '" + tmFile + "' not found.");
				}
				else {
					logging.debug("Loading TopicMap '" + tmFile + "' ...");
					handler.importOntology( in );
				}
			}
			else {
				logging.debug("No import handler for file '" + tmFile + "' found.");
			}
		}

		logging.debug("OntologyManager init done.");
	}


	public void initCompleted(Properties config, ConsensusFoundationManager cfm) throws Exception {
		super.initCompleted(config, cfm);
		this.drm = cfm.getDynamicRightsManager();

		// creating query engine:

		String queryClassName = config.getProperty( ConsensusFoundation.CF_QUERY_KEY );

		if ((queryClassName != null) && (queryClassName.length() > 0)) {
			queryEngine = (QueryEngine)getHandler( "Query engine", config, cfm.getLoggingManager(), queryClassName );
		}

		if (queryEngine == null) {
			queryEngine = new QueryEngineImpl( cfm.getLoggingManager(), this ); // TODO: Abhängigkeit entfernen durch Default-Klassenname als String?!!!
		}
		
		cfm.getLoggingManager().debug("OntologyManager initCompleted done.");
	}


	public void shutdown() {
		if (running) {
			running = false;
			getLoggingManager().debug("OntologyManager shutdown...");
		}
	}


	protected Collection getInternalTopics() {
		return Collections.unmodifiableCollection( this.internalTopics );
	}


	protected void createTopicTypes() throws OntologyManagerException {
		this.internalTopics = new ArrayList(20);
		try {
			internalTopicType           = getOrCreateTopicType("Internal","ConsensusFoundation internal root",null,null);
			userTopicType               = getOrCreateTopicType("User","ConsensusFoundation user account",internalTopicType,null);
			commentTopicType            = getOrCreateTopicType("Comment","ConsensusFoundation comment",internalTopicType,null);
			createdbyTopicType          = getOrCreateTopicType("CreatedBy","ConsensusFoundation created-by",internalTopicType,null);
			nameTopicType               = getOrCreateTopicType("Name","ConsensusFoundation name",internalTopicType,null);
			attributeTopicType          = getOrCreateTopicType("Attribute","ConsensusFoundation attribute",internalTopicType,null);
			valueTopicType              = getOrCreateTopicType("Value","ConsensusFoundation value",internalTopicType,null);
			versionTopicType            = getOrCreateTopicType("Version","ConsensusFoundation version",internalTopicType,null);
			// TODO: kann man das noch optimieren, indem man die folgenden nicht in die Liste einhängt, sondern den Assoz. etc. den TM4J-Typ "internal" gibt?
			synonymoustermsTopicType    = getOrCreateTopicType("SynonymousTerms","ConsensusFoundation synonymous terms",internalTopicType,this.getSynonymousTermsPSI());
			synonymTopicType            = getOrCreateTopicType("Synonym","ConsensusFoundation synonym",internalTopicType,this.getSynonymPSI());
			superclasssubclassTopicType = getOrCreateTopicType("SuperclassSubclass","ConsensusFoundation superclass subclass",internalTopicType,this.getSuperclassSubclassPSI());
			superclassTopicType         = getOrCreateTopicType("Superclass","ConsensusFoundation superclass",internalTopicType,this.getSuperclassPSI());
			subclassTopicType           = getOrCreateTopicType("Subclass","ConsensusFoundation subclass",internalTopicType,this.getSubclassPSI());
			classInstanceTopicType      = getOrCreateTopicType("ClassInstance","ConsensusFoundation class instance",internalTopicType,this.getClassInstancePSI());
			classTopicType              = getOrCreateTopicType("Class","ConsensusFoundation class",internalTopicType,this.getClassPSI());
			instanceTopicType           = getOrCreateTopicType("Instance","ConsensusFoundation instance",internalTopicType,this.getInstancePSI());
		}
		catch (Exception e) {
			throw new OntologyManagerException(e);
		}
	}


	protected boolean isInternalTopicType(org.tm4j.topicmap.Topic tmTopic) {
		if (tmTopic != null) {
			int i = this.internalTopics.size();
			while ( i > 0 ) {
				i--;
				org.tm4j.topicmap.Topic t = (org.tm4j.topicmap.Topic)this.internalTopics.get( i );
				if (tmTopic.equals( t )) return true;
				if (tmTopic.isOfType( t )) return true;
			}
		}
		return false;
	}


	protected org.tm4j.topicmap.Topic getOrCreateTopicType(String id, String name, org.tm4j.topicmap.Topic type, Locator psi) throws Exception {

		id = INTERNAL_TYPE_PREFIX + id;
		
		org.tm4j.topicmap.Topic topicType = tm.getTopicByID(id);

		if (topicType == null) {
			topicType = createNewTopic( id, name, type );

			if (psi != null) {
				topicType.addSubjectIndicator( psi );
			}
		}

		this.internalTopics.add( topicType );

		return topicType;
	}


	protected void createSourceLocator(org.tm4j.topicmap.TopicMapObject tmo) throws DuplicateResourceLocatorException, LocatorResolutionException {
		tmo.addSourceLocator( this.getTopicMapBase().resolveRelative( "#" + tmo.getID() ) );
	}


	protected org.tm4j.topicmap.Topic createNewTopic(String id, String name, org.tm4j.topicmap.Topic type)
				throws DuplicateObjectIDException,PropertyVetoException,DuplicateResourceLocatorException,LocatorResolutionException {

		org.tm4j.topicmap.Topic topic  = tm.createTopic( id );
		
		this.createSourceLocator( topic );

		if (type != null) {
			topic.addType( type );
		}

		if ((name != null) && (name.length() > 0)) {
			this.setTopicName( topic, name );
		}

		return topic;
	}
	
	
	private String getTopicNameId(org.tm4j.topicmap.Topic topic) {
		assert (topic != null);
		return topic.getID() + ".name";
	}


	protected void setTopicName(org.tm4j.topicmap.Topic topic, String name) throws PropertyVetoException, DuplicateObjectIDException {
		assert (topic != null);
		
		if (name == null) {
			name = "";
		}

		final String NAME_ID = this.getTopicNameId( topic );

		org.tm4j.topicmap.TopicMapObject tmo = tm.getObjectByID( NAME_ID );

		if ((tmo != null) && (tmo instanceof org.tm4j.topicmap.BaseName)) {
			org.tm4j.topicmap.BaseName bn = (org.tm4j.topicmap.BaseName)tmo;
			bn.setData( name );
		}
		else {
			topic.createName( NAME_ID, name );
		}
	}

	
	protected boolean hasTopicName(org.tm4j.topicmap.Topic topic) {
		if (topic != null) {
			org.tm4j.topicmap.TopicMapObject tmo = tm.getObjectByID( this.getTopicNameId( topic ) );
	
			if ((tmo != null) && (tmo instanceof org.tm4j.topicmap.BaseName)) {
				if (((org.tm4j.topicmap.BaseName)tmo).getData() != null) {
					return true;
				}
			}
			
			Collection c = topic.getNames();
			
			if (c != null) {
				Iterator it = c.iterator();
				
				while (it.hasNext()) {
					org.tm4j.topicmap.BaseName bn = (org.tm4j.topicmap.BaseName)it.next();

					if (bn.getData() != null) {
						return true;
					}
				}
			}
		}
		
		return false;
	}


	protected String getTopicName(org.tm4j.topicmap.Topic topic) {
		if (topic != null) {
			org.tm4j.topicmap.TopicMapObject tmo = tm.getObjectByID( this.getTopicNameId( topic ) );
	
			if ((tmo != null) && (tmo instanceof org.tm4j.topicmap.BaseName)) {
				String name = ((org.tm4j.topicmap.BaseName)tmo).getData();
				
				if (name != null) {
					return name;
				}
			}
			
			Collection c = topic.getNames();
			
			if (c != null) {
				Iterator it = c.iterator();
				
				while (it.hasNext()) {
					org.tm4j.topicmap.BaseName bn = (org.tm4j.topicmap.BaseName)it.next();

					String name = bn.getData();

					if (name != null) {
						return name;
					}
				}
			}
		}
		return "";
//		String name = tm.getUtils().getDisplayName( topic, EMPTY_SCOPE );
	}
	
	
	protected void createNewCreatedByAssociation(User creator, org.tm4j.topicmap.Topic creatableTopic)
				throws DuplicateObjectIDException, DuplicateResourceLocatorException, PropertyVetoException, LocatorResolutionException {
		
		if (creator == null) return;
		if (creatableTopic == null) return;

		org.tm4j.topicmap.Association tmAssociation = tm.createAssociation( null, null, this.getCreatedbyTopicType(), null );
		
		this.createSourceLocator( tmAssociation );

		org.tm4j.topicmap.Member memberTopic = tmAssociation.createMember(null);

		this.createSourceLocator( memberTopic );

		memberTopic.addPlayer( creatableTopic );

		org.tm4j.topicmap.Member memberUser = tmAssociation.createMember(null);

		this.createSourceLocator( memberUser );

		memberUser.setRoleSpec( this.getUserTopicType() );
		memberUser.addPlayer( ((UserImpl)creator).getWrappedTopic() );
	}


	protected org.tm4j.topicmap.Association getFirstAssociation(org.tm4j.topicmap.Topic tmTopic, org.tm4j.topicmap.Topic tmAssocType) {
		org.tm4j.topicmap.TopicMapUtils utils = this.getTopicMap().getUtils();
		Collection all = utils.getAssociations( tmTopic );
		if ((all != null) && (all.size() > 0)) {
			Collection associations = utils.getAssociationsOfType( tmAssocType, all );
			if (associations != null) {
				Iterator it = associations.iterator();
				if (it.hasNext()) {
					return (org.tm4j.topicmap.Association)it.next();
				}
			}
		}
		return null;
	}


	protected Collection getPlayers(org.tm4j.topicmap.Topic tmTopic, org.tm4j.topicmap.Topic tmAssocType, org.tm4j.topicmap.Topic tmRoleType) {
		org.tm4j.topicmap.Association association = this.getFirstAssociation( tmTopic, tmAssocType );
		return this.getPlayers( association, tmRoleType );
	}


	protected Collection getPlayers(org.tm4j.topicmap.Association tmAssoc, org.tm4j.topicmap.Topic tmRoleType) {
		Collection members = this.getMembersOfRole( tmAssoc, tmRoleType );
		if (members != null) {
			Iterator itm = members.iterator();
			if (itm.hasNext()) {
				org.tm4j.topicmap.Member member = (org.tm4j.topicmap.Member)itm.next();
				return member.getPlayers();
			}
		}
		return null;
	}
	
	
	private Collection getPlayers(org.tm4j.topicmap.Association tmAssoc, Locator tmLocator) {
		Collection members = this.getTopicMap().getUtils().getMembersOfType( tmAssoc, tmLocator );
		if (members != null) {
			Iterator itm = members.iterator();
			if (itm.hasNext()) {
				org.tm4j.topicmap.Member member = (org.tm4j.topicmap.Member)itm.next();
				return member.getPlayers();
			}
		}
		return null;
	}


	protected org.tm4j.topicmap.Topic getFirstPlayer(org.tm4j.topicmap.Association tmAssoc, org.tm4j.topicmap.Topic tmRoleType) {
		Collection players = this.getPlayers( tmAssoc, tmRoleType );
		if (players != null) {
			Iterator itp = players.iterator();
			if (itp.hasNext()) {
				return (org.tm4j.topicmap.Topic)itp.next();
			}
		}
		return null;
	}
	
	
	private org.tm4j.topicmap.Topic getFirstPlayer(org.tm4j.topicmap.Association tmAssoc, Locator tmLocator) {
		Collection players = this.getPlayers( tmAssoc, tmLocator );
		if (players != null) {
			Iterator itp = players.iterator();
			if (itp.hasNext()) {
				return (org.tm4j.topicmap.Topic)itp.next();
			}
		}
		return null;
	}


	protected org.tm4j.topicmap.Topic getFirstPlayer(org.tm4j.topicmap.Topic tmTopic, org.tm4j.topicmap.Topic tmAssocType, org.tm4j.topicmap.Topic tmRoleType) {
		Collection players = this.getPlayers( tmTopic, tmAssocType, tmRoleType );
		if (players != null) {
			Iterator itp = players.iterator();
			if (itp.hasNext()) {
				return (org.tm4j.topicmap.Topic)itp.next();
			}
		}
		return null;
	}


	protected User getCreator(org.tm4j.topicmap.Topic creatableTopic) {
		org.tm4j.topicmap.Topic first = this.getFirstPlayer( creatableTopic, this.getCreatedbyTopicType(), this.getUserTopicType() );
		Object obj = TM4JObjectWrapper.wrap( this, first );
		if ((obj != null) && (obj instanceof User)) {
			return (User)obj;
		}
		else {
			return null;
		}
	}


	protected User getCreator(org.tm4j.topicmap.Association assoc) {
		org.tm4j.topicmap.Topic player = this.getFirstPlayer( assoc, this.getCreatedbyTopicType() );
		if (player != null) {
			Object obj = TM4JObjectWrapper.wrap( this, player );
			if (obj instanceof User) {
				return (User)obj;
			}
		}
		return null;
	}


	protected Attributeable getAttributeable(org.tm4j.topicmap.Topic attributeableTopic) {
		org.tm4j.topicmap.Topic first = this.getFirstPlayer( attributeableTopic, this.getAttributeTopicType(), null );
		Object obj = TM4JObjectWrapper.wrap( this, first );
		if ((obj != null) && (obj instanceof Attributeable)) {
			return (Attributeable)obj;
		}
		else {
			return null;
		}
	}


	protected Collection getMembersOfRole(org.tm4j.topicmap.Association assoc, org.tm4j.topicmap.Topic roleSpec) {
		// WORKAROUND for TM4J 0.9.7, that crashes if the association has no members at all or if a null argument is passed for roleSpec
		if ((assoc != null) && (assoc.getMembers().size() > 0)) {
			if (roleSpec != null) {
				return assoc.getMembersOfRole(roleSpec);
			}
			else {
				Collection ret = new ArrayList();
				Iterator   it  = assoc.getMembers().iterator();

				while (it.hasNext()) {
					org.tm4j.topicmap.Member m = (org.tm4j.topicmap.Member)it.next();

					if (m.getRoleSpec() == null) {
						ret.add( m );
					}
				}

				return Collections.unmodifiableCollection( ret );
			}
		}
		return Collections.EMPTY_SET;
	}


	private Object getHandler(String title, Properties config, LoggingManager logging, String handlerClassName)
			throws ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException {

		Class handlerClass = Class.forName(handlerClassName);
		
		Object handler;
		
		try {
			try {
				java.lang.reflect.Constructor constr = handlerClass.getDeclaredConstructor(new Class[] { Properties.class, LoggingManager.class, OntologyManager.class });
	
				handler = constr.newInstance(new Object[] { config, logging, this });
			}
			catch (NoSuchMethodException e) {
	
				try {
					java.lang.reflect.Constructor constr = handlerClass.getDeclaredConstructor(new Class[] { LoggingManager.class, OntologyManager.class });
	
					handler = constr.newInstance(new Object[] { logging, this });
				}
				catch (NoSuchMethodException e2) {
	
					try {
						java.lang.reflect.Constructor constr = handlerClass.getDeclaredConstructor(new Class[] { OntologyManager.class });
	
						handler = constr.newInstance(new Object[] { this });
					}
					catch (NoSuchMethodException e3) {
	
						handler = handlerClass.newInstance();
					}
				}
			}
		}
		catch (ClassCastException e) {
			logging.warn(title+ " " + handlerClass + " is not compatible with this implementation " + this.getClass());
			handler = null;
		}
		catch (InvocationTargetException e) {
			logging.warn(title+ " " + handlerClass + " is not compatible with this implementation " + this.getClass());
			handler = null;
		}
		
		return handler;
	}


	private org.tm4j.topicmap.Association getSuperTypeAssociation(org.tm4j.topicmap.Topic topic, org.tm4j.topicmap.Topic superclass, Locator subpsi, Locator superpsi ) {
		assert(topic != null);
		assert(superclass != null);
		
		Collection subclassMembers = this.getTopicMap().getUtils().getMembersOfType( topic, subpsi );
		
		if ((subclassMembers == null) || subclassMembers.isEmpty()) return null;

		Iterator it = subclassMembers.iterator();

		while (it.hasNext()) {
			org.tm4j.topicmap.Member subclassMember = (org.tm4j.topicmap.Member)it.next();

			org.tm4j.topicmap.Association assoc = subclassMember.getParent();

			Collection superclassMembers = this.getTopicMap().getUtils().getMembersOfType( assoc, superpsi );

			Iterator its = superclassMembers.iterator();

			while (its.hasNext()) {
				org.tm4j.topicmap.Member superclassMember = (org.tm4j.topicmap.Member)its.next();
				
				Collection players = superclassMember.getPlayers();

				if (players != null) {
					Iterator itp = players.iterator();
					
					while (itp.hasNext()) {
						org.tm4j.topicmap.Topic t = (org.tm4j.topicmap.Topic)itp.next();
						
						if (superclass.equals( t )) {
							return assoc;
						}
					}
				}
			}
		}

		return null;
	}


	protected org.tm4j.topicmap.Association getSuperclassAssociation(org.tm4j.topicmap.Topic topic, org.tm4j.topicmap.Topic superclass) {
		return this.getSuperTypeAssociation( topic, superclass, this.getSubclassPSI(), this.getSuperclassPSI() );
	}


	protected org.tm4j.topicmap.Association getTypeAssociation(org.tm4j.topicmap.Topic topic, org.tm4j.topicmap.Topic type) {
		return this.getSuperTypeAssociation( topic, type, this.getInstancePSI(), this.getClassPSI() );
	}


	protected Association addSuperclass(User adder, Topic topic, Topic superclass, boolean fire) throws OntologyManagerException {
		assert(topic != null);
		
		if (superclass == null) return null;
		
		org.tm4j.topicmap.Topic tmSuperclass = ((TopicImpl)superclass).getWrappedTopic();
		org.tm4j.topicmap.Topic tmSubclass =   ((TopicImpl)topic).getWrappedTopic();
		
		org.tm4j.topicmap.Association tmAssoc = this.getSuperclassAssociation( tmSubclass, tmSuperclass );
		
		if (tmAssoc == null) {
			try {
				tmAssoc = this.getTopicMap().createAssociation( null, null, this.getSuperclasssubclassTopicType(), null );

				this.createSourceLocator( tmAssoc );

				org.tm4j.topicmap.Member superclassMember = tmAssoc.createMember( null );
				org.tm4j.topicmap.Member subclassMember   = tmAssoc.createMember( null );

				this.createSourceLocator( superclassMember );
				this.createSourceLocator( subclassMember );
				
				superclassMember.setRoleSpec( this.getSuperclassTopicType() );
				subclassMember.setRoleSpec( this.getSubclassTopicType() );
				
				superclassMember.addPlayer( tmSuperclass );
				subclassMember.addPlayer( tmSubclass );

				if (fire) {
					((TopicImpl)topic).incVersion();
					
					if (adder != null) {
						if (this.useClasses()) {
							this.fireTopicClassChanged( topic, adder, null );
						}
						else {
							this.fireConceptTypeChanged( topic, adder, null );
						}
					}
				}
			}
			catch (DuplicateObjectIDException e) {
				throw new OntologyManagerDuplicateIdException( e );
			}
			catch (Exception e) {
				throw new OntologyManagerException( e );
			}
		}
		
		Association assoc = TM4JObjectWrapper.wrap( this, tmAssoc );
		
		return assoc;
	}
	
	
	protected Association addInstanceType(User adder, Topic topic, Topic type, boolean fire) throws OntologyManagerException {
		assert(topic != null);
		
		if (type == null) return null;
		
		org.tm4j.topicmap.Topic tmType     = ((TopicImpl)type).getWrappedTopic();
		org.tm4j.topicmap.Topic tmInstance = ((TopicImpl)topic).getWrappedTopic();
		
		org.tm4j.topicmap.Association tmAssoc = this.getTypeAssociation( tmInstance, tmType );
		
		if (tmAssoc == null) {
			try {
				tmAssoc = this.getTopicMap().createAssociation( null, null, this.getClassInstanceTopicType(), null );

				this.createSourceLocator( tmAssoc );

				org.tm4j.topicmap.Member typeMember     = tmAssoc.createMember( null );
				org.tm4j.topicmap.Member instanceMember = tmAssoc.createMember( null );

				this.createSourceLocator( typeMember );
				this.createSourceLocator( instanceMember );
				
				typeMember.setRoleSpec( this.getClassTopicType() );
				instanceMember.setRoleSpec( this.getInstanceTopicType() );
				
				typeMember.addPlayer( tmType );
				instanceMember.addPlayer( tmInstance );

				if (fire) {
					((TopicImpl)topic).incVersion();
					
					if (adder != null) {
						this.fireConceptTypeChanged( topic, adder, null );
					}
				}
			}
			catch (DuplicateObjectIDException e) {
				throw new OntologyManagerDuplicateIdException( e );
			}
			catch (Exception e) {
				throw new OntologyManagerException( e );
			}
		}
		
		Association assoc = TM4JObjectWrapper.wrap( this, tmAssoc );
		
		return assoc;
	}


	public Topic createTopic(User creator, String id, String name, Topic type) throws OntologyManagerException,DynamicRightsManagerException {
		if ((name != null) && name.startsWith(INTERNAL_TYPE_PREFIX)) {
			throw new OntologyManagerException("Topic name must not start with internal prefix " + INTERNAL_TYPE_PREFIX);
		}

		if (this.useClasses() && (type != null) && !type.isClassTopic()) {
			throw new OntologyManagerClassException("Cannot create instance of non-class type " + type);
		}

		if (this.drm != null) {
			this.drm.checkCreateTopic( creator, type );
		}

		try {
			if (type != null) {
				org.tm4j.topicmap.Topic tmType = ((TopicImpl)type).getWrappedTopic();
				
				if (isInternalTopicType(tmType)) {
					throw new OntologyManagerException("Cannot create instance of internal type " + tmType.getID());
				}
			}

			org.tm4j.topicmap.Topic tmTopic = this.createNewTopic( id, name, null );
			
			this.createNewCreatedByAssociation( creator, tmTopic );

			TopicImpl ti = (TopicImpl)TM4JObjectWrapper.wrap( this, tmTopic );
			
			if (this.useClasses()) {
				this.addInstanceType( creator, ti, type, false );
			}
			else {
				this.addSuperclass( creator, ti, type, false );
			}
			
			ti.incVersion();

			fireTopicCreated(ti,creator);

			return ti;
		}
		catch (DuplicateObjectIDException e) {
			throw new OntologyManagerDuplicateIdException(e);
		}
		catch (Exception e) {
			throw new OntologyManagerException(e);
		}
	}


	public Topic createClassTopic(User creator, String id, String name, Topic cls) throws OntologyManagerException,DynamicRightsManagerException {
		if (!this.useClasses()) {
			throw new UnsupportedOperationException();
		}

		if (name.startsWith(INTERNAL_TYPE_PREFIX)) {
			throw new OntologyManagerException("Topic name must not start with internal prefix " + INTERNAL_TYPE_PREFIX);
		}
		
		if ((cls != null) && !cls.isClassTopic()) {
			throw new OntologyManagerClassException("Cannot create subclass of non-class " + cls);
		}

		if (this.drm != null) {
			this.drm.checkCreateClassTopic( creator, cls );
		}

		try {
			if (cls != null) {
				org.tm4j.topicmap.Topic tmType = ((TopicImpl)cls).getWrappedTopic();
				
				if (isInternalTopicType(tmType)) {
					throw new OntologyManagerException("Cannot create subclass of internal topic " + tmType.getID());
				}
			}

			org.tm4j.topicmap.Topic tmTopic = this.createNewTopic( id, name, null );
			
			this.createNewCreatedByAssociation( creator, tmTopic );

			TopicImpl ti = (TopicImpl)TM4JObjectWrapper.wrap( this, tmTopic );
			
			ti.setClassTopic( true );
			
			this.addSuperclass( creator, ti, cls, false );
			
			ti.incVersion();

			fireTopicCreated(ti,creator);

			return ti;
		}
		catch (DuplicateObjectIDException e) {
			throw new OntologyManagerDuplicateIdException(e);
		}
		catch (Exception e) {
			throw new OntologyManagerException(e);
		}
	}


	public Topic createInternalTopic(User creator, String id, String name, Topic type) throws OntologyManagerException {
		if (name.startsWith(INTERNAL_TYPE_PREFIX)) {
			throw new OntologyManagerException("Topic name must not start with internal prefix " + INTERNAL_TYPE_PREFIX);
		}

		if (id != null) {
			org.tm4j.topicmap.Topic tmTopic = this.getTopicMap().getTopicByID( id );
			if (tmTopic != null) {
				Object obj = TM4JObjectWrapper.wrap( this, tmTopic );
				if ((obj instanceof Topic) && tmTopic.isOfType( this.getInternalTopicType() )) {
					return (Topic)obj;
				}
				else {
					throw new OntologyManagerDuplicateIdException( id + " is already in use for a non-internal topic");
				}
			}
		}

		if (this.useClasses() && (type != null) && !type.isClassTopic()) {
			throw new OntologyManagerClassException("Cannot create internal topic of non-class type " + type);
		}

		try {
			org.tm4j.topicmap.Topic tmTopic = this.createNewTopic( id, name, this.getInternalTopicType() );
			
			this.createNewCreatedByAssociation( creator, tmTopic );

			TopicImpl ti = (TopicImpl)TM4JObjectWrapper.wrap( this, tmTopic );

			if (this.useClasses()) {
				ti.setClassTopic( true );
			}
			
			this.addSuperclass( creator, ti, type, false );
			
			ti.incVersion();

			if (creator != null) {
				fireTopicCreated(ti,creator);
			}

			return ti;
		}
		catch (DuplicateObjectIDException e) {
			throw new OntologyManagerDuplicateIdException(e);
		}
		catch (Exception e) {
			throw new OntologyManagerException(e);
		}
	}
	
	
	public Attribute createAttribute(User creator, String id, String name, String value, Topic type) throws OntologyManagerException,DynamicRightsManagerException {
		if (name.startsWith(INTERNAL_TYPE_PREFIX)) {
			throw new OntologyManagerException("Attribute name must not start with internal prefix " + INTERNAL_TYPE_PREFIX);
		}
		
		if ((type != null) && this.useClasses() && !type.isClassTopic()) {
			throw new OntologyManagerClassException("Type " + type + " is not a class");
		}

		if (this.drm != null) {
			this.drm.checkCreateAttribute( creator, type );
		}
		
		org.tm4j.topicmap.Topic tmTopic;

		AttributeImpl a;
		
		try {
			org.tm4j.topicmap.Topic tmType = null;
			
			if (type != null) {
				tmType = ((TopicImpl)type).getWrappedTopic();
				
//				if (isInternalTopicType(tmType)) {
//					throw new OntologyManagerException("Cannot create instance of internal type " + tmType.getID());
//				}
			}

			tmTopic = this.createNewTopic( id, name, tmType );
			
			tmTopic.addType( this.getAttributeTopicType() );

			this.createNewCreatedByAssociation( creator, tmTopic );

			a = (AttributeImpl)TM4JObjectWrapper.wrap( this, tmTopic );

			a.initValue( value );
			a.incVersion();
		}
		catch (DuplicateObjectIDException e) {
			throw new OntologyManagerDuplicateIdException(e);
		}
		catch (Exception e) {
			throw new OntologyManagerException(e);
		}

		return a;
	}
	
	
	protected void addAttribute(User adder, Topic topic, Attribute attr) throws OntologyManagerException {
		assert( topic != null );
		assert( attr != null );

		try {
			org.tm4j.topicmap.Association assoc = this.getFirstAssociation( ((TopicImpl)topic).getWrappedTopic(), this.getAttributeTopicType() );
			
			org.tm4j.topicmap.Member memberAttr = null;

			if (assoc == null) {
				assoc = tm.createAssociation( null, null, this.getAttributeTopicType(), null );
				
				this.createSourceLocator( assoc );

				org.tm4j.topicmap.Member memberTopic = assoc.createMember(null);

				this.createSourceLocator( memberTopic );

				memberTopic.addPlayer( ((TopicImpl)topic).getWrappedTopic() );
			}
			else {
				Collection c = this.getMembersOfRole( assoc, this.getAttributeTopicType() );
				
				if (c != null) {
					Iterator it = c.iterator();
					
					if (it.hasNext()) {
						memberAttr = (org.tm4j.topicmap.Member)it.next();
					}
				}
			}
			
			if (memberAttr == null) {
				memberAttr = assoc.createMember(null);

				this.createSourceLocator( memberAttr );

				memberAttr.setRoleSpec( this.getAttributeTopicType() );
			}

			memberAttr.addPlayer( ((AttributeImpl)attr).getWrappedTopic() );
			
			this.fireAttributeAdded( attr, adder, topic );
		}
		catch (DuplicateObjectIDException e) {
			throw new OntologyManagerDuplicateIdException( e );
		}
		catch (Exception e) {
			throw new OntologyManagerException( e );
		}
	}
	
	
	protected void removeAttribute(User remover, Topic topic, Attribute attr) throws OntologyManagerException {
		assert( topic != null );
		assert( attr != null );
		
		org.tm4j.topicmap.Association assoc = this.getFirstAssociation( ((TopicImpl)topic).getWrappedTopic(), this.getAttributeTopicType() );
		
		if (assoc == null) return;

		org.tm4j.topicmap.Member memberAttr = null;

		Collection c = this.getMembersOfRole( assoc, this.getAttributeTopicType() );
		
		if (c != null) {
			Iterator it = c.iterator();
			
			if (it.hasNext()) {
				memberAttr = (org.tm4j.topicmap.Member)it.next();
			}
		}
		
		if (memberAttr == null) return;
		
		try {
			RatingManager rm = this.getRatingManager();
			if (rm != null) {
				rm.deleteRatings( attr );
			}
			
			memberAttr.removePlayer( ((AttributeImpl)attr).getWrappedTopic() );
			
			// TODO: Attr-Topic löschen?
			
			this.fireAttributeRemoved( attr, remover, topic );
		}
		catch (Exception e) {
			throw new OntologyManagerException( e );
		}
	}


	public Association createAssociation(User creator, String id, String name, Topic type) throws OntologyManagerException,DynamicRightsManagerException {
		if (name.startsWith(INTERNAL_TYPE_PREFIX)) {
			throw new OntologyManagerException("Association name must not start with internal prefix " + INTERNAL_TYPE_PREFIX);
		}
		
		if ((type != null) && this.useClasses() && !type.isClassTopic()) {
			throw new OntologyManagerClassException("Type " + type + " is not a class");
		}
		
		if (this.drm != null) {
			this.drm.checkCreateAssociation( creator, type );
		}

		org.tm4j.topicmap.Association tmAssociation;
		
		try {
			org.tm4j.topicmap.Topic tmType = null;
			
			if (type != null) {
				tmType = ((TopicImpl)type).getWrappedTopic();
				
//				if (isInternalTopicType(tmType)) {
//					throw new OntologyManagerException("Cannot create instance of internal type " + tmType.getID());
//				}
			}

			tmAssociation = tm.createAssociation( id, null, tmType, null );
			
			this.createSourceLocator( tmAssociation );
			
			// add creator (single player of member with internal type "created-by"):
			
			org.tm4j.topicmap.Member member = tmAssociation.createMember( null, null, this.getCreatedbyTopicType(), Collections.EMPTY_SET );

			this.createSourceLocator( member );
			
			member.addPlayer( ((UserImpl)creator).getWrappedTopic() );
		}
		catch (DuplicateObjectIDException e) {
			throw new OntologyManagerDuplicateIdException(e);
		}
		catch (Exception e) {
			throw new OntologyManagerException(e);
		}
		
		AssociationImpl ai = TM4JObjectWrapper.wrap( this, tmAssociation );

		if ((name != null) && (name.length() > 0)) {
			ai.setName( null, name );
		}

		ai.incVersion();

		fireAssociationCreated(ai,creator);

		return ai;
	}


	public Rateable checkInternalRateable(Rateable rateable) {
		if (rateable instanceof AssociationImpl) {
			AssociationImpl assoc = (AssociationImpl)rateable;
			org.tm4j.topicmap.Association tmAssoc = assoc.getWrappedAssociation();
			if (tmAssoc.isOfType( this.getSuperclasssubclassTopicType() )) {
				org.tm4j.topicmap.Topic tsub   = this.getFirstPlayer( tmAssoc, this.getSubclassPSI() );
				org.tm4j.topicmap.Topic tsuper = this.getFirstPlayer( tmAssoc, this.getSuperclassPSI() );

				if ((tsub != null) && (tsuper != null)) {
					Object objSub   = TM4JObjectWrapper.wrap( this, tsub );
					Object objSuper = TM4JObjectWrapper.wrap( this, tsuper );

					if ((objSub instanceof Topic) && (objSuper instanceof Topic)) {
						if (this.useClasses()) {
							return new SuperSubclassImpl( (Topic)objSub, (Topic)objSuper, assoc );
						}
						else {
							return new TypeInstanceImpl( (Topic)objSub, (Topic)objSuper, assoc );
						}
					}
				}
			}
			else if (tmAssoc.isOfType( this.getClassInstanceTopicType() )) {
				org.tm4j.topicmap.Topic tinst = this.getFirstPlayer( tmAssoc, this.getInstancePSI() );
				org.tm4j.topicmap.Topic ttype = this.getFirstPlayer( tmAssoc, this.getClassPSI() );

				if ((tinst != null) && (ttype != null)) {
					Object objInst = TM4JObjectWrapper.wrap( this, tinst );
					Object objType = TM4JObjectWrapper.wrap( this, ttype );
					
					if ((objInst instanceof Topic) && (objType instanceof Topic)) {
						return new TypeInstanceImpl( (Topic)objInst, (Topic)objType, assoc );
					}
				}
			}
			else if (tmAssoc.isOfType( this.getSynonymoustermsTopicType() )) {
				Collection players = this.getPlayers( tmAssoc, this.getSynonymPSI() );

				if (players != null) {
					Iterator itp = players.iterator();
					
					org.tm4j.topicmap.Topic t1 = null;
					org.tm4j.topicmap.Topic t2 = null;

					while (itp.hasNext()) {
						org.tm4j.topicmap.Topic t = (org.tm4j.topicmap.Topic)itp.next();
						
						if (t1 == null) {
							t1 = t;
						}
						else {
							t2 = t;
							break;
						}
					}
					
					if ((t1 != null) && (t2 != null)) {
						Object tobj1 = TM4JObjectWrapper.wrap( this, t1 );
						Object tobj2 = TM4JObjectWrapper.wrap( this, t2 );
						
						if ((tobj1 instanceof Topic) && (tobj2 instanceof Topic)) {
							return new SynonymImpl( (Topic)tobj1, (Topic)tobj2, assoc );
						}
					}
				}
			}
		}
		return rateable;
	}


	public void deleteTopic(Topic topic) throws OntologyManagerException {
		if (topic == null) return;
		try {
			// TODO: Attribute löschen?
			((TopicImpl)topic).getWrappedTopic().destroy();
		}
		catch (Exception e) {
			throw new OntologyManagerException( e );
		}
	}


	public void deleteAssociation(Association assoc) throws OntologyManagerException {
		if (assoc == null) return;
		org.tm4j.topicmap.Association tmAssoc = ((AssociationImpl)assoc).getWrappedAssociation();
		try {
			tmAssoc.setMembers( null );
			tmAssoc.dispose();
			tmAssoc.destroy();
		}
		catch (Exception e) {
			throw new OntologyManagerException( e );
		}
	}


	public void deleteAttribute(Attribute attr) throws OntologyManagerException {
		if (attr == null) return;
		try {
			// TODO: Member im Topic löschen (falls noch vorhanden)?
			((AttributeImpl)attr).getWrappedTopic().destroy();
		}
		catch (Exception e) {
			throw new OntologyManagerException( e );
		}
	}


	public QueryEngine getQueryEngine() {
		return queryEngine;
	}
	
	
	public void addOntologyListener(OntologyListener listener) {
		listenerList.add(OntologyListener.class, listener);
	}


	public void removeOntologyListener(OntologyListener listener) {
		listenerList.remove(OntologyListener.class, listener);
	}


	protected void fireTopicCreated(Topic topic, User creator) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), topic, creator);
				((OntologyListener) listeners[i + 1]).topicCreated(event);
			}
		}
	}
	
	
	protected void fireAssociationCreated(Association assoc, User creator) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), assoc, creator);
				((OntologyListener) listeners[i + 1]).associationCreated(event);
			}
		}
	}
	
	
	protected void fireConceptCommentChanged(Concept concept, User changer, String oldComment) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), concept, changer, oldComment, null, null, null, null);
				((OntologyListener) listeners[i + 1]).conceptCommentChanged(event);
			}
		}
	}
	
	
	protected void fireConceptNameChanged(Concept concept, User changer, String oldName) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), concept, changer, oldName, null, null, null, null);
				((OntologyListener) listeners[i + 1]).conceptNameChanged(event);
			}
		}
	}


	protected void fireConceptTypeChanged(Concept concept, User changer, Topic oldTopic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), concept, changer, null, oldTopic, null, null, null);
				((OntologyListener) listeners[i + 1]).conceptTypeChanged(event);
			}
		}
	}
	
	
	protected void fireTopicClassChanged(Topic topic, User changer, Topic oldTopic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), topic, changer, null, oldTopic, null, null, null);
				((OntologyListener) listeners[i + 1]).topicClassChanged(event);
			}
		}
	}
	
	
	protected void fireAssociationTopicAdded(Association assoc, User adder, Topic topic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), assoc, adder, null, null, topic, null, null);
				((OntologyListener) listeners[i + 1]).associationTopicAdded(event);
			}
		}
	}
	
	
	protected void fireAssociationTopicRemoved(Association assoc, User remover, Topic topic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), assoc, remover, null, null, topic, null, null);
				((OntologyListener) listeners[i + 1]).associationTopicRemoved(event);
			}
		}
	}
	
	
	protected void fireOccurrenceCreated(Occurrence occ, User creator, Topic topic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), topic, creator, null, null, null, occ, null);
				((OntologyListener) listeners[i + 1]).occurrenceCreated(event);
			}
		}
	}
	
	
	protected void fireOccurrenceChanged(Occurrence occ, User changer, Topic topic, String oldData) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), topic, changer, oldData, null, null, occ, null);
				((OntologyListener) listeners[i + 1]).occurrenceChanged(event);
			}
		}
	}
	
	
	protected void fireOccurrenceRemoved(Occurrence occ, User remover, Topic topic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), topic, remover, null, null, null, occ, null);
				((OntologyListener) listeners[i + 1]).occurrenceRemoved(event);
			}
		}
	}

	
	protected void fireAttributeAdded(Attribute attr, User adder, Topic topic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), attr, adder, null, null, topic, null, null);
				((OntologyListener) listeners[i + 1]).attributeAdded(event);
			}
		}
	}
	
	
	protected void fireAttributeValueChanged(Attribute attr, User changer, String oldValue) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), attr, changer, oldValue, null, null, null, null);
				((OntologyListener) listeners[i + 1]).attributeValueChanged(event);
			}
		}
	}
	
	
	protected void fireAttributeRemoved(Attribute attr, User remover, Topic topic) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), attr, remover, null, null, topic, null, null);
				((OntologyListener) listeners[i + 1]).attributeRemoved(event);
			}
		}
	}


	protected void fireSynonymAdded(Synonym synonym, User adder) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), synonym.getTopic(), adder, null, null, synonym.getSynonym(), null, synonym);
				((OntologyListener) listeners[i + 1]).synonymAdded(event);
			}
		}
	}
	
	
	protected void fireSynonymRemoved(Synonym synonym, User remover) {
		OntologyEvent event = null;
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == OntologyListener.class) {
				if (event == null) event = new OntologyEvent(getConsensusFoundationManager(), synonym.getTopic(), remover, null, null, synonym.getSynonym(), null, synonym);
				((OntologyListener) listeners[i + 1]).synonymRemoved(event);
			}
		}
	}

	
	public Object openTransaction(Object scope) throws OntologyManagerException  {
		// we need this for Tolog queries where TM4J forgets to create its own transactions...
		if (provider.isTransactionOpen()) {
			// if a transaction is already open, we mustn't close it in closeTransaction (see below)
			return null;
		}
		else {
			try {
				return provider.openTransaction();
			}
			catch (Exception e) {
				throw new OntologyManagerTransactionException(e);
			}
		}
	}


	public void abortTransaction(Object scope, Object ta) {
		// close the transaction only if we opened it (see above)
		if ((ta != null) && ((ProviderTransaction)ta).isOpen()) {
			try {
				((ProviderTransaction)ta).abort();
			}
			catch (Exception e) { }
		}
	}


	public void commitTransaction(Object scope, Object ta) throws OntologyManagerException {
		// close the transaction only if we opened it (see above)
		if ((ta != null) && ((ProviderTransaction)ta).isOpen()) {
			try {
				((ProviderTransaction)ta).commit();
			}
			catch (ProviderTransactionException e) {
				throw new OntologyManagerTransactionException(e);
			}
		}
	}


	public void rollbackTransaction(Object scope, Object ta) throws OntologyManagerException {
		// close the transaction only if we opened it (see above)
		if ((ta != null) && ((ProviderTransaction)ta).isOpen()) {
			try {
				((ProviderTransaction)ta).rollback();
			}
			catch (ProviderTransactionException e) {
				throw new OntologyManagerTransactionException(e);
			}
		}
	}


	public TopicMap getTopicMap() {
		return tm;
	}


	protected void setTopicMap(TopicMap tm) {
		this.tm = tm;
	}
	
	
	protected TopicMapProvider getTopicMapProvider() {
		return this.provider;
	}
	
	
	protected Locator getTopicMapBase() {
		return this.base;
	}
	
	
	protected Locator getSuperclassSubclassPSI() {
		return this.psiSuperclassSublass;
	}
	
	
	protected Locator getSuperclassPSI() {
		return this.psiSuperclass;
	}
	
	
	protected Locator getSubclassPSI() {
		return this.psiSublass;
	}
	
	
	protected Locator getClassInstancePSI() {
		return this.psiClassInstance;
	}
	
	
	protected Locator getClassPSI() {
		return this.psiClass;
	}
	
	
	protected Locator getInstancePSI() {
		return this.psiInstance;
	}

	
	protected Locator getSynonymousTermsPSI() {
		return this.psiSynonymousTerms;
	}

	
	protected Locator getSynonymPSI() {
		return this.psiSynonym;
	}


	protected org.tm4j.topicmap.Topic getInternalTopicType() {
		return internalTopicType;
	}

	
	protected org.tm4j.topicmap.Topic getUserTopicType() {
		return userTopicType;
	}

	
	protected org.tm4j.topicmap.Topic getCommentTopicType() {
		return commentTopicType;
	}

	
	protected org.tm4j.topicmap.Topic getCreatedbyTopicType() {
		return createdbyTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getNameTopicType() {
		return nameTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getAttributeTopicType() {
		return attributeTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getValueTopicType() {
		return valueTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getVersionTopicType() {
		return versionTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getSynonymoustermsTopicType() {
		return synonymoustermsTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getSynonymTopicType() {
		return synonymTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getSuperclasssubclassTopicType() {
		return superclasssubclassTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getSuperclassTopicType() {
		return superclassTopicType;
	}
	
	
	protected org.tm4j.topicmap.Topic getSubclassTopicType() {
		return subclassTopicType;
	}


	protected org.tm4j.topicmap.Topic getClassInstanceTopicType() {
		return classInstanceTopicType;
	}


	protected org.tm4j.topicmap.Topic getClassTopicType() {
		return classTopicType;
	}


	protected org.tm4j.topicmap.Topic getInstanceTopicType() {
		return instanceTopicType;
	}


	protected boolean useClasses() {
		return this.useclasses;
	}


	public void addExportHandler(ExportHandler handler) {
		if (handler != null) {
			exporters.put( handler.getExtension().toLowerCase(), handler );
		}
	}


	public ExportHandler getExportHandler(String format) {
		format = extractLowercaseExtension(format);
		if (format != null) {
			return (ExportHandler)exporters.get( format );
		}
		else {
			return null;
		}
	}


	public Iterator getExportHandlers() {
		return exporters.values().iterator();
	}

	
	public int getExportHandlerCount() {
		return exporters.size();
	}


	public void addImportHandler(ImportHandler handler) {
		if (handler != null) {
			importers.put( handler.getExtension().toLowerCase(), handler );
			if (handler.canMerge()) {
				mergers.put( handler.getExtension().toLowerCase(), handler );
			}
		}
	}


	public ImportHandler getImportHandler(String format) {
		format = extractLowercaseExtension(format);
		if (format != null) {
			return (ImportHandler)importers.get( format );
		}
		else {
			return null;
		}
	}

	
	public ImportHandler getMergeHandler(String format) {
		format = extractLowercaseExtension(format);
		if (format != null) {
			return (ImportHandler)mergers.get( format );
		}
		else {
			return null;
		}
	}

	
	public Iterator getImportHandlers() {
		return importers.values().iterator();
	}


	public Iterator getMergeHandlers() {
		return mergers.values().iterator();
	}

	
	public int getImportHandlerCount() {
		return importers.size();
	}

	
	public int getMergeHandlerCount() {
		return mergers.size();
	}

	
	private String extractLowercaseExtension(String filename) {
		if (filename != null) {
			int idx = filename.lastIndexOf(".");
			if (idx >= 0) {
				filename = filename.substring( idx + 1 );
			}
			filename = filename.trim();
			if (filename.length() > 0) {
				return filename.toLowerCase();
			}
		}
		return null;
	}


	public boolean reorg() throws OntologyManagerException {
		// unused topics, associations etc. should be removed from the topic map here
		// maybe optimizations... but no changes to the structure of the topic map itself
		return true;
	}


	public String getVersion() {
		return "2006-06-14 tm (" + this.getClass().getName() + ")";
	}
}
