package com.openMap1.mapper.views;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICellEditorListener;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import com.openMap1.mapper.actions.MakeITSMappingsAction;
import com.openMap1.mapper.presentation.MapperEditor;
import com.openMap1.mapper.structures.ITSAssociation;
import com.openMap1.mapper.structures.ITSAttribute;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.MappedStructure;
/**
* superclass of AttributeView and AssociationView.
*
* @author robert
*
*/
public abstract class FeatureView extends ViewPart
implements ISelectionChangedListener, SortableView{
protected CheckboxTableViewer viewer;
protected Table table;
protected EClass selectedClass;
protected LabelledEClass selectedLabelledClass;
protected Hashtable<String,EStructuralFeature> features;
protected Hashtable<String,Boolean> checkedFeatures;
protected Vector<Vector<String>> featureRows = new Vector<Vector<String>>();
protected RowSorter qrSorter;
public RowSorter rowSorter() {return qrSorter;}
protected ClassModelView classModelView() {return WorkBenchUtil.getClassModelView(false);}
protected boolean isRMIMClassModel;
protected boolean tracing = false;
protected void trace(String s) {if (tracing) System.out.println(s);}
//---------------------------------------------------------------------------------------------
// Defining editable columns
//---------------------------------------------------------------------------------------------
// hard-wired assumptions about names and column indexes of editable columns
int[] editableColumnNumbers ={1,6};
String[] editableColumnNames = {"Business Name","Value"};
boolean isEditable(String property) {return(GenUtil.inArray(property, editableColumnNames));}
boolean isEditable(int column)
{
boolean editable = false;
for (int i = 0; i < editableColumnNumbers.length; i++)
if (column == editableColumnNumbers[i]) editable = true;
return editable;
}
int editableColumnIndex(String property)
{
int index = -1;
for (int i = 0; i < editableColumnNames.length; i++)
if (property.equals(editableColumnNames[i])) index = editableColumnNumbers[i];
return index;
}
protected CellEditor[] editors;
//---------------------------------------------------------------------------------------------
// Initialisation
//---------------------------------------------------------------------------------------------
/**
* Callback to create the viewer and initialize it.
*/
public void createPartControl(Composite parent) {
table = new Table(parent, SWT.CHECK | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION );
viewer = new CheckboxTableViewer(table);
ICheckStateListener listener = new CheckStateListener();
viewer.addCheckStateListener(listener);
table.setHeaderVisible(true);
table.setLinesVisible(true);
qrSorter = new RowSorter(viewer, this);
viewer.setSorter(qrSorter);
String[] properties = setColumnHeaders();
// define a property name for each column (which is the column header)
viewer.setColumnProperties(properties);
// define the cell editors for each column (only the business name and value column editors work)
editors = editors(table, properties.length);
viewer.setCellEditors(editors);
// define how the editors get and change the values shown in the cells
viewer.setCellModifier(new CellModifier());
// make the actions that will be items on the menu of this view
makeActions();
// attach the menu to this view
contributeToActionBars();
}
/**
* CellModifier class to make only some columns text-editable
* @author robert
*/
class CellModifier implements ICellModifier
{
/* only the 'Business Name' and 'Value' columns are editable, and then only when the class model view
* is attached to a mapping set made from an ITS. */
public boolean canModify(Object element, String property)
{
return ((isEditable(property))&& canEditEcoreModel());
}
public Object getValue(Object element, String property)
{
Object value = "";
if ((isEditable(property)) && (element instanceof Vector<?>))
value = ((Vector<?>)element).get(editableColumnIndex(property));
trace("\nValue of '" + property + "' is '" + value + "' at column " + editableColumnIndex(property));
if (value == null) value="";
return (String)value;
}
@SuppressWarnings("unchecked")
// this seems never to get called; I never see the trace
public void modify(Object element, String property, Object value)
{
trace("call to modify");
if ((isEditable(property)) && (element instanceof Vector<?>))
{
Vector<String> vec = (Vector<String>)element;
vec.set(editableColumnIndex(property),(String)value );
}
}
}
/* subclasses AttributeView and AssociationView must define
* the headers for the columns */
abstract protected String[] setColumnHeaders();
/* define an array of editors for each column of the view.
* Only the editors for the business name and value columns are used */
private CellEditor[] editors(Composite parent, int cols)
{
CellEditor[] editors = new CellEditor[cols];
for (int c =0; c < cols; c++)
{
TextCellEditor ed = new TextCellEditor(parent);
ed.addListener(new CellEditorListener(ed));
editors[c] = ed;
}
return editors;
}
class CellEditorListener implements ICellEditorListener
{
private TextCellEditor ed;
CellEditorListener(TextCellEditor ed)
{
this.ed = ed;
}
int editorColumnIndex()
{
int index = -1;
for (int i = 0; i< editors.length; i++)
if (ed.equals(editors[i])) index = i;
return index;
}
/* When the user presses return or moves away from the cell editor,
* record the new business name in the Ecore model */
@SuppressWarnings("unchecked")
public void applyEditorValue()
{
trace("Apply editor value to column " + editorColumnIndex());
Object rowObj = viewer.getElementAt(table.getSelectionIndex());
if (rowObj instanceof Vector<?>)
{
Vector<String> row = (Vector<String>)rowObj;
String oldValue = row.get(editorColumnIndex());
String newValue = (String)ed.getValue();
trace("Old: '" + oldValue + "'; new: '" + newValue + "'");
// record the edited name in the row to be displayed
row.set(editorColumnIndex(),newValue );
boolean changed = false;
if ((oldValue == null) && (!("".equals(newValue)))) changed = true;
if ((oldValue != null) && (!(oldValue.equals(newValue)))) changed = true;
// record the edited name in the Ecore model, only if there is a change
if (changed)
{
EStructuralFeature feature = features.get(rowKey(row));
trace("Changing feature " + feature.getName());
if (feature instanceof EAttribute)
{
AttributeView.applyNewValueToFeature(feature,newValue,editorColumnIndex(),selectedLabelledClass );
}
else if (feature instanceof EReference)
{
AssociationView.applyNewValueToFeature(feature,newValue,editorColumnIndex(),selectedLabelledClass );
}
}
else trace("No change");
// refresh the whole view, and force the refreshed row to be shown
showResultAgain(rowObj);
}
}
// if the user cancels the cell editing, do nothing
public void cancelEditor() {}
// do nothing at each keystroke in the cell editor
public void editorValueChanged(boolean oldValidState,
boolean newValidState) {}
}
/**
* do something whenever a check button is checked or unchecked
* @author robert
*/
class CheckStateListener implements ICheckStateListener
{
public CheckStateListener() {}
public void checkStateChanged(CheckStateChangedEvent event)
{
boolean checkState = event.getChecked();
Object checkedObject = event.getElement();
// only handle the check event if the Ecore model can have edits with its current mapping set
if ((checkedObject instanceof Vector<?>) && (canEditEcoreModel()))
{
String key = rowKey((Vector<?>)checkedObject);
EStructuralFeature feature = features.get(key);
handleCheckEvent(feature,checkState,selectedLabelledClass);
checkedFeatures.put(key, new Boolean(checkState));
// show the results again, and make sure the checked row is visible
showResultAgain(checkedObject);
}
}
}
// see AttributeView and AssociationView for overrides
public static void handleCheckEvent(EStructuralFeature feature, boolean checkState, LabelledEClass lClass)
{
if (feature instanceof EAttribute) AttributeView.handleCheckEvent(feature, checkState, lClass);
if (feature instanceof EReference) AssociationView.handleCheckEvent(feature, checkState, lClass);
}
/**
*
* @param row the fields on a row
* @return a key for the EAttribute or EReference, which is
* unique and does not depend on fields which might be edited
*/
protected String rowKey(Vector<?> row)
{
String key = "";
for (int i = 2; i < row.size(); i++) if (!isEditable(i))
{
Object obj = row.get(i);
if (obj instanceof String) key = key + (String)obj + "$";
}
return key;
}
//---------------------------------------------------------------------------------------------
// Annotations
//---------------------------------------------------------------------------------------------
public static String microITSURIStart = "urn:hl7-org:v3/microITS/";
public static String microITSURI()
{
String mappingSetName = "";
try
{
String uri = WorkBenchUtil.getClassModelView(false).mappingSetURI().toString();
StringTokenizer st = new StringTokenizer(uri,"/");
while(st.hasMoreTokens()) mappingSetName = st.nextToken();
}
catch (Exception ex) {} // in case mappingSetURI() is null
return (microITSURIStart + mappingSetName);
}
/**
*
* @return true if the Ecore model in the class model view has already been annotated with some
* other mapping set, not the mapping set the model is currently attached to.
*/
public static boolean hasBeenAnnotatedForOtherMappingSet()
{
boolean hasBeenAnnotated = false;
// fail-safe in case of null anything; return false
ClassModelView classModelView = WorkBenchUtil.getClassModelView(false);
if (classModelView != null)
{
LabelledEClass entryClass = classModelView.topLabelledEClass();
if (entryClass != null)
{
/* loop over all associations of the entry class. Any simplification edit will
* have propagated up to annotate one of these associations */
for (Iterator<EReference> it = entryClass.eClass().getEAllReferences().iterator(); it.hasNext();)
{
EReference ref = it.next();
// check all annotations of each association
for (Iterator<EAnnotation> ir = ref.getEAnnotations().iterator(); ir.hasNext();)
{
EAnnotation ann = ir.next();
// check all microITS annotations
if (ann.getSource().startsWith(microITSURIStart))
{
// if the annotation is not labelled for the current mapping set, return true.
if (!ann.getSource().equals(microITSURI())) hasBeenAnnotated = true;
}
}
}
}
}
return hasBeenAnnotated;
}
/**
*
* @return true only if the class model view is attached to a Mapper Editor,
* and the mapping set of the mapper editor was made from an ITS,
* and this ecore model has not been annotated for another mapping set
*/
public boolean canEditEcoreModel()
{
boolean canEdit = false;
MapperEditor mapperEditor = WorkBenchUtil.getClassModelView(false).mapperEditor();
if (mapperEditor != null)
{
MappedStructure mappedStructure = WorkBenchUtil.mappingRoot(mapperEditor);
canEdit = (MakeITSMappingsAction.isMadeFromITS(mappedStructure) && (!hasBeenAnnotatedForOtherMappingSet()));
}
return canEdit;
}
/**
*
* @param toObj a Ecore model element to which an annotation is either to be added,
* or extended with a new String key and value
* @param key the key (new or possibly already existing)
* @param value the new value for the key
*/
public static void addMicroITSAnnotation(EModelElement toObj, String key, String value)
{
EAnnotation ann = toObj.getEAnnotation(microITSURI());
if (ann == null)
{
ann = EcoreFactory.eINSTANCE.createEAnnotation();
ann.setSource(microITSURI());
toObj.getEAnnotations().add(ann);
}
ann.getDetails().put(key, value);
}
/**
* remove a micro-ITS annotation
* @param toObj
* @param key
*/
public static void removeMicroITSAnnotation(EModelElement toObj, String key)
{
EAnnotation ann = toObj.getEAnnotation(microITSURI());
if (ann != null)
{
ann.getDetails().removeKey(key);
if (ann.getDetails().size() == 0)
{
toObj.getEAnnotations().remove(ann);
}
}
}
/**
* @param toObj an Ecore model element
* @param key the key to an annotation detail
* @return the value for that key, or null if there is none
*/
public static String getMicroITSAnnotation(EModelElement toObj, String key)
{
String value = null;
EAnnotation ann = toObj.getEAnnotation(microITSURI());
if (ann != null) value = ann.getDetails().get(key);
return value;
}
public static ITSAttribute getITSAttribute(EAttribute ea, String key)
{
ITSAttribute itsa = new ITSAttribute();
String val = getMicroITSAnnotation(ea,key);
if (val != null) try {itsa = new ITSAttribute(val);}
catch (Exception ex) {}
return itsa;
}
public static ITSAssociation getITSAssociation(EReference er, String key)
{
ITSAssociation itsa = new ITSAssociation();
String val = getMicroITSAnnotation(er,key);
if (val != null) try {itsa = new ITSAssociation(val);}
catch (Exception ex) {}
return itsa;
}
/**
* @param feature an EReference or EAttribute
* @return from its EAnnotation, whether it should be checked or not
*/
protected Boolean isChecked(EStructuralFeature feature)
{
boolean checked = false;
if (selectedLabelledClass != null)
{
String path = selectedLabelledClass.getPath();
String note = getMicroITSAnnotation(feature,path);
if ((note != null) && (note.startsWith("T:"))) checked = true;
}
return new Boolean(checked);
}
/**
* add a row to a viewer, and make the row checked or unchecked
* @param row
*/
protected void addToViewer(Vector<String> row)
{
viewer.add(row);
Boolean checked = checkedFeatures.get(rowKey(row));
if (checked == null) trace("Missing row key " + rowKey(row));
else viewer.setChecked(row, checked.booleanValue());
}
//---------------------------------------------------------------------------------------------
// Listening for the selection of a new class in the Class Model View
//---------------------------------------------------------------------------------------------
public void selectionChanged(SelectionChangedEvent event)
{
ISelection selection = event.getSelection();
if (selection instanceof IStructuredSelection && ((IStructuredSelection)selection).size() == 1) {
Object object = ((IStructuredSelection)selection).getFirstElement();
// vanilla class model view
if (object instanceof EClass)
{
isRMIMClassModel = false;
selectedClass = (EClass)object;
selectedLabelledClass = null;
updateForSelection(selectedClass);
}
// RMIM class model view
else if (object instanceof LabelledEClass)
{
isRMIMClassModel = true;
selectedLabelledClass = (LabelledEClass)object;
selectedClass = selectedLabelledClass.eClass();
updateForSelection(selectedLabelledClass);
}
}
}
abstract protected void updateForSelection(EClass selectedClass);
abstract protected void updateForSelection(LabelledEClass selectedLablledClass);
/**
* Use the locally cached results to show them again - possibly with a new sort order
*/
public void showResultAgain()
{
// if there are any previous results showing, remove them
table.removeAll();
// add new rows to the view, and set their check boxes
for (Iterator<Vector<String>> it = featureRows.iterator();it.hasNext();)
addToViewer(it.next());
}
/**
* show the results again, and ensure the window shows a particular row
* (that has just been edited)
* @param rowObj
*/
public void showResultAgain(Object rowObj)
{
showResultAgain();
StructuredSelection sel = new StructuredSelection(rowObj);
viewer.setSelection(sel, true);
}
/**
* Add a column to the view and store information to later tell the row sorter about it.
* After this has been called for all columns, RowSorter.initialiseForResult(cols, comps)
* should be called.
* @param title title of the column
* @param width width of the column
* @param comp Comparator used for sorting the column; may be null if the column is not sortable
* @param comps Vector of non-null Comparators
* @param cols Vector of table columns that are sortable (have non-null Comparators)
* @return the column name
*/
protected String addColumn(String title, int width ,
Comparator<String> comp,
Vector<Comparator<String>> comps,
Vector<TableColumn> cols)
{
TableColumn t0 = new TableColumn(table,SWT.LEFT);
t0.setText(title);
t0.setWidth(width);
// only add to the sorter information if a comparator has been supplied
if (comp != null)
{
comps.add(comp);
cols.add(t0);
}
return title;
}
//---------------------------------------------------------------------------------------------
// Menu and methods
//---------------------------------------------------------------------------------------------
abstract protected void makeActions();
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
}
abstract protected void fillLocalPullDown(IMenuManager manager);
//---------------------------------------------------------------------------------------------
// Odds and ends
//---------------------------------------------------------------------------------------------
/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
}