/*
 * $Id$
 *
 * JSDAI(TM), a way to implement STEP, ISO 10303
 * Copyright (C) 1997-2008, LKSoftWare GmbH, Germany
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License
 * version 3 as published by the Free Software Foundation (AGPL v3).
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * JSDAI is a registered trademark of LKSoftWare GmbH, Germany
 * This software is also available under commercial licenses.
 * See also http://www.jsdai.net/
 */

package jsdai.lang;

import java.lang.reflect.Field;
import java.util.*;

import jsdai.dictionary.*;
import jsdai.mapping.*;
import jsdai.util.Debug;
import jsdai.util.LangUtils;
import jsdai.util.MappingOperations;
import jsdai.util.SimpleOperations;

//  This class only provides bridge between interface of mapping funcitions
// and its implementation.

/**
	The mapping schema defines a mapping of a source express schema to a target express schema.
	For STEP AP protocols the ARM is the source schema, while the AIM is the target schema.
	The static methods in this class uses population of mapping schema to convert information
	between source and target.
	<p>Read only methods to convert information from target to source:
	<ul>
		<li>{@link #findEntityMappings findEntityMappings}
		<li>{@link #testSourceEntity testSourceMapping}
		<li>{@link #testMappedEntity testMappedEntity}
		<li>{@link #testSourceAttribute testSourceAttribute}
		<li>{@link #testMappedAttribute testMappedAttribute}
		<li>{@link #getMappedAttribute getMappedAttribute}
	</ul>
	This class contains static methods for working with mapping data. You may perform operation
	like test, set attribute, get attribute for source schema entities, but with target schema
	instances. All mapping constraints and path for attributes are tested or generated by the
	methods event a user do not know about them.
	<p> In future these methods will be moved to instance methods of jsdai.lang.EEntity.
*/
class Mapping {
	static ThreadLocal emo = new ThreadLocal() 
		{
			protected Object initialValue() { return new EntityMapping(); }
		};

	static EntityMapping getEntityMapping(SdaiSession session, ASdaiModel targetDomain,
										  ASdaiModel mappingDomain) throws SdaiException {
		EntityMapping em = (EntityMapping)emo.get();
    	em.initialize(session, targetDomain, mappingDomain, null);
		return em;
	}

	static private AEntity_mapping findEntityMappings(jsdai.lang.EEntity targetInstance, ASdaiModel targetDomain, ASdaiModel mappingDomain, int mode) throws jsdai.lang.SdaiException {
		AEntity_mapping posibleEntities = new AEntity_mapping();
		ArrayList subtypes = new ArrayList();
		EEntity_definition aimEntity = targetInstance.getInstanceType();
		LangUtils.findSupertypes(aimEntity, subtypes);
		if(aimEntity.getComplex(null)) {
			// Special more complicated logic for complex type
			Set compatibleComplexTypes = new HashSet();
			AEntity_or_view_definition parents = aimEntity.getGeneric_supertypes(null);
			for(SdaiIterator i = parents.createIterator(); i.next(); ) {
				EEntity_definition parent = (EEntity_definition) parents.getCurrentMember(i);
				AEntity parentSubtypes = new AEntity();
				CEntity_or_view_definition.usedinGeneric_supertypes(null,
						parent, mappingDomain, parentSubtypes);
				for(SdaiIterator j = parentSubtypes.createIterator(); j.next(); ) {
					EEntity_definition parentSubtype =
						(EEntity_definition) parentSubtypes.getCurrentMemberEntity(j);
					if(parentSubtype != aimEntity && parentSubtype.getComplex(null)
							&& !compatibleComplexTypes.contains(parentSubtypes)
							&& targetInstance.isKindOf(parentSubtype)) {
						compatibleComplexTypes.add(parentSubtype);
					}
				}
			}
			subtypes.addAll(compatibleComplexTypes);
		}
		subtypes.add(aimEntity);
		int n = subtypes.size();
		for (int j = 0; j < n; j++) {
			aimEntity = (EEntity_definition)subtypes.get(j);
			CEntity_mapping.usedinTarget(null, aimEntity, mappingDomain, posibleEntities);
			AAttribute attributes = new AAttribute();
			CAttribute.usedinParent_entity(null, aimEntity, mappingDomain, attributes);
			jsdai.lang.SdaiIterator i = attributes.createIterator();
			while (i.next()) {
				EAttribute attribute = attributes.getCurrentMember(i);
				CEntity_mapping.usedinTarget(null, attribute, mappingDomain, posibleEntities);
			}
		}

		jsdai.lang.SdaiIterator iterator = posibleEntities.createIterator();
		while (iterator.next()) {
			if (!testMappedEntity(targetInstance, posibleEntities.getCurrentMember(iterator), targetDomain, mappingDomain, mode)) {
				iterator.remove();
				iterator.previous();
			}
		}

		return posibleEntities;
	}

