Réalisation d'un plug-out de tri pour BOUML

Bruno Pagès

Ce tutoriel vous aidera à écrire vos propres plug-out pour BOUML, un modeleur UML2 gratuit fonctionnant sous Windows, Linux, Solaris, MacOS X disponible ici. Ce tutoriel détaille la réalisation complète d'un plug-out triant les éléments du browser par type, puis par ordre alphabétique.

- Titre : Réalisation d'un plug-out de tri pour BOUML

- Auteur : Bruno Pagès

- Parution : 21 novembre 2006

- Mise à jour : 16 juillet 2020

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La modification par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Introduction

- II. But du plug-out

- III. Réalisation du plug-out

- III-A. Créer un plug-out

- III-B. Début d'exécution

- III-C. Traiter les erreurs de configuration du plug-out

- III-D. Trier les sous-éléments

- III-E. Trier en Java

- III-F. Trier en C++ avec l'aide de Qt

- III-G. L'opération orderWeight

- III-H. L'opération sortChildren

- IV. Générer le code

- V. Utiliser le plug-out

- VI. Conclusion

- VI-A. Épilogue

- VI-B. Liens

I - Introduction

Le but de ce tutoriel est de vous aider à écrire vos plug-out, en vous détaillant la réalisation complète d'un plug-out triant les éléments du browser de BOUML, par type puis par ordre alphabétique. Ce plug-out fait partie de la distribution, vous pouvez vous référer à lui en cas de problème, mais le mieux est de le réécrire pour vous aider à comprendre.

Un chapitre du manuel de référence est consacré aux plug-outs. Seule une partie de l'API du modeleur est utilisé par ce plug-out, la description complète de l'API est ici.

Vous devez être familiarisés avec l'utilisation de BOUML, si ce n'est pas le cas lisez les deux tutoriels présentant BOUML.

II - But du plug-out

Le but est de réaliser un plug-out triant les éléments du browser, d'abord en fonction de leur type, puis en suivant l'ordre alphabétique de leur nom au sein de chaque type. Pour contenter un maximum de personnes, ainsi que pour montrer les capacités multi langages de BOUML, ce plug-out est implémenté à la fois en C++ et en Java (les deux définitions sont séparées, cela produit deux programmes indépendants, un dans chaque langage).

Ce tutoriel est la traduction de son équivalent en Anglais, écrit avant la prise en compte par BOUML des diagrammes d'instances et des activités, ces derniers ne sont pas pris en compte, mais cela n'a pas d'importance.

Le premier critère porte sur le type, l'ordre de tri est le suivant :

- dans un package : sous-packages, use case views, class views, component views, deployment views, généralisations, dépendances ;

- dans une use case view : sous-use case views, use case diagrams, sequence diagrams, collaboration diagrams, use cases, acteurs ;

- dans une class view : class diagrams, sequence diagrams, collaboration diagrams, state machines, classes ;

- dans une component view : components diagrams, components ;

- dans une deployment view : deployment diagrams, nodes, artefacts ;

- dans un use case : use case diagrams, sequence diagrams, collaboration diagrams, sous-use cases, acteurs, généralisations, dépendances ;

- dans une classe ou une state machine : rien n'est réordonné, à vous de le faire à titre d'exercice. Remarque : l'ordre de génération des membres d'une classe suit l'ordre de ces membres dans le browser, pensez-y lorsque vous les trierez.

Le second critère de tri est l'ordre alphabétique du nom des éléments.

III - Réalisation du plug-out

III-A - Créer un plug-out

Démarrez BOUML, et chargez le plug-out appelé empty, celui-ci faisant partie de la distribution. BOUML vous demande immédiatement de sauver le projet, en effet vous ne pouvez pas modifier le plug-out empty, il s'agit donc d'un save-as forcé, appelons ce plug-out sort :

Comme vous pouvez le voir, le projet contient deux packages principaux :

