How to create a Petri net editor using GMF (Part 4)

If you followed the tutorial well until now (you can check them out here: first, second and third part) you must have learned how to create a working diagram editor from a Domain Model. You now have a code that represents the base of your editor. However, that code is not yet complete. On one hand maybe some features don't work (as in the case of this tutorial), on the other hand you might want to add some other functionality that is not yet there, maybe input some constraints, maybe perform some on the fly validation.

In this part of the tutorial we are going to cover the following topics:

  • Working with the generated code
  • Modifying your models and regenerating your code
  • Fixing the missing functionality of our Petri net editor

Working with the generated code

You have generated three projects until now: the Model Code, the Edit Code and the Diagram Code. Before messing with the code you have to know a little bit about what was the intent behind it.

The first thing to know is that the code is generated with GMF is intended to be manually modified. You can, of course, use subclasses to avoid modifying the code, but the code was not designed for it. So, if they intended you to use the code in that way, i.e. modify it and not subclass it, then why not do it like that? When you modify some function, you just need to mark it. Generated code is marked with a javadoc @generated comment like in the following image.

    /**
     * @generated
     */
    public Arc2CreateCommand(CreateRelationshipRequest request, EObject source,
            EObject target) {
        super(request.getLabel(), null, request);
        this.source = source;
        this.target = target;
        container = deduceContainer(source, target);
    }

If you modify that piece of code and you want GMF not to modify that piece of code, you need to change the @generated into @generated NOT like in the following example:

    /**
     * @generated NOT
     */
    public Arc2CreateCommand(CreateRelationshipRequest request, EObject source,
            EObject target) {
        super(request.getLabel(), null, request);
        this.source = source;
        this.target = target;
        container = deduceContainer(source, target);
    }

You can also add new methods and classes to the generated one. If you do not mark them with the @generated annotation then they will not be modified. 

Modifying your models and regenerating your code

Here are some tips to modify your models and regenerate your code:

  • The files plugin.xml and MANIFEST.MF will never be regenerated by the code generator, so you need to delete them in order to have them regenerated.
  • When modifying the models: The GMF Mapping Model contains links to the three models it combines. If you need to modify the domain, graphical def or tooling model, it is better to open the Mapping Model and modifying them there. That way, you are sure that they are all synchronized.
  • When you delete a class from your domain model, regeneration will not delete it it from the generated code, you have to do it manually. In general, try not to delete or rename classes in the domain model, you'll have to fix a lot of red before being able to launch your diagram editor again. Adding classes, on the other hand, works pretty well.
  • If you modify the Domain Model, you need to regenerate the Model and Edit code.
  • This is a little bit obvious but I have to say it: Keep all your generated code and your models in version control. Please!

Fixing the missing functionality of our Petri net editor

The explanation of the whole architecture of the diagram editor is enough for several blog posts. We will concentrate in the explanation of the actual fix. There are three problems that hinder our diagram editor:

  1. The diagram editor cannot find the containers of arcs from places to transitions.
  2. The diagram editor's default rules for deciding if an edge can be created are not suited for our meta model.
  3. The diagram editor is not smart enough to decide which kind of arc to create. 

Let us tackle this problems one by one:

The diagram editor cannot find the containers of arcs from places to transitions. As we have seen, the arcs in our model belong to the transitions. If you look in the generated code in the class Arc2CreateCommand the constructor calls a method deduceContainer. By default, that method searches the container in the source of the arc. Given that incoming arcs have their source at the place, that method fails to return a valid container, which in turn makes the canExecute method fail.

To make it work, you need to change that method's implementation to the following:

    /**
     * Returns the target as the container.
     * @generated NOT
     */
    private static Transition deduceContainer(EObject source, EObject target) {
        return (Transition)target;
    }

The diagram editor's default rules for deciding if an edge can be created are not suited for our meta model. We need to modify the canExecute() function of the Arc2CreateCommand. The function canExecute is executed during the creation of the arc, i.e. when you put your mouse over a place of a transition and when you are dragging the arc over a figure. The default implementation assumes that having a source for the arc implies that the container has been found, if this is not true, then it automatically returns false. To modify that behavior you just need to modify the if that contains the getSource and replace it for a getTarget.

    /**
     * @generated NOT
     */
    public boolean canExecute() {
        if (source == null && target == null) {
            return false;
        }
        if (source != null && false == source instanceof Place) {
            return false;
        }
        if (target != null && false == target instanceof Transition) {
            return false;
        }
        if (getTarget() == null) { // in the original implementation, this if tests for getSource()
            return true; // link creation is in progress; target is not defined yet
        }
        // target may be null here but it's possible to check constraint
        if (getContainer() == null) {
            return false;
        }
        return PetrinetBaseItemSemanticEditPolicy.getLinkConstraints()
                .canCreateArc_4002(getContainer(), getSource(), getTarget());
    }

The diagram editor is not smart enough to decide which kind of arc to create. GMF was smart enough to realize that two different mappings to the arc class should imply two different kinds of figures in the generated code. However, it has problems deciding which graphical representation to choose. GMF decides the graphical representation to choose based on the domain element. In our case, we have the exact same domain representation for two mappings so it doesn't help (i.e. we map input and output arcs to the same element of the Domain Model). The solution to help GMF to differenciate between input arcs and output arcs in the domain model. In the Domain Model, the difference between an input arc and an output arc is to which field of the transition they belong. Input arcs belong to the incomingArcs field and outgoing arcs to the outgoingArcs field. In GMF, the members of a class are called features. To do this in GMF, you need to constraint the mappings, i.e. tell GMF you can only map this graphical representation to this domain element if some condition is true. In our example the condition is that the are has the right containing feature.

We do this in the Mapping model. Let us show how to do it for outgoing arcs. Go to the link mapping for the outgoing arc in your mapping model and create a new constraint under the Mapping (Right click on the Mapping, and in the contextual menu select New Child → Constraint). Select the constraint and edit it in the Properties view. The constraint can be expressed in several languages but for this tutorial we are using OCL, because it is the shortest way of doing it. In the Properties view, select ocl as the language and for the body use the following expression: self.eContainingFeature().name = 'outgoingArcs'. To be sure that it will work, you can add the constraint on both link mappings. The actual test to choose the mapping is done in the class PetrinetVisualIDRegistry (package net.lopezbobeda.petrinet.diagram.part), the function that does the job is getLinkWithClassVisualID.

Conclusion

This concludes this small tutorial. We have scratched the surface of how to generate the right model for your graphical editor. You should have an idea of how to work with the generated code and where are the parts of the code that handle some of the functionality. I really hope this was useful to you.

If you like these tutorials and would like to get them on your email, please subscribe using the side bar form. Also, do not hesitate to ask questions and give feedback in the comments!

 

 

 

tweet: