/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * * 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: * CEA LIST - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.uml.textedit.transition.xtext.ui.contributions; import java.util.ArrayList; import java.util.List; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.gmf.runtime.common.core.command.CommandResult; import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart; import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand; import org.eclipse.papyrus.commands.wrappers.GMFtoEMFCommandWrapper; import org.eclipse.papyrus.extensionpoints.editors.ui.IPopupEditorHelper; import org.eclipse.papyrus.infra.core.services.ServicesRegistry; import org.eclipse.papyrus.infra.core.utils.ServiceUtils; import org.eclipse.papyrus.uml.textedit.transition.xtext.ui.internal.UmlTransitionActivator; import org.eclipse.papyrus.uml.textedit.transition.xtext.umlTransition.BehaviorKind; import org.eclipse.papyrus.uml.textedit.transition.xtext.umlTransition.CallOrSignalEventRule; import org.eclipse.papyrus.uml.textedit.transition.xtext.umlTransition.ChangeEventRule; import org.eclipse.papyrus.uml.textedit.transition.xtext.umlTransition.EventRule; import org.eclipse.papyrus.uml.textedit.transition.xtext.umlTransition.RelativeTimeEventRule; import org.eclipse.papyrus.uml.textedit.transition.xtext.umlTransition.TimeEventRule; import org.eclipse.papyrus.uml.textedit.transition.xtext.umlTransition.TransitionRule; import org.eclipse.ui.IEditorPart; import org.eclipse.uml2.common.util.UML2Util; import org.eclipse.uml2.uml.Activity; import org.eclipse.uml2.uml.Behavior; import org.eclipse.uml2.uml.CallEvent; import org.eclipse.uml2.uml.ChangeEvent; import org.eclipse.uml2.uml.Constraint; import org.eclipse.uml2.uml.Event; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.OpaqueBehavior; import org.eclipse.uml2.uml.OpaqueExpression; import org.eclipse.uml2.uml.Operation; import org.eclipse.uml2.uml.Package; import org.eclipse.uml2.uml.PackageableElement; import org.eclipse.uml2.uml.Signal; import org.eclipse.uml2.uml.SignalEvent; import org.eclipse.uml2.uml.StateMachine; import org.eclipse.uml2.uml.TimeEvent; import org.eclipse.uml2.uml.TimeExpression; import org.eclipse.uml2.uml.Transition; import org.eclipse.uml2.uml.Trigger; import org.eclipse.uml2.uml.UMLFactory; import org.eclipse.uml2.uml.ValueSpecification; import org.eclipse.xtext.gmf.glue.PopupEditorConfiguration; import org.eclipse.xtext.gmf.glue.edit.part.DefaultXtextSemanticValidator; import org.eclipse.xtext.gmf.glue.edit.part.IXtextEMFReconciler; import org.eclipse.xtext.ui.editor.utils.EditorUtils; import com.google.inject.Injector; /** * @author CEA LIST * * This class is used for contribution to the Papyrus extension point DirectEditor. It is used for the integration * of an xtext generated editor, for Transitions of UML StateMachines. * */ public class TransitionPopupEditorConfigurationContribution extends PopupEditorConfiguration { private Transition transition = null; private final static String EVENTS = "events"; private TransitionRule transitionRuleObject = null; /* * (non-Javadoc) * * @see * org.eclipse.xtext.gmf.glue.PopupEditorConfiguration#createPopupEditorHelper(org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart) */ @Override public IPopupEditorHelper createPopupEditorHelper(Object editPart) { // resolves the edit part, and the associated semantic element if(!(editPart instanceof IGraphicalEditPart)) { return null; } final IGraphicalEditPart graphicalEditPart = (IGraphicalEditPart)editPart; if(!(graphicalEditPart.resolveSemanticElement() instanceof Transition)) { return null; } transition = (Transition)graphicalEditPart.resolveSemanticElement(); // retrieves the XText injector Injector injector = UmlTransitionActivator.getInstance().getInjector("org.eclipse.papyrus.uml.textedit.transition.xtext.UmlTransition"); // builds the text content and extension for a temporary file, to be edited by the xtext editor String textToEdit = "" + this.getTextToEdit(graphicalEditPart.resolveSemanticElement()); String fileExtension = "" + ".umltransition"; // builds a new IXtextEMFReconciler. // Its purpose is to extract any relevant information from the textual specification, // and then merge it in the context UML model if necessary IXtextEMFReconciler reconciler = new IXtextEMFReconciler() { /** * {@inheritDoc} */ public void reconcile(EObject modelObject, EObject xtextObject) { // first: retrieves / determines if the xtextObject is a TransitionRule object EObject modifiedObject = xtextObject; if(!(modelObject instanceof Transition)) { return; } while(xtextObject != null && !(xtextObject instanceof TransitionRule)) { modifiedObject = modifiedObject.eContainer(); } if(modifiedObject == null) { return; } transitionRuleObject = (TransitionRule)xtextObject; // Creates and executes the update command TransactionalEditingDomain dom = graphicalEditPart.getEditingDomain(); UpdateUMLTransitionCommand updateCommand = new UpdateUMLTransitionCommand(dom,transition); dom.getCommandStack().execute(new GMFtoEMFCommandWrapper(updateCommand)); } }; return super.createPopupEditorHelper(graphicalEditPart, injector, reconciler, textToEdit, fileExtension, new DefaultXtextSemanticValidator()); } /* * (non-Javadoc) * * @see org.eclipse.xtext.gmf.glue.PopupEditorConfiguration#getTextToEdit(java.lang.Object) */ @Override public String getTextToEdit(Object editedObject) { if(editedObject instanceof Transition) { Transition transition = (Transition)editedObject; String textToEdit = ""; // Triggers if(!transition.getTriggers().isEmpty()) { boolean isFirstTrigger = true; for(Trigger t : transition.getTriggers()) { if(!isFirstTrigger) { textToEdit = textToEdit + ", "; } else { isFirstTrigger = false; } Event e = t.getEvent(); if(e instanceof CallEvent) { textToEdit = textToEdit + ((CallEvent)e).getOperation().getName(); } else if(e instanceof SignalEvent) { textToEdit = textToEdit + ((SignalEvent)e).getSignal().getName(); } else if(e instanceof ChangeEvent) { textToEdit = textToEdit + "when " + "\"" + retrieveBody((OpaqueExpression)((ChangeEvent)e).getChangeExpression(), "Natural language") + "\""; } else if(e instanceof TimeEvent) { String absRelPrefix = "" + (((TimeEvent)e).isRelative() ? "after " : "at "); textToEdit = textToEdit + absRelPrefix + "\"" + retrieveBody((OpaqueExpression)((TimeEvent)e).getWhen().getExpr(), "Natural language") + "\""; } else { // any receive event textToEdit = textToEdit + "all"; } } } // Guard if(transition.getGuard() != null && transition.getGuard().getSpecification() != null) { textToEdit = textToEdit + " [" + "\"" + retrieveBody((OpaqueExpression)transition.getGuard().getSpecification(), "Natural language") + "\"" + "]"; } if(transition.getEffect() != null) { textToEdit = textToEdit + " / "; String behaviorKind = ""; behaviorKind = behaviorKind + ((behaviorKind.equals("") && (transition.getEffect() instanceof Activity)) ? "Activity " : ""); behaviorKind = behaviorKind + ((behaviorKind.equals("") && (transition.getEffect() instanceof StateMachine)) ? "StateMachine " : ""); behaviorKind = behaviorKind + ((behaviorKind.equals("") && (transition.getEffect() instanceof OpaqueBehavior)) ? "OpaqueBehavior " : ""); textToEdit = textToEdit + behaviorKind + " " + transition.getEffect().getName(); } return textToEdit; } return "not a State"; } private String retrieveBody(OpaqueExpression exp, String languageName) { String body = ""; if(exp == null) { return body; } int index = 0; for(String _languageName : exp.getLanguages()) { if(_languageName.equals(languageName)) { if(index < exp.getBodies().size()) { return exp.getBodies().get(index); } else { return ""; } } index++; } return body; } /** * @author CEA LIST * * A command for updating the context UML model */ protected class UpdateUMLTransitionCommand extends AbstractTransactionalCommand { private Transition transition; private String newName = ""; private List<Trigger> newTriggers = new ArrayList<Trigger>(); private Constraint newConstraint = null; private Behavior newEffectBehavior = null; /* * (non-Javadoc) * * @see * org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand#doExecuteWithResult(org.eclipse.core.runtime.IProgressMonitor * , org.eclipse.core.runtime.IAdaptable) */ @Override protected CommandResult doExecuteWithResult(IProgressMonitor arg0, IAdaptable arg1) throws ExecutionException { //////////////////////////////////////////////////////////// // First delete any elements associated with the transition //////////////////////////////////////////////////////////// // - Owned effect behavior Behavior effect = transition.getEffect(); transition.setEffect(null); if(effect != null) { effect.destroy(); } // - Events associated with triggers of this transition for(Trigger t : transition.getTriggers()) { Event e = t.getEvent(); t.setEvent(null); if(UML2Util.getNonNavigableInverseReferences(e).size() == 0) { // no trigger is referencing the event any more, delete call event e.destroy(); } } // - Triggers owned by this transition transition.getTriggers().removeAll(transition.getTriggers()); // - Guard associated with the transition Constraint guard = transition.getGuard(); transition.setGuard(null); if(guard != null) { guard.destroy(); } ////////////////////////////////////////////////////////////////////////////////////////////////// // Then extract any relevant information from the TransitionRuleObject, and update the Transition ////////////////////////////////////////////////////////////////////////////////////////////////// // Create the new triggers if(transitionRuleObject.getTriggers() != null) { for(EventRule eventRule : transitionRuleObject.getTriggers()) { Trigger newTrigger = UMLFactory.eINSTANCE.createTrigger(); this.newTriggers.add(newTrigger); newTrigger.setEvent(createUMLEvent(eventRule)); } transition.getTriggers().addAll(this.newTriggers); } // Create the new constraint if(transitionRuleObject.getGuard() != null && transitionRuleObject.getGuard().getConstraint() != null) { this.newConstraint = transition.createGuard(""); OpaqueExpression guardSpecification = UMLFactory.eINSTANCE.createOpaqueExpression(); guardSpecification.getLanguages().add("Natural language"); guardSpecification.getBodies().add("" + transitionRuleObject.getGuard().getConstraint()); this.newConstraint.setSpecification(guardSpecification); } // Create the new behavior if(transitionRuleObject.getEffect() != null && transitionRuleObject.getEffect().getKind() != null && transitionRuleObject.getEffect().getBehaviorName() != null) { this.newEffectBehavior = createUMLBehavior(transitionRuleObject.getEffect().getKind(), transitionRuleObject.getEffect().getBehaviorName()); this.transition.setEffect(newEffectBehavior); } return CommandResult.newOKCommandResult(transition); } /** * put events in a sub-directory of the nearest package * * @return the resulting package */ protected Package getEventPackage() { Package np = transition.getNearestPackage(); for(int i = 0;; i++) { String name = EVENTS; if(i > 0) { name += i; } PackageableElement ep = np.getPackagedElement(name); if(ep instanceof Package) { return (Package)ep; } else if(ep == null) { // does not exist, create return np.createNestedPackage(name); } // exists, but is not a package, try again with different name ... } } /** * Create a new call event (or get an existing call event) for an operation * * @param operation * @return */ private CallEvent getOrCreateCallEvent(Operation operation) { String name = "CE - " + operation.getClass_().getName() + " - " + operation.getName(); Package eventPkg = getEventPackage(); for(PackageableElement existingPE : eventPkg.getPackagedElements()) { if(existingPE instanceof CallEvent) { // Call event with this operation exists already if(((CallEvent)existingPE).getOperation() == operation) { ((CallEvent)existingPE).setName(name); return (CallEvent)existingPE; } } } CallEvent ce = UMLFactory.eINSTANCE.createCallEvent(); ce.setOperation(operation); ce.setName(name); eventPkg.getPackagedElements().add(ce); return ce; } /** * Create a new signal event (or get an existing) for a signal * * @param operation * @return */ private SignalEvent getOrCreateSignalEvent(Signal signal) { Package eventPkg = getEventPackage(); String name = "SE - " + signal.getName(); for(PackageableElement existingPE : eventPkg.getPackagedElements()) { if(existingPE instanceof SignalEvent) { // Call event with this operation exists already if(((SignalEvent)existingPE).getSignal() == signal) { ((SignalEvent)existingPE).setName(name); return (SignalEvent)existingPE; } } } SignalEvent se = UMLFactory.eINSTANCE.createSignalEvent(); se.setSignal(signal); se.setName(name); eventPkg.getPackagedElements().add(se); return se; } /** * Create a new change event (or get an existing) for an opaque change expression * * @param operation * @return */ private ChangeEvent getOrCreateChangeEvent(String opaqueChangeExpr) { Package eventPkg = getEventPackage(); String name = "CE - " + opaqueChangeExpr; for(PackageableElement existingPE : eventPkg.getPackagedElements()) { if(existingPE instanceof ChangeEvent) { // Call event with this operation exists already ValueSpecification vs = ((ChangeEvent)existingPE).getChangeExpression(); if(vs instanceof OpaqueExpression) { EList<String> bodies = ((OpaqueExpression)vs).getBodies(); if((bodies.size() > 0) && bodies.get(0).equals(opaqueChangeExpr)) { ((ChangeEvent)existingPE).setName(name); return (ChangeEvent)existingPE; } } } } ChangeEvent ce = UMLFactory.eINSTANCE.createChangeEvent(); OpaqueExpression changeExpression = UMLFactory.eINSTANCE.createOpaqueExpression(); changeExpression.getLanguages().add("Natural language"); changeExpression.getBodies().add(opaqueChangeExpr); ce.setChangeExpression(changeExpression); ce.setName(name); eventPkg.getPackagedElements().add(ce); return ce; } /** * Create a new time event (or get an existing) for an opaque time expression * * @param operation * @return */ private TimeEvent getOrCreateTimeEvent(String opaqueWhen, boolean isRelative) { Package eventPkg = getEventPackage(); String name = "TE - " + opaqueWhen; for(PackageableElement existingPE : eventPkg.getPackagedElements()) { if(existingPE instanceof TimeEvent) { // Call event with this operation exists already ValueSpecification vs = ((TimeEvent)existingPE).getWhen().getExpr(); if(vs instanceof OpaqueExpression) { EList<String> bodies = ((OpaqueExpression)vs).getBodies(); if((bodies.size() > 0) && bodies.get(0).equals(opaqueWhen)) { ((TimeEvent)existingPE).setName(name); return (TimeEvent)existingPE; } } } } TimeEvent te = UMLFactory.eINSTANCE.createTimeEvent(); OpaqueExpression timeExpressionExp = UMLFactory.eINSTANCE.createOpaqueExpression(); timeExpressionExp.getLanguages().add("Natural language"); timeExpressionExp.getBodies().add(opaqueWhen); TimeExpression timeExpression = UMLFactory.eINSTANCE.createTimeExpression(); timeExpression.setExpr(timeExpressionExp); te.setWhen(timeExpression); te.setIsRelative(isRelative); te.setName(name); eventPkg.getPackagedElements().add(te); return te; } private Event createUMLEvent(EventRule eventRule) { Event e = null; // TODO : implement if(eventRule instanceof CallOrSignalEventRule) { CallOrSignalEventRule callOrSignalEventRule = (CallOrSignalEventRule)eventRule; if(callOrSignalEventRule.getOperationOrSignal() != null) { NamedElement operationOrSignal = callOrSignalEventRule.getOperationOrSignal(); if(operationOrSignal instanceof Operation) { e = getOrCreateCallEvent((Operation)operationOrSignal); } else { // instanceof Signal e = getOrCreateSignalEvent((Signal)operationOrSignal); } } } else if(eventRule instanceof ChangeEventRule) { ChangeEventRule changeEventRule = (ChangeEventRule)eventRule; if(changeEventRule.getExp() != null) { e = getOrCreateChangeEvent(changeEventRule.getExp()); } } else if(eventRule instanceof TimeEventRule) { TimeEventRule timeEventRule = (TimeEventRule)eventRule; if(timeEventRule.getExpr() != null) { e = getOrCreateTimeEvent(timeEventRule.getExpr(), timeEventRule instanceof RelativeTimeEventRule); } } else { // AnyReceiveEventRule e = UMLFactory.eINSTANCE.createAnyReceiveEvent(); getEventPackage().getPackagedElements().add(e); e.setName("any"); } return e; } private Behavior createUMLBehavior(BehaviorKind kind, String name) { if(kind == null) { return null; } Behavior behavior = null; switch(kind) { case ACTIVITY: behavior = UMLFactory.eINSTANCE.createActivity(); break; case OPAQUE_BEHAVIOR: behavior = UMLFactory.eINSTANCE.createOpaqueBehavior(); break; case STATE_MACHINE: behavior = UMLFactory.eINSTANCE.createStateMachine(); break; default: break; } behavior.setName("" + name); return behavior; } public UpdateUMLTransitionCommand(TransactionalEditingDomain domain, Transition transition) { super(domain, "Transition Update", getWorkspaceFiles(transition)); this.transition = transition; } } }