blob: 8b50d42295820ecba4fa0a925666c7d015598c41 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2020 CEA LIST
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Matteo Morelli - initial API and implementation
*
*****************************************************************************/
package org.eclipse.papyrus.robotics.diagrams.advices;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.gmf.runtime.common.core.command.CompositeCommand;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.emf.type.core.edithelper.AbstractEditHelperAdvice;
import org.eclipse.gmf.runtime.emf.type.core.requests.ConfigureRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.DestroyDependentsRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.DestroyElementRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.GetEditContextRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.IEditCommandRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.SetRequest;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.papyrus.designer.transformation.core.transformations.LazyCopier;
import org.eclipse.papyrus.designer.transformation.core.transformations.LazyCopier.CopyExtResources;
import org.eclipse.papyrus.infra.emf.gmf.command.EMFtoGMFCommandWrapper;
import org.eclipse.papyrus.infra.services.edit.service.ElementEditServiceUtils;
import org.eclipse.papyrus.infra.services.edit.service.IElementEditService;
import org.eclipse.papyrus.robotics.core.commands.CancelCommand;
import org.eclipse.papyrus.robotics.core.utils.NamingUtil;
import org.eclipse.papyrus.robotics.diagrams.Activator;
import org.eclipse.papyrus.robotics.profile.robotics.skills.SkillDefinition;
import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil;
import org.eclipse.swt.widgets.Display;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.PackageImport;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.util.UMLUtil;
/**
* Edit Helper Advice for creating a new SkillDefinition
*/
public class SkillDefinitionEditHelperAdvice extends AbstractEditHelperAdvice {
private static final String SKILLDEF_PREFIX = "SkillDefinition"; //$NON-NLS-1$
private static final String SKILLDEF_DEFAULT_SEMANTICS_NAME_SUFFIX = "DefaultFSM"; //$NON-NLS-1$
private static final String SKILLDEF_DEFAULT_SEMANTICS_PKG_NAME = "defaultsemantics";
private static final String SKILLDEF_DEFAULT_SEMANTICS_TEMPLATE_NAME = "DefaultSkillFSMTemplate";
boolean errShown = false;
/**
* The attribute which identifies the template definition of default skill semantics
*/
StateMachine defSemantics;
/**
* The package in the current model where the template of default skill semantics must be copied
*/
Package destPkg;
/**
* The copy of default skill semantics template in the local model
*/
StateMachine newDefSemantics;
/**
* Logics to compute the name of the FSM associated to the skill definition
*/
protected static String calcFSMName(String skdefName) {
return skdefName + SKILLDEF_DEFAULT_SEMANTICS_NAME_SUFFIX;
}
/**
* Utility method to get the default semantics associated to a UML::Operation that represent a skill definition
*/
protected static StateMachine getDefaultSemantics(Operation op) {
StateMachine fsm = null;
if (op != null) {
SkillDefinition skdef = UMLUtil.getStereotypeApplication(op, SkillDefinition.class);
if (skdef != null && skdef.getDefaultSemantic() != null) {
fsm = skdef.getDefaultSemantic().getBase_StateMachine();
}
}
return fsm;
}
/**
*
*/
protected static void displayErrorMsgForSkillModelsUnableToManageFSM() {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
MessageDialog.openWarning(Display.getCurrent().getActiveShell(),
"Cannot modify the current model",
String.format("The current model does not support the definition of SkillSemantics.\n"
+ "See https://wiki.eclipse.org/Papyrus/customizations/robotics/faq#Q:_I_get_the_error_.22Cannot_modify_the_current_model.22_when_I_try_to_modify_an_existing_skill_model"));
}
});
}
/**
* Decide whether the requested command should be approved
*/
@Override
public ICommand getBeforeEditCommand(IEditCommandRequest request) {
// The request must fall in the following 3 cases:
// - EditContext
// - Set
// - DestroyDependents
if (! (request instanceof GetEditContextRequest ||
request instanceof SetRequest ||
request instanceof DestroyDependentsRequest)) {
return super.getBeforeEditCommand(request);
}
// We are in one of the 3 cases above: check model consistency
Model umlModel = null;
if (request instanceof GetEditContextRequest) {
// EditContext
Object elem = ((GetEditContextRequest) request).getEditContext();
// (get model)
if (elem instanceof Interface)
umlModel = ((Interface) elem).getModel();
}
else {
EObject elem = null;
if (request instanceof SetRequest) {
// Set
elem = ((SetRequest) request).getElementToEdit();
}
else if (request instanceof DestroyDependentsRequest) {
// Destroy
elem = ((DestroyDependentsRequest) request).getElementToDestroy();
}
// (get model)
if (elem instanceof Operation)
umlModel = ((Operation) elem).getModel();
}
// The request is accepted if the model is consitent with the new version
if (umlModel != null) {
// It must exist a package with name SKILLDEF_DEFAULT_SEMANTICS_PKG_NAME
destPkg = null;
for (Element e : umlModel.allOwnedElements())
if (e instanceof Package && ((Package) e).getName().equals(SKILLDEF_DEFAULT_SEMANTICS_PKG_NAME))
destPkg = (Package) e;
// It must exist a template FSM in the imported oepr.library
defSemantics = null;
for (PackageImport pi : umlModel.getPackageImports()) {
NamedElement el = pi.getImportedPackage().getOwnedMember(SKILLDEF_DEFAULT_SEMANTICS_TEMPLATE_NAME);
if (el != null) defSemantics = (StateMachine) el;
}
if (destPkg != null && defSemantics != null) {
errShown = false;
return super.getBeforeEditCommand(request);
}
// otherwise -> destPkg == null || defSemantics == null
displayErrorMsgForSkillModelsUnableToManageFSM();
errShown = true;
}
return null;
}
/**
* Set skill semantics after creation
*/
@Override
protected ICommand getAfterConfigureCommand(ConfigureRequest request) {
// * Init
//
// Get the skill definition which has just been created and the UML model where it is defined
EObject newElement = request.getElementToConfigure();
if (!(newElement instanceof Operation)) {
return super.getAfterConfigureCommand(request);
}
// (non-initialized class attributes to null, prepare the command, ...)
final Operation op = (Operation) newElement;
final Model umlModel = op.getModel();
CompositeCommand compositeCommand = new CompositeCommand("SkillDefinition configuration command"); //$NON-NLS-1$
final IElementEditService commandProvider = ElementEditServiceUtils.getCommandProvider(op);
SetRequest setSkillDefNameReq = new SetRequest(op, UMLPackage.eINSTANCE.getNamedElement_Name(),
NamingUtil.getName(op, SKILLDEF_PREFIX, "%04d")); //$NON-NLS-1$
ICommand setSkillDefNameCmd = commandProvider.getEditCommand(setSkillDefNameReq);
try {
setSkillDefNameCmd.execute(null, null);
} catch (ExecutionException ee) {
Activator.log.error(ee);
}
// * Copy the default semantics template to the 'defaultsemantics' package (provided by the wizard)
//
// - select destination package
if (destPkg == null) {
for (Element e : umlModel.allOwnedElements())
if (e instanceof Package && ((Package) e).getName().equals(SKILLDEF_DEFAULT_SEMANTICS_PKG_NAME))
destPkg = (Package) e;
}
// - select the FSM to be copied (element in the imported oepr.library)
if (defSemantics == null) {
for (PackageImport pi : umlModel.getPackageImports()) {
NamedElement el = pi.getImportedPackage().getOwnedMember(SKILLDEF_DEFAULT_SEMANTICS_TEMPLATE_NAME);
if (el != null) defSemantics = (StateMachine) el;
}
}
// The request has already been approved => destPkg and defSemantics cannot be null
//
// - do copy (and change the name according to the one of the skill definition)
RecordingCommand copyDefSemantics = new RecordingCommand(TransactionUtil.getEditingDomain(defSemantics)) {
/**
* Utility method for copying the stereotype applications of all elements of template FSM
* @param copy
* @param orig
*/
protected void addFSMStereotypesToResources(StateMachine copy, StateMachine orig) {
// add SkillSemantics stereotype (it's the unique stereotype applied)
UMLUtil.safeApplyStereotype(copy, orig.getAppliedStereotypes().get(0));
// visit all the elements
TreeIterator<EObject> copyIterator = copy.eAllContents();
TreeIterator<EObject> origIterator = orig.eAllContents();
while (origIterator.hasNext()) {
EObject copyObj = copyIterator.next();
EObject origObj = origIterator.next();
if (origObj instanceof Element) {
for (Stereotype s : ((Element) origObj).getAppliedStereotypes()) {
// apply the stereotype on the copied element
UMLUtil.safeApplyStereotype((Element) copyObj, s);
}
}
}
}
@Override
protected void doExecute() {
LazyCopier copier = new LazyCopier(umlModel, destPkg, CopyExtResources.ALL, false);
newDefSemantics = copier.getCopy(defSemantics);
newDefSemantics.setName(calcFSMName(op.getName())); // change name
//boolean x = StereotypeUtil.isApplied(newDefSemantics, SkillSemantic.class);
destPkg.getPackagedElements().add(newDefSemantics); // stereotype applications are not preserved in the copy
addFSMStereotypesToResources(newDefSemantics, defSemantics); // need to navigate the original FSM and do the copy
}
};
copyDefSemantics.execute(); // do not add to compositeCommand but execute it directly
// * Reference the new FSM as operation method
RecordingCommand assignDefFSMCmd = new RecordingCommand(TransactionUtil.getEditingDomain(defSemantics)) {
@Override
protected void doExecute() {
op.getMethods().add(newDefSemantics);
}
};
compositeCommand.add(EMFtoGMFCommandWrapper.wrap(assignDefFSMCmd));
// * Apply the SkillDefinition stereotype to the new element
RecordingCommand applySkillDefStereotypeCmd = new RecordingCommand(TransactionUtil.getEditingDomain(newElement)) {
@Override
protected void doExecute() {
StereotypeUtil.apply(op, SkillDefinition.class);
}
};
compositeCommand.add(EMFtoGMFCommandWrapper.wrap(applySkillDefStereotypeCmd));
// Execute the composite command
return compositeCommand.isEmpty() ? super.getAfterConfigureCommand(request) : compositeCommand;
}
/**
* Set name of default skill semantics according to that of skill
*/
@Override
protected ICommand getAfterSetCommand(SetRequest request) {
if (errShown == true) return new CancelCommand(request.getElementToEdit());
EStructuralFeature feat = request.getFeature();
EObject newElement = request.getElementToEdit();
// Do execute the afterSetCommand only if the modified structural feature is "name"
// (synch names of skill def and the state machine representing its default semantics)
if (!(feat.getName().equals("name") && newElement instanceof Operation)) {
return super.getAfterSetCommand(request);
}
final Operation op = (Operation) newElement;
String newName = (String) request.getValue();
CompositeCommand compositeCommand = new CompositeCommand("SkillDefinition/SkillSemantic name synch command");
// * Change name of FSM
StateMachine fsm = getDefaultSemantics(op);
if (fsm != null) {
ICommand setNameCmd = ElementEditServiceUtils.
getCommandProvider(op).
getEditCommand(
new SetRequest(fsm, UMLPackage.eINSTANCE.getNamedElement_Name(), calcFSMName(newName))
);
compositeCommand.add(setNameCmd);
}
// Eventually execute the composite command
return compositeCommand.isEmpty() ? super.getAfterSetCommand(request) : compositeCommand;
}
@Override
protected ICommand getBeforeDestroyElementCommand(
DestroyElementRequest request) {
if (errShown == true) return new CancelCommand(request.getElementToDestroy());
return super.getBeforeDestroyElementCommand(request);
}
/**
* Remove created FSM, if the skill definition is destroyed
*/
@Override
protected ICommand getAfterDestroyDependentsCommand(DestroyDependentsRequest request) {
if (errShown == true) return new CancelCommand(request.getElementToDestroy());
// create command and get the skill definition to be destroyed
CompositeCommand compositeCommand = new CompositeCommand("Skill definition destruction command"); //$NON-NLS-1$
EObject destroyElement = request.getElementToDestroy();
if (!(destroyElement instanceof Operation)) {
return super.getAfterDestroyDependentsCommand(request);
}
final Operation op = (Operation) destroyElement;
// get the associated semantics to be destroyed as well
StateMachine fsm = getDefaultSemantics(op);
if (fsm != null) {
final IElementEditService commandProvider = ElementEditServiceUtils.getCommandProvider(op);
DestroyElementRequest destroyDepReq = new DestroyElementRequest(fsm, false);
ICommand destroyDepCmd = commandProvider.getEditCommand(destroyDepReq);
compositeCommand.add(destroyDepCmd);
}
// Eventually execute the composite command
return compositeCommand.isEmpty() ? super.getAfterDestroyDependentsCommand(request) : compositeCommand;
}
}