package com.openMap1.mapper.views;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Iterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EAttribute;
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 com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.mapping.propertyMapping;
import com.openMap1.mapper.structures.ITSAttribute;
import com.openMap1.mapper.util.ModelUtil;
import org.eclipse.jface.viewers.ISelectionChangedListener;
/**
* Shows the attributes of the currently selected class in
* the Class Model View, and their mappings.
*
* @author robert
*
*/
public class AttributeView 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 AttributeTableLabelProvider());
/* 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().connectToAttributeView(this);
}
class AttributeTableLabelProvider 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("Include", 50, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Business Name", 120, 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("Attribute", 100, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Min", 35, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Value", 100, RowSorter.stringComparator, comps,cols));
props.add(addColumn("Type", 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(EClass selectedClass)
{
LabelledEClass lc = new LabelledEClass(selectedClass,"", null);
updateForSelection(lc);
}
protected void updateForSelection(LabelledEClass selectedLabelledClass)
{
// 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>>();
setAttributes(selectedLabelledClass,selectedLabelledClass.eClass(),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 attributes vector 'features' the attributes of this class,
* then recursively add those of its superclasses, keeping
* track of the level of inheritance.
* @param superClass
* @param level
*/
private void setAttributes(LabelledEClass labelledClass, EClass superClass,int level)
{
for (Iterator<EAttribute> it = superClass.getEAttributes().iterator();it.hasNext();)
{
EAttribute ea = it.next();
String attName= ea.getName(); // for debugging
ITSAttribute itsa = getITSAttribute(ea,selectedLabelledClass);
Vector<String> row = new Vector<String>();
row.add(""); // include flag
row.add(itsa.businessName()); // business name
row.add(new Integer(level).toString());
row.add(superClass.getName());
row.add(ea.getName());
// get the lower bound as it may have been constrained by the user
row.add(new Integer(getConstrainedLowerBound(ea,selectedLabelledClass)).toString());
String value = "";
// normal RMIM case with no superclasses - preferred way to find fixed values
if (superClass.equals(labelledClass.eClass())) try
{
value = labelledClass.getAnnotatedFixedValue(ea.getName());
}
catch (MapperException ex) {trace(ex.getMessage());}
// I need to keep this code in case of superclasses
else
{
// find any fixed value specified in the RMIM before annotation
value = ModelUtil.getEAnnotationDetail(ea, "fixed value");
if (value == null) value = "";
// find any fixed value requested specifically by annotation of the REcore model; this takes precedence
String specificFixedValue = getFixedValue(ea);
if (specificFixedValue != null) value = specificFixedValue;
}
row.add(value);
String type = "-";
if (ea.getEAttributeType() != null) type = ea.getEAttributeType().getName();
row.add(type);
row.add(mappingText(labelledClass.eClass(),superClass,ea.getName()));
// record the row, the EAttribute, and whether it is to be checked
featureRows.add(row);
features.put(rowKey(row), ea);
checkedFeatures.put(rowKey(row), isChecked(ea));
}
for (Iterator<EClass> ic = superClass.getESuperTypes().iterator();ic.hasNext();)
setAttributes(labelledClass,ic.next(),level + 1);
}
private String mappingText (EClass originalClass, EClass superClass, String propName)
{
String text = "";
if ((classModelView() != null) && (classModelView().mdlBase() != null))
{
String className = ModelUtil.getQualifiedClassName(originalClass);
Vector<propertyMapping> propMappings = classModelView().mdlBase().propertyMappingsByClassName(className);
for (int p = 0; p < propMappings.size(); p++)
if (propMappings.get(p).propertyName().equals(propName))
{
propertyMapping pm = propMappings.get(p);
String path = pm.nodePath().stringForm();
if (pm.hasSubset()) path = path + "(" + pm.subset() + ")";
text = text + path + "; ";
}
// I have not added the number of mappings at the front, to preserve sort order of paths in the view
// if (mappings > 1) text = "(" + new Integer(mappings).toString() + "): " +text;
}
return text;
}
//---------------------------------------------------------------------------------------------
// Handling check box switches and business name edits
//---------------------------------------------------------------------------------------------
/**
* When the state of a check box changes,
* (a) record it on the EAttribute affected
* (b) for all ancestor EAssociations, set or unset their 'used' flags appropriately
*/
public static void handleCheckEvent(EStructuralFeature feature, boolean checkState, LabelledEClass lClass)
{
if (feature instanceof EAttribute)
{
// change the included flag on the attribute affected
EAttribute ea = (EAttribute)feature;
ITSAttribute itsa = getITSAttribute(ea,lClass);
itsa.setIncluded(checkState);
putITSAttribute(ea,itsa,lClass);
/* for all ancestor EAssociations, set or unset the used flag.
* Ascend the tree until you find a flag that is already set correctly,
* or you reach the top of the tree */
LabelledEClass current = lClass;
while (current != null)
{
boolean oldUsedState = current.isMarkedUsedInMicroITS();
boolean newUsedState = current.isActuallyUsedInMicroITS();
if (newUsedState != oldUsedState) // need to mark and ascend
{
current.markAsUsedInMicroITS(newUsedState);
current = current.parent(); // null if current was top of the tree
}
else if (newUsedState == oldUsedState) current = null; // need go no further
}
}
}
public static void applyNewValueToFeature(EStructuralFeature feature, String newValue, int columnIndex, LabelledEClass lClass)
{
// business name column
if ((feature instanceof EAttribute) && (columnIndex == 1))
{
EAttribute ea = (EAttribute)feature;
ITSAttribute itsa = getITSAttribute(ea,lClass);
itsa.setBusinessName(newValue);
putITSAttribute(ea,itsa,lClass);
}
// value column
if ((feature instanceof EAttribute) && (columnIndex == 6))
{
EAttribute ea = (EAttribute)feature;
if (lClass != null)
{
String attKey = "fixed:" + lClass.getPath();
// after editing to '', remove any previous fixed value
if (newValue.equals("")) FeatureView.removeMicroITSAnnotation(ea, attKey);
// specific fixed value requested for this path on the annotated example instance
else FeatureView.addMicroITSAnnotation(ea, attKey, newValue);
}
}
}
/**
* @param ea an EAttribute
* @return its ITSAttibute (the default one if it has no EAnnotation for this path)
* @throws MapperException
*/
public static ITSAttribute getITSAttribute(EAttribute ea, LabelledEClass lClass)
{
ITSAttribute itsa = new ITSAttribute();
if (lClass != null)
{
try
{
String path = lClass.getPath();
String note = getMicroITSAnnotation(ea,path);
if (note != null) itsa = new ITSAttribute(note);
}
catch (MapperException ex) {}
}
return itsa;
}
/**
* @param ea
* @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(EAttribute ea, LabelledEClass lClass)
{
int lower = ea.getLowerBound();
if (getITSAttribute(ea,lClass).lowerBoundIsConstrained()) lower = 1;
return lower;
}
/**
* get a specific fixed value for this attribute which has been annotated on the Ecore model;
* or null if there is none
* @param ea
* @return
*/
private String getFixedValue(EAttribute ea)
{
String fixed = null;
if (selectedLabelledClass != null)
{
String attKey = "fixed:" + selectedLabelledClass.getPath();
// specific fixed value requested for this path on the annotated example instance
fixed = FeatureView.getMicroITSAnnotation(ea, attKey);
}
return fixed;
}
/**
* @param ea an EAttribute
* @param itsa ITSAttribute to be put in its ITS annotation for this path
*/
public static void putITSAttribute(EAttribute ea,ITSAttribute itsa, LabelledEClass lClass)
{
if (lClass != null)
{
String path = lClass.getPath();
addMicroITSAnnotation(ea, path, itsa.stringForm());
}
}
//---------------------------------------------------------------------------------------------
// Menu and methods
//---------------------------------------------------------------------------------------------
private Action constrainLowerBoundAction;
private Action revertBoundsAction;
protected void makeActions()
{
constrainLowerBoundAction = new Action() {
public void run() {
doConstrainLowerBound();
}
};
constrainLowerBoundAction.setText("Constrain lower bound to 1");
revertBoundsAction = new Action() {
public void run() {
doRevertBounds();
}
};
revertBoundsAction.setText("Revert lower bound to 0");
}
protected void fillLocalPullDown(IMenuManager manager)
{
manager.add(constrainLowerBoundAction);
manager.add(revertBoundsAction);
}
@SuppressWarnings("unchecked")
private EAttribute getSelectedAtt()
{
EAttribute att = 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 EAttribute) att = (EAttribute)feature;
}
return att;
}
/**
* constrain the min multiplicity of the selected attribute to 1, if it is not 1, for this path only;
* and note that it has been constrained.
* Do not actually change the value in the Ecore model, as that would show up in lots of other places besides
* the path you actually want to change it for.
*/
private void doConstrainLowerBound()
{
EAttribute att = getSelectedAtt();
if (att != null)
{
if (att.getLowerBound() == 1) WorkBenchUtil.showMessage("Error", "Lower bound of attribute '" + att.getName() + "' is already 1");
else
{
ITSAttribute itsa = getITSAttribute(att,selectedLabelledClass);
itsa.setLowerBoundConstraint(true);
putITSAttribute(att,itsa,selectedLabelledClass);
updateForSelection(selectedClass);
}
}
}
/**
* revert the min multiplicity of the selected association to its unconstrained value 0
*/
private void doRevertBounds()
{
EAttribute att = getSelectedAtt();
if (att != null)
{
ITSAttribute itsa = getITSAttribute(att,selectedLabelledClass);
// if the lower bound has been constrained, revert it
if (itsa.lowerBoundIsConstrained())
{
itsa.setLowerBoundConstraint(false);
putITSAttribute(att,itsa,selectedLabelledClass);
updateForSelection(selectedClass);
}
else WorkBenchUtil.showMessage("Error", "Lower bound of attribute '" + att.getName() + "' has not been changed.");
}
}
}