package com.openMap1.mapper.actions;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.Vector;
import java.util.Hashtable;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.MenuManager;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.presentation.MapperEditor;
import com.openMap1.mapper.structures.MappableAssociation;
import com.openMap1.mapper.structures.StructureDefinition;
import com.openMap1.mapper.views.LabelledEClass;
import com.openMap1.mapper.views.WorkBenchUtil;
import com.openMap1.mapper.AssocEndMapping;
import com.openMap1.mapper.AssocMapping;
import com.openMap1.mapper.AttributeDef;
import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.MapperPackage;
import com.openMap1.mapper.MaxMult;
import com.openMap1.mapper.NodeDef;
import com.openMap1.mapper.ObjMapping;
/**
* Class to create mapper editor menu actions depending on the model class
* selected in the mapped class model view, and possibly on other criteria.
*
* @author robert
*
*/
public class AddMapperEditorActions {
private MapperEditor mapperEditor;
private MappedStructure mappedStructure;
private List<IAction> actionsForModelObject;
public List<IAction> actionsForModelObject() {return actionsForModelObject;}
private List<IContributionItem> subMenusForModelObject;
public List<IContributionItem> subMenusForModelObject() {return subMenusForModelObject;}
public AddMapperEditorActions(MapperEditor mapperEditor) {
this.mapperEditor = mapperEditor;
Object thing = mapperEditor.getEditingDomain().getResourceSet().getResources().get(0).getContents().get(0);
if (thing instanceof MappedStructure)
{
mappedStructure = (MappedStructure)thing;
}
else System.out.println("Not editing a mapped structure");
}
/**
* Create menu actions depending on the model class
* selected in the mapped class model view, and possibly on other criteria.
* @param domain the mapper editor editing domain
* @param object the object selected in the mapper editor
*/
public void createActionsForModelObject(EditingDomain domain, Object object, Object selectedFromClassModelView)
{
actionsForModelObject = new ArrayList<IAction>();
subMenusForModelObject = new ArrayList<IContributionItem>();
if (object instanceof ElementDef)
{
ElementDef el = (ElementDef)object;
createActionsForElement(domain, el, selectedFromClassModelView);
}
else if (object instanceof AttributeDef)
{
AttributeDef at = (AttributeDef)object;
createActionsForAttribute(domain, at, selectedFromClassModelView);
}
else if (object instanceof ObjMapping)
{
ObjMapping om = (ObjMapping)object;
createActionsForMapping(om);
}
}
/**
* Actions when an Element node is selected in the mapped structure tree
* @param domain the mapper editor editing domain
* @param el the Element node selected
* @param fromClassModel the object currently selected in the mapped class model view
*/
private void createActionsForElement(EditingDomain domain, ElementDef el, Object fromClassModel)
{
/* for any element of a complex type,
* which has not yet been expanded in the tree, add actions to expand it,
* or to create a new mapping set, or to link to an existing one. */
if ((el.getType() != null) && (!el.getType().equals(""))
&& (attachedStructure() != null))
{
if (!el.isExpanded()) actionsForModelObject.add(new ExtendElementTreeAction(domain,el,attachedStructure()));
actionsForModelObject.add(new CreateNewMappingSetForTypeAction(mapperEditor,domain,el,attachedStructure()));
actionsForModelObject.add(new ImportMappingSetForTypeAction(mapperEditor,domain,el));
}
/* If the ElementDef has any child ElementDefs of unbounded max multiplicity,
* add an option to add an AttributeDef with name ElementDefImpl.ELEMENT_POSITION_ATTRIBUTE
* to all its child elements */
if (hasUnboundedChildElements(el))
{
actionsForModelObject.add(new AddVirtualPositionAttributeAction(mapperEditor,domain,el));
}
EClass selectedClass = extractEClassFrom(fromClassModel);
// if a Class has been selected in the Class Model View, add the necessary mapping actions
if (selectedClass != null)
{
// always add an action to map to the class
String qualifiedName = ModelUtil.getQualifiedClassName(selectedClass);
String nextSubset = nextSubset(mappingRoot(el),qualifiedName);
actionsForModelObject.add(new MakeObjectMappingAction(domain, el, selectedClass.getName(),
selectedClass.getEPackage().getName(),nextSubset,""));
// for V3 Mappings to an RMIM class model, add a chain mapping menu item
if (fromClassModel instanceof LabelledEClass)
makeChainMappingAction(domain,el,(LabelledEClass)fromClassModel);
/* add Property and Association Mapping actions
only if there is already an object mapping to the selected class */
addPropertyMappingActions(domain, el, selectedClass, fromClassModel);
addAssociationMappingActions(domain, el, selectedClass,fromClassModel);
// add an auto-mapping sub-menu
addAutoMappingMenu(domain, el, selectedClass,fromClassModel);
}
}
/**
* @param el an ElementDef
* @return true if it has any child ElementDefs of unbounded max multiplicity,
* or if it has more than one child ElementDef
*/
private boolean hasUnboundedChildElements(ElementDef el)
{
boolean unbounded = false;
int children = 0;
for (Iterator<ElementDef> it = el.getChildElements().iterator();it.hasNext();)
{
ElementDef child = it.next();
if (child.getMaxMultiplicity() == MaxMult.UNBOUNDED) unbounded = true;
children++;
}
if (children > 1) unbounded = true;
return unbounded;
}
/**
* @param fromClassModel The object selected in the class model view is either an EClass (vanilla class model view)
* or a LabelledEClass (RMIM class model view)
* @return the EClass extracted from it, in either case
*/
private EClass extractEClassFrom(Object fromClassModel)
{
EClass selectedClass = null;
if (fromClassModel != null)
{
if (fromClassModel instanceof EClass)
selectedClass = (EClass)fromClassModel;
if (fromClassModel instanceof LabelledEClass)
selectedClass = ((LabelledEClass)fromClassModel).eClass();
}
return selectedClass;
}
/**
* Actions when an Attribute node is selected in the mapped structure tree
* @param domain the mapper editor editing domain
* @param at the Attribute node selected
* @param fromClassModel the object currently selected in the mapped class model view
*/
private void createActionsForAttribute(EditingDomain domain, AttributeDef at, Object fromClassModel)
{
EClass selectedClass = extractEClassFrom(fromClassModel);
// if a Class has been selected in the Class Model View, add the necessary mapping actions
if (selectedClass != null)
{
/* add Property and Association Mapping actions
only if there is already an object mapping to the selected class */
addPropertyMappingActions(domain, at, selectedClass,fromClassModel);
addAssociationMappingActions(domain, at, selectedClass,fromClassModel);
// add an auto-mapping sub-menu
addAutoMappingMenu(domain, at, selectedClass,fromClassModel);
}
}
private MappedStructure mappingRoot(NodeDef nd)
{
return ModelUtil.getMappedStructure(nd);
}
//------------------------------------------------------------------------------------------------
// Auto-Mapping (advance property mapping) Menu
//------------------------------------------------------------------------------------------------
private void addAutoMappingMenu(EditingDomain domain, NodeDef nd, EClass selectedClass, Object fromClassModel)
{
MenuManager autoSubMenu = new MenuManager("Pre-map");
subMenusForModelObject.add(autoSubMenu);
// add menu items to pre-map any property of the class, or of a superclass
Vector<String> allProperties = allPropertyNames(selectedClass,"");
for (Iterator<String> it = allProperties.iterator(); it.hasNext();)
{
String classProp = it.next(); // of the form 'class:property'
autoSubMenu.add(new MakePreMappingAction(domain, nd, classProp,selectedClass,fromClassModel,true));
}
// add a menu item to pre-map the class
String className = selectedClass.getName();
autoSubMenu.add(new MakePreMappingAction(domain, nd,className,selectedClass,fromClassModel,false));
}
//------------------------------------------------------------------------------------------------
// Property Mapping sub-menu
//------------------------------------------------------------------------------------------------
/**
* If there are any object mappings to the class selected in the mapped class model view,
* make actions to add a property mapping to that class on the selected structure node
* @param domain
* @param nd
* @param selectedClass
*/
private void addPropertyMappingActions(EditingDomain domain, NodeDef nd, EClass selectedClass, Object fromClassModel)
{
Vector<String> subsets = getMappedSubsets(mappingRoot(nd), fromClassModel);
if (subsets.size() > 0)
{
MenuManager propSubMenu = new MenuManager("Map Property");
subMenusForModelObject.add(propSubMenu);
for (Iterator<String> is = subsets.iterator();is.hasNext();)
{
String subset = is.next();
Vector<String> allProperties = allPropertyNames(selectedClass,subset);
addPseudoPropertyNames(selectedClass,nd,allProperties,subset);
for (Iterator<String> it = allProperties.iterator(); it.hasNext();)
{
String classProp = it.next(); // of the form 'class:property'
propSubMenu.add(new MakePropertyMappingAction(domain, nd, classProp,selectedClass,subset,""));
}
}
}
}
/**
* If there are any object mappings to the class selected in the mapped class model view,
* make actions to add any allowed association mapping to that class on the selected structure node
* @param domain
* @param nd
* @param selectedClass
*/
private void addAssociationMappingActions(EditingDomain domain, NodeDef nd, EClass selectedClass, Object fromClassModel)
{
Vector<String> subsets = getMappedSubsets(mappingRoot(nd), fromClassModel);
if (subsets.size() > 0)
{
MenuManager assocSubMenu = new MenuManager("Map Association");
subMenusForModelObject.add(assocSubMenu);
//find existing object mappings with their subsets
Hashtable <String,Vector<String>> theClassMappings = allClassMappings(mappingRoot(nd));
for (Iterator<String> is = subsets.iterator();is.hasNext();)
{
String thisSubset = is.next();
//find associations from the selected class to these classes
Vector<MappableAssociation> mappableAssociations = getMappableAssociations(selectedClass,thisSubset, theClassMappings);
for (Iterator<MappableAssociation> it = mappableAssociations.iterator(); it.hasNext();)
{
MappableAssociation am = it.next();
assocSubMenu.add(new MakeAssociationMappingAction(domain, nd, am,""));
}
}
}
}
/**
* @param root root of the MappedStructure
* @param fromClassModel a node selected in the class model view; EClass or LabelledEClass
* @return the mapped subsets of the class in the mapping. If it s an RMIM view,
* these are only those consistent with the position of the class in the RMIM
*/
private Vector<String> getMappedSubsets(EObject root, Object fromClassModel)
{
Vector<String> subsets = new Vector<String>();
if (fromClassModel instanceof EClass)
subsets = mappedSubsets(root, ModelUtil.getQualifiedClassName((EClass)fromClassModel));
if (fromClassModel instanceof LabelledEClass)
subsets = restrictedMappedSubsets(root,(LabelledEClass)fromClassModel);
return subsets;
}
/**
* @param root the MappedStructure root of a mapping set
* @param qualifiedClassName a class name preceded by its package name, if nonempty
* @return a Vector of all mapped subsets; an empty Vector if there are no mappings
*/
public static Vector<String> mappedSubsets(EObject root, String qualifiedClassName)
{
Vector<String> subsets = new Vector<String>();
if (allClassMappings(root).get(qualifiedClassName) != null)
subsets = allClassMappings(root).get(qualifiedClassName);
return subsets;
}
/**
* @param root the MappedStructure root of a mapping set
* @param qualifiedClassName a class name preceded by its package name, if nonempty
* @return the subset name to use for the next object mapping to that class,
* avoiding all clashes with subset names already used
*/
public static String nextSubset(EObject root, String qualifiedClassName)
{
String subset = "";
Vector<String> subsets = mappedSubsets(root,qualifiedClassName);
if (subsets.size() > 0)
{
boolean clash = true;
int index = 1;
while (clash)
{
subset = "s" + index;
clash = GenUtil.inVector(subset, subsets);
index++;
}
}
return subset;
}
/**
* Find all class mappings in a subtree of the mapped structure tree
* @param EObject root of the subtree
* @return Hashtable<String,Vector<String>> for each qualified class name, a Vector of its mapped subsets,
* or null if there are no mappings
*/
public static Hashtable<String,Vector<String>> allClassMappings(EObject root)
{
Hashtable<String,Vector<String>> addClassMappings = new Hashtable<String,Vector<String>>();
addClassMappings(addClassMappings,root);
return addClassMappings;
}
/**
* recursive descent of a model tree, finding all mapped subsets
* of all classes
* @param classMappings
* @param node
*/
public static void addClassMappings(Hashtable<String,Vector<String>> classMappings, EObject node)
{
if (node instanceof ObjMapping)
{
ObjMapping om = (ObjMapping)node;
String qClassName = om.getQualifiedClassName(); // package name first
String subset = om.getSubset();
Vector<String> subsetsMapped = classMappings.get(qClassName);
if (subsetsMapped == null) subsetsMapped = new Vector<String>();
subsetsMapped.addElement(subset);
classMappings.put(qClassName, subsetsMapped);
}
for (Iterator<EObject> it = node.eContents().iterator(); it.hasNext();)
addClassMappings(classMappings,it.next());
}
/**
*
* @param selectedClass an ecore EClass object
* @return a Vector of 'className:propertyName' for the Class and all its superclasses
*/
private Vector<String> allPropertyNames(EClass selectedClass,String subset)
{
Vector<String> allProps = new Vector<String>();
addPropertyNames(allProps,selectedClass,subset);
return allProps;
}
/**
* Add to the Vector allProperties an entry 'className:pseudoPropertyName' for every
* pseudo-property of the class (regardless of the mapped subset)
* (Pseudo-properties in the mapped structure are converted to properties in the class
* model by property conversions)
* @param selectedClass
* @param nd a Node in the mapped structure
* @param allProperties the Vector that is to be extended with the pseudo-properties
*/
private void addPseudoPropertyNames(EClass selectedClass,NodeDef nd, Vector<String>allProperties, String subset)
{
String className = subClassName(selectedClass,subset);
String qualifiedClassName = qualifiedSubClassName(selectedClass,subset);
Hashtable<String,String> pseudoProps = ModelUtil.getPseudoProperties(qualifiedClassName,nd);
for (Iterator<String> it = pseudoProps.keySet().iterator();it.hasNext();)
{allProperties.add(className + ":" + it.next());}
}
/**
* add 'className:propertyName' for all properties of this class, recursively going
* to superclasses , so properties of this class come first on the list
* @param allProps
* @param c
*/
private void addPropertyNames(Vector<String> allProps,EClass c,String subset)
{
for (Iterator<EAttribute> it = c.getEAttributes().iterator();it.hasNext();)
{allProps.addElement(subClassName(c,subset) + ":" + it.next().getName());}
for (Iterator<EClass> iu = c.getESuperTypes().iterator();iu.hasNext();)
{addPropertyNames(allProps,iu.next(),subset);}
}
/**
* @param ec a class
* @param subset a mapped subset
* @return the qualfied class name , followed by the subset name in brackets if not empty
*/
private String qualifiedSubClassName(EClass ec, String subset)
{
String subName = ModelUtil.getQualifiedClassName(ec);
if (!subset.equals("")) subName = subName + "(" + subset + ")";
return subName;
}
/**
* @param ec a class
* @param subset a mapped subset
* @return the unqualified class name , followed by the subset name in brackets if not empty
*/
private String subClassName(EClass ec, String subset)
{
String subName = ec.getName();
if (!subset.equals("")) subName = subName + "(" + subset + ")";
return subName;
}
/**
* find all associations from the selected class (or one of its superclasses)
* to some other mapped class (or one of its superclasses)
* @param EClass the selectedClass
* @param Hashtable allClassMappings; the keys to this are the names of mapped classes
* @return
*/
private Vector<MappableAssociation> getMappableAssociations
(EClass selectedClass, String thisEndSubset, Hashtable<String,Vector<String>> allClassMappings)
{
Vector<MappableAssociation> assocs = new Vector<MappableAssociation>();
EClass originalClass = selectedClass;
EClass endClass = selectedClass;
addMappableAssociations(assocs, originalClass, endClass, thisEndSubset, allClassMappings);
return assocs;
}
/**
* recurse up through superclasses finding associations,
* but labelling them always with the original class
* @param assocs Vector of MappableAssociation objects being built up
* @param originalClass the original class selected in the mapped class model view
* @param endClass the original class or a superclass -end of the association
* @param allClassMappings supplies the names of all classes currently mapped
*/
private void addMappableAssociations(Vector<MappableAssociation> assocs,
EClass originalClass, EClass endClass, String thisEndSubset,
Hashtable<String,Vector<String>> allClassMappings)
{
// check all the associations of this class
for (Iterator<EReference> it = endClass.getEReferences().iterator();it.hasNext();)
{
EReference ref = it.next();
/* pick out only those associations whose other end class (or some subclasses of it)
* are mapped, and note the actual mapped subclasses */
for (Iterator<EClassSubset> iw = mappedSubClasses(ref, allClassMappings).iterator();iw.hasNext();)
{
EClassSubset ecs = iw.next();
MappableAssociation ma = new MappableAssociation(originalClass, thisEndSubset,ecs.eClass,ecs.subset, ref,false);
assocs.addElement(ma);
}
}
// repeat the checks for all superclasses of the selected class
for (Iterator<EClass> it = endClass.getESuperTypes().iterator(); it.hasNext();)
{addMappableAssociations(assocs,originalClass,it.next(),thisEndSubset, allClassMappings);}
}
/**
* if the other end class of the EReference (or any of its subclasses)
* are mapped classes, return a Vector the mapped classes;
* otherwise return an empty Vector
* @param am
* @param allClassMappings
* @return
*/
private Vector<EClassSubset> mappedSubClasses(EReference am, Hashtable<String,Vector<String>> allClassMappings)
{
Vector<EClassSubset> result = new Vector<EClassSubset>();
EClass endSuperClass = (EClass)am.getEType();
// find all subclasses of the class at the other end of the association
Vector<EClass> endSubClasses = new Vector<EClass>();
try {endSubClasses = mappedStructure.getAllSubClasses(endSuperClass);}
catch (Exception ex) {}
for (Iterator<EClass> it = endSubClasses.iterator();it.hasNext();)
{
EClass endSubClass = it.next();
String qualifiedClassName = ModelUtil.getQualifiedClassName(endSubClass);
Vector<String> subsets = allClassMappings.get(qualifiedClassName);
// find all mapped subsets of each subclass
if (subsets != null) for (Iterator<String> is = subsets.iterator();is.hasNext();)
{
String subset = is.next();
result.add(new EClassSubset(endSubClass,subset));
}
}
return result;
}
private class EClassSubset{
EClass eClass;
String subset;
EClassSubset(EClass eClass,String subset)
{
this.eClass = eClass;
this.subset = subset;
}
}
/**
*
* @return the Structure definition attached to the current mapping set
*/
private StructureDefinition attachedStructure()
{
StructureDefinition res = null;
try
{
res = WorkBenchUtil.mappingRoot(mapperEditor).getStructureDefinition();
}
catch (MapperException ex) {}
return res;
}
//-----------------------------------------------------------------------------------------------
// Chain Mapping Menu Item for V3 RMIM mappings
//-----------------------------------------------------------------------------------------------
private void makeChainMappingAction(EditingDomain domain,ElementDef el,LabelledEClass selected)
{
EObject root = mappingRoot(el);
Vector<String> subsets = getMappedSubsets(root, selected);
String ancestorMappedClass = null;
String ancestorMappedSubset = null;
/* You can make chain mappings even if the class is already mapped, because you may
* want to make repeated mappings to data type classes like II */
{
boolean classIsMapped = false;
LabelledEClass current = selected;
// Iterate up through parent classes until you find one that is mapped
while ((current != null) && (!classIsMapped))
{
// find the subset to use when mapping this class
String className = ModelUtil.getQualifiedClassName(current.eClass());
String subsetToMap = nextSubset(root,className);
current.setSubsetToMap(subsetToMap);
// find the next parent class and check if it exists and is mapped
current = current.parent();
if (current != null)
{
subsets = getMappedSubsets(mappingRoot(el), current);
if (subsets.size() > 0) // found the first ancestor class already mapped
{
classIsMapped = true;
// record the mapped class and subset, for use in the top association mapping
ancestorMappedClass = ModelUtil.getQualifiedClassName(current.eClass());
ancestorMappedSubset = subsets.get(0);
}
}
} // end of iteration over ancestor classes
if (ancestorMappedSubset != null)
actionsForModelObject.add(new MakeChainMappingAction(domain,el,ancestorMappedClass,ancestorMappedSubset,selected));
}
}
/**
* @param root the MappedStructure root of a mapping set
* @param selected a node selected in the RMIM class model view
* @return the restricted list of mapped subsets for the selected class,
* which are also consistent with its position in the RMIM tree.
*
* The restricted list is generally expected to have length 1.
*
* If there is one or more mapped subset, but the selected RMIM class does not have a
* chain of association mappings linking it to ancestor classes, then the result is an
* empty Vector. (an object mapping on its own does not define which instance of an RMIM
* class, eg which instance of a data type class, it refers to)
*/
private Vector<String> restrictedMappedSubsets(EObject root, LabelledEClass selected)
{
Vector<String> filteredSubsets = null;
// get the long list of mapped subsets, restricted by the qualified class name
Vector<String> subsets = mappedSubsets(root,ModelUtil.getQualifiedClassName(selected.eClass()));
// Only try to restrict the list if there is more than one subset
if ((subsets != null) && (subsets.size() == 1)) return subsets;
if ((subsets != null) && (subsets.size() > 1))
{
String assocName = selected.associationName();
LabelledEClass parent = selected.parent();
// The top class of the RMIM has no parent class, so its subsets cannot be filtered
if (parent != null)
{
filteredSubsets = new Vector<String>();
// find the restricted mapped subsets of the parent
Vector<String> parentSubsets = restrictedMappedSubsets(root, parent);
// and the parent class must have one or more object mappings
if ((parentSubsets != null) && (parentSubsets.size() > 0))
{
// try out all mapped subsets in the long list
for (Iterator<String> is = subsets.iterator();is.hasNext();)
{
String subset = is.next();
// for each subset, try out all mapped subsets of the parent (but there should be only 1)
for (Iterator<String> it = parentSubsets.iterator();it.hasNext();)
{
String pSubset = it.next();
// retain the subset only if the association mapping exists
if (mappedAssociationExists(root, parent.eClass(),pSubset,selected.eClass(),subset,assocName))
filteredSubsets.add(subset);
}
}
}
}
}
if (filteredSubsets != null) subsets = filteredSubsets;
return subsets;
}
/**
* @param root MappedStructure root
* @param parent parent class
* @param parentSubset mapped subset of parent class
* @param selected class selected in the class model view
* @param subset a possible subset of the selected class
* @param assocName role name to go from the parent class to the selected class
* @return true if the association mapping exits with all these parameters
*/
private boolean mappedAssociationExists(EObject root, EClass parent,String parentSubset,
EClass selected,String subset,String assocName)
{
boolean exists = false;
List<EObject> assocMappings = ModelUtil.getEObjectsUnder(root, MapperPackage.eINSTANCE.getAssocMapping());
for (Iterator<EObject> it = assocMappings.iterator();it.hasNext();)
{
AssocMapping am = (AssocMapping)it.next();
AssocEndMapping navigable = am.getMappedEnd2();
AssocEndMapping other = am.getMappedEnd1();
if ((navigable.getQualifiedClassName().equals(ModelUtil.getQualifiedClassName(selected))) &&
(navigable.getSubset().equals(subset)) &&
(navigable.getMappedRole().equals(assocName)) &&
(other.getQualifiedClassName().equals(ModelUtil.getQualifiedClassName(parent))) &&
(other.getSubset().equals(parentSubset)))
exists = true;
}
return exists;
}
//----------------------------------------------------------------------------------
// Expanding the class model tree to show the class of the selected mapping
//----------------------------------------------------------------------------------
private void createActionsForMapping(ObjMapping om)
{
actionsForModelObject.add(new ShowMappedClassAction(om));
}
}