	/**
		Finds all entity_mappings for which the mappings fit with the given target instance.
  		Excluding mappings for abstract types and mappings where there are mappings of subtypes.
		Having entity mapping an application can directly access the corresponding source entities (ARM).
		@param targetInstance an instance which is mapping of some entity_mappings. These entity mappings are returned.
      @param targetDomain a domain of the target instance and instances related to it,
          as defined in the reference path. It may be null. Then owning model of
          targetInstance will be used as domain.
		@param mappingDomain a domain for mapping constraints, target and source
          schemas. It may be null. Then owning model of sourceEntity will be used as
          domain.
      @param mappings the mappings that was found.
      @param mode 0 - no restrictions, 1 - retured mappings are restricted to most specific.
		@return number of entity mappings that meet constraints.
  */
	static public int findEntityMappings(jsdai.lang.EEntity targetInstance, ASdaiModel targetDomain, ASdaiModel mappingDomain, AEntity_mapping mappings, int mode) throws jsdai.lang.SdaiException {
		int rv = 0;
		AEntity_mapping allMappings = findEntityMappings(targetInstance, targetDomain, mappingDomain, mode);
		SdaiIterator i = allMappings.createIterator();
		while (i.next()) {
			EEntity_mapping m1 = allMappings.getCurrentMember(i);
			i.remove();
			i.previous();
			EEntity_definition e1 = m1.getSource(null);
			boolean addThisMapping = true;
   		if ((mode == EEntity.MOST_SPECIFC_ENTITY) || (mode == EEntity.MANDATORY_ATTRIBUTES_SET)) {
			   addThisMapping = e1.getInstantiable(null);
				SdaiIterator j = allMappings.createIterator();
				while (j.next() && addThisMapping) {
					EEntity_mapping m2 = allMappings.getCurrentMember(j);
					EEntity_definition e2 = m2.getSource(null);
					if (m1 != m2) {
						addThisMapping = !((CEntity_definition)e2).isSubtypeOf(e1) || !((CEntity_definition)e2).getInstantiable(null);
					}
    			}
    			if (addThisMapping) {
    				mappings.attachIterator(j);
    				while (j.next() && addThisMapping) {
						EEntity_mapping m2 = mappings.getCurrentMember(j);
						EEntity_definition e2 = m2.getSource(null);
						if (m1 != m2) {
							addThisMapping = !((CEntity_definition)e2).isSubtypeOf(e1) || !((CEntity_definition)e2).getInstantiable(null);
						} else {
							addThisMapping = m1 != m2;
						}
					}
				}
			}
			if (addThisMapping) {
				mappings.addByIndex(mappings.getMemberCount() + 1, m1);
				rv++;
			}
		}
		return rv;
	}

	static long findMostSpecificMappingsMillis = 0;//FIXME:time
	static public AEntity_mapping findMostSpecificMappings(EEntity instance, ASdaiModel targetDomain,
	ASdaiModel mappingDomain, AEntity_mapping baseMappings, int mode) throws SdaiException {
		long startMillis = System.currentTimeMillis();//FIXME:time
		Set mostSpecificMappingSet = new HashSet();
		AEntity_mapping mostSpecificMappings = new AEntity_mapping();
		SdaiIterator baseMappingIter = baseMappings.createIterator();
		while(baseMappingIter.next()) {
			EEntity_mapping baseMapping = baseMappings.getCurrentMember(baseMappingIter);
			CEntityMappingBase baseMappingBase = (CEntityMappingBase)baseMapping;
			baseMappingBase.findSubtypeMappings(mappingDomain);
			int tokens[] = new int[baseMappingBase.armSubtypeLevelSize];
			if(testMappedEntity(instance, baseMapping, targetDomain, mappingDomain, mode)) {
				addMostSpecificMappings(baseMappingBase.armSubtypeLevels, instance, baseMapping,
										targetDomain, mappingDomain, mostSpecificMappings, 
										mostSpecificMappingSet, mode, tokens, 1);
			}
		}
		findMostSpecificMappingsMillis += System.currentTimeMillis() - startMillis;//FIXME:time
		return mostSpecificMappings;
	}

	static public AEntity_mapping findMostSpecificMappings(AEntity instances, ASdaiModel targetDomain, 
	ASdaiModel mappingDomain, AEntity_mapping baseMappings, int mode) throws SdaiException {
		long startMillis = System.currentTimeMillis();//FIXME:time
		int baseMappingsSize = baseMappings.getMemberCount();
		List[] mappingLevels = new List[baseMappingsSize];
		int[][] mappingLevelTokens = new int[baseMappingsSize][];
		SdaiIterator baseMappingIter = baseMappings.createIterator();
		int mappingLevelsIdx = 0;
		while(baseMappingIter.next()) {
			EEntity_mapping baseMapping = baseMappings.getCurrentMember(baseMappingIter);
			CEntityMappingBase baseMappingBase = (CEntityMappingBase)baseMapping;
			baseMappingBase.findSubtypeMappings(mappingDomain);
			mappingLevelTokens[mappingLevelsIdx] = new int[baseMappingBase.armSubtypeLevelSize];
			mappingLevels[mappingLevelsIdx] = baseMappingBase.armSubtypeLevels;
			mappingLevelsIdx++;
		}
		AEntity_mapping mostSpecificMappings = new AEntity_mapping();
		Set mostSpecificMappingSet = new HashSet();
		int token = 1;
		SdaiIterator instanceIter = instances.createIterator();
		while(instanceIter.next()) {
			EEntity instance = (EEntity)instances.getCurrentMemberObject(instanceIter);
			int addedNum = 0;
			baseMappingIter = baseMappings.createIterator();
			mappingLevelsIdx = 0;
			while(baseMappingIter.next()) {
				EEntity_mapping baseMapping = baseMappings.getCurrentMember(baseMappingIter);
				if (testMappedEntity(instance, baseMapping, targetDomain, mappingDomain, mode)) {
					mostSpecificMappingSet.clear();
					addedNum += addMostSpecificMappings(mappingLevels[mappingLevelsIdx], instance, 
														baseMapping, targetDomain, mappingDomain, 
														mostSpecificMappings, mostSpecificMappingSet, 
														mode, mappingLevelTokens[mappingLevelsIdx], 
														token++);
				}
				mappingLevelsIdx++;
			}
			if(addedNum != 0) {
				while(--addedNum != 0) {
					instances.addBefore(instanceIter, instance);
				}
			} else {
				instanceIter.remove();
				instanceIter.previous();
			}
		}
		findMostSpecificMappingsMillis += System.currentTimeMillis() - startMillis;//FIXME:time
		return mostSpecificMappings;
	}