- le premier (API BASE) contient des classes système et leurs artefacts. Ces classes sont interfacées avec l'API du modeleur. Vous ne pouvez pas modifier ces classes, et la plupart d'entre elles ne sont pas instanciables ;

- le second package (API USER) contient la prédéfinition des classes utilisateur et de leur artefacts, généralement ces classes héritent d'une classe système.

De façon générale, ces classes sont une vision des éléments du browser, sont également définies des classes auxiliaires permettant d'accéder aux autres informations, par exemple les différents settings. Ainsi, un package est représenté par la classe utilisateur UmlPackage qui hérite de la classe système UmlBasePackage, etc. Par exemple, les classes relatives aux packages et vues sont les suivantes :

III-B - Début d'exécution

Un plug-out est toujours appliqué à un élément du browser, et comme tout programme qui se respecte son exécution débute par le main. Une définition par défaut est donnée via l'artefact main, celui-ci est défini dans le Deployment view API USER :

Notre but est de trier les sous-éléments de l'élément du browser sur lequel le plug-out est appliqué, par exemple de trier les éléments se trouvant dans une class view. L'élément sur lequel est appliqué le plug-out est obtenu en appelant l'opération targetItem définie pour UmlCom, le résultat est un UmlItem qui est la classe de base de toutes les classes associées aux éléments du browser. Comme vous le voyez ci-dessus, cette opération est appelée dans la définition par défaut de main.

III-C - Traiter les erreurs de configuration du plug-out

Le plug-out de tri n'a d'intérêt que s'il est appliqué à un package, une vue ou un use case, et normalement il sera configuré dans BOUML pour n'être appliqué qu'a ceux-ci. Mais parce que la configuration du plug-out peut être erronée, il faut prendre en compte les autres cas. Pour se faire, définissons l'opération sort au niveau UmlItem pour indiquer l'erreur, et redéfinissons cette opération pour les classes UmlPackage, UmlUseCaseView, UmlClassView, UmlComponentView, UmlDeploymentView et UmlUseCase. Dans le main vous avez donc juste à appeler sort sur le résultat de targetItem :

Cette façon de faire n'est pas la seule possible, mais c'est la plus propre. L'opération kind définie pour chaque sous-classe système terminale de UmlItem permet de connaître le type réel d'un élément, mais faire un switch dessus n'est vraiment pas joli joli !

Maintenant, définissez sort pour UmlItem. Pour indiquer l'erreur à l'utilisateur, vous disposez de l'opération message définie sur UmlCom. Le message envoyé est une chaîne de caractères qui sera affichée dans la fenêtre de trace de BOUML. Cette chaîne doit être un bloc HTML valide, attention donc aux caractères &, < ou > !

L'ensemble des capacités d'HTML n'est pas accessible, mais vous pouvez par exemple écrire en italique ou en gras, et changer la couleur de la police de caractères.

L'opération sort ne retourne pas de valeur, et ne prend pas de paramètres. En C++ cette opération doit bien sûr être déclarée virtuelle :

En C++, l'artefact doit être modifié pour ajouter #include « UmlCom.h » dans le source.

Pour rapidement retrouver cet artefact, appelez le menu de la classe UmlItem et choisissez select associated artefact, puis, pour revenir vers la classe une fois l'artefact édité, appelez le menu de l'artefact et choisissez select the associated class.

III-D - Trier les sous-éléments

Dans tous les cas le but est le même : trier les sous-éléments, faisons faire cela par l'opération sortChildren définie sur UmlItem. Ainsi il suffira d'appeler sortChildren à partir de l'opération sort définie sur UmlPackage, UmlUseCaseView, UmlClassView, UmlComponentView, UmlDeploymentView et UmlUseCase. Donc, définissions sort pour UmlPackage appelant simplement sortChildren et, pour rapidement avoir la même définition pour les autres classes, utilisons les marques : clic gauche avec la touche Control enfoncée sur l'opération sort de UmlPackage dans le browser, puis utilisez l'entrée de menu duplicate marked into sur UmlUseCaseView, etc. dans le browser.

