package com.openMap1.mapper.views;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import com.openMap1.mapper.mapping.AssociationMapping;
import com.openMap1.mapper.structures.ITSAssociation;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.core.MapperException;
/**
* Shows the associations of the currently selected class in
* the Class Model View, and their mappings.
*
* @author robert
*
*/
public class AssociationView extends FeatureView
implements ISelectionChangedListener, SortableView
{
//---------------------------------------------------------------------------------------------
// Initialisation
//---------------------------------------------------------------------------------------------
/**
* Callback to create the viewer and initialize it.
*/
public void createPartControl(Composite parent) {
super.createPartControl(parent);
viewer.setLabelProvider(new AssociationTableLabelProvider());
/* the next call was put in because I do not know in what order this view
* and the class model view are created at startup, so I wanted to make the
* link in either case. But it makes the class model view call itself recursively.
* Now the connection is made when a class model is attached to the class model view. */
// if (classModelView() != null) classModelView().connectToAssociationView(this);
}
class AssociationTableLabelProvider extends LabelProvider implements ITableLabelProvider
{
public String getColumnText(Object element, int index)
{
String text = "";
if ((element instanceof Vector<?>) && (index < ((Vector<?>)element).size()))
{
Object obj = ((Vector<?>)element).get(index);
if (obj instanceof String) text = (String)obj;
}
return text;
}
public Image getColumnImage(Object element, int index) {return null;}
}
protected String[] setColumnHeaders()
{
// build these up to tell the query sorter which columns might be sorted on
Vector<Comparator<String>> comps = new Vector<Comparator<String>>();
Vector<TableColumn> cols = new Vector<TableColumn>();
Vector<String> props = new Vector<String>();
props.add(addColumn("Collapse", 60, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Business Name", 120, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Used", 50, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Level", 50, RowSorter.numberComparator, comps,cols));
props.add(addColumn("From Class", 100, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Role", 100, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Min", 35, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Max", 35, RowSorter.stringComparator, comps,cols));
props.add(addColumn("To Class", 100, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Inverse Role", 100, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Mappings", 300, RowSorter.stringComparator, comps, cols));
// tell the query sorter about this set of columns
qrSorter.initialiseForResult(cols, comps);
// record the column names (properties) to enable cell editors
String[] properties = new String[props.size()];
for (int i = 0; i < props.size(); i++) properties[i] = props.get(i);
return properties;
}
//---------------------------------------------------------------------------------------------
// Listening for the selection of a new class in the Class Model View
//---------------------------------------------------------------------------------------------
protected void updateForSelection(LabelledEClass selectedLabelledClass)
{
updateForSelection(selectedLabelledClass.eClass());
}
protected void updateForSelection(EClass selectedClass)
{
// if there are any previous results showing, remove them
table.removeAll();
features = new Hashtable<String,EStructuralFeature>();
checkedFeatures = new Hashtable<String,Boolean>();
featureRows = new Vector<Vector<String>>();
setAssociations(selectedClass,selectedClass,0);
// add new rows to the view, and set their check boxes
for (Iterator<Vector<String>> it = featureRows.iterator();it.hasNext();)
addToViewer(it.next());
}
/**
* add to the associations vector 'features' the associations of this class,
* then recursively add those of its superclasses, keeping
* track of the level of inheritance
* @param superClass
* @param level
*/
private void setAssociations(EClass originalClass, EClass superClass,int level)
{
for (Iterator<EReference> it = superClass.getEReferences() .iterator();it.hasNext();)
{
EReference er = it.next();
ITSAssociation itsa = getITSAssociation(er,selectedLabelledClass);
Vector<String> row = new Vector<String>();
row.add(""); // collapse flag
row.add(itsa.businessName()); // business name
String used = "";
if (itsa.attsIncluded()) used = "yes";
if (itsa.childrenAreOrdered()) used = "ordered";
row.add(used); // used flag
row.add(new Integer(level).toString()); // level of inheritance
row.add(superClass.getName()); // class the association is inherited from
row.add(er.getName()); // role name to get to the other end
// lower bound, as possibly constrained for this path
row.add(new Integer(getConstrainedLowerBound(er,selectedLabelledClass)).toString());
// upper bound, as possibly constrained for this path
String maxVal = "1";
if (getConstrainedUpperBound(er,selectedLabelledClass) == -1) maxVal = "*";
row.add(maxVal);
EClass otherEndClass = er.getEReferenceType();
String className = "no class";
if (otherEndClass != null) className = otherEndClass.getName();
row.add(className); // class name at the other end
if (er.getEOpposite() != null) // inverse role if it exists
row.add(er.getEOpposite().getName());
else row.add("-");
row.add(getAssociationMappingText(originalClass,superClass,er.getName(),otherEndClass));
// record the row, the EReference, and whether it is to be checked
features.put(rowKey(row), er);
featureRows.add(row);
checkedFeatures.put(rowKey(row), isChecked(er));
}
for (Iterator<EClass> ic = superClass.getESuperTypes().iterator();ic.hasNext();)
setAssociations(originalClass,ic.next(),level + 1);
}
private String getAssociationMappingText (EClass originalClass, EClass superClass, String roleName, EClass otherEndClass )
{
String[] c1c2 = new String[2];
String text = "";
String originalClassName= ModelUtil.getQualifiedClassName(originalClass);
try {
if ((classModelView() != null)
&& (classModelView().mdlBase() != null)
&& (otherEndClass != null))
for (Iterator<EClass> it = classModelView().mdlBase().ms().getAllSubClasses(otherEndClass).iterator();
it.hasNext();)
{
EClass otherSubClass = it.next();
String otherSubClassName= ModelUtil.getQualifiedClassName(otherSubClass);
c1c2[0] = originalClassName + "$" + otherSubClassName;
c1c2[1] = otherSubClassName + "$" + originalClassName;
for (int end = 0; end < 2; end++)
{
Vector<AssociationMapping> aMaps =
classModelView().mdlBase().associationMappingsByClass1Class2(c1c2[end]);
for (int a = 0; a < aMaps.size();a++)
{
AssociationMapping am = aMaps.get(a);
// pick out the role name that gets to the other end class
if (am.assocEnd(1-end).roleName().equals(roleName))
{
String path = am.nodePath().stringForm();
if (path == null) path = "[no path found]";
if (am.assocEnd(1-end).hasSubset())
path = path + "(" + am.assocEnd(1-end).subset() + ")";
if (am.assocEnd(end).hasSubset())
path = path + "(" + am.assocEnd(end).subset() + ")";
text = text + path + "; ";
}
}
}
}
}
catch (MapperException ex) {GenUtil.surprise(ex,"AssociationView.getAssociationMappingText");}
return text;
}
//---------------------------------------------------------------------------------------------
// Handling check box switches and business name edits
//---------------------------------------------------------------------------------------------
public static void handleCheckEvent(EStructuralFeature feature, boolean checkState, LabelledEClass lClass)
{
if (feature instanceof EReference)
{
EReference er = (EReference)feature;
ITSAssociation itsa = getITSAssociation(er,lClass);
itsa.setCollapsed(checkState);
putITSAssociation(er,itsa,lClass);
}
}
public static void applyNewValueToFeature(EStructuralFeature feature, String newBusinessName, int columnIndex, LabelledEClass lClass)
{
// business name column
if ((feature instanceof EReference) && (columnIndex == 1))
{
EReference er = (EReference)feature;
ITSAssociation itsa = getITSAssociation(er,lClass);
itsa.setBusinessName(newBusinessName);
putITSAssociation(er,itsa,lClass);
}
}
/**
* @param er an EAttribute
* @return its ITSAttibute (the default one if it has no EAnnotation for this path)
* @throws MapperException
*/
public static ITSAssociation getITSAssociation(EReference er,LabelledEClass lClass)
{
ITSAssociation itsa = new ITSAssociation();
if (lClass != null)
{
try
{
String path = lClass.getPath();
String note = getMicroITSAnnotation(er,path);
if (note != null) itsa = new ITSAssociation(note);
}
catch (MapperException ex) {}
}
return itsa;
}
/**
* @param ref
* @param lClass
* @return the lower bound of an EAttribute, as it may have been constrained by the user for this path
*/
public static int getConstrainedLowerBound(EReference ref, LabelledEClass lClass)
{
int lower = ref.getLowerBound();
if (getITSAssociation(ref,lClass).lowerBoundIsConstrained()) lower = 1;
return lower;
}
/**
* @param ref
* @param lClass
* @return the lower bound of an EAttribute, as it may have been constrained by the user for this path
*/
public static int getConstrainedUpperBound(EReference ref, LabelledEClass lClass)
{
int upper = ref.getUpperBound();
if (getITSAssociation(ref,lClass).upperBoundIsConstrained()) upper = 1;
return upper;
}
/**
* @param er an EReference
* @param itsa ITSAssociation to be put in its ITS annotation for this path
*/
public static void putITSAssociation(EReference er,ITSAssociation itsa,LabelledEClass lClass)
{
String path = lClass.getPath();
addMicroITSAnnotation(er, path, itsa.stringForm());
}
//---------------------------------------------------------------------------------------------
// Menu and methods
//---------------------------------------------------------------------------------------------
private Action constrainLowerBoundAction;
private Action constrainUpperBoundAction;
private Action revertBoundsAction;
private Action deleteAssociationAction;
private Action orderChildNodesAction;
protected void makeActions()
{
constrainLowerBoundAction = new Action() {
public void run() {
doConstrainLowerBound();
}
};
constrainLowerBoundAction.setText("Constrain lower bound to 1");
constrainUpperBoundAction = new Action() {
public void run() {
doConstrainUpperBound();
}
};
constrainUpperBoundAction.setText("Constrain upper bound to 1");
revertBoundsAction = new Action() {
public void run() {
doRevertBounds();
}
};
revertBoundsAction.setText("Revert bounds to model values");
deleteAssociationAction = new Action() {
public void run() {
doDeleteAssociation();
}
};
deleteAssociationAction.setText("Delete Association");
orderChildNodesAction = new Action() {
public void run() {
doOrderChildNodes();
}
};
orderChildNodesAction.setText("Order child nodes");
}
protected void fillLocalPullDown(IMenuManager manager)
{
manager.add(constrainLowerBoundAction);
manager.add(constrainUpperBoundAction);
manager.add(revertBoundsAction);
manager.add(orderChildNodesAction);
manager.add(deleteAssociationAction);
}
@SuppressWarnings("unchecked")
private EReference getSelectedRef()
{
EReference ref = null;
Object rowObj = viewer.getElementAt(table.getSelectionIndex());
if ((rowObj != null) && (rowObj instanceof Vector<?>))
{
Vector<String> row = (Vector<String>)rowObj;
EStructuralFeature feature = features.get(rowKey(row));
if (feature instanceof EReference) ref = (EReference)feature;
}
return ref;
}
/**
* constrain the min multiplicity of the selected association to 1 for this path only, if it is not 1;
* and note that it has been constrained
*/
private void doConstrainLowerBound()
{
EReference ref = getSelectedRef();
if (ref != null)
{
if (ref.getLowerBound() == 1) WorkBenchUtil.showMessage("Error", "Lower bound of association '" + ref.getName() + "' is already 1");
else
{
ITSAssociation itsa = getITSAssociation(ref,selectedLabelledClass);
itsa.setLowerBoundConstraint(true);
putITSAssociation(ref,itsa,selectedLabelledClass);
updateForSelection(selectedClass);
}
}
}
/**
* constrain the max multiplicity of the selected association to 1 for this path only, if it is not 1;
* and note that it has been constrained
*/
private void doConstrainUpperBound()
{
EReference ref = getSelectedRef();
if (ref != null)
{
if (ref.getUpperBound() == 1) WorkBenchUtil.showMessage("Error", "Upper bound of association '" + ref.getName() + "' is already 1");
else
{
ITSAssociation itsa = getITSAssociation(ref,selectedLabelledClass);
itsa.setUpperBoundConstraint(true);
putITSAssociation(ref,itsa,selectedLabelledClass);
updateForSelection(selectedClass);
}
}
}
/**
* revert the min and max multiplicity of the selected association to their unconstrained values
*/
private void doRevertBounds()
{
EReference ref = getSelectedRef();
if (ref != null)
{
boolean changesMade = false;
ITSAssociation itsa = getITSAssociation(ref,selectedLabelledClass);
// if the lower bound has been constrained, revert it
if (itsa.lowerBoundIsConstrained())
{
itsa.setLowerBoundConstraint(false);
changesMade = true;
}
// if the upper bound has been constrained, revert it
if (itsa.upperBoundIsConstrained())
{
itsa.setUpperBoundConstraint(false);
changesMade = true;
}
if (changesMade)
{
putITSAssociation(ref,itsa,selectedLabelledClass);
updateForSelection(selectedClass);
}
else WorkBenchUtil.showMessage("Error", "Neither bound of association '" + ref.getName() + "' has been changed.");
}
}
/**
* annotate an association so that the child nodes beneath it will be ordered
* in the simplified class model and mappings
*/
private void doOrderChildNodes()
{
EReference ref = getSelectedRef();
if (ref != null)
{
ITSAssociation itsa = getITSAssociation(ref,selectedLabelledClass);
// toggle on this command - need a way to see which way you are toggling
itsa.setChildrenAreOrdered(!itsa.childrenAreOrdered());
putITSAssociation(ref,itsa,selectedLabelledClass);
updateForSelection(selectedClass);
}
}
/**
* irreversibly delete an association (but not the class at the end of it)
*/
private void doDeleteAssociation()
{
EReference ref = getSelectedRef();
selectedClass.getEStructuralFeatures().remove(ref);
updateForSelection(selectedClass);
}
}