385 lines
12 KiB
Java
Executable File
385 lines
12 KiB
Java
Executable File
package pyUML.views;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Vector;
|
|
|
|
import org.eclipse.core.resources.IProject;
|
|
import org.eclipse.emf.common.util.EList;
|
|
import org.eclipse.emf.ecore.EObject;
|
|
import org.eclipse.emf.ecore.resource.Resource;
|
|
import org.eclipse.jface.action.IAction;
|
|
import org.eclipse.jface.dialogs.MessageDialog;
|
|
import org.eclipse.jface.viewers.ISelection;
|
|
import org.eclipse.jface.viewers.TreeSelection;
|
|
import org.eclipse.ui.IObjectActionDelegate;
|
|
import org.eclipse.ui.IWorkbenchPage;
|
|
import org.eclipse.ui.IWorkbenchPart;
|
|
import org.eclipse.ui.PlatformUI;
|
|
import org.eclipse.uml2.uml.Association;
|
|
import org.eclipse.uml2.uml.Class;
|
|
import org.eclipse.uml2.uml.Classifier;
|
|
import org.eclipse.uml2.uml.Element;
|
|
import org.eclipse.uml2.uml.Generalization;
|
|
import org.eclipse.uml2.uml.Model;
|
|
import org.eclipse.uml2.uml.Package;
|
|
import org.eclipse.uml2.uml.ParameterableElement;
|
|
import org.eclipse.uml2.uml.Property;
|
|
import org.eclipse.uml2.uml.Relationship;
|
|
import org.eclipse.uml2.uml.Type;
|
|
import org.eclipse.uml2.uml.resource.UMLResource;
|
|
|
|
import pyUML.backend.EclipseHelperMethods;
|
|
import pyUML.backend.GlobalConstants;
|
|
import pyUML.backend.UMLToolsHelperMethods;
|
|
|
|
|
|
|
|
/**
|
|
* This is an action class where the global UML model
|
|
* is to be updated by a view.
|
|
* Classes cannot be copied, because this would destroy all
|
|
* model relationships of the class.
|
|
* New classes in a view are ignored, because it is not known where
|
|
* they are to be inserted in the model.
|
|
*
|
|
* At the moment, class attributes and methods are *not* synchronized
|
|
* with the mail model.
|
|
*
|
|
*/
|
|
public class SynchronizeViews implements IObjectActionDelegate{
|
|
private String pathToModel;;
|
|
private String pathToView;
|
|
//private Model model;
|
|
private Model viewModel;
|
|
private Resource[] modelResource = {null};
|
|
private Map<String, EObject> xmiModelDict;
|
|
private Map<EObject, String> modelXmiDict;
|
|
private Map<String, EObject> xmiViewDict;
|
|
private Map<EObject, String> viewXmiDict;
|
|
private IProject project;
|
|
|
|
|
|
public static void synchronizeGlobalModel(String pathToView, IProject project) {
|
|
SynchronizeViews sv = new SynchronizeViews();
|
|
sv.project = project;
|
|
sv.synchronizeGlobalModel(pathToView);
|
|
}
|
|
|
|
/**
|
|
* Updates the global model by changes made by the given view.
|
|
* Class deletions are removed from the view, not from the model.
|
|
*
|
|
* New classes/packages are ignored, because it is not known
|
|
* where they should be inserted.
|
|
*
|
|
* Other changes are updated in the global model.
|
|
*
|
|
* @param pathToModel
|
|
* @param pathToView
|
|
* @return
|
|
*/
|
|
public boolean synchronizeGlobalModel(String pathToView) {
|
|
String pathToModel = this.project.getLocation()
|
|
.append(GlobalConstants.getPyUmlDir())
|
|
.append(this.project.getName()+".uml").toOSString();
|
|
|
|
//init models
|
|
this.pathToModel = pathToModel;
|
|
this.pathToView = pathToView;
|
|
initModels();
|
|
|
|
// synchronize by all child classes
|
|
try{
|
|
List<Element> elementCopy = new Vector<Element>();
|
|
elementCopy.addAll(this.viewModel.getOwnedElements());
|
|
for (Element child : elementCopy) {
|
|
if (child instanceof Class)
|
|
synchronizeClass((Class)child);
|
|
}
|
|
} catch (Throwable t) {
|
|
t.printStackTrace();
|
|
}
|
|
|
|
// save model
|
|
return saveModel();
|
|
}
|
|
|
|
/**
|
|
* initializes the Model of the view and the main model
|
|
*/
|
|
private void initModels() {
|
|
// save all open editors
|
|
IWorkbenchPage page =
|
|
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
|
|
page.saveAllEditors(false);
|
|
|
|
// initialize XMI dictionaries as empty -> they are filled, if a
|
|
// model already exists
|
|
this.modelXmiDict = new HashMap<EObject, String>();
|
|
this.xmiModelDict = new HashMap<String, EObject>();
|
|
this.viewXmiDict = new HashMap<EObject, String>();
|
|
this.xmiViewDict = new HashMap<String, EObject>();
|
|
|
|
// load view and model
|
|
// this.model =
|
|
initModel(this.pathToModel, this.xmiModelDict, this.modelXmiDict, this.modelResource);
|
|
this.viewModel = initModel(this.pathToView, this.xmiViewDict, this.viewXmiDict, null);
|
|
}
|
|
|
|
/**
|
|
* loads a .uml model (main model or view)
|
|
* and fills the given dictionaries
|
|
* @param pathToModel
|
|
* @param xmiModelDict
|
|
* @param modelXmiDict
|
|
* @param modelResource
|
|
* @return
|
|
*/
|
|
private Model initModel(String pathToModel, Map<String, EObject> xmiModelDict,
|
|
Map<EObject, String> modelXmiDict, Resource[] modelResource) {
|
|
|
|
// look for model file
|
|
Model model;
|
|
Resource resource;
|
|
File modelFile = new File(pathToModel);
|
|
if (! modelFile.exists()) {
|
|
MessageDialog.openError(null, "Error synchronizing Model by View",
|
|
"Model can not be found: "+pathToModel);
|
|
return null;
|
|
}
|
|
// read Model
|
|
EObject diagramRoot = UMLToolsHelperMethods.loadUMLDiagram(pathToModel);
|
|
model = (Model) diagramRoot;
|
|
resource = model.eResource();
|
|
|
|
// get model -> xmi-id dictionary
|
|
UMLResource r = (UMLResource) resource;
|
|
modelXmiDict.putAll(r.getEObjectToIDMap());
|
|
|
|
// create reverse dict, so that xmi_id can be the key
|
|
for (EObject modelObject : modelXmiDict.keySet()) {
|
|
String xmi_id = modelXmiDict.get(modelObject);
|
|
xmiModelDict.put(xmi_id, modelObject);
|
|
}
|
|
if (modelResource != null)
|
|
modelResource[0] = resource;
|
|
return model;
|
|
}
|
|
|
|
|
|
/**
|
|
* Synchronizes a model class by a view class
|
|
* @param viewClass
|
|
*/
|
|
private void synchronizeClass(Class viewClass) {
|
|
String classXmiId = this.viewXmiDict.get(viewClass);
|
|
if (! xmiModelDict.containsKey(classXmiId)) {
|
|
MessageDialog.openWarning(null, "Error saving View",
|
|
"You created a new class \""+ viewClass.getName() +
|
|
"\" in the view.\n" +
|
|
"This is not supported, because it is not " +
|
|
"known \nwhere to put the class in the original " +
|
|
"model.\n" +
|
|
"The inserted class will be ignored.");
|
|
return;
|
|
|
|
}
|
|
|
|
Class modelClass = (Class) xmiModelDict.get(classXmiId);
|
|
Package parentPackage = (Package) modelClass.getOwner();
|
|
|
|
// destroy all generalizations and associations in model,
|
|
// if both ends are present in view and association is no more
|
|
// present in view
|
|
|
|
for (Relationship rel : modelClass.getRelationships()) {
|
|
if (rel instanceof Association) {
|
|
String modelRelationXmiID = this.modelXmiDict.get(rel);
|
|
if (modelRelationXmiID != null && ! this.xmiViewDict.containsKey(modelRelationXmiID)) {
|
|
rel.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
// now, copy all associations / attributes/methods from
|
|
// View to Model.
|
|
// We cannot just copy the whole class, because this would
|
|
// destroy the associations/generalizations etc.
|
|
// Also, we have to make sure to delete Attributes/methods in
|
|
// the model if they were deleted in the code.
|
|
|
|
for (Relationship rel : viewClass.getRelationships()) {
|
|
if (rel instanceof Association) {
|
|
Association assoc = (Association) rel;
|
|
|
|
EList<Element> elList = rel.getRelatedElements();
|
|
|
|
Type connectedElement = (Type) elList.get(0);
|
|
|
|
Property end1 = (Property) rel.getOwnedElements().get(0);
|
|
Property end2 = (Property) (rel.getOwnedElements().size() > 1 ? rel.getOwnedElements().get(1) : end1);
|
|
|
|
Type secondElement = (Type) connectedElement;
|
|
if (elList.size() > 1) {
|
|
secondElement = (Type) elList.get(1);
|
|
}
|
|
|
|
// if source (first) element is not current class,
|
|
// switch first and second element
|
|
// to be sure element to be connected by this class is second.
|
|
if (! (connectedElement == viewClass)) {
|
|
Type tmpElement = secondElement;
|
|
secondElement = connectedElement;
|
|
connectedElement = tmpElement;
|
|
|
|
Property tmpProp = end1;
|
|
end1=end2;
|
|
end2=tmpProp;
|
|
}
|
|
|
|
String secondElXmiID = this.viewXmiDict.get(secondElement);
|
|
// continue if connected element is not present in model
|
|
// -> this relationship can not be handled!
|
|
if (! this.xmiModelDict.containsKey(secondElXmiID)) {
|
|
continue;
|
|
}
|
|
|
|
Type connectedElementInModel = (Type) this.xmiModelDict.get(secondElXmiID);
|
|
|
|
if (rel.getOwnedElements().size() == 0) {
|
|
continue;
|
|
}
|
|
|
|
// get old association, if possible
|
|
Relationship modelRelationship = null;
|
|
if (this.xmiModelDict.containsKey(this.viewXmiDict.get(rel))) {
|
|
modelRelationship = (Relationship) this.xmiModelDict.get(this.viewXmiDict.get(rel));
|
|
}
|
|
|
|
|
|
boolean changed = true;
|
|
if (modelRelationship != null) {
|
|
changed = false;
|
|
Property modelEnd2 = (Property) modelRelationship.getOwnedElements().get(0);
|
|
Property modelEnd1 = (Property) (modelRelationship.getOwnedElements().size() > 1 ? modelRelationship.getOwnedElements().get(1) : modelEnd2);
|
|
|
|
// update classes that take part in this association
|
|
modelEnd1.setType(modelClass);
|
|
modelEnd2.setType(connectedElementInModel);
|
|
|
|
// look if anything changed in association; if yes, move view association
|
|
// to model
|
|
if ( modelEnd1.isNavigable() != end1.isNavigable()
|
|
|| modelEnd1.isNavigable() != end1.isNavigable()
|
|
|| modelEnd2.isNavigable() != end2.isNavigable()
|
|
|| ! modelEnd1.getAggregation().equals(end1.getAggregation())
|
|
|| ! modelEnd2.getAggregation().equals(end2.getAggregation())
|
|
|| ! modelEnd1.getName().equals(end1.getName())
|
|
|| ! modelEnd2.getName().equals(end2.getName())
|
|
|| modelEnd1.getLower() != end1.getLower()
|
|
|| modelEnd2.getLower() != end2.getLower()
|
|
|| modelEnd1.getUpper() != end1.getUpper()
|
|
|| modelEnd2.getUpper() != end2.getUpper()
|
|
|| ! this.modelXmiDict.get(modelEnd1.getType()).equals(this.viewXmiDict.get(end1.getType()))
|
|
|| ! this.modelXmiDict.get(modelEnd2.getType()).equals(this.viewXmiDict.get(end2.getType())))
|
|
changed = true;
|
|
}
|
|
|
|
if (changed) {
|
|
assoc.setPackage(parentPackage);
|
|
|
|
end1.setType(modelClass);
|
|
end2.setType(connectedElementInModel);
|
|
//modelClass.createAssociation(end1.isNavigable(), end1.getAggregation(), end1.getName(), end1.getLower(), end1.getUpper(), connectedElementInModel, end2.isNavigable(), end2.getAggregation(), end2.getName(), end2.getLower(), end2.getUpper());
|
|
|
|
if (modelRelationship != null)
|
|
modelRelationship.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
// delete all generalizations in model not present in view
|
|
List<Generalization> tmpGens= new Vector<Generalization>();
|
|
tmpGens.addAll(modelClass.getGeneralizations());
|
|
for (Generalization general: tmpGens) {
|
|
String modelSupXmiID = this.modelXmiDict.get(general.getGeneral());
|
|
Classifier viewSuperClass = (Classifier) this.xmiViewDict.get(modelSupXmiID);
|
|
|
|
boolean generalIsPresentInView = false;
|
|
for (Generalization viewSuper : viewClass.getGeneralizations()) {
|
|
if (viewSuper.getGeneral() == viewSuperClass) {
|
|
generalIsPresentInView = true;
|
|
break;
|
|
}
|
|
}
|
|
if (! generalIsPresentInView) {
|
|
general.destroy();
|
|
}
|
|
}
|
|
|
|
// copy all generalizations of classes
|
|
for (Generalization general: viewClass.getGeneralizations()) {
|
|
String superClassXmiID = viewXmiDict.get(general.getGeneral());
|
|
Classifier modelSuperClass = (Classifier) xmiModelDict.get(superClassXmiID);
|
|
|
|
boolean generalIsPresentInModel = false;
|
|
for (Generalization modelSuper : modelClass.getGeneralizations()) {
|
|
if (modelSuper.getGeneral() == modelSuperClass) {
|
|
generalIsPresentInModel = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! xmiViewDict.containsKey(superClassXmiID))
|
|
continue;
|
|
|
|
if (! generalIsPresentInModel) {
|
|
modelClass.createGeneralization(modelSuperClass);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Saves the edited model .uml file
|
|
* @return
|
|
*/
|
|
private boolean saveModel() {
|
|
try {
|
|
this.modelResource[0].save(null);
|
|
EclipseHelperMethods.updateFile(pathToModel, project);
|
|
return true;
|
|
} catch (IOException e) {
|
|
MessageDialog.openError(null, "Error saving model",
|
|
"The model cannot be saved. Reason:\n" + e.getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
|
|
// Auto-generated method stub
|
|
}
|
|
|
|
public void run(IAction action) {
|
|
this.synchronizeGlobalModel(this.pathToView);
|
|
|
|
}
|
|
|
|
public void selectionChanged(IAction action, ISelection selection) {
|
|
// save selected uml view
|
|
if (selection instanceof TreeSelection) {
|
|
TreeSelection ts = (TreeSelection) selection;
|
|
if (ts.getFirstElement() instanceof org.eclipse.core.internal.resources.File) {
|
|
org.eclipse.core.internal.resources.File selectedFile = (org.eclipse.core.internal.resources.File) ts.getFirstElement();
|
|
this.pathToView = selectedFile.getLocation().toOSString();
|
|
this.project = selectedFile.getProject();
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|