	static public EMappedARMEntity buildMappedInstance(EEntity aimInstance, SdaiContext context,
													   EEntity_definition mappedInstanceType)
	throws SdaiException {
		EMappedARMEntity existingArm = 
			CMappedARMEntity.findLinkedMappedInstance(aimInstance, mappedInstanceType);
		if(existingArm != null) {
			return existingArm;
		} else {
			AEntity_mapping allMappings = findEntityMappings(aimInstance, context.domain, 
														  context.mappingDomain, 0);
			AEntity_mapping mappings = findMostSpecificMappings(aimInstance, context.domain, 
																context.mappingDomain, allMappings, 0);
			SdaiIterator mappingIter = mappings.createIterator();
			while(mappingIter.next()) {
				EEntity_mapping mapping = mappings.getCurrentMember(mappingIter);
				EEntity_definition armEntity = mapping.getSource(null);
				if(mappedInstanceType == null || (armEntity == mappedInstanceType
				|| ((CEntityDefinition)armEntity).isSubtypeOf(mappedInstanceType))) {
					EMappedARMEntity armInstance = 
						(EMappedARMEntity)context.mappedWorkingModel.createEntityInstance(armEntity);
					armInstance.setAimInstance(aimInstance);
					((CMappedARMEntity)armInstance).setAttributeState(CMappedARMEntity.ATTRIBUTES_UNKNOWN);
					return armInstance;
				}
			}
			return null;
			//throw new SdaiException(SdaiException.EI_NEXS, "No valid mapped instance found");
		}
		
	}

	static EMappedARMEntity buildMappedInstance(EEntity aimInstance, SdaiContext context,
												EEntity_mapping mappedInstanceType)
	throws SdaiException {
		EMappedARMEntity existingArm = 
			CMappedARMEntity.findLinkedMappedInstance(aimInstance, mappedInstanceType.getSource(null));
		if(existingArm != null) {
			return existingArm;
		} else {
			EEntity_definition armEntity = mappedInstanceType.getSource(null);
			EMappedARMEntity armInstance = 
				(EMappedARMEntity)context.mappedWorkingModel.createEntityInstance(armEntity);
			armInstance.setAimInstance(aimInstance);
			((CMappedARMEntity)armInstance).setAttributeState(CMappedARMEntity.ATTRIBUTES_UNKNOWN);
			return armInstance;
		}
	}

	/**
		Tests whether a given instance does fit to a specified source entity. It returns null if it
     does not fit. Otherwise, it returns an aggregate containing all the mating
     entity_mappings for the specified source entity. This aggregate contains at least one
     matting entity_mapping. If there are several mapping alternatives aggregate may
     contain several instances.
		@param targetInstance an instance, which is tested to be mapping of
          sourceEntity.
		@param sourceEntity an entity which mapping is tested
		@param targetDomain a target instances domain where to search instances to
          satisfy mapping constraints.  It may be null. Then owning model of
          targetInstance will be used as domain.
		@param mappingDomain a domain for mapping constraints, target and source
          schemas. It may be null. Then owning model of sourceEntity will be used as
          domain.
		@return an aggregate of mapping_entities that may be mapping to target entity, null if
          there is no mappings of this targetInstance for specified target.
	*/
	static public AEntity_mapping testSourceEntity(jsdai.lang.EEntity targetInstance, EEntity_definition sourceEntity, ASdaiModel targetDomain, ASdaiModel mappingDomain, int mode) throws jsdai.lang.SdaiException {
		AEntity_mapping rv = new AEntity_mapping();
		if ((targetInstance == null) || (sourceEntity == null)) {
			return null;
		}
		AEntity_mapping aema = new AEntity_mapping();
		jsdai.mapping.CEntity_mapping.usedinSource(null, sourceEntity, mappingDomain, aema);
		jsdai.lang.SdaiIterator i = aema.createIterator();
		boolean f = true;
		while(i.next()) {
			if (testMappedEntity(targetInstance, (EEntity_mapping)aema.getCurrentMember(i), targetDomain, mappingDomain, mode)) {
				rv.addByIndex(1, (EEntity_mapping)aema.getCurrentMember(i));
				f = false;
			}
		}
		if (f) {
			return null;
		}
		return rv;
	}

