Jump to: navigation, search

Difference between revisions of "Papyrus Developer Guide/Editing Domains and Commands"

(Commands executed in a Post-Commit context (for advanced users))
(Initialize a new command with an editing domain)
Line 27: Line 27:
  
 
You can find more about this classes and methods in  
 
You can find more about this classes and methods in  
http://dev.eclipse.org/svnroot/modeling/org.eclipse.mdt.papyrus/trunk/doc/DevelopperDocuments/cookbook/PapyrusCookBook.odt
+
http://git.eclipse.org/c/papyrus/org.eclipse.papyrus.git/tree/doc/DevelopperDocuments/cookbook/PapyrusCookBook.odt
  
 
==EMF Transaction framework==
 
==EMF Transaction framework==

Revision as of 09:28, 7 May 2014

Initialize a new command with an editing domain

The same editing domain must always be used. Otherwise, you can result in actions which are stored in different domains and different stacks, which leads to several operation history which manage the same diagram but different operations.

The best way to achieve this is to always recover the editing domain using the method

   org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart.getEditingDomain()

when you have an edit part at hand. You can also use a method from one of the following classes, in order of preference:
If you have the serviceRegistry:

   import org.eclipse.papyrus.core.utils.ServiceUtils;
   ServiceUtils.getTransactionalEditingDomain(serviceRegistry);

If you have an EditPart:

   import org.eclipse.papyrus.diagram.common.util.ServiceUtilsForGMF;
   ServiceUtilsForGMF.getTransactionalEditingDomain( editpart.getDiagramEditDomain() );

If you have an EditPolicy:

   import org.eclipse.papyrus.diagram.common.util.ServiceUtilsForGMF;
   ServiceUtilsForGMF.getTransactionalEditingDomain( policy.getHost().getDiagramEditDomain() );

If you are in an ActionHandler (to be used with care !)

   import org.eclipse.papyrus.core.utils.ServiceUtilsForActionHandlers;
   ServiceUtilsForActionHandlers.getTransactionalEditingDomain();

Do not use anymore the old deprecated method:

   org.eclipse.papyrus.core.utils.EditorUtils.getTransactionalEditingDomain()

You can find more about this classes and methods in http://git.eclipse.org/c/papyrus/org.eclipse.papyrus.git/tree/doc/DevelopperDocuments/cookbook/PapyrusCookBook.odt

EMF Transaction framework

Always use a transactional command and domain when you can. The avantages of the EMF Transaction framework are :

  • Operations will be validated (emf validation is called by gmf framework on the editing domain). This validation can check a model or eventually correct it or reject modifications (using rules defined with the
   org.eclipse.emf.validation.constraintBindings

extension point).

  • Operations in a same transaction, will be rejected (in case validation fails), or undone, or will fail (in case an exception occurs) all at the same time. This avoids having unconsistent models with half-executed actions.

Implementing new commands

  • Note: Avoid to use GMF packages if your plugin doesn't need to depend on GMF. Use EMF transactional commands instead.
  • For model changes commands, try using transactionnal commands, by implementing
   org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand

note: Avoid to use the GMF packages as often as possible. Use the EMF transactional 
commands instead.


They may seem painfull to implement, but it ensures a better model consistency and its the only way to respect the EMF_Transaction_framework. In most cases, extending

   org.eclipse.gmf.runtime.common.core.command.CompositeCommand

note: Avoid to use the GMF packages as often as possible. Use the EMF transactional 
commands instead.

to add classic commands (AddCommand, SetPropertyCommand, ...) to make the job works fine and avoids any coding error.

  • You may also use GEF commands for graphical changes, inheriting
   org.eclipse.gef.commands.Command

note: Avoid to use the GMF packages as often as possible. Use the EMF transactional 
commands instead.


  • GMF non transactional commands extending
   org.eclipse.gmf.runtime.common.core.command.AbstractCommand

note: Avoid to use the GMF packages as often as possible. Use the EMF transactional 
commands instead.

are rarely used. Generally, they act like a proxy for GEF or transactional EMF commands, or may extend

   org.eclipse.gmf.runtime.common.core.command.CompositeCommand

note: Avoid to use the GMF packages as often as possible. Use the EMF transactional 
commands instead.

to enclose other GMF commands (which themselves follow the same rules). Otherwise, they must have no impact on the resource, for example, opening a popup.

Call a command's execution

Classic way

  • Changes which impact the model should get the CommandStack from the  transactionalEditingDomain:
   transactionalEditingDomain.getCommandStack().execute(cmd);    
  • It is also possible to execute against the IOperationHistory:
   OperationHistoryFactory.getOperationHistory().execute(cmd)


  • Changes which impact a GMF diagram should call the method
   org.eclipse.gmf.runtime.diagram.ui.parts.DiagramCommandStack.execute(ICommand, IProgressMonitor)

