About model driven engineering
How to develop your MagicDraw plugin
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
This entry was posted by xavier on 04/03/2010 at 22 h 18 min, and is filed under Model Driven. Follow any responses to this post through RSS 2.0. Both comments and pings are currently closed. |
Comments are closed.
about 13 years ago
This is an awesome example to start building your plugin
In mi particular case, helped me to realize that there was a Visitor pattern to use
So many thanks Xavier
Greeting from Argentina