	/**
		Test whether a given targetInstance does fit to the mapping of entity_mapping.
		@param targetInstance an instance, which is tested to be mapping of
          sourceEntity.
		@param sourceEntity an entity_mapping which mapping is tested
		@param targetDomain a target instances domain where to search instances to
          satisfy mapping constraints.  It may be null. Then owning model of
          targetInstance will be used as domain.
		@param mappingDomain a domain for mapping constraints, target and source
          schemas. It may be null. Then owning model of sourceEntity will be used as
          domain.
		@return true if this target instance is mapping of specified entity_mapping, false if is
          otherwise.
	*/
	static public boolean testMappedEntity(jsdai.lang.EEntity targetInstance, EEntity_mapping sourceEntity, ASdaiModel targetDomain, ASdaiModel mappingDomain, int mode) throws jsdai.lang.SdaiException {
		EntityMapping em = 
			getEntityMapping(targetInstance.findEntityInstanceSdaiModel().repository.session,
							 targetDomain, mappingDomain);
		return em.testMappedEntity(targetInstance, sourceEntity, mode);
	}

	/**
		Tests mapping of source attribute. Specified target instance should be mapping of
		sourceAttribute parent entity. It returns aggregate of attribute_mappings which
		reference path corresponds to targetInstance. If source attribute has, several
		attribute_mappings return aggregate may contain some of them. If there is no
		attribute_mappings witch reference path is met, then method returns null.
		@param targetInstance an instance to which parent source entity of attribute is
          mapped
		@param sourceAttribute source attribute which mapping is tested
		@param targetDomain a target instances domain where to search instances to
          satisfy mapping constraints.  It may be null. Then owning model of
          targetInstance will be used as domain.
		@param mappingDomain a domain for mapping constraints, target and source
          schemas. It may be null. Then owning model of sourceAttribute will be used as
          domain.
		@return aggregate of attribute mappings that are mappings to target instance, null if no
          attribute mappings found.
	*/
	static public AGeneric_attribute_mapping testSourceAttribute(jsdai.lang.EEntity targetInstance, EAttribute sourceAttribute, ASdaiModel targetDomain, ASdaiModel mappingDomain, int mode) throws jsdai.lang.SdaiException {
		AGeneric_attribute_mapping rv = new AGeneric_attribute_mapping();
		if ((targetInstance == null) || (sourceAttribute == null)) {
			return null;
		}
		AGeneric_attribute_mapping aema = new AGeneric_attribute_mapping();
		jsdai.mapping.CGeneric_attribute_mapping.usedinSource(null, sourceAttribute, mappingDomain, aema);
		jsdai.lang.SdaiIterator i = aema.createIterator();
		boolean f = false;
		while(i.next()) {
			if (testMappedAttribute(targetInstance, (EGeneric_attribute_mapping)aema.getCurrentMember(i), targetDomain, mappingDomain, mode)) {
				//rv.addUnordered((EGeneric_attribute_mapping)aema.getCurrentMember(i));
				rv.addByIndex(1, (EGeneric_attribute_mapping)aema.getCurrentMember(i));
				f = true;
			}
		}
		if (!f) {
			rv = null;
		}
		return rv;
	}

	/**
		Test whether a given targetInstance does fit to the contraints of attribute_mapping.
		@param targetInstance an instance to which parent entity_mapping of
          attribute_mapping is mapped
		@param sourceAttribute source attribute which mapping is tested
		@param targetDomain a target instances domain where to search instances to
          satisfy mapping constraints.  It may be null. Then owning model of
          targetInstance will be used as domain.
		@param mappingDomain a domain for mapping constraints, target and source
          schemas. It may be null. Then owning model of sourceAttribute will be used as
          domain.
		@return true if this target instance has mapping of specified attribute mapping, false
          otherwise.
	*/
	static public boolean testMappedAttribute(jsdai.lang.EEntity targetInstance, EGeneric_attribute_mapping sourceAttribute, ASdaiModel targetDomain, ASdaiModel mappingDomain, int mode) throws jsdai.lang.SdaiException {
		EntityMapping em = 
			getEntityMapping(targetInstance.findEntityInstanceSdaiModel().repository.session,
							 targetDomain, mappingDomain);
		return em.testMappedAttribute(targetInstance, sourceAttribute, mode);
	}

	/**
		Returns aggregate of values for source attribute. If mapping of attribute has several
		alternatives and these alternatives are satisfied by targetInstance, then this aggregate
		may contain several values.
		@param targetInstance an instance to which parent entity of attribute is
          mapped
		@param sourceAttribute source attribute which mappings are returned
		@param targetDomain a target instances domain where to search instances to
          satisfy mapping constraints.  It may be null. Then owning model of
          targetInstance will be used as domain.
		@param mappingDomain a domain for mapping constraints, target and source
          schemas. It may be null. Then owning model of sourceAttribute will be used as
          domain.
		@return aggregate of sourceAttribute values. They are target instances or simple
          values (like string, integer) that are mappings of sourceAttribute.
	*/
	static public Object[] getSourceAttribute(jsdai.lang.EEntity targetInstance, EAttribute sourceAttribute, ASdaiModel targetDomain, ASdaiModel mappingDomain, int mode) throws jsdai.lang.SdaiException {
		EntityMapping em = 
			getEntityMapping(targetInstance.findEntityInstanceSdaiModel().repository.session,
							 targetDomain, mappingDomain);
		return em.getSourceAttribute(targetInstance, sourceAttribute, mode);
	}

