/*******************************************************************************
* 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 java.util.Iterator;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
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.provider.IEditingDomainItemProvider;
import org.eclipse.jface.action.IAction;
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.IEditorActionDelegate;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import ca.uwaterloo.gsd.fsml.core.Markers;
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.ReconciliationDecision;
import ca.uwaterloo.gsd.fsml.sync.StructuralFeatureSyncItem;
import ca.uwaterloo.gsd.fsml.sync.SyncFactory;
import ca.uwaterloo.gsd.fsml.sync.SyncItem;
import ca.uwaterloo.gsd.fsml.sync.SynchronizationResult;
import ca.uwaterloo.gsd.fsml.sync.SynchronizationState;
import ca.uwaterloo.gsd.fsml.sync.provider.SyncEditPlugin;
/**
* @author Michal Antkiewicz <mantkiew@gsd.uwaterloo.ca>
*/
public class ModelCodeSynchronizeAction implements IEditorActionDelegate {
/**
* This has to be set by the SynchronizeRunnable
*/
protected SynchronizationResult synchronizationResult;
protected IAction action;
protected IFsmlEditor editor;
protected IProject project;
protected Model assertedModel;
protected Model lastReconciledModel;
protected Model implementationModel;
protected HashMap<String, EObject> fsmlId2assertedModelFeature;
protected HashMap<String, EObject> fsmlId2implementationModelFeature;
protected HashMap<String, EObject> fsmlId2lastReconciledModelFeature;
/**
* This method has to perform comparison of the implementation model and asserted model.
* The result of the comparison has to be stored in synchronizationResult field.
* The clients may override this method to create custom synchronization runnable.
* The clients may extend default synchronization runnable implementation: SynchronizeRunnable
* By default this method instantiates the generic SynchronizeRunnable.
* @return
*/
protected IRunnableWithProgress createSynchronizeRunnable() {
return new SynchronizeRunnable();
}
public void run(IAction action) {
fsmlId2assertedModelFeature = new HashMap<String, EObject>();
fsmlId2implementationModelFeature = new HashMap<String, EObject>();
fsmlId2lastReconciledModelFeature = new HashMap<String, EObject>();
// adapter factory used by SyncItemProviders
SyncEditPlugin.INSTANCE.putAdapterFactory(assertedModel.eClass().getEPackage(), editor.getAdapterFactory());
try {
Markers.INSTANCE.removeMarkers();
IRunnableWithProgress op = createSynchronizeRunnable();
// run the operation
new ProgressMonitorDialog(editor.getEditorSite().getShell()).run(true, true, op);
// compute default decisions for states
computeDefaultDecisions(synchronizationResult);
// and finally store the result of the synchronization
SyncEditPlugin.INSTANCE.putSynchronizationResult(assertedModel, synchronizationResult);
} catch (InvocationTargetException e) {
e.printStackTrace();
return;
} catch (InterruptedException e) {
Stats.INSTANCE.printMessage("Model Synchronization Interrupted");
//e.printStackTrace();
return;
}
// TODO: this is a hack! how to create adapters for code concepts in ClassSyncItemItemProvider?
// adapt all elements in the synchronization result from code
for (Iterator i = synchronizationResult.getClassSyncItems().iterator(); i.hasNext(); )
createAdapter((ClassSyncItem) i.next());
// show the results in the model-code synchronization view
IWorkbenchPage page = editor.getSite().getPage();
try {
IViewPart view = page.showView(
"ca.uwaterloo.gsd.fsml.ModelCodeSynchronizationView");
if (view instanceof ModelCodeSynchronizationView) {
ModelCodeSynchronizationView syncView = ((ModelCodeSynchronizationView) view);
// this will hook up the view with the editor; the view will grab the synchronization result.
page.activate(editor);
syncView.setSynchronizationResult(synchronizationResult);
syncView.refresh();
}
} catch (PartInitException e1) {
e1.printStackTrace();
}
editor.getViewer().refresh();
}
protected void computeDefaultDecisions(SynchronizationResult result) {
for (Object aux : result.getClassSyncItems()) {
ClassSyncItem conceptSyncItem = (ClassSyncItem) aux;
computeDefaultDecisions(conceptSyncItem);
}
}
/**
* @param conceptSyncItem
* Takes default decisions for all undecided sync items.
* Assumes no unresolved conflicts.
*/
private void computeDefaultDecisions(ClassSyncItem conceptSyncItem) {
// decide for the concept
if (conceptSyncItem.getReconciliationDecision().isUnspecified())
conceptSyncItem.setReconciliationDecision(defaultDecision(conceptSyncItem.getSynchronizationState()));
// decide for sub features
for (Object aux : conceptSyncItem.getSyncItems()) {
if (aux instanceof StructuralFeatureSyncItem) {
StructuralFeatureSyncItem attributeSyncItem = (StructuralFeatureSyncItem) aux;
if (attributeSyncItem.getReconciliationDecision().isUnspecified())
attributeSyncItem.setReconciliationDecision(defaultDecision(attributeSyncItem.getSynchronizationState()));
}
else if (aux instanceof ClassSyncItem) {
ClassSyncItem subfeatureSyncItem = (ClassSyncItem) aux;
computeDefaultDecisions(subfeatureSyncItem);
}
}
}
private ReconciliationDecision defaultDecision(SynchronizationState synchronizationState) {
if (synchronizationState.isForward())
return ReconciliationDecision.ENFORCE_LITERAL;
else if (synchronizationState.isReverse())
return ReconciliationDecision.UPDATE_LITERAL;
else if (synchronizationState.isChanged())
return ReconciliationDecision.ENFORCE_AND_UPDATE_LITERAL;
else if (synchronizationState.isConflict())
return ReconciliationDecision.UNSPECIFIED_LITERAL;
return ReconciliationDecision.IGNORE_LITERAL;
}
protected void createAdapter(ClassSyncItem item) {
EObject codeConcept = item.getCode();
if (codeConcept != null) {
editor.getAdapterFactory().adapt(codeConcept, IEditingDomainItemProvider.class);
for (Iterator i = item.getSyncItems().iterator(); i.hasNext(); ) {
Object aux = i.next();
if (aux instanceof ClassSyncItem)
createAdapter((ClassSyncItem) aux);
}
}
}
public void setActiveEditor(IAction action, IEditorPart targetEditor) {
this.action = action;
if (this.editor != targetEditor) {
if (targetEditor != null) {
if (targetEditor instanceof IFsmlEditor) {
editor = (IFsmlEditor) targetEditor;
IFileEditorInput input = (IFileEditorInput) editor.getEditorInput();
project = input.getFile().getProject();
if (project != null) {
Queries.INSTANCE.setProject(project);
action.setEnabled(editor != null && assertedModel != null);
Markers.INSTANCE.setProject(project);
return;
}
}
}
this.editor = null;
action.setEnabled(false);
}
}
/**
* Clients may override this method. Clients MUST set the assertedModel field.
*/
public void selectionChanged(IAction action, ISelection selection) {
if (selection instanceof IStructuredSelection) {
Object object = ((IStructuredSelection) selection).getFirstElement();
if (object instanceof Model)
assertedModel = (Model) object;
else
assertedModel = null;
}
action.setEnabled(project != null && editor != null && assertedModel != null);
}
// synchronization utilities
public void synchronizeInstanceAndSubfeatures(EObject model, EObject code, EObject lrm, ClassSyncItem classSyncItem) {
if (code == null && model == null && lrm == null) {
classSyncItem.setSynchronizationState(SynchronizationState.UNCHANGED_LITERAL);
return;
}
boolean hasForward = false;
boolean hasReverse = false;
boolean hasConflict = false;
boolean hasChanged = false;
EList sFeatures = null;
if (model != null)
sFeatures = model.eClass().getEAllStructuralFeatures();
else if (code != null)
sFeatures = code.eClass().getEAllStructuralFeatures();
else if (lrm != null)
sFeatures = lrm.eClass().getEAllStructuralFeatures();
// analyze subfeatures
for (Iterator features = sFeatures.iterator(); features.hasNext(); ) {
EStructuralFeature feature = (EStructuralFeature) features.next();
// simply ignore volatile & transient features
if (feature.isVolatile() || feature.isTransient())
continue;
SyncItem current = null;
if (feature instanceof EReference && ((EReference) feature).isContainment()) {
// so this is a subfeature
// a subfeature
if (feature.isMany()) {
EList<EObject> assertedSubfeatures = model != null ? (EList) model.eGet(feature) : new BasicEList<EObject>();
EList<EObject> implementationSubfeatures = code != null ? (EList) code.eGet(feature) : new BasicEList<EObject>();
//EList lastReconciledSubfeatures = lrm != null ? (EList) lrm.eGet(feature) : new BasicEList();
for (EObject assertedSubfeature : assertedSubfeatures) {
String featureFsmlID = FSMLEcoreUtil.getFSMLId(assertedSubfeature, null);
// find corresponding implementation and last reconciled features
EObject implementationSubfeature = fsmlId2implementationModelFeature.get(featureFsmlID);
EObject lastReconciledSubfeature = fsmlId2lastReconciledModelFeature.get(featureFsmlID);
ClassSyncItem subfeatureSyncItem = createClassSyncItem(assertedSubfeature, implementationSubfeature, lastReconciledSubfeature);
classSyncItem.getSyncItems().add(subfeatureSyncItem);
SynchronizationState state = subfeatureSyncItem.getSynchronizationState();
hasForward = hasForward || state.isForward();
hasReverse = hasReverse || state.isReverse();
hasConflict = hasConflict || state.isConflict();
hasChanged = hasChanged || state.isChanged();
}
for (Object object : implementationSubfeatures) {
EObject implementationSubfeature = (EObject) object;
String featureFsmlID = FSMLEcoreUtil.getFSMLId(implementationSubfeature, null);
// skip all features that have a corresponding asserted feature
EObject assertedSubfeature = fsmlId2assertedModelFeature.get(featureFsmlID);
if (assertedSubfeature == null) {
EObject lastReconciledSubfeature = fsmlId2lastReconciledModelFeature.get(featureFsmlID);
ClassSyncItem subfeatureSyncItem = createClassSyncItem(assertedSubfeature, implementationSubfeature, lastReconciledSubfeature);
classSyncItem.getSyncItems().add(subfeatureSyncItem);
SynchronizationState state = subfeatureSyncItem.getSynchronizationState();
hasForward = hasForward || state.isForward();
hasReverse = hasReverse || state.isReverse();
hasConflict = hasConflict || state.isConflict();
hasChanged = hasChanged || state.isChanged();
}
}
// do the same for lastReconciledSubfeatures
continue;
}
EObject assertedSubfeature = model != null ? (EObject) model.eGet(feature) : null;
EObject implementationSubfeature = code != null ? (EObject) code.eGet(feature) : null;
EObject lastReconciledSubfeature = lrm != null ? (EObject) lrm.eGet(feature) : null;
current = createClassSyncItem(assertedSubfeature, implementationSubfeature, lastReconciledSubfeature);
}
else
// normal attribute or reference
current = createStructuralFeatureSyncItem(model, code, lrm, feature);
if (current != null) {
// store concept or attribute sync item
classSyncItem.getSyncItems().add(current);
SynchronizationState state = current.getSynchronizationState();
hasForward = hasForward || state.isForward();
hasReverse = hasReverse || state.isReverse();
hasConflict = hasConflict || state.isConflict();
hasChanged = hasChanged || state.isChanged();
current = null;
}
}
SynchronizationState classInstanceState = SynchronizationState.getClassInstanceSynchronizationState(model, code, lrm, hasConflict, hasForward, hasReverse, hasChanged);
classSyncItem.setSynchronizationState(classInstanceState);
}
public ClassSyncItem createClassSyncItem(EObject modelObject, EObject codeObject, EObject archiveObject) {
ClassSyncItem classSyncItem = SyncFactory.eINSTANCE.createClassSyncItem();
// new sync item
classSyncItem.setModel(modelObject);
classSyncItem.setCode(codeObject);
synchronizeInstanceAndSubfeatures(modelObject, codeObject, archiveObject, classSyncItem);
return classSyncItem;
}
/**
* @param model may be null
* @param code may be null
* @param lrm may be null
* @param feature
* @return attribute sync item with appropriate synchronization state
*/
public StructuralFeatureSyncItem createStructuralFeatureSyncItem(EObject model, EObject code, EObject lrm, EStructuralFeature feature) {
boolean key = feature.getEAnnotation("key") != null;
boolean mandatory = feature.getLowerBound() == 1 && feature.getUpperBound() == 1;
boolean optional = feature.getLowerBound() == 0 && feature.getUpperBound() == 1;
/*if (!mandatory && !optional)
Stats.INSTANCE.logError("(!mandatory && !optional) for structural feature: " + feature.getName());
if (key && optional)
Stats.INSTANCE.logError("(key && optional) for structural feature: " + feature.getName());*/
// new attribute sync item
StructuralFeatureSyncItem attributeSyncItem = SyncFactory.eINSTANCE.createStructuralFeatureSyncItem();
attributeSyncItem.setStructuralFeature(feature);
Object modelValue = null;
Object codeValue = null;
Object lrmValue = null;
if (feature instanceof EReference && !feature.isMany()) {
// for references use keys
modelValue = model != null ? FSMLEcoreUtil.getFSMLId((EObject) model.eGet(feature), null) : null;
codeValue = code != null ? FSMLEcoreUtil.getFSMLId((EObject) code.eGet(feature), null) : null;
lrmValue = lrm != null ? FSMLEcoreUtil.getFSMLId((EObject) lrm.eGet(feature), null) : null;
}
else if (feature instanceof EReference && feature.isMany()) {
//Stats.INSTANCE.logMessage("Calculating keys for multi-valued references not supported yet.");
return null;
}
else {
if (feature.getEType().getName().equals("EBoolean")) {
boolean modelValueBoolean = (model != null ? (Boolean) model.eGet(feature) : false);
modelValue = modelValueBoolean ? modelValueBoolean : null;
boolean codeValueBoolean = code != null ? (Boolean) code.eGet(feature) : false;
codeValue = codeValueBoolean ? codeValueBoolean : null;
boolean archiveValueBoolean = lrm != null ? (Boolean) lrm.eGet(feature) : false;
lrmValue = archiveValueBoolean ? archiveValueBoolean : null;
}
else {
modelValue = model != null ? model.eGet(feature) : null;
codeValue = code != null ? code.eGet(feature) : null;
lrmValue = lrm != null ? lrm.eGet(feature) : null;
}
}
SynchronizationState featureState = SynchronizationState.getFeatureSynchronizationState(modelValue, codeValue, lrmValue);
attributeSyncItem.setSynchronizationState(featureState);
return attributeSyncItem;
}
protected class SynchronizeRunnable implements IRunnableWithProgress {
public SynchronizeRunnable() {
}
/**
* This method can be overridden by clients to provide custom analysis.
* By default, the method executes generic Queries.performAnalysis().
* @param progressMonitor
* @return implementation model
*/
protected final Model performAnalysis(IProgressMonitor progressMonitor) {
Queries.INSTANCE.reset();
registerCustomInterpreters();
Model model = createInitialModel();
return Queries.INSTANCE.performAnalysis(model, progressMonitor);
}
protected Model createInitialModel() {
return (Model) EcoreUtil.create(assertedModel.eClass());
}
/**
* This is the place where custom interpreters should be registered by calling
* Queries.registerCustomInterpreter(FSMLAnnotationInterpreter) method.
* This method is called after resetting the Queries and just before the start of
* Queries.performAnalysis(Model, IProgressMonitor)
*/
protected void registerCustomInterpreters() {
}
public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
lastReconciledModel = ((ModelContainer)assertedModel.eContainer()).getLastReconciledModel();
implementationModel = performAnalysis(progressMonitor);
// traverse each model and put elements to appropriate hash map
FSMLEcoreUtil.createFsmlId2EObjectMap(assertedModel, fsmlId2assertedModelFeature);
FSMLEcoreUtil.createFsmlId2EObjectMap(implementationModel, fsmlId2implementationModelFeature);
FSMLEcoreUtil.createFsmlId2EObjectMap(lastReconciledModel, fsmlId2lastReconciledModelFeature);
synchronizationResult = SyncFactory.eINSTANCE.createSynchronizationResult();
synchronizationResult.setAssertedModel(assertedModel);
synchronizationResult.setImplementationModel(implementationModel);
ClassSyncItem classSyncItem = SyncFactory.eINSTANCE.createClassSyncItem();
classSyncItem.setModel(assertedModel);
classSyncItem.setCode(implementationModel);
synchronizationResult.getClassSyncItems().add(classSyncItem);
synchronizeInstanceAndSubfeatures(assertedModel, implementationModel, lastReconciledModel, classSyncItem);
}
}
}