package com.openMap1.mapper.actions;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import com.openMap1.mapper.AssocEndMapping;
import com.openMap1.mapper.AssocMapping;
import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.NodeMappingSet;
import com.openMap1.mapper.ObjMapping;
import com.openMap1.mapper.core.ClassSet;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.presentation.MapperEditor;
import com.openMap1.mapper.util.FileUtil;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.views.ClassModelView;
import com.openMap1.mapper.views.LabelledEClass;
import com.openMap1.mapper.views.WorkBenchUtil;
/**
* This action will do the following:
*
* on a set of mappings and its simplified class model, using refinement annotations on the class model:
* (1) to note which classes in the model have been 'promoted' to some ancestor class
* which is linked to the promoted class by a 1..1 relationship.
* (thus a promoted class might be represented on the same node of an XML as the ancestor class;
* although the mappings of the promoted class are not actually moved)
* (2) to add some associations to the promoted class to the class model, if they have not been added
* already
* (3) Where directed, to change some association mappings from other classes to be association
* mappings to the newly added associations from the promoted classes
*
* @author Robert
*
*/
public class RefineMappingsAction extends Action implements IAction{
private MapperEditor mapperEditor;
private ClassModelView classModelView;
/** the package which is the root of the model */
private EPackage ecoreRoot;
// URI used for refinement annotations on the simplified ECore model
public static String REFINEMENT_URI = "refineModel";
// classes that are promoted to be represented on the same node as an ancestor class
private Hashtable<String,String> promotedClasses;
// classes for which a superclass has been defined
private Hashtable<String,String> superClasses;
// classes for which all associations are to be replicated on another class
private Hashtable<String,String> allTransfers;
// associations which are to be transferred to a promoted class
private Hashtable<String,Hashtable<String,String>> transfers;
boolean tracing = true;
public RefineMappingsAction()
{
super("Refine Model and Mappings");
}
public void run()
{
classModelView = WorkBenchUtil.getClassModelView(false);
if (classModelView != null) try
{
ecoreRoot = classModelView.ecoreRoot();
LabelledEClass rootClass = ClassModelView.getRootLabelledEClass(ecoreRoot);
// check that the package structure is as expected for a simplified class model
// FIXME - there is no longer a single main package so this won't work
EPackage mainPackage = ecoreRoot;
String mappingSetURIString = classModelView.mappingSetURI().toString();
mapperEditor = WorkBenchUtil.getMapperEditor(mappingSetURIString);
if (mapperEditor != null)
{
MappedStructure mappedStructure = (MappedStructure)mapperEditor.getEditingDomain().getResourceSet().getResources().get(0).getContents().get(0);
refineModelAndMappings(mainPackage,rootClass,mappedStructure);
}
}
catch (Exception ex)
{
WorkBenchUtil.showMessage("Error",ex.getMessage());
ex.printStackTrace();
}
}
private void refineModelAndMappings(EPackage mainPackage,LabelledEClass rootClass, MappedStructure mappedStructure) throws MapperException
{
// read and check the annotations
readAnnotations(mainPackage, rootClass);
trace("Annotation checks succeeded");
// add the transferred associations to the class model, if they have not been added previously
addTransferredAssociations(mainPackage);
trace("Transferred associations added");
// do not move the mappings for promoted classes - it is not necessary
// movePromotedMappings(mappedStructure, rootClass);
// trace("Mappings promoted");
// transfer association mappings so they involve associations to the promoted classes
transferAssociationMappings(mappedStructure, rootClass);
trace("Association mappings transferred");
// save the changes
FileUtil.saveResource(ecoreRoot.eResource());
FileUtil.saveResource(mappedStructure.eResource());
trace("Changes saved");
}
/**
* For any associations that are to be transferred to a promoted class, add the associations
* to the promoted class in the class model, if they have not been added already
* @param mainPackage
* @throws MapperException
*/
private void addTransferredAssociations(EPackage mainPackage) throws MapperException
{
// classes for which all association mappings are to be transferred
for (Enumeration<String> en = allTransfers.keys(); en.hasMoreElements();)
{
String fromClassName = en.nextElement();
EClass fromClass = (EClass)mainPackage.getEClassifier(fromClassName);
String toClassName = allTransfers.get(fromClassName);
EClass toClass = (EClass)mainPackage.getEClassifier(toClassName);
for (Iterator<EStructuralFeature> it = fromClass.getEStructuralFeatures().iterator(); it.hasNext();)
{
EStructuralFeature next = it.next();
if (next instanceof EReference) addOrCheckTransferredAssociation(fromClass,toClass,next.getName());
}
}
// classes for which selected association mappings are to be transferred
for (Enumeration<String> en = transfers.keys(); en.hasMoreElements();)
{
String fromClassName = en.nextElement();
EClass fromClass = (EClass)mainPackage.getEClassifier(fromClassName);
Hashtable<String,String> classTransfers = transfers.get(fromClassName);
for (Enumeration<String> ea = classTransfers.keys(); ea.hasMoreElements();)
{
String assocName = ea.nextElement();
String toClassName = classTransfers.get(assocName);
EClass toClass = (EClass)mainPackage.getEClassifier(toClassName);
addOrCheckTransferredAssociation(fromClass,toClass,assocName);
}
}
}
/**
* add an association involving a promoted class to the class model;
* or if it already there, check it is correct
* @param fromClass
* @param toClass
* @param assocName
* @throws MapperException
*/
private void addOrCheckTransferredAssociation(EClass fromClass,EClass toClass,String assocName)
throws MapperException
{
EReference sourceRef = (EReference)fromClass.getEStructuralFeature(assocName);
EReference newRef = (EReference)toClass.getEStructuralFeature(assocName);
// if the transfer class already has the transferred association, check it
if (newRef != null)
{
String preface = "Association '" + assocName + "' already exists on class '"
+ toClass.getName() + "' with wrong ";
if (!sourceRef.getEType().equals(newRef.getEType()))
throw new MapperException(preface + " target class '" + newRef.getEType().getName() + "'");
if (sourceRef.getLowerBound() != newRef.getLowerBound())
throw new MapperException(preface + " min multiplicity " + newRef.getLowerBound());
if (sourceRef.getUpperBound() != newRef.getUpperBound())
throw new MapperException(preface + " min multiplicity " + newRef.getUpperBound());
if (sourceRef.isContainment() != newRef.isContainment())
throw new MapperException(preface + " containment " + newRef.isContainment());
}
// if the transfer class does not have the transferred association, add it
else if (newRef == null)
{
newRef = EcoreFactory.eINSTANCE.createEReference();
newRef.setName(assocName);
newRef.setEType(sourceRef.getEType());
newRef.setLowerBound(sourceRef.getLowerBound());
newRef.setUpperBound(sourceRef.getUpperBound());
newRef.setContainment(sourceRef.isContainment());
toClass.getEStructuralFeatures().add(newRef);
}
}
/**
* For any class which has been promoted, so its object mapping should be on the same node
* as the object mapping for an ancestor class, move the object mapping and the association
* mapping to the node with the mapping of the promotion target class.
* There has to be a direct association from the promotion target class to the promoted class -
* so that there are no intermediate classes and associations.
*
* This method is currently not used, as it is not necessary to move up the mappings -
* XML reading and writing works OK without moving them.
*
* @param mappedStructure
* @param rootClass
* @throws MapperException
*/
@SuppressWarnings("unused")
private void movePromotedMappings(MappedStructure mappedStructure,LabelledEClass rootClass) throws MapperException
{
for (Enumeration<String> en = promotedClasses.keys(); en.hasMoreElements();)
{
String promotedClassName = en.nextElement();
LabelledEClass promotedClass = rootClass.getDescendant(promotedClassName);
String qualifiedPromotedName = ModelUtil.getQualifiedClassName(promotedClass.eClass());
ClassSet promotedCSet = new ClassSet(qualifiedPromotedName,"");
ObjMapping promotedObjectMapping = ModelUtil.getObjectMapping(mappedStructure, promotedCSet);
if (promotedObjectMapping == null)
throw new MapperException("Cannot find object mapping for promoted class " + promotedCSet.stringForm());
String promotionTargetName = promotedClasses.get(promotedClassName);
LabelledEClass targetClass = rootClass.getDescendant(promotionTargetName);
String qualifiedTargetName = ModelUtil.getQualifiedClassName(targetClass.eClass());
ClassSet targetCSet = new ClassSet(qualifiedTargetName,"");
ObjMapping targetMapping = ModelUtil.getObjectMapping(mappedStructure, targetCSet);
if (targetMapping == null)
throw new MapperException("Cannot find object mapping for promotion target class " + targetCSet.stringForm());
AssocMapping promotedAssociationMapping = getPromotedAssociationMapping(promotedObjectMapping,targetCSet);
NodeMappingSet fromSet = (NodeMappingSet)promotedObjectMapping.eContainer();
NodeMappingSet toSet = (NodeMappingSet)targetMapping.eContainer();
fromSet.getObjectMappings().remove(promotedObjectMapping);
toSet.getObjectMappings().add(promotedObjectMapping);
fromSet.getAssociationMappings().remove(promotedAssociationMapping);
toSet.getAssociationMappings().add(promotedAssociationMapping);
if (fromSet.countAllMappings() == 0)
{
ElementDef fromNode = (ElementDef)fromSet.eContainer();
fromNode.removeNodeMappingSet();
}
}
}
/**
* @param promotedObjectMapping
* @param targetCSet
* @return a direct association mapping from the promotion target class to the promoted class,
* on the same node as the object mapping of the promoted class
* @throws MapperException if there is no such association
*/
AssocMapping getPromotedAssociationMapping(ObjMapping promotedObjectMapping,ClassSet targetCSet) throws MapperException
{
AssocMapping assocMapping = null;
ClassSet promotedCSet = promotedObjectMapping.getClassSet();
NodeMappingSet nms = (NodeMappingSet)promotedObjectMapping.eContainer();
for (Iterator<AssocMapping> it = nms.getAssociationMappings().iterator(); it.hasNext();)
{
AssocMapping next = it.next();
if ((next.getMappedEnd1().getClassSet().equals(targetCSet))
&& (next.getMappedEnd2().getClassSet().equals(promotedCSet)))
assocMapping = next;
}
if (assocMapping == null) throw new MapperException("Cannot find direct association mapping from class " + targetCSet.stringForm()
+ " to class " + promotedCSet.stringForm());
return assocMapping;
}
/**
* transfer association mappings, so that they involve a promoted class, rather than
* their original outer class. The inner class and the role name are unchanged.
* @param mappedStructure
* @param rootClass
* @throws MapperException
*/
private void transferAssociationMappings(MappedStructure mappedStructure,LabelledEClass rootClass) throws MapperException
{
// classes for which all association mappings are to be transferred to a promoted class
for (Enumeration<String> en = allTransfers.keys(); en.hasMoreElements();)
{
String fromClassName = en.nextElement();
LabelledEClass fromClass = rootClass.getDescendant(fromClassName);
ClassSet fromCSet = new ClassSet(ModelUtil.getQualifiedClassName(fromClass.eClass()),"");
String toClassName = allTransfers.get(fromClassName);
// LabelledEClass toClass = rootClass.getDescendant(toClassName);
// ClassSet toCSet = new ClassSet(ModelUtil.getQualifiedClassName(toClass.eClass()),"");
for (Iterator<EObject> it = mappedStructure.eAllContents(); it.hasNext();)
{
EObject next = it.next();
if (next instanceof AssocMapping)
{
AssocMapping aMap = (AssocMapping)next;
AssocEndMapping aeMap = aMap.getMappedEnd1();
if (aeMap.getClassSet().equals(fromCSet)) aeMap.setMappedClass(toClassName);
}
}
}
// classes for which selected association mappings are to be transferred to a promoted class
for (Enumeration<String> en = transfers.keys(); en.hasMoreElements();)
{
String fromClassName = en.nextElement();
LabelledEClass fromClass = rootClass.getDescendant(fromClassName);
ClassSet fromCSet = new ClassSet(ModelUtil.getQualifiedClassName(fromClass.eClass()),"");
Hashtable<String,String> classTransfers = transfers.get(fromClassName);
for (Enumeration<String> ep = classTransfers.keys(); ep.hasMoreElements();)
{
String assocName = ep.nextElement();
String toClassName = classTransfers.get(assocName);
LabelledEClass toClass = rootClass.getDescendant(toClassName);
ClassSet toCSet = new ClassSet(ModelUtil.getQualifiedClassName(toClass.eClass()),"");
for (Iterator<EObject> it = mappedStructure.eAllContents(); it.hasNext();)
{
EObject next = it.next();
if (next instanceof AssocMapping)
{
AssocMapping aMap = (AssocMapping)next;
AssocEndMapping aeMap = aMap.getMappedEnd1();
if ((aeMap.getClassSet().equals(fromCSet)) && (aMap.getMappedEnd2().getMappedRole().equals(assocName)))
aeMap.setMappedClass(toCSet.className());
}
}
}
}
}
/**
* read and check the refinement annotations on a simplified class model
* @param mainPackage
* @param rootClass
* @throws MapperException
*/
private void readAnnotations(EPackage mainPackage, LabelledEClass rootClass) throws MapperException
{
promotedClasses = new Hashtable<String,String>();
superClasses = new Hashtable<String,String>();
allTransfers = new Hashtable<String,String>();
transfers = new Hashtable<String,Hashtable<String,String>>();
// find any promoted classes and superclasses in the model; there must be at least one promoted class
for (Iterator<EClassifier> it = mainPackage.getEClassifiers().iterator(); it.hasNext();)
{
EClassifier next = it.next();
String className = next.getName();
if (next instanceof EClass)
{
// read annotations for promotion, superclass and transfer of all associations on each class
EAnnotation note = next.getEAnnotation(REFINEMENT_URI);
if (note != null)
{
String promoteToClass = note.getDetails().get("promote");
if (promoteToClass != null) promotedClasses.put(className, promoteToClass);
String superClass = note.getDetails().get("superclass");
if (superClass != null) superClasses.put(className, superClass);
String allTransferClass = note.getDetails().get("transferAll");
if (allTransferClass != null) allTransfers.put(className, allTransferClass);
}
// read specific association transfer annotations on EReferences
for (Iterator<EStructuralFeature> ir = ((EClass)next).getEStructuralFeatures().iterator(); ir.hasNext();)
{
EStructuralFeature feat = ir.next();
String refName = feat.getName();
if (feat instanceof EReference)
{
EAnnotation rNote = feat.getEAnnotation(REFINEMENT_URI);
if (rNote != null)
{
String transferToClass = rNote.getDetails().get("transfer");
if (transferToClass != null)
{
Hashtable<String,String> classTransfers = transfers.get(className);
if (classTransfers == null) classTransfers = new Hashtable<String,String>();
classTransfers.put(refName, transferToClass);
transfers.put(className,classTransfers);
trace("Transfer " + className + "." + refName + " to " + transferToClass);
}
}
}
}
}
}
// check that there are some promotions
if (promotedClasses.size() == 0) throw new MapperException("No classes in the mapper class model have been promoted, so it cannot be refined.");
// check that all promotions are to an ancestor class (the promotion target class) connected by 1..1 associations
for (Enumeration<String> en = promotedClasses.keys(); en.hasMoreElements();)
{
String promotedClassName = en.nextElement();
String promotionTarget = promotedClasses.get(promotedClassName);
LabelledEClass promoted = rootClass.getDescendant(promotedClassName);
if (promoted == null) throw new MapperException("Promoted class '" + promotedClassName + "' not found in class tree");
if (promoted.countOccurrences(promotionTarget) == 0)
throw new MapperException("Class '" + promotionTarget + "' is not an ancestor of promoted class '" + promotedClassName + "'");
if (!promoted.isOneToOneDescendant(promotionTarget))
throw new MapperException("Path from class '" + promotionTarget + "' to '" + promotedClassName + "' is not all 1..1 associations");
}
// check that all association transfers are to a promoted class, from a class under its promotion target connected by 1..1 associations
for (Enumeration<String> en = allTransfers.keys(); en.hasMoreElements();)
{
String fromClass = en.nextElement();
LabelledEClass source = rootClass.getDescendant(fromClass);
if (source == null) throw new MapperException("Source class for association transfer '" + fromClass + "' is not found in class tree");
String toClass = allTransfers.get(fromClass);
checkTransferClasses(source, toClass, "any association");
}
// check that all individual association transfers are to a promoted class, from a class under its promotion target connected by 1..1 associations
for (Enumeration<String> en = transfers.keys(); en.hasMoreElements();)
{
String fromClass = en.nextElement();
LabelledEClass source = rootClass.getDescendant(fromClass);
if (source == null) throw new MapperException("Source class for association transfer '" + fromClass + "' is not found in class tree");
Hashtable<String,String> classTransfers = transfers.get(fromClass);
for (Enumeration<String> ea = classTransfers.keys(); ea.hasMoreElements();)
{
String assocName = ea.nextElement();
String toClass = classTransfers.get(assocName);
checkTransferClasses(source, toClass, assocName);
}
}
}
/**
* check that a class which some association mappings are being transferred from, to a promoted class,
* is appropriate for the transfer - that both classes are 1..1 descendants of the promotion target class
* @param source
* @param toClass
* @param assocName
* @throws MapperException
*/
private void checkTransferClasses(LabelledEClass source, String toClass, String assocName) throws MapperException
{
String fromClass = source.eClass().getName();
String promotionTarget = promotedClasses.get(toClass);
if (promotionTarget == null) throw new MapperException("Association " + assocName + " from class " + fromClass +
" cannot be transferred to class " + toClass + ", because that class has not been promoted.");
if (source.countOccurrences(promotionTarget) == 0)
throw new MapperException("Source class for association transfer '" +
fromClass + "' is not under the promotion target class '" + promotionTarget + "'");
if (!source.isOneToOneDescendant(promotionTarget))
throw new MapperException("Path from class '" + promotionTarget
+ "' to source class for association transfer '" + source + "' is not all 1..1 associations");
}
private void trace(String s) {if (tracing) System.out.println(s);}
}