	/**
		Returns value of attribute_mapping. The target_instance must be mapping of
		entity_mapping of which attribute_mapping is get. It returns value of element to which
		attribute_mapping is mapped. If mapping of attribute_mapping is not met it returns
		null.
		@param targetInstance target instance to which attribute parent
          entity_mapping is mapped
		@param sourceAttribute source attribute which mapping are tested
		@param targetDomain a target instances domain where to search instances to
          satisfy mapping constraints.  It may be null. Then owning model of
          targetInstance will be used as domain.
		@param mappingDomain a domain for mapping constraints, target and source
          schemas. It may be null. Then owning model of sourceAttribute will be used as
          domain.
		@return value of source attribute, null if attribute_mapping reference path is not met.
	*/
	static public Object getMappedAttribute(jsdai.lang.EEntity targetInstance, EGeneric_attribute_mapping sourceAttribute, ASdaiModel targetDomain, ASdaiModel mappingDomain, int mode) throws jsdai.lang.SdaiException {
		EntityMapping em = 
			getEntityMapping(targetInstance.findEntityInstanceSdaiModel().repository.session,
							 targetDomain, mappingDomain);
		return em.getMappedAttribute(targetInstance, sourceAttribute, mode);
	}

	// add mode to check Explicit attributes
	static public AEntity findMappingInstances(SdaiModel model, EEntity_definition sourceEntity, 
	ASdaiModel targetDomain, ASdaiModel mappingDomain, AEntity_mapping instanceMappings, int mode)
	throws SdaiException {
		if ((model == null) || (sourceEntity == null)) {
			return null;
		}
		AEntity rv = new AEntity();
		AEntity_mapping aema = new AEntity_mapping();
		jsdai.mapping.CEntity_mapping.usedinSource(null, sourceEntity, mappingDomain, aema);
		jsdai.lang.SdaiIterator i = aema.createIterator();
		while(i.next()) {
			AEntity t = findMappingInstances(model, (EEntity_mapping)aema.getCurrentMember(i), targetDomain, mappingDomain, instanceMappings, mode);
			SdaiIterator j = t.createIterator();
			while (j.next()) {
				EEntity e = (EEntity)t.getCurrentMemberObject(j);
				if (!rv.isMember(e)) {
					rv.addByIndex(1, e);
				}
			}
		}
		return rv;
	}

	static public AEntity findMappingInstances(SdaiModel model, EEntity_mapping entityMapping,
	ASdaiModel targetDomain, ASdaiModel mappingDomain, AEntity_mapping instanceMappings, int mode) 
	throws SdaiException {
		if (entityMapping == null) {
			return null;
		}
		if (model == null) {
			return null;
		}

		CEntityMappingBase entityMappingBase = (CEntityMappingBase)entityMapping;
		int tokens[] = null;
		if(instanceMappings != null) {
			entityMappingBase.findSubtypeMappings(mappingDomain);
			tokens = new int[entityMappingBase.armSubtypeLevelSize];
		}
		EEntity targetDefinition = entityMapping.getTarget(null);
		AEntity rv = new AEntity();
		if (targetDefinition instanceof EAttribute) {
			targetDefinition = ((EAttribute)targetDefinition).getParent_entity(null);
		}
		if(model.underlying_schema.owning_model.schemaData
		   .findEntityExtentIndex((CEntity_definition)targetDefinition) >= 0) {

			Set instanceMappingSet = new HashSet();
			EEntity_definition targetEntDefinition = (EEntity_definition)targetDefinition;
			AEntity instances;
			if(targetEntDefinition.getComplex(null)) {
				AEntity_or_view_definition supertypes = targetEntDefinition.getGeneric_supertypes(null);
				instances = model.getInstances((EEntity_definition) supertypes.getByIndexEntity(1));
			} else {
				instances = model.getInstances(targetEntDefinition);
				targetEntDefinition = null;
			}
			SdaiIterator i = instances.createIterator();
			int token = 1;
			while (i.next()) {
				EEntity e = (EEntity)instances.getCurrentMemberObject(i);
				if(targetEntDefinition != null && !e.isKindOf(targetEntDefinition)) {
					continue;
				}
				if (testMappedEntity(e, entityMapping, targetDomain, mappingDomain, mode)) {
					if(instanceMappings != null) {
						instanceMappingSet.clear();
						int addedNum = addMostSpecificMappings(entityMappingBase.armSubtypeLevels, 
															   e, entityMapping, targetDomain,
															   mappingDomain, instanceMappings,
															   instanceMappingSet, mode, tokens, token++);
						int rvMemberCount = rv.getMemberCount();
						while(addedNum-- != 0) {
							rv.addByIndex(++rvMemberCount, e);
						}
					} else {
						rv.addByIndex(rv.getMemberCount() + 1, e);
					}
				}
			}
		}
		return rv;
	}

