| /***************************************************************************** |
| * 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.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.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.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"); |
| |
| // * Set a name for the skill definition |
| RecordingCommand setNameCmd = new RecordingCommand(TransactionUtil.getEditingDomain(op)){ |
| @Override |
| protected void doExecute() { |
| NamingUtil.setName(op, SKILLDEF_PREFIX); |
| } |
| }; |
| setNameCmd.execute(); |
| |
| // * 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; |
| } |
| |
| } |