MagicDraw is a great modeling tool¹ for lot of reasons. First of all :

  • this is a mature tool,
  • enterprise ready,
  • and easy to use.

Then there’s a lot of great side functionalities like :

  • reporting templates,
  • dependency matrix,
  • active validation
  • and even more²!

But in a model driven environment, MagicDraw is great because of its open API which allows you to manipulate models and diagrams really easily.

So it’s time to introduce a simple plugin to get familiar with this open API!

Some resources to start

First of all, you’ll find javadoc and samples in the “<MagicDraw_Install_Dir>\openapi” directory and documentation in “<MagicDraw_Install_Dir>\manual\MagicDraw OpenAPI UserGuide.pdf“.

The documentation is really good, so I think you’ll find all what you need there.

How plugins are working?

Each time MagicDraw starts, it scans the “<MagicDraw_Install_Dir>\plugin” directory to find subdirectories containing two main things :

  • A “plugin.xml” file which describes your plugin, the required libraries, and how to start it.
  • A jar containing a class which extends the “com.nomagic.magicdraw.plugins.Plugin” and implements its “init()” method.

Plugin.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- The id must be unique, I generally put the class that extends the Plugin class as id. -->
<plugin
    <!-- The id must be unique, I generally put the class that extends the Plugin class as id. -->
    id="fr.free.mdwhatever.simplePlugin.magicDraw.SimplePlugin"
    name="SimplePlugin"

    <!-- Here I put project.version as version because Maven will filter it and put the same version you find in the pom.xml. -->
    version="${project.version}" 

    <!-- Who is providing this plugin (i.e. your company or whatever). -->
    provider-name="Xavier Seignard"

    <!-- The class which extends the com.nomagic.magicdraw.plugins.Plugin class
    in order to init your plugin within MagicDraw. -->
    class="fr.free.mdwhatever.simplePlugin.magicDraw.SimplePlugin">

    <!-- MagicDraw API version required by plug-in. Plug-ins and their requires versions required by plug-in. -->
    <requires>
        <api version="1.0" />
    </requires>

    <!-- The runtime libraries your plugin needs. -->
    <runtime>
        <library name="${artifactId}-${version}.${packaging}" />
    </runtime>
</plugin>

The SimplePlugin class

Here is the plugin entrance, take a look at the commented code.

package fr.free.mdwhatever.simplePlugin;

import com.nomagic.magicdraw.plugins.Plugin;
import com.nomagic.actions.ActionsCategory;
import com.nomagic.magicdraw.actions.ActionsConfiguratorsManager;
import com.nomagic.magicdraw.core.Application;

/**
 * This class allows you to load your plugin in MagicDraw
 *
 * @author Xavier Seignard
 */
public class SimplePlugin extends Plugin {

    // Methods
    /**
     * Plugin initialization and registration of the action in MagicDraw
     */
    public void init() {
        try {
            // We create a new actions category
            ActionsCategory category = new ActionsCategory(null, null);
            // We add our plugin action in this newly created category
            category.addAction(new SimplePluginAction());
            // We get the MagicDraw action manager
            ActionsConfiguratorsManager manager = ActionsConfiguratorsManager.getInstance();
            // We add our new configuration for our category in the MagicDraw action manager
            manager.addContainmentBrowserContextConfigurator(new BrowserContextConfigurator(category));
            // If everything is OK we log it to the MagicDraw GUI logger
            // Not really clever to put it there, but it's just to show how to log something in MagicDraw
            Application.getInstance().getGUILog().log("[Simple Plugin] Loading OK");
        }
        catch (Exception e) {
            // If something goes wrong we log it to the MagicDraw GUI logger
            Application.getInstance().getGUILog().log(
                    "[Simple Plugin] Could not instantiate plugin : " + e.toString());
        }
    }

    /**
     * The plugin is always supported, no matter the version of MagicDraw, or the perspective, or what you want.
     *
     * @return always true
     */
    public boolean isSupported() {
        return true;
    }

    /**
     * Return true always, because this plugin does not have any close specific actions.
     *
     * @return always true
     */
    public boolean close() {
        return true;
    }
}

This plugin has to do something!

How to trigger it?

Some plugin can just listen to some event, or it can be triggered by a contextual menu item or a button or whatever. In the case below we add a contextual menu item. (Have a look at the “com.nomagic.magicdraw.actions.ActionsConfiguratorsManager“)

package fr.free.mdwhatever.simplePlugin;

import com.nomagic.actions.ActionsManager;
import com.nomagic.magicdraw.actions.BrowserContextAMConfigurator;
import com.nomagic.magicdraw.ui.browser.Tree;
import com.nomagic.actions.ActionsCategory;
import com.nomagic.actions.NMAction;
import com.nomagic.magicdraw.actions.MDActionsCategory;
import com.nomagic.actions.AMConfigurator;