(this method encloses the IOperationHistory.execute method, but catches raised exceptions to avoid the diagram exploding in front of the user).

  • Make a single call per action, by constructing a compound (or composite) command.
  • You may use proxies or other execute methods of the same class to call any kind of commands (GEF, GMF, transactional EMF). The java compiler will avoid your getting lost in the commands hierarchies.

The diagram command stack can be obtained from an edit part with

   getDiagramEditDomain().getDiagramCommandStack();

You can also use, in case there is no edit part at your disposition :

   CommandStack stack = (CommandStack)EditorUtils.getMultiDiagramEditor().getAdapter(CommandStack.class);
   if(stack != null) {
     stack.execute(org.eclipse.gef.commands.Command);
   }

This call is in fact hiding a call to the same method by inheritance (Papyrus editor has a diagram command stack). But this call is less satisfying, because it can be confused with

   org.eclipse.emf.edit.domain.EditingDomain.getCommandStack().execute(...)

which MUST NOT be used (often called with the wrong editing domain, Initialize a new command with an editing domain).

Commands executed in a Post-Commit context (for advanced users)

Direct command

In a post-commit context, you may call directly

   org.eclipse.gmf.runtime.diagram.core.util.ViewUtil.setStructuralFeatureValue(View, EStructuralFeature, Object)

(for graphical changes), or use

   org.eclipse.emf.common.command.CompoundCommand.execute()

only in case your operation must not be undone nor logged in the history.

There are very few examples of such a case. In fact, I have met only two cases so far :

  • When the figure is being updated in an edit part thanks to a listener on the model (generally through the usage of
   org.eclipse.papyrus.diagram.common.helper.NotificationHelper

). In such a case, the graphic change is only a consequence of the model change, and undoing the model change will modify the figure again through the same listener.

  • In a validator registered with
   org.eclipse.emf.validation.constraintProviders

extension point. You can make a corrector, which will correct the model at each modification instead of simply validate and reject an unvalid modification. Any other way will raise an exception since the operation being validated/corrected is in post-commit state. This corrector approach works only in case you have redundant information in the model. For examples, a call behavior action's pins are heavily synchronized with the behavior's parameters ; when parameters are modified, you can deduce that the same modifications must occur on the corresponding pin (as implemented in org.eclipse.papyrus.diagram.activity.helper.PinAndParameterSynchronizer).

Editing Domain Listener

Thanks to an extension point you can listen the editing domain in order to interact with it. This extension point is org.eclipse.emf.transaction.listeners. To register on the Papyrus Editing Domain use the id org.eclipse.papyrus.SharedEditingDomainID. For example:

<extension
      point="org.eclipse.emf.transaction.listeners">
   <listener
         class="org.eclipse.papyrus.diagram.activity.listeners.InterruptibleEdgeListener">
      <editingDomain
            id="org.eclipse.papyrus.SharedEditingDomainID">
      </editingDomain>
   </listener>
</extension>

Now their is a convenient Abstract Class to create quick listener that you can find at org.eclipse.papyrus.diagram.common.listeners.AbstractModifcationTriggerListener<T>. This listener inherit from TriggerListener which allow to concatenate command before post commit state. The abstract class will ask you to implement methods such as isCorrectStructuralfeature which return true if the feature of the event concern you.

You can use the following example to implement it:

 
public class InterruptibleEdgeListener extends AbstractModifcationTriggerListener<ActivityEdge> {

	@Override
	protected boolean isCorrectStructuralfeature(EStructuralFeature eStructuralFeature) {
		if(UMLPackage.Literals.ACTIVITY_EDGE__INTERRUPTS.equals(eStructuralFeature)) {
			return true;
		}
		return false;
	}

	@Override
	protected ICommand getModificationCommand(Notification notif) {
		if(Notification.SET == notif.getEventType()) {
			IGraphicalEditPart edgeEditPart = getChildByEObject((EObject)notif.getNotifier(), getDiagramEditPart(), true);
			if(edgeEditPart != null && edgeEditPart instanceof InterruptibleEdge) {
				InterruptibleEdgeRequest request = new InterruptibleEdgeRequest();
				if(notif.getNewValue() != null) {
					request.setType(InterruptibleEdgeRequest.SET_INTERRUPTIBLE_EDGE);
				} else {
					request.setType(InterruptibleEdgeRequest.UNSET_INTERRUPTIBLE_EDGE);
				}
				Command command = edgeEditPart.getCommand(request);
				if(command != null && command.canExecute()) {
					return new CommandProxy(command);
				}
			}
		}
		return null;
	}

	@Override
	protected ActivityEdge getElement(Notification notif) {
		Object element = notif.getNotifier();
		if(element instanceof ActivityEdge) {
			return (ActivityEdge)element;
		}
		return null;
	}
}