Pour réaliser le tri, il y a deux possibilités : écrire l'algorithme de tri, ou utiliser une bibliothèque. Même si implémenter quicksort est trivial, utilisons une bibliothèque, car c'est plus intéressant pour ce tutoriel. En Java une opération de tri est définie sur les tableaux dont les éléments implémentent Comparable. En C++ vous pouvez utiliser l'opération de tri définie pour les QVector via une sous-classe implémentant compareItems. Dans les deux cas, le tri est d'abord fait dans la mémoire du plug-out, puis les éléments du browser seront déplacés pour suivre le même ordre. Remarquez que l'implémentation du tri met en jeu des classes différentes dans les deux langages.

III-E - Trier en Java

Parce que le tri porte sur des instances de plusieurs classes différentes, je propose de définir Comparable sur UmlItem. Étant donné que seules une classe et une opération doivent être définies, il est inutile d'utiliser Java catalog et tout sera fait à la main :

- créez un package appelé aux sous le projet ;

- dans aux, créez une class view également appelée aux ;

- dans cette class view, créez la classe Comparable. Changez son stéréotype en interface. Dites que la classe est externe en Java, grâce à cela le générateur de code ne produira pas de message d'avertissement parce qu'elle n'a pas de définition. Enfin, même si ce n'est pas obligatoire, définissez l'opération compareTo (avec un corps vide) ;

- créez un class diagram pour vous permettre de dire que UmlItem réalise Comparable, et dites que cette réalisation n'existe pas en C++ ;

- définissez compareTo sur UmlItem, par exemple en appelant le menu de la classe et en choisissant add inherited operation. Dites que l'opération n'est pas définie en C++ ;

- la comparaison doit d'abord tenir compte du type de l'élément, puis de son nom. Pour cela, définissez l'opération orderWeight retournant un int valant 0 pour les UmllItem (la valeur n'est pas importante si vous prenez en compte tout les types d'éléments possibles) ;

- le nom d'un élément du browser est obtenu par l'opération name, la définition de int compareTo(Object o) est donc :

III-F - Trier en C++ avec l'aide de Qt

- Coté C++, dites que orderWeight est virtuelle au niveau UmlItem et qu'elle retourne aussi 0.

- Dans la class view aux créez la classe paramétrée (template) QVector, dites qu'elle n'est pas implémentée en Java, et dites qu'elle est externe en C++ avec la définition suivante :

- Même si cela n'est pas obligatoire, définissez l'opération int compareItems(QCollection::Item d1, Qcollection::Item d2).

- Définissez la classe VectorOfUmlItem héritant de Qvector et dites que cette classe n'est pas définie en Java. En C++ sa définition doit être (l'héritage doit être défini avant d'éditer la classe afin d'accéder aux actuals) :

- Sur VectorOfUmlItem définissez compareItems ainsi :

- Pour éviter de créer un artefact pour VectorOfUmlItem, éditez l'artefact UmlItem et ajoutez VectorOfUmlItem parmi les classes associées.

Finalement :

III-G - L'opération orderWeight

Maintenant il faut définir orderWeight, pour avoir l'ordre voulu la valeur retournée peut être :

- UmlPackage (package) : 1 ;

- UmlUseCaseView (use case view) : 2 ;

- UmlClassView (class view) : 3 ;

- UmlComponentView (component view): 4 ;

- UmlDeploymentView (deployment view) : 5 ;

- UmlUseCaseDiagram (use case diagram), UmlClassDiagram (class diagram), UmlComponentDiagram (components diagram), UmlDeploymentDiagram (deployment diagram) : 6 ;

- UmlSequenceDiagram (sequence diagram) : 7 ;

- UmlCollaborationDiagram (collaboration diagram) : 8 ;

- UmlUseCase (use case), UmlState (state machine), UmlComponent (component), UmlNode (deployment node) : 9 ;

- UmlClass (class), UmlArtifact (artifact) : 10 ;

- UmlRelation et UmlNcRelation de type généralisation (relationKind() retourne aGeneralisation) : 11 ;