/**
 * This class allows you to add your plugin action (i.e. SimplePluginAction.java) in MagicDraw contextual menu.
 * @author Xavier Seignard
 */
public class BrowserContextConfigurator implements BrowserContextAMConfigurator {
    // Attributes

    // Here is the category of the contextual menu where you'll find you plugin
    public final static String SIMPLE_PLUGIN_MENU_CATEGORY = "Simple Plugin Menu Category";
    // Here is the ID of your category
    public final static String SIMPLE_PLUGIN_MENU_ID = "SimplePluginMenuID";
    // Plugin action
    private NMAction action;

    /**
     * Default constructor.
     *
     * @param nmAction the NMAction to set
     */
    public BrowserContextConfigurator(NMAction nmAction) {
        this.action = nmAction;
    }

    // Methods
    /**
     * Adds the plugin in the MagicDraw ui.
     *
     * @param mngr the action manager of MagicDraw to be configured
     * @param tree the containement tree of MagicDraw, where the plugin will be added
     */
    public void configure(ActionsManager mngr, Tree tree) {
        // We try to find the existing category
        ActionsCategory category = (ActionsCategory) mngr.getActionFor(SIMPLE_PLUGIN_MENU_ID);
        // If it doesn't exist we create it
        if (category == null) {
            category = new MDActionsCategory(SIMPLE_PLUGIN_MENU_ID, SIMPLE_PLUGIN_MENU_CATEGORY);
            category.setNested(true);
            mngr.addCategory(category);
        }
        // Then we add the action to the category
        category.addAction(action);
    }

    /**
     * Getter for priority.
     *
     * @return the priority int
     */
    public int getPriority() {
        return AMConfigurator.MEDIUM_PRIORITY;
    }
}

Set its behaviour

You have to extend the corresponding action, in this case : DefaultBrowserAction.

package fr.free.mdwhatever.simplePlugin;

import java.awt.event.ActionEvent;
import javax.swing.JOptionPane;
import com.nomagic.magicdraw.ui.browser.actions.DefaultBrowserAction;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;

/**
 * This class defines the behavior of the action in your plugin.
 *
 * @author Xavier Seignard
 */
public class SimplePluginAction extends DefaultBrowserAction {
    // Attributes
    // Serial version UID.
    private static final long serialVersionUID = 1L;
    // The name of the action which will be displayed in MagicDraw
    private static final String SIMPLE_PLUGIN_ACTION_NAME = "Simple Plugin Action";
    // The id of the action which will be displayed in MagicDraw
    private static final String SIMPLE_PLUGIN_ACTION_ID = "SimplePluginActionID";

    /**
     * Default constructor.
     *
     * @throws Exception if the super(...) call fails
     */
    public SimplePluginAction() throws Exception {
        super(SIMPLE_PLUGIN_ACTION_ID, SIMPLE_PLUGIN_ACTION_NAME, null, null);
    }

    // Methods
    /**
     * Here you define what does your action
     *
     * @param actionEvent the triggering event
     */
    public void actionPerformed(ActionEvent actionEvent) {
        SimplePluginBehavior simplePluginBehavior = new SimplePluginBehavior();
        CountingVisitor countingVisitor = simplePluginBehavior.getVisitor();
        // Clear the map for a clean count
        countingVisitor.getMap().clear();

        // Visit the children for counting elements of each type
        simplePluginBehavior.visitChildren((Element) getSelectedObject());
        // Display the counting results
        String result = simplePluginBehavior.returnResults(countingVisitor.getMap());
        JOptionPane.showMessageDialog(null, result);
    }

    /**
     * Defines when your action is available.
     */
    public void updateState() {
        // This action is only available when your click on an instance of Element in the containment tree
        if (this.getSelectedObject() != null) {
            if (this.getSelectedObject() instanceof Element) {
                setEnabled(true);
            }
            else {
                setEnabled(false);
            }
        }
        else {
            setEnabled(false);
        }
    }
}

This visitor pattern

The visitor pattern is well implemented in the Magicdraw open API, and it saves you a lot of efforts, so use it!

package fr.free.mdwhatever.simplePlugin;

import java.util.HashMap;
import com.nomagic.magicdraw.uml.BaseElement;
import com.nomagic.magicdraw.uml.ClassTypes;
import com.nomagic.magicdraw.uml.InheritanceVisitor;
import com.nomagic.uml2.ext.jmi.reflect.VisitorContext;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;

/**
 * This class extends InheritanceVisitor to count element through the visitor pattern.
 * @author Xavier Seignard
 *
 */
public class CountingVisitor extends InheritanceVisitor {

    // Map for counting results. Class type is a key, number is a value.
    private HashMap countMap = new HashMap();