	static public AEntity findMappingInstances(AEntity instances, EEntity_definition sourceEntity, 
	ASdaiModel targetDomain, ASdaiModel mappingDomain, AEntity_mapping instanceMappings, int mode)
	throws SdaiException {
		AEntity rv = new AEntity();
		AEntity_mapping aema = new AEntity_mapping();
		jsdai.mapping.CEntity_mapping.usedinSource(null, sourceEntity, mappingDomain, aema);
		jsdai.lang.SdaiIterator i = aema.createIterator();
		while(i.next()) {
			AEntity t = findMappingInstances(instances, (EEntity_mapping)aema.getCurrentMember(i), 
											 targetDomain, mappingDomain, instanceMappings, mode);
			SdaiIterator j = t.createIterator();
			while (j.next()) {
				EEntity e = (EEntity)t.getCurrentMemberObject(j);
				if (!rv.isMember(e)) {
					rv.addByIndex(1, e);
				}
			}
		}
		return rv;
	}

	static public AEntity findMappingInstances(AEntity instances, EEntity_mapping entityMapping,
	ASdaiModel targetDomain, ASdaiModel mappingDomain, AEntity_mapping instanceMappings, int mode) 
	throws SdaiException {
		CEntityMappingBase entityMappingBase = (CEntityMappingBase)entityMapping;
		int tokens[] = null;
		if(instanceMappings != null) {
			entityMappingBase.findSubtypeMappings(mappingDomain);
			tokens = new int[entityMappingBase.armSubtypeLevelSize];
		}
		EEntity target = entityMapping.getTarget(null);
		AEntity rv = new AEntity();
		if (target instanceof EAttribute) {
			target = ((EAttribute)target).getParent_entity(null);
		}
		Set instanceMappingSet = new HashSet();
		EEntity_definition targetDefinition = (EEntity_definition)target;
		SdaiIterator i = instances.createIterator();
		int token = 1;
		while (i.next()) {
			EEntity e = (EEntity)instances.getCurrentMemberObject(i);
			if(!e.isKindOf(targetDefinition)) continue;
			if(testMappedEntity(e, entityMapping, targetDomain, mappingDomain, mode)) {
				if(instanceMappings != null) {
					instanceMappingSet.clear();
					int addedNum = addMostSpecificMappings(entityMappingBase.armSubtypeLevels, e, 
														   entityMapping, targetDomain, mappingDomain, 
														   instanceMappings, instanceMappingSet, mode, 
														   tokens, token++);
					int rvMemberCount = rv.getMemberCount();
					while(addedNum-- != 0) {
						rv.addByIndex(++rvMemberCount, e);
					}
				} else {
					rv.addByIndex(rv.getMemberCount() + 1, e);
				}
			}
		}
		return rv;
	}

	
	static long addMostSpecificMappingsMillis = 0;//FIXME:time
	static private int addMostSpecificMappings(List armSubtypeLevels, EEntity e, 
	EEntity_mapping entityMapping, ASdaiModel targetDomain, ASdaiModel mappingDomain, 
	AEntity_mapping instanceMappings, Set instanceMappingSet, int mode, int[] tokens, int token)
	throws SdaiException {
		long startMillis = System.currentTimeMillis();//FIXME:time
		int addedNum = 0;
		int instanceMappingsMemberCount = instanceMappings.getMemberCount();
		boolean specific = false;
		ListIterator armSubtypeLevelsIter =
			armSubtypeLevels.listIterator(armSubtypeLevels.size());
		while(armSubtypeLevelsIter.hasPrevious()) {
			List levelSubtypeNodes = (List)armSubtypeLevelsIter.previous();
			ListIterator levelSubtypeNodesIter = levelSubtypeNodes.listIterator();
			while(levelSubtypeNodesIter.hasNext()) {
				CEntityMappingBase.SubtypeNode subtypeNode = 
					(CEntityMappingBase.SubtypeNode)levelSubtypeNodesIter.next();
				if(tokens[subtypeNode.index] != token) {
					Iterator mappingsIter = subtypeNode.mappings.iterator();
					while(mappingsIter.hasNext()) {
						EEntity_mapping mapping = (EEntity_mapping)mappingsIter.next();
						if(testMappedEntity(e, mapping, targetDomain, mappingDomain, mode)) {
							if(!instanceMappingSet.contains(mapping)) {
								addedNum++;
								instanceMappings.addByIndex(instanceMappingsMemberCount + addedNum,
															mapping);
								instanceMappingSet.add(mapping);
							}
							specific = true;
							// Mark supertype nodes
							CEntityMappingBase.SubtypeNode parentNode = subtypeNode.parent;
							while(parentNode != null) {
								tokens[parentNode.index] = token;
								parentNode = parentNode.parent;
							}
						} else {
							tokens[subtypeNode.index] = token;
						}
					}
				}
			}
		}
		if(!specific && entityMapping.getSource(null).getInstantiable(null)
		   &&  !instanceMappingSet.contains(entityMapping)) {
			addedNum++;
			instanceMappings.addByIndex(instanceMappingsMemberCount + addedNum, entityMapping);
			instanceMappingSet.add(entityMapping);
		}
		addMostSpecificMappingsMillis += System.currentTimeMillis() - startMillis;//FIXME:time
		return addedNum;
	}
					
	public static AEntity findMappedUsers(EEntity instance, EEntity_mapping sourceType, AAttribute_mapping attributeMapping,
	ASdaiModel dataDomain, ASdaiModel mappingDomain, AAttribute_mapping users, int mode)
	throws SdaiException {
		EntityMapping em = 
			getEntityMapping(instance.findEntityInstanceSdaiModel().repository.session,
							 dataDomain, mappingDomain);
		return em.findMappedUsers(instance, sourceType, attributeMapping, users, mode);
	}

