/*******************************************************************************
* Copyright (c) 2010 Michal Antkiewicz.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michal Antkiewicz - initial API and implementation
******************************************************************************/
package ca.uwaterloo.gsd.fsml.ui;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.ui.EMFEditUIPlugin;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import ca.uwaterloo.gsd.fsml.core.FSMLMappingException;
import ca.uwaterloo.gsd.fsml.core.Queries;
import ca.uwaterloo.gsd.fsml.ecore.FSMLEcoreUtil;
import ca.uwaterloo.gsd.fsml.fsml.Model;
import ca.uwaterloo.gsd.fsml.fsml.ModelContainer;
import ca.uwaterloo.gsd.fsml.stats.Stats;
import ca.uwaterloo.gsd.fsml.sync.ClassSyncItem;
import ca.uwaterloo.gsd.fsml.sync.RA;
import ca.uwaterloo.gsd.fsml.sync.ReconciliationDecision;
import ca.uwaterloo.gsd.fsml.sync.StructuralFeatureSyncItem;
import ca.uwaterloo.gsd.fsml.sync.SyncItem;
import ca.uwaterloo.gsd.fsml.sync.SynchronizationResult;
import ca.uwaterloo.gsd.fsml.sync.SynchronizationState;
/**
* @author Michal Antkiewicz <mantkiew@gsd.uwaterloo.ca>
* Generic implementation of the reconcile action.
*/
public class ModelCodeReconcileAction implements IObjectActionDelegate {
protected ClassSyncItem classSyncItem;
protected IAction action;
protected IFsmlEditor editor;
ModelCodeSynchronizationView syncView;
protected IProject project;
protected Model assertedModel;
protected Model implementationModel;
protected Model lastReconciledModel;
protected HashMap<String, EObject> fsmlId2assertedModelObject;
protected HashMap<String, EObject> fsmlId2implementationModelObject;
protected HashMap<String, EObject> fsmlId2lastReconciledModelObject;
protected EditingDomain editingDomain;
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
if (targetPart instanceof ModelCodeSynchronizationView) {
this.syncView = (ModelCodeSynchronizationView) targetPart;
this.editor = ((ModelCodeSynchronizationView) targetPart).getEditor();
}
else {
this.syncView = null;
this.editor = null;
}
}
public void run(IAction action) {
if (editor == null || classSyncItem == null)
return;
try {
fsmlId2assertedModelObject = new HashMap<String, EObject>();
fsmlId2implementationModelObject = new HashMap<String, EObject>();
fsmlId2lastReconciledModelObject = new HashMap<String, EObject>();
// stats
Stats.INSTANCE.printMessage("Reconciliation results and statistics for " + classSyncItem.toString());
Stats.INSTANCE.reset();
// validate the model if forward
// TODO: need to write custom validation that'll take into account states & decisions
/*if (classSyncItem.isDecisionEnforce()) {
Diagnostic diagnostic = Diagnostician.INSTANCE.validate(classSyncItem.getModel());
handleDiagnostic(diagnostic);
if (diagnostic.getSeverity() != Diagnostic.OK)
return;
}*/
IRunnableWithProgress op = createReconcileRunnable();
new ProgressMonitorDialog(((IEditorPart) editor).getSite().getShell()).run(true, true, op);
} catch (InvocationTargetException e) {
// handle exception
e.printStackTrace();
return;
} catch (InterruptedException e) {
// handle cancelation
e.printStackTrace();
return;
}
editor.getViewer().refresh();
syncView.refresh();
// stats
Stats.INSTANCE.printMessage("\n" + Stats.INSTANCE.toString());
}
/**
* construct IRunnableWithProgress that will do the actual reconciliation
*/
protected IRunnableWithProgress createReconcileRunnable() {
return new ReconcileRunnable();
}
protected void handleDiagnostic(Diagnostic diagnostic) {
int severity = diagnostic.getSeverity();
String title = null;
String message = null;
if (severity == Diagnostic.ERROR || severity == Diagnostic.WARNING) {
title = EMFEditUIPlugin.INSTANCE.getString("_UI_ValidationProblems_title");
message = EMFEditUIPlugin.INSTANCE.getString("_UI_ValidationProblems_message");
}
else {
title = EMFEditUIPlugin.INSTANCE.getString("_UI_ValidationResults_title");
message = EMFEditUIPlugin.INSTANCE.getString(severity == Diagnostic.OK ? "_UI_ValidationOK_message" : "_UI_ValidationResults_message");
}
ErrorDialog.openError(((IEditorPart) editor).getSite().getShell(),
title, message, BasicDiagnostic.toIStatus(diagnostic));
}
public void selectionChanged(IAction action, ISelection selection) {
if (selection instanceof IStructuredSelection) {
Object object = ((IStructuredSelection) selection).getFirstElement();
if (object instanceof ClassSyncItem) {
classSyncItem = (ClassSyncItem) object;
action.setEnabled(
classSyncItem != null &&
editor != null &&
checkNoUndecidedConflicts(classSyncItem)
);
}
else
action.setEnabled(false);
}
}
private static boolean checkNoUndecidedConflicts(ClassSyncItem classSyncItem) {
for (Object aux : classSyncItem.getSyncItems()) {
if (aux instanceof StructuralFeatureSyncItem) {
StructuralFeatureSyncItem attributeSyncItem = (StructuralFeatureSyncItem) aux;
if (attributeSyncItem.getSynchronizationState().isConflict()) {
int decision = attributeSyncItem.getReconciliationDecision().getValue();
if (decision != ReconciliationDecision.OVERRIDE_AND_ENFORCE && decision != ReconciliationDecision.OVERRIDE_AND_UPDATE)
// unresolved conflict
return false;
break;
}
}
else if (aux instanceof ClassSyncItem) {
ClassSyncItem subfeatureSyncItem = (ClassSyncItem) aux;
if (!checkNoUndecidedConflicts(subfeatureSyncItem))
return false;
}
}
return true;
}
/**
* A generic reconcile runnable. This propagates changes according to the reconciliation
* decisions.
* @author Michal Antkiewicz <mantkiew@gsd.uwaterloo.ca>
*/
public class ReconcileRunnable implements IRunnableWithProgress {
IProgressMonitor progressMonitor;
/**
* Assuming that reconciliation decisions have been made for all non-consistent sync items
*/
public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
this.progressMonitor = progressMonitor;
// we do not clear the cache made during the synchronization because we use the same
// annotation interpreters and contexts.
// navigate to the SynchronizationResult
EObject aux = classSyncItem.eContainer();
while (!(aux instanceof SynchronizationResult) && aux != null)
aux = aux.eContainer();
if (aux == null) {
// should never happen!
Stats.INSTANCE.logBug("BUG: ConceptSynchItem does not belong to any SynchronizationResult!");
return;
}
SynchronizationResult synchronizationResult = (SynchronizationResult) aux;
assertedModel = synchronizationResult.getAssertedModel();
implementationModel = synchronizationResult.getImplementationModel();
lastReconciledModel = ((ModelContainer) assertedModel.eContainer()).getLastReconciledModel();
FSMLEcoreUtil.createFsmlId2EObjectMap(assertedModel, fsmlId2assertedModelObject);
FSMLEcoreUtil.createFsmlId2EObjectMap(implementationModel, fsmlId2implementationModelObject);
FSMLEcoreUtil.createFsmlId2EObjectMap(lastReconciledModel, fsmlId2lastReconciledModelObject);
editingDomain = editor.getEditingDomain();
try {
// recursively traverse the sync items tree in a depth-first manner
reconcileObject(classSyncItem);
// traverse the sync items tree and update the last reconcile model for each reconciled sync item
updateLastReconciledModel(classSyncItem);
} catch (FSMLMappingException e) {
e.printStackTrace();
}
// notify registered mapping interpreters that the process ended
Queries.INSTANCE.terminate(progressMonitor);
}
protected void reconcileObject(ClassSyncItem classSyncItem) throws FSMLMappingException {
EObject modelObject = classSyncItem.getModel();
EObject codeObject = classSyncItem.getCode();
// at least one object should exist
if (modelObject != null)
Stats.INSTANCE.printMessage("Model: " + modelObject);
else if (codeObject != null)
Stats.INSTANCE.printMessage("Code: " + codeObject);
else {
classSyncItem.setResult("Both model and code objects are null!");
return;
}
// determine action for the object
RA objectAction = classSyncItem.getReconciliationAction();
switch (objectAction) {
case MODEL_ADD:
modelObject = EcoreUtil.create(codeObject.eClass());
classSyncItem.setModel(modelObject);
EStructuralFeature containingFeature = codeObject.eContainingFeature();
EObject parent = ((ClassSyncItem)classSyncItem.eContainer()).getModel();
if (containingFeature.isMany())
((EList)parent.eGet(containingFeature)).add(modelObject);
else
parent.eSet(containingFeature, modelObject);
break;
case MODEL_REMOVE:
EcoreUtil.remove(modelObject);
classSyncItem.setModel(null);
// TODO: what about children sync states? should the model ref be nullified too?
break;
case CODE_ADD:
case CODE_REMOVE:
Queries.INSTANCE.forwardFeatureRepresentedAsClass(classSyncItem, progressMonitor);
break;
}
classSyncItem.setResult(objectAction.name());
// process the structural features
for (SyncItem syncItem : classSyncItem.getSyncItems()) {
// feature or object?
if (syncItem instanceof StructuralFeatureSyncItem) {
StructuralFeatureSyncItem structuralFeatureSyncItem = (StructuralFeatureSyncItem) syncItem;
Stats.INSTANCE.printMessage(structuralFeatureSyncItem.getStructuralFeature().toString());
EStructuralFeature feature = structuralFeatureSyncItem.getStructuralFeature();
RA featureAction = syncItem.getReconciliationAction();
switch (featureAction) {
case MODEL_ADD:
case MODEL_CHANGE:
case MODEL_REMOVE:
if (!feature.isDerived()) {
// need to treat references in a different way
if (feature instanceof EReference) {
if (codeObject != null) {
EObject codeAux = (EObject) codeObject.eGet(feature);
EObject modelAux = fsmlId2assertedModelObject.get(FSMLEcoreUtil.getFSMLId((EObject) codeAux, null));
modelObject.eSet(feature, modelAux);
}
else
modelObject.eUnset(feature);
}
else {
// regular attribute
if (codeObject != null) {
if (feature.isMany()) {
EList modelValues = (EList) modelObject.eGet(feature);
modelValues.clear();
EList codeValues = (EList) codeObject.eGet(feature);
for (Object value : codeValues)
modelValues.add(value);
}
else
modelObject.eSet(feature, codeObject.eGet(feature));
}
else {
if (feature.getEType().getName().equals("EBoolean"))
modelObject.eSet(feature, false);
else
modelObject.eUnset(feature);
}
}
}
structuralFeatureSyncItem.setReconciliationDecision(ReconciliationDecision.IGNORE_LITERAL);
structuralFeatureSyncItem.setSynchronizationState(SynchronizationState.UNCHANGED_LITERAL);
break;
case CODE_ADD:
case CODE_CHANGE:
case CODE_REMOVE:
if (feature instanceof EAttribute)
Queries.INSTANCE.forwardFeatureRepresentedAsAttribute(structuralFeatureSyncItem, progressMonitor);
// ignore references, because they only correspond to model queries.
// containment references are processed indirectly by processing their values
// see ClassSyncItem case below.
break;
}
structuralFeatureSyncItem.setResult(featureAction.toString());
}
else if (syncItem instanceof ClassSyncItem){
ClassSyncItem subfeatureSyncItem = (ClassSyncItem) syncItem;
reconcileObject(subfeatureSyncItem);
}
}
// reset state and decision for every object in the hierarchy
classSyncItem.setSynchronizationState(SynchronizationState.UNCHANGED_LITERAL);
classSyncItem.setReconciliationDecision(ReconciliationDecision.IGNORE_LITERAL);
}
protected void updateLastReconciledModel(ClassSyncItem classSyncItem) {
EObject modelObject = classSyncItem.getModel();
EObject codeObject = classSyncItem.getCode();
String fsmlId = null;
EClass eClass = null;
EReference eContainingFeature = null;
EObject lrmContainer = null;
// at least one object should exist
if (modelObject != null) {
fsmlId = FSMLEcoreUtil.getFSMLId(modelObject, null);
eClass = modelObject.eClass();
eContainingFeature = (EReference) modelObject.eContainingFeature();
if (modelObject.eContainer() instanceof Model)
lrmContainer = lastReconciledModel;
else
lrmContainer = fsmlId2lastReconciledModelObject.get(FSMLEcoreUtil.getFSMLId(modelObject.eContainer(), null));
}
else if (codeObject != null) {
fsmlId = FSMLEcoreUtil.getFSMLId(codeObject, null);
eClass = codeObject.eClass();
eContainingFeature = (EReference) codeObject.eContainingFeature();
}
else
return;
String result = classSyncItem.getResult();
EObject lrmObject = fsmlId2lastReconciledModelObject.get(fsmlId);
if (modelObject instanceof Model)
lrmObject = lastReconciledModel;
Command command = null;
if (RA.CODE_ADD.toString().equals(result) || RA.MODEL_ADD.toString().equals(result)) {
if (lrmObject == null && lrmContainer != null) {
lrmObject = EcoreUtil.create(eClass);
fsmlId2lastReconciledModelObject.put(fsmlId, lrmObject);
if (eContainingFeature.isMany())
command = new AddCommand(editingDomain, lrmContainer, eContainingFeature, lrmObject);
else
command = new SetCommand(editingDomain, lrmContainer, eContainingFeature, lrmObject);
}
// already existed - process subfeatures
}
else if (RA.CODE_CHANGE.toString().equals(result) || RA.MODEL_CHANGE.toString().equals(result)) {
// lrmObject must existed. Create if missing
if (lrmObject == null && lrmContainer != null) {
lrmObject = EcoreUtil.create(eClass);
fsmlId2lastReconciledModelObject.put(fsmlId, lrmObject);
if (eContainingFeature.isMany())
command = new AddCommand(editingDomain, lrmContainer, eContainingFeature, lrmObject);
else
command = new SetCommand(editingDomain, lrmContainer, eContainingFeature, lrmObject);
}
// process subfeatures
}
else if (RA.CODE_REMOVE.toString().equals(result) || RA.MODEL_REMOVE.toString().equals(result)) {
if (lrmObject != null && lrmContainer != null)
// need to remove it
command = new RemoveCommand(editingDomain, lrmContainer, eContainingFeature, lrmObject);
// can safely return because the subfeatures were just removed together with lrmObject
return;
}
else if (RA.STOP.toString().equals(result))
return;
else
// RA.PROCEED.equals(result)
;
if (command != null)
editingDomain.getCommandStack().execute(command);
// process subfeatures
for (SyncItem syncItem : classSyncItem.getSyncItems()) {
// feature or object?
if (syncItem instanceof StructuralFeatureSyncItem) {
if (lrmObject == null)
continue;
StructuralFeatureSyncItem structuralFeatureSyncItem = (StructuralFeatureSyncItem) syncItem;
EStructuralFeature feature = structuralFeatureSyncItem.getStructuralFeature();
result = structuralFeatureSyncItem.getResult();
if (RA.CODE_ADD.toString().equals(result) || RA.CODE_CHANGE.toString().equals(result)) {
// so the new values comes from model
if (feature instanceof EAttribute) {
if (feature.isMany()) {
EList modelValues = (EList) modelObject.eGet(feature);
EList lrmValues = (EList) lrmObject.eGet(feature);
lrmValues.clear();
for (Object value : modelValues) {
lrmValues.add(value);
}
}
else
lrmObject.eSet(feature, modelObject.eGet(feature));
}
else {
// feature is an EReference
// target EObjects in lrm might not have been created yet!!! TODO:
EObject modelTarget = (EObject) modelObject.eGet(feature);
if (modelTarget != null) {
// try to find a corresponding target
EObject lrmTarget = fsmlId2lastReconciledModelObject.get(modelTarget);
lrmObject.eSet(feature, lrmTarget);
}
}
}
else if (RA.MODEL_ADD.toString().equals(result) || RA.MODEL_CHANGE.toString().equals(result)) {
// so the new values comes from code
if (feature instanceof EAttribute) {
if (feature.isMany()) {
EList modelValues = (EList) modelObject.eGet(feature);
EList lrmValues = (EList) lrmObject.eGet(feature);
lrmValues.clear();
for (Object value : modelValues) {
lrmValues.add(value);
}
}
else
lrmObject.eSet(feature, modelObject.eGet(feature));
}
else {
// feature is an EReference
// target EObjects in lrm might not have been created yet!!! TODO:
EObject codeTarget = (EObject) codeObject.eGet(feature);
if (codeTarget != null) {
// try to find a corresponding target
EObject lrmTarget = fsmlId2lastReconciledModelObject.get(codeTarget);
lrmObject.eSet(feature, lrmTarget);
}
}
}
// Michal: the problem is with the default values for booleans and integers. Maybe we should always
// use object types!!! Null would represent undecided.
}
else if (syncItem instanceof ClassSyncItem){
ClassSyncItem subfeatureSyncItem = (ClassSyncItem) syncItem;
updateLastReconciledModel(subfeatureSyncItem);
}
}
}
}
}