- UmlRelation et UmlNcRelation de type dépendence (relationKind() retourne aDependency) : 12.

Définissez donc l'opération pour chaque classe, avec la bonne valeur retournée, pour C++ et pour Java.

Le plus rapide pour le faire est de dupliquer l'opération déjà définie sur UmlItem.

III-H - L'opération sortChildren

L'opération children définie sur UmlBaseItem retourne un vecteur d'UmlItems.

Pour changer l'ordre des éléments dans le browser, il faut utiliser l'opération moveAfter définie sur UmlBaseItem. Cette opération prend un paramètre, si celui-ci est nul, l'élément est déplacé pour être le premier sous-élément de l'élément qui le contient, sinon l'élément est déplacé pour être juste après l'élément donné en paramètre.

Il est seulement possible de changer l'ordre des éléments dans le browser, il n'est pas possible de déplacer un élément d'un conteneur à un autre (même si cela peut être fait dans certains cas à la main dans le browser). Donc l'opération moveAfter ne fait rien si l'élément sur laquelle elle est appliquée et le paramètre n'ont pas le même conteneur dans le browser.

La définition de sortChildren en C++ est donc :

La définition de sortChildren en Java est donc :

L'écriture du plug-out est terminée.

IV - Générer le code

Pour générer le code du plug-out vous devez dire où les sources seront placés, éditez les generation settings par exemple pour avoir :

Tout devant être généré, demandez la génération de code pour C++ et/ou Java via le menu Tools, ou en utilisant le menu au niveau du projet dans le browser.

Pour compiler les sources C++ sous Linux ou Mac OS X vous avez besoin d'un Makefile. Le plus simple est de produire le fichier .pro en appliquant le plug-out genpro sur l'artefact appelé executable. Comme sort n'utilise rien aucune information spécifique à un des langages (on n'accède qu'au nom des éléments) autant retirer la définition des defines (l'exécutable sera plus petit), et disons que le nom de l'exécutable à produire est browsersort :

Utilisez qmake pour produire le Makefile, puis make pour compiler.

En Java vous utilisez bien sûr javac pour compiler les sources, mais comme certains fichiers contiennent plusieurs classes de base vous ne devez pas compiler les sources un à un, mais tous ensemble par javac *.java dans une ligne de commande.

Voilà, tout est fini, mais n'oubliez pas de sauver votre projet !

V - Utiliser le plug-out

Pour pouvoir être utilisé afin de trier les éléments d'un projet, vous devez déclarer le plug-out dans le projet. Dans le menu Tools choisissez Tools settings, ci-dessous les déclarations sont faites pour les implémentations en C++ et Java (mais une seule est suffisante) :

Maintenant le tri est disponible dans le sous-menu tool du menu du projet, des packages, des vues et des use cases.

VI - Conclusion

VI-A - Épilogue

Facile, non ?

N'hésitez pas à écrire vos propres plug-outs, l'automatisation des actions vous rendra la vie plus agréable, et plus sûre en évitant les erreurs des opérations manuelles. Regardez la définition des plug-outs distribués avec BOUML pour vous aider si besoin.

Si vous pensez avoir développé un plug-out utile à d'autres, vous pouvez me proposer de le diffuser via le site de BOUML.

Il n'y a pas de problème de compatibilité des plug-outs d'une version de BOUML à une autre, de la même façon d'un projet défini avec une version de BOUML est utilisable avec les versions postérieures. Qui plus est, lorsque des nouveautés sont introduites dans BOUML, le plug-out upgrade vous permet de mettre à jour vos plug-outs existants pour bénéficier de ces nouveautés (mais ce n'est pas une obligation). C'est par exemple que je mets à jour le générateur de document HTML.

VI-B - Liens

- Le site de Bouml : http://bouml.free.fr

- Le tutoriel premiers pas avec Bouml est ici

- Le tutoriel les classes et la génération de code sous Bouml est ici

Merci à Miles pour la relecture de ce tutoriel.

Bonnes modélisations !