    /**
     * The visitation of the element.
     *
     * @param element the element to visit.
     * @param context the context of the visitor.
     */
    public void visitElement(Element element, VisitorContext context) {
        super.visitElement(element, context);
        // Call the count
        countElements(element);
    }

    /**
     * Count elements of each type.
     *
     * @param element the element to count.
     */
    public void countElements(BaseElement element) {
        // We get the human readable name of the element
        String classType = ClassTypes.getShortName(element.getClassType());
        // Then we get the count of this type of elements
        Integer count = countMap.get(classType);
        // If it is the first element of this type we visit, we need to create a new value for the counting
        if (count == null) {
            count = Integer.valueOf(0);
        }
        // Finally we increase the count
        countMap.put(classType, Integer.valueOf(count.intValue() + 1));
    }

    /**
     * Getter for countMap.
     * @return the countMap
     */
    public HashMap getMap() {
        return countMap;
    }
}

The implementation of the behavior

Finally here is the implementation of the behaviour which allows you to calculate some metric like classes per package, or methods per classes :

package fr.free.mdwhatever.simplePlugin;

import java.util.HashMap;
import com.nomagic.magicdraw.core.Application;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;

/**
 * The behavior of our MagicDraw plugin
 *
 * @author Xavier Seignard
 */
public class SimplePluginBehavior {  

    // Our visitor to count the type of each element.
    private CountingVisitor visitor = new CountingVisitor();

    /**
     * Returns a string containing the counting results
     *
     * @param map the HashMap containing the counting results
     * @return string describing the counting results
     */
    public String returnResults(HashMap map)
    {
        // The string to return
        String textToReturn = "";
        textToReturn += "Number of elements for each types :\n";
        for (String key : map.keySet()) {
            textToReturn += "\t"  + key + " : " + (map.get(key)).intValue() + "\n";
        }

        // Calculation for some means (the real interesting part)
        Integer packages = map.get("Package");
        Integer classes = map.get("Class");
        Integer attributes = map.get("Property");
        Integer operations = map.get("Operation");

        if (packages != null && classes != null) {
            textToReturn += "Average classes per package : " + (classes.doubleValue()/packages.doubleValue()) + "\n";
        }

        if (classes!=null && operations!=null) {
            textToReturn += "Average operations per class : " + (operations.doubleValue()/classes.doubleValue()) + "\n";
        }

        if (classes!=null && attributes!=null)
            textToReturn += "Average attributes per class : " + (attributes.doubleValue()/classes.doubleValue()) + "\n";

        // if map was empty and no elements was found.
        if (textToReturn.length()==0)
        {
            textToReturn = "No elements found!";
        }

        return textToReturn;
    }

    /**
     * We parse all the children of the given root element.
     * For each element, we visit it to count the types of the elements
     *
     * @param rootElement the root element to start counting
     */
    public void visitChildren(Element rootElement)
    {
        try {
            rootElement.accept(visitor);
        }
        catch (Exception e) {
            // If something goes wrong we log it to the MagicDraw GUI logger
            Application.getInstance().getGUILog().log(
                    "[Simple Plugin] Exception occured : " + e.toString());
        }
        // We visit the children elements
        for (Element child : rootElement.getOwnedElement()) {
              visitChildren(child);
        }
    }

    /**
     * Getter for the visitor.
     * @return the visitor
     */
    public CountingVisitor getVisitor() {
        return visitor;
    }
}

Conclusion

You’re now familiar with the MagicDraw openAPI! So lets go and develop your own plugin!

You’ll find a packaged version of this Eclipse/Maven2 project there : magicdraw-simple-plugin.tar.gz
To set up your environement, you’ll have to upload some MagicDraw libs to your maven2 local repo. You’ll find them in the “<MagicDraw_Install_Dir>\lib\” directory. The list of the needed libs are described in the “dependencies” section in the “pom.xml” file.

You’ll also need to declare an environement variable “MDUML_HOME” which is the “<MagicDraw_Install_Dir>“.

Then if you execute a “mvn clean install” it will compile the plugin and install it on your MagicDraw installation. And you’ll just have to start MagicDraw to test the plugin.

In a next blog post we’ll see how to debug your plugins within Eclipse.


1 : Have a look here : http://www.magicdraw.com/main.php?ts=navig&=&cmd_show=1&menu=testimonials, you’ll find my testimonial!

2 : Here is the features list of MagicDraw: http://www.magicdraw.com/main.php?ts=navig&cmd_show=1&menu=feature_list

Share and Enjoy:
  • Twitter
  • RSS
  • email
  • Digg
  • Reddit
  • FriendFeed
  • del.icio.us
  • Google Bookmarks
  • Identi.ca
  • viadeo FR
  • Technorati
  • Add to favorites
  • Print
  • PDF