	public static AEntity findMappingInstances(ASdaiModel models, EEntity_definition sourceEntity, 
	ASdaiModel targetDomain, ASdaiModel mappingDomain, AEntity_mapping instanceMappings, int mode) 
	throws SdaiException {
		if (models == null) {
			return null;
		}
		if (sourceEntity == null) {
			return null;
		}
		SdaiIterator i = models.createIterator();
		AEntity rv = null;
		while (i.next()) {
			SdaiModel model = models.getCurrentMember(i);
			AEntity tmp = findMappingInstances(model,sourceEntity, targetDomain, mappingDomain, instanceMappings, mode);
			if (rv != null) {
				SimpleOperations.appendAggregateToAggregate(rv, tmp);
			} else {
				rv = tmp;
			}
		}
		return rv;
	}

	public static AEntity findMappingInstances(ASdaiModel models, EEntity_mapping entityMapping, 
	ASdaiModel targetDomain, ASdaiModel mappingDomain, AEntity_mapping instanceMappings, int mode) 
	throws SdaiException {
		if (models == null) {
			return null;
		}
		if (entityMapping == null) {
			return null;
		}
		SdaiIterator i = models.createIterator();
		AEntity rv = null;
		while (i.next()) {
			SdaiModel model = models.getCurrentMember(i);
			AEntity tmp = findMappingInstances(model, entityMapping, targetDomain, mappingDomain, instanceMappings, mode);
			if (rv != null) {
				SimpleOperations.appendAggregateToAggregate(rv, tmp);
			} else {
				rv = tmp;
			}
		}
		return rv;
	}

	public static EAttribute_mapping[] findInversesForMapping(EEntity_definition definition, ASdaiModel domain) throws SdaiException {
		HashSet result = new HashSet();
		EExplicit_attribute attrs[] = SimpleOperations.findAttributesForDomainDefinition(definition, domain);
		ArrayList a = new ArrayList();
		SimpleOperations.addArrayToVector(a, attrs);
		for (int i = 0; i < attrs.length; i++) {
			AAttribute_mapping mappings = new AAttribute_mapping();
			CAttribute_mapping.usedinSource(null, attrs[i], domain, mappings);
			SimpleOperations.addArrayToVector(result, LangUtils.aggregateToArray(mappings));
		}
		Iterator it = result.iterator();
		while (it.hasNext()) {
			EAttribute_mapping am = (EAttribute_mapping)it.next();
		}
		return (EAttribute_mapping[])result.toArray(new EAttribute_mapping[result.size()]);
	}

// 	public static AEntity findMappedUsers(EEntity instance, EEntity_mapping mentity, AAttribute_mapping attribute,
// 			ASdaiModel targetDomain, ASdaiModel mappingDomain, AAttribute_mapping result2) throws SdaiException
// 	{
// 		EEntity_definition source = mentity.getSource(null);
// 		return  findMappingInversesForInstance(instance, source, targetDomain, mappingDomain,
// 			findInversesForMapping(source, mappingDomain), result2);
// 	}

	public static AEntity findMappingInversesForInstance(EEntity instance, EEntity_definition arm,
			ASdaiModel targetDomain, ASdaiModel mappingDomain, EAttribute_mapping mattrs[], AAttribute_mapping result2) throws SdaiException
	{
		AEntity result = new AEntity();
		SdaiModel model = instance.findEntityInstanceSdaiModel();
		for (int i = 0; i < mattrs.length; i++) {
			AEntity entities = model.findMappingInstances(mattrs[i].getParent_entity(null), targetDomain, mappingDomain, 0);
			SdaiIterator it_entities = entities.createIterator();
			while (it_entities.next()) {
				EEntity entity = entities.getCurrentMemberEntity(it_entities);
				Object value = getMappedAttributeObject(entity, mattrs[i], targetDomain, mappingDomain);
				if ((value == instance)) {
					result2.addByIndex(1, mattrs[i], null);
					result.addByIndex(1, entity);
				} else if (value instanceof AEntity) {
					AEntity aggr = (AEntity)value;
					SdaiIterator it_aggr = aggr.createIterator();
					while(it_aggr.next()) {
						EEntity member = aggr.getCurrentMemberEntity(it_aggr);
						if ((member == instance)) {
							result2.addByIndex(1, mattrs[i], null);
							result.addByIndex(1, entity);
						}
					}
				} else if (value instanceof ArrayList) {
					ArrayList arr = (ArrayList)value;
					for (int ii = 0; ii < arr.size(); ii++) {
						Object member = arr.get(ii);
						if ((member == instance)) {
							result2.addByIndex(1, mattrs[i], null);
							result.addByIndex(1, entity);
						}
					}
				} else if (value instanceof Object[]) {
					Object arr[] = (Object[])value;
					for (int ii = 0; ii < arr.length; ii++) {
						Object member = arr[ii];
						if ((member == instance)) {
							result2.addByIndex(1, mattrs[i], null);
							result.addByIndex(1, entity);
						}
					}
				}
			}
		}
		return result;
	}

	public static AEntity findMappingInversesForInstance(EEntity instance, EEntity_definition arm,
			ASdaiModel targetDomain, ASdaiModel mappingDomain, EAttribute attribute) throws SdaiException 	{
		EEntity_definition definition = attribute.getParent_entity(null);
		AAttribute_mapping aam = new AAttribute_mapping();
		CAttribute_mapping.usedinSource(null, attribute, null, aam);
		AEntity result = new AEntity();
		AAttribute_mapping aem = new AAttribute_mapping();
		AEntity inverses = findMappingInversesForInstance(instance, arm, targetDomain, mappingDomain, (EAttribute_mapping[])LangUtils.aggregateToArray(aam, AAttribute_mapping.class), aem);
		SdaiIterator it_inverses = inverses.createIterator();
		while (it_inverses.next()) {
			EEntity e = inverses.getCurrentMemberEntity(it_inverses);
			if (e.testMappedEntity(definition, targetDomain, mappingDomain, 0) != null) {
				result.addByIndex(result.getMemberCount()+1, e);
			}
		}
		return result;
	}

	public static void removeSubtypeInverses(AEntity instances, AAttribute_mapping attributes) throws SdaiException {
		HashSet remove  = new HashSet();
		Vector indexes = new Vector();
		for (int i = 1; i <= instances.getMemberCount(); i++) {
			EEntity instance = (EEntity)instances.getByIndexEntity(i);
			indexes.clear();
			int k = i;
			while (k != -1) {
				indexes.add(new Integer(k));
				k = SimpleOperations.indexInAggregate(instances, instance, k+1);
			}
			for (int j = 0; j < indexes.size(); j++) {
				for (int l = j+1; l < indexes.size(); l++) {
					int ij = ((Integer)indexes.get(j)).intValue();
					int il = ((Integer)indexes.get(l)).intValue();
					EEntity_definition dj = ((EAttribute_mapping)attributes.getByIndex(ij)).getParent_entity(null).getSource(null);
					EEntity_definition dl = ((EAttribute_mapping)attributes.getByIndex(il)).getParent_entity(null).getSource(null);
					if (dj.isSubtypeOf(dl)) {
						remove.add(new Integer(il));
					}
					if (dl.isSubtypeOf(dj)) {
						remove.add(new Integer(ij));
					}
				}
			}
		}
		Vector rr = new Vector(remove);
		Collections.sort(rr);
		Integer r[] = (Integer[])rr.toArray(new Integer[rr.size()]);
		for (int i = r.length-1; i > -1; i--) {
			int k = r[i].intValue();
			instances.removeByIndex(k);
			attributes.removeByIndex(k);
		}
	}

	public static Object getMappedAttributeObject(EEntity instance, EGeneric_attribute_mapping attribute,
					ASdaiModel targetDomain, ASdaiModel mappingDomain) throws SdaiException {
		Object result = null;
		if (instance.testMappedAttribute(attribute, targetDomain, mappingDomain, 0)) {
			result = instance.getMappedAttribute(attribute, targetDomain, mappingDomain, 0);
			if ((SimpleOperations.getAttributeDomain(attribute.getSource(null)) instanceof EAggregation_type) && !(result instanceof ArrayList)) {
				ArrayList a = new ArrayList();
				a.add(result);
				result = a;
			}
		}
		return result;
	}

	static public int findEntityMappings(EEntity instance, EEntity_definition sourceEntity,
				ASdaiModel dataDomain, ASdaiModel mappingDomain, AEntity_mapping mappings, int mode) throws SdaiException {
// 		if (sourceEntity == null) {
// 			return Mapping.findEntityMappings(instance, dataDomain, mappingDomain, mappings, mode);
// 		}
		//boolean rv = false;
		AEntity_definition subtypes = new AEntity_definition();
		sourceEntity.findEntityInstanceUsedin(CEntity_definition.attributeGeneric_supertypes(null), mappingDomain, subtypes);
		if (subtypes.getMemberCount() == 0) {
			sourceEntity.findEntityInstanceUsedin(CEntity_definition.attributeGeneric_supertypes(null), 
												  null, subtypes);
		}
		if (subtypes.getMemberCount() != 0) {
		   SdaiIterator it = subtypes.createIterator();
		   while (it.next()) {
				EEntity_definition subtype = subtypes.getCurrentMember(it);
				//rv = rv || (findEntityMappings(instance, subtype, dataDomain, mappingDomain, mappings, mode) != 0);
				findEntityMappings(instance, subtype, dataDomain, mappingDomain, mappings, mode);
		   }
		}
		//if (!rv) {
		   AEntity_mapping aem = testSourceEntity(instance, sourceEntity, dataDomain, mappingDomain, mode);
		   if (aem != null) {
				SimpleOperations.appendAggregateToAggregate(mappings, aem);
				//rv = true;
		   }
		//}
		return mappings.getMemberCount();
	}

	static boolean hasMappedAttribute(EEntity_mapping entityMapping, EAttribute attribute, ASdaiModel mappingDomain) throws SdaiException {
		AAttribute_mapping mappings = new AAttribute_mapping();
		boolean rv = CAttribute_mapping.usedinSource(null, attribute, mappingDomain, mappings) != 0;
		if (entityMapping != null) {
			SdaiIterator i = mappings.createIterator();
			while (i.next()) {
				EAttribute_mapping mapping = mappings.getCurrentMember(i);
				if (mapping.getParent_entity(null) == entityMapping) {
					rv = true;
					break;
				}
			}
		} else {
			rv = mappings.getMemberCount() != 0;
		}
		return rv;
	}
	
}
