Table of Contents
This document has been extracted from some tutorial in french and converted to english, it should help people who want to write modules for ArgoUML. (note that the depth of the titles have been preserved, it will make me easier to traduce the whole document)
You will find the example that I wrote while I was writing this document here. You can test it directly untaring it in the right directory (modules) and running the command ant run in the newly created directory (you should have developpement tools correctly installed if you want to do that.
First, you will have to create a directory in argouml/modules where you will put all the files that will be used by your module. In our example, we will call this directory test.
In this directory, you will first place a build.xml file, this is a configuration file for ant, you will create a module.properties file too, in this file, you will place some parameters related to your module. You will find these files in other module's source such as the php module.
If you use build.xml from php module, you should have nothing to modify because for this modules, all the parameters related to the module are placed in module.properties (however as the Junit module uses external libraries, the build.xml file is slightly different).
The file module.properties is composed of two lines for the moment, these lines are ant properties.
argo.module.name let you specify the name of the module
argo.module.jarfile let you specify the name you want for the jar archive
For our module, the property file will contain this :
argo.module.name=test argo.module.jarfile=argo_test
Once your build.xml and module.properties files are ready, you can test your module's configuration by typing ant.
Here is what ant should tell you :
$ ant Buildfile: build.xml init: [echo] ------------------- ArgoModule-test 0.9.5-20020718-0858 [2001] ---------------- usage: [echo] [echo] [echo] ArgoModule-test Build file [echo] ------------------------------------------------------------- [echo] [echo] available targets are: [echo] [echo] compile --> compiles the source code to the tree under ../../modules/test/build [echo] package --> generates the argo_test.jar file [echo] run --> runs ArgoUML argo_test.jar [echo] install --> merges ./org into ../src_new/org tree [echo] usage --> show this message (default) [echo] [echo] See the comments inside the build.xml file for more details. [echo] ------------------------------------------------------------- [echo] [echo] BUILD SUCCESSFUL Total time: 2 seconds
You should have obtained something similar, verify that the archive name is the one you have asked, you should see this on the lines package and run. You can also verify the name of the module, you should see ArgoModule-test during init.
When ant is configured, you can create the directory hierarchy.
First, create the directory src that will hold the sources (the package hierarchy). In this director, you will have to put the root of the package hierarchy (org/argouml).
You should also create a lib directory where you will put external libraries.
If you want module loading not to fail, you should write a manifest.mf file where you will put necessary information for the module to load. Here is the manifest.mf file for our module test.
Manifest-Version: 2.0 Class-Path: test.jar Created-By: 1.3.1 (Sun Microsystem Inc.) Name: org/argouml/uml/ui/ActionTestDiagram.class Extension-name: module.tools.test Specification-Title: ArgoUML Dynamic Load Module Specification-Version: 0.9.10 Specification-Vendor: University of California Implementation-Title: Test Module Implementation-Version: 0.9.10 Implementation-Vendor: test
I feel like I should explain it a little bit ...
The Manifest-Version field let you specify the version of the format used, here it is 2.0.
The ClassPath field gives us the name of the jar files that will be loaded with our module
The Created-By field contains the name of the compiler used for the classes.
The Name field let you give the name of the main class, this class will be loaded by Argo's classloader as a module.
...
manifest.mf should be put at the root of the archive, in the src/org directory.
We will now be able to test that everything is ok before starting.
First, we will try to package our program with the command : ant package that you should execute in the base directory for your module.
$ ant package Buildfile: build.xml init: [echo] ------------------- ArgoModule-test 0.9.5-20020718-0941 [2001] ---------------- prepare: [echo] Preparing the build directories compile: [echo] Compiling the sources package: [copy] Copying 1 file to /usr/src/argo/modules/test/build/classes [jar] Building jar: /usr/src/argo/modules/test/build/argo_test.jar BUILD SUCCESSFUL Total time: 3 seconds
Once again, you should have obtained the same result. When the execution of the command is over, a build/argo_test.jar file should have been created.
The newly generated archive file argo_test.jar should only contain manifestt.mf file.
You can also try ant run that should normally launch ArgoUML with your module. As the class you have specified in the Name field of the manifest.mf doesn't exist the loading of the module should fail and you should see :
[java] Could not instantiate module org/argouml/uml/ui/ActionTestDiagram
If ArgoUML doesn't even launch and that you obtain
run: [echo] --- Executing ArgoModule-test --- [java] Exception in thread "main" java.lang.NoClassDefFoundError: org/argouml/application/Main [java] Java Result: 1
That's because you haven't created argouml.jar that need to be placed in Argo's build directory.
A simple ant package in src_new directory should solve the problem.
In order to create a new diagram, we must create a new entry in one of Argo's menus. To achieve that, we will use functionnalites from the PluggableMenu interface.
PluggableMenu is an interface that let you create PlugIns for Argo that adds an entry into the menus. PluggableMenu comes from Pluggable which in its turn comes from Module.
Next figure presents the class diagram corresponding to modules.
Methods providedd by the Module interface are mainly used to identify the module, to initialize it and finally to close it in a clean way.
The inContext methode from the Pluggable interface is used by the pluggin to plug itself to some part of Argo.
getMenuItem method from the PluggableMenu interface is the function by which Argo will be able to get the JMenuItem provided by our PluggableMenu and insert it in the good context which was asked by argo before.
The buildContext method from the PluggableMenu interface is used to make the array of Objects that will be passed as arguments to the inContext method. This method is used by Argo to build the context for modules but I haven't been able to see why. (maybe it can be usefull when you so some inheritance ?)
The sequence diagram coming next explains how the insertion of an entry in the menu of ArgoUML is done.
We will now write our own PluggableMenu. We will name it ActionTestDiagram, it will be a son for the class UMLAction while implementing PluggableMenu (this will permit us to have in the same class the action that creates the MenuItem and the one that is executed when it is selected).
Our new class will implement all the methods from Module, Pluggable and PluggableMenu, and will also implement ActionPerformed that will be the action executed when our menuItem will be seclected (for the begining it will be a simple System.out.println.
This new class will be placed in the directory src/org/argouml/uml/ui that you must create and will be part of the org.argouml.uml.ui package.
package org.argouml.uml.ui; import org.argouml.ui.*; import org.argouml.application.api.*; import org.argouml.uml.ui.*; import org.argouml.uml.*; import org.argouml.kernel.*; import org.tigris.gef.base.*; import org.tigris.gef.util.*; import ru.novosoft.uml.foundation.core.*; import ru.novosoft.uml.behavior.state_machines.*; import ru.novosoft.uml.model_management.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import java.beans.*; /** * Notre classe ActionTestDiagram */ public class ActionTestDiagram extends UMLAction implements PluggableMenu { public ActionTestDiagram() { super ("TestDiagram"); } private static JMenuItem _menuItem = null; ///////////////////////////////////////// // implementation de UMLAction /** * Action executée par la selection du menu */ public void actionPerformed (ActionEvent ae) { System.out.println("HelloWorld!!"); } ///////////////////////////////////////////////////////////////// // implementation de l'interface Module private boolean _initialized = false; public boolean initializeModule() { Argo.log.info("*** Initialisation de TestDiagram"); _initialized = true; return _initialized; } public void setModuleEnabled(boolean enabled) { } public boolean isModuleEnabled() { return true; } public Vector getModulePopUpActions(Vector v, Object o) { return null; } public boolean shutdownModule() { return true; } public String getModuleName() { return "TestDiagram"; } public String getModuleDescription() { return "TestDiagram"; } public String getModuleAuthor() { return "Florent de Lamotte"; } public String getModuleVersion() { return "0.9.10"; } public String getModuleKey() { return "module.tools.test"; } ///////////////////////////////////////////////////////////////// // Implémentation de l'interface Pluggable /** * Permet au module d'insérer le menu ou il le veut * @param o l'objet décrivant le contexte le premier objet est un JMenuItem * et le second une clé correspondant à ce JMenuItem et correspondant à une entrée * dans le menu * @return true si l'emplacement nous convient */ public boolean inContext(Object[] o) { if (o.length < 2) return false; if ((o[0] instanceof JMenuItem) && ("Create Diagrams".equals(o[1]))) { return true; } return false; } ///////////////////////////////////////////////////////////////// // Implémentation de l'interface PluggableMenu /** * Retourne le JMenuItem que l'on veut ajouter au menu * * @param o construit avec buildContext * @return Le JMenuItem correspondant à ce menu */ public JMenuItem getMenuItem(Object[] o) { if ((o[0] instanceof JMenuItem) && (o[1] instanceof String)) { JMenuItem mi = (JMenuItem) o[0]; String s = (String) o[1]; if (_menuItem == null) { _menuItem = new JMenuItem("Test Diagram", ResourceLoader.lookupIconResource("TestDiagram")); } _menuItem.addActionListener(this); return _menuItem; } return null; } /** * Construit l'objet qui sera envoyé a inContext */ public Object[] buildContext(JMenuItem a, String b) { return new Object[] { a, b }; } }
Once the new class is written, you can launch ArgoUML with your new module by executing the command ant run from your module's directory.
Your module should be loaded by Argo and you should see the following message
[java] *** Initialisation de TestDiagram [java] Loaded Module: TestDiagramIn the "Create Diagram" entry in the menu, a new item should appear : "Test Diagram", if you select this item, HelloWorld!! should appear on the console.
While the module is loading, it complains because it can't find an Icon ... In fact, when we created our JMenuItem, we associated it the icon TestDiagram that we have neither created nor placed in the jar.
Icons are placed in the directory src/org/argouml/Imafes so you can put an icon with the name TestDiagram.gif in the gif format in this directory.
We will now have to make a little modification in our build.xml file so that it can copy every icon from the src/org/argouml/Images directory int hte build/classes/org/argouml/Images directory, that will then enable GEF to find the icon (in fact it is the RessourceLoader from GEF that does this task.)
The modifications are added to the prepare target which aim is to prepare the directories where the files to be archived are placed.
<!-- =================================================================== --> <!-- Prepares the build directory --> <!-- =================================================================== --> <target name="prepare" depends="init"> <!-- create directories --> <echo message="Preparing the build directories"/> <copy todir="${module.build.dest}/org/argouml/Images"> <fileset dir="${module.build.src}/org/argouml/Images" includes="**/**" excludes="CVS/**"/> </copy> <!-- These must be there already --> </target>
Now if you execute ant run, you should see your icon near the text of your JMenuItem.