Frédéric Mallet > Programmation Avancée et Patrons de conception - 2010/2011 > TD6 - Etude d'un programme utilisant des plugins

TD6 - Etude d'un programme utilisant des plugins

De $1

Introduction

Il s'agit du dernier "vrai TP", aujourd'hui vous allez juste vous familiariser avec une version du TP précédent (le jeu vidéo 2D) mais qui utilise un système de plugins pour charger des comportements. Avant toute choses, vous allez récupérer les différents éléments : le projet du jeu lui même, un autre projet pour développer les plugins de comportement, et un troisième projet (un peu compliqué) qui contient une librairie de gestion de plugins que vous pourrez réutiliser dans vos propres projets.

Récupération des projets

Les trois projets existent pour Netbeans et pour Eclipse. Les source sont les mêmes.

Archive des trois projets Eclipse : EclipseProjectsPlugins.zip

Archive des trois projets Netbeans : NetBeansProjectsPlugins.zip 

Dezippez les trois projets Eclipse dans votre workspace ou les trois projets Netbeans dans l'endroit où vous stockez les projets Netbeans (par défaut : Mes Documents/Netbeans projects, mais pour Netbeans cela n'a pas tellement d'importance, il sait ouvrir des projets n'importe où).

Il se peut que vous ayez des warnings, ignorez les. Vous devriez obtenir dans Eclipse ceci :

Snap1.jpg

Exécution du jeu

Vous allez maintenant exécuter le projet du jeu. Par défaut aucun plugins n'est installé dans le sous-répertoire "plugins" du projet. Il est vide.

 

Snap2.jpg

Si tout s'est bien passé, vous devriez obtenir une fenêtre avec un Cercle (pas encore animé !). Pour le moment aucun comportement n'est associé à ce cercle, donc il ne bouge pas. Le menu plugins (qui permettra justement d'ajouter ou d'enlever des comportements au cercle) est vide. On peut voir dans la console la trace du chargement des plugins qui indique clairement qu'aucun plugin n'a été trouvé. Les plus curieux peuvent regarder la classe Fenetre.java car c'est là que tout se passe...

Snap3.jpg

Arrêtez le programme. Nous allons maitenant copier des plugins dans le répertoire "plugins", et relancer l'application. Pour cela, le projet MiniJeu1Plugins vient avec un jar qui contient 4 plugins de comportement. Nous allons donc depuis l'interface d'Eclipse, copier le jar du projet MiniJeu1Plugins vers le répertoire "plugins" du projet MiniJeu1 :

Snap4.jpg

Et on le colle au bon endroit :

Snap5.jpg

Remarque : Le fichier .jar contient toutes les classes du projet des plugins, soit l'ensemble des comportents et leurs dépendances (qui sont dans le package pluginsSDK). Mettre des plugins (des classes chargées à la volée par un Class.forName ou à l'aide d'un UrlClassLoader, comme nous l'avons vu en TP) dans un jar est effectivement très pratique pour les déployer dans un projet qui est capable de les charger.

Relancez l'application. Vous devriez voir maintenant 4 plugins dans le menu. Commencez par ajouter au Cercle le comportement "rebondir", puis "par defaut", le cercle doit avancer et rebondir. Vous pouvez ensuite ajouter le comportement "ivre", le retirer, ajouter le comportement "suivre souris", le retirer, etc..

Nous allons maintenant supprimer un plugin, pour cela il suffit de modifier une classe du package des comportements, ici ComportementSuitSouris.java, pour ne plus qu'elle implémente ComportementPlugin.java. Ainsi, notre classe n'est plus un Plugin de comportement.

Snap6.jpg

Pour faire un jar avec Eclipse, c'est un peu plus compliqué qu'avec Netbeans (oui, M.Buffa aime bien Netbeans, vous l'avez compris...) : il faut utiliser le menu "Export" de Eclipse. Suivez le guide !

Snap7.jpg

Snap8.jpg

Une fenêtre apparait, là vous allez devoir utiliser le bouton Browse pour choisir l'emplacement et le nom du fichier .jar qui sera produit. J'ai choisi de le mettre à la racine du projet et de lui donner le nom du projet terminé par .jar. Attention, si vous avez déjà fait un jar et que vous voulez re-exporter le projet dans un jar, cet idiot d'Eclipse propose par défaut de remettre le jar dans le jar (cf photo), il faut décocher le jar dans la partie en haut à droite...

Snap11.jpg

Cliquez surt Finish et vous devriez obtenir un fichier jar mis à jour. Supprimez celui qui est situé dans le répertoire plugins du Jeu, puis recopiez le nouveau à la place. Relancez le jeu, vous ne devriez avoir plus que trois plugins.

Etude du code, de la conception

Projet MiniJeu1

Lorsque il a fallu transformer le TP du jeu pour mettre en évidence une conception à base de plugins, il a fallu refactorer le projet. Pourquoi ? Parceque dans l'hypothèse où un groupe développe le jeu et où d'autres groupes développent les plugins, il faut identifier quelles seront les classes qui seront nécessaires à l'écriture de plugins. On les a mises dans le package pluginsSDK. Vous remarquerez que la classe Jeu.java y figure, simplement, on ne "construit" plus le jeu dans cette classe, regardez là et comparez avec la version de l'ancien TP. Le constructeur ne crée plus d'objets...er

En revanche la classe MaFenetre.java qui construit la GUI du jeu a légèrement changé. Elle crée la fenêtre principale, instancie le Jeu (qui est un JPanel) et le met au centre, puis ajoute un Cercle dans le jeu. Ensuite on utilise les classes PluginManager et PluginLoader de la librairie de gestion des plugins pour :

  1. Charger les classes de Plugins dans un tableau de plugins (toutes celles qui sont des Plugin)
  2. Instancier celles qui sont des ComportementPlugins (rappel, si vous regardez le code, ComportementPlugin implémente Plugin)
  3. Créer un menu déroulant avec des CheckBoxMenuItems pour chaque comportement, associer un ActionListener à chaque entrée pour ajouter/enlever le comportement sélectionné au cercle.

Etudions plus en détail... tout se passe dans l'appel de la méthode :

  1. private void litPluginsEtConstruitMenuDesComportements(final JMenuBar mb) {  
  2.         // Ici on lit les plugins et on construit le menu  
  3.         pluginManager = PluginManager.getPluginManager();  
  4.   
  5.         try {  
  6.             // Tous les jars dans le sous dir plugins seront lus  
  7.             pluginManager.addJarURLsInDirectories(new URL[]{new URL("file:plugins")});  
  8.         } catch (MalformedURLException ex) {  
  9.             Logger.getLogger(MaFenetre.class.getName()).log(Level.SEVERE, null, ex);  
  10.         }   
  11.   
  12.         // On charge les classes qui oimplémentent l'interface Plugin.class. Voir le package pluginsSDK  
  13.         pluginManager.loadPlugins();  
  14.   
  15.         // Ici on crée un tableau avec une instance de chaque plugin. On s'en sert un peu plus bas pour  
  16.         // construire un menu de comportements  
  17.         plugins = (ComportementPlugin[]) pluginManager.getPluginInstances(ComportementPlugin.class);  
  18.   
  19.         // On construit le menu nous indiquant les comportements possibles  
  20.         // Dans cette méthode on ajoute un écouteur à chaque menuItem  
  21.         // qui associe un comportement/plugin au cercle créé dans le jeu  
  22.         buildPluginMenu(mb);  
  23.     }  

 Une fois le tableau des ComportementPlugins rempli avec une instance de chaque comportement, on peut construire le menu. Pour cela on utilise les classes PluginMenuFactory.java et PluginMenuItem.java qui nous facilitent la vie.

  1. private void buildPluginMenu(JMenuBar menuBar) {  
  2.         menuPlugins = new JMenu("Plugins");  
  3.   
  4.         // L'actionListener qui va écouter les entrées du menu des plugins. C'est une instance d'une       
  5.         // classe interne anonyme...  
  6.         ActionListener listener = new ActionListener() {  
  7.   
  8.             public void actionPerformed(ActionEvent e) {  
  9.                 // On récupère le menu item qui a été choisi  
  10.                 JCheckBoxMenuItem cb = (JCheckBoxMenuItem) e.getSource();  
  11.   
  12.                 // On récupère la chaine qui était affichée dans le menu,  
  13.                 // c'est par défaut dans l'attribut ActionCommand du menu item  
  14.                 String nameCommand = cb.getActionCommand();  
  15.   
  16.                 // On va chercher dans le tableau des plugins celui qui porte le même nom  
  17.                 ComportementPlugin c = searchPlugin(nameCommand);  
  18.   
  19.                 // On regarde si il est selectionné, si il est selectionné c'est que  
  20.                 // l'objet avait déjà ce comportement, dans ce cas on le retire,  
  21.                 // sinon on l'ajoute. Dans tous les cas, on inverse l'état du  
  22.                 // checkbox menu item  
  23.                 if (cb.getState() == true) {  
  24.                     System.out.println("J'ajoute le " + c.getName());  
  25.                     objetAnime.ajouteComportement(c);  
  26.                 } else {  
  27.                     System.out.println("Je supprime le " + c.getName());  
  28.                     objetAnime.supprimeComportement(c);  
  29.                 }  
  30.             }  
  31.         };  
  32.   
  33.   
  34.         if (pluginMenuItemFactory == null) {  
  35.             // Utilisation d'une factory pour construire les menus à partir du tableau de plugins  
  36.             // Remarque : le dernier paramètre est l'écouteur qui sera exécuté lorsque le menu item sera selectionné  
  37.             pluginMenuItemFactory = new PluginMenuItemFactory(menuPlugins, pluginManager, listener);  
  38.   
  39.         }  
  40.   
  41.         buildPluginMenuEntries();    
  42.   
  43.         // On rajoute le menu à la barre de menus  
  44.         menuBar.add(menuPlugins);  
  45.   
  46.   
  47.     }   
  48.   
  49. private void buildPluginMenuEntries() {  
  50.   
  51.         // Fait construire les entrées du menu des plugins  
  52.         pluginMenuItemFactory.buildMenu(null);  
  53.     }  

 

Projet MinJeu1Plugins

Dans ce projet on retrouve les comportements du TP précédent, à quelques différences près :

  1. Les noms des packages ont changé,
  2. Une classe ComportementPlugin a fait son apparition, tous les Comportements vont l'étendre. Cette classe implémente la classe Plugin qui vient de la librairie de plugins. Elle oblige tous les plugins à implémenter des méthodes getName(), getDescription(), getVersion() c'est-à-dire qu'elle oblige tous les plugins à se décrire. Elle oblige aussi à implémenter canProcess(Object o) et une autre méthode, nous en verrons l'utilité plus tard. Pour le moment, lorsque vous les implémenterez elle renverront true. (Pour info : elles servent à dire si un plugin donné peut traiter un autre type d'objet).
  1. package minijeu.pluginsSDK;  
  2.   
  3. import fr.unice.plugin.Plugin;  
  4.   
  5. public interface ComportementPlugin extends Plugin {  
  6.   
  7.     public void deplace(ObjetAnime o);  
  8.   
  9.   
  10. }  

Et voici par exemple le comportement par défaut :

  1. package minijeu.comportements;  
  2.   
  3. import minijeu.pluginsSDK.ComportementPlugin;  
  4. import minijeu.pluginsSDK.ObjetAnime;  
  5.   
  6. public class ComportementParDefaut implements ComportementPlugin {  
  7.   
  8.  public void deplace(ObjetAnime o) {  
  9.      // le comportement de base, fait avancer l'objet dans sa direction,  
  10.      // en fonction de sa vitesse linéaire. Il fait aussi varier sa vitesse linéaire  
  11.      // et angulaire en fonction des accélérations linéaires et angulaires  
  12.   
  13.          o.xPos +=  o.vitesse * Math.cos( o.direction);  
  14.          o.yPos +=  o.vitesse * Math.sin( o.direction);  
  15.          o.vitesse =  o.vitesse +  o.acceleration;  
  16.          o.direction =  o.direction + o. accelerationAngulaire;  
  17.   
  18.         // On garde la direction entre 0 et 2*PI  
  19.        o.normaliseDirection();  
  20.   
  21.     }  
  22.   
  23.   
  24.     public String getName()  {  
  25.         return "Comportement par defaut";  
  26.     }  
  27.   
  28.     public Class getType() {  
  29.         return ComportementParDefaut.class;  
  30.     }  
  31.   
  32.     public String getDescription() {  
  33.         return "Comportement par defaut : l'objet avance dans sa direction, à  sa vitesse...";  
  34.     }  
  35.   
  36.     ...  
  37.   
  38.     public String getVersion() {  
  39.         return "1.0";  
  40.     }  
  41. }  

Projet libPlugins

Ce projet est assez compliqué, en plus sous Eclipse, tous les accents dans les commentaires ont été transformés en trucs bizarre suite à des problèmes d'encodage (ça marche dans Netbeans)... Regardez surtout les classes Plugin.java, PluginLoader.java, PluginManager.java

On ne vous demande pas de tout comprendre dans le détail (à propos, ce projet utilise des enums qui ont changé depuis java 1.3, il a été écrit à l'époque de java 1.3, avant que cela change, c'est pourquoi dans les propriétés du projet nous sommes dans un mode de compatibilité java 1.3). Notez cependant que gérer tous les cas de figures (plugins dans un jar, dans plusieurs jars, hors des jars, filesystem unix ou windows, nombreux cas d'erreurs gérés par de très nombreuses exceptions, etc) représente pas mal de travail.

Cette librairie a été écrite par Richard Grin et pas mal modifiée par Michel Buffa il y a quelques années. Vous pouvez la réutiliser telle quelle dans vos projets.

Travail à faire

  • Ecrire un plugin "tout en un", un ComportementQuiRebonditEtQuiSuitLaSouris
  • Réfléchir à comment écrire un autre type de plugin, par exemple, des plugins "niveaux" qui lorsqu'on les invoque créent plusieurs objets avec des comportements (un peu comme dans le TP précédent). Vous remarquerez que la méthode qui sert à créer les instances des plugins (voir MaFenetre.java) permet de spécifier le type des plugins que l'on veut instancier :
  1. // Ici on crée un tableau avec une instance de chaque plugin. On s'en sert un peu plus bas pour  
  2.         // construire un menu de comportements  
  3.         plugins = (ComportementPlugin[]) pluginManager.getPluginInstances(ComportementPlugin.class);  

Imaginez que l'on veuille faire un second menu "Objets" avec des Cecles, des Rectangles, etc... Ajoutez au projet des plugins une classe ObjetPlugin.java, et faites des sous-classes pour créer un ou plusieurs autres types d'objets, peut etre avec des Comportements (note : les plugins objet auront accès au plugins comportement puisque dans le même projet). Faites en sorte que 1) un menu des objets se remplisse, 2) qu'en sélectionnant une entrée cela crée un objet correspondant dans le jeu (vous lui donnerez une position aléatoire pour démarrer).

A LIRE AVANT DE FAIRE LE TRAVAIL :

  1. Les plugins doivent avoir un constructeur par défaut sans paramètres, en effet la librairie de plugins, lorsqu'elle créée des instances, fait juste un appel à la méthode newInstance() de la classe du plugin.
  2. Chaque plugin doit renvoyer dans la methode matches(type, name, object) true si le plugin est du type demandé lors de l'appel à pluginManager.getInstances. C'est également ce type qui sera celui des éléments du tableau d'instance rendu. Par exemple, si j'ai un type de plugins ObjetPlugin.java qui est une interface qui étends Plugin. Si j'ai ObjetAnime.java qui étend cette classe (et qui est donc un ObjetPlugin), et que j'ai Cercle qui étend ObjetAnime, je dois déclarer mon tableau de plugins comme ObjetAnime[], faire le getInstances en demandant des ObjetAnime.class et la méthode matches() de Cercle doit renvoyer (type == ObjetAnime.class).

CORRECTION qui propose un menu d'objetPlugins, pour voir comment gérer deux menus

  • MiniJeu1EtPluginsCorrection.zip : attention, projet netbeans seulement (mais ne prenez que les sources). Montre comment on gère deux menus et deux types de plugins différents.

 

 

Mots clés:
FichierTailleDateAttaché par 
 EclipseProjectsPlugins.zip
Aucune description
188.5 Ko09:37, 22 Nov 2010MichelBuffaActions
 MiniJeu1EtPluginsCorrection.zip
Aucune description
150.26 Ko08:59, 25 Nov 2010MichelBuffaActions
 NetBeansProjectsPlugins.zip
Aucune description
234.58 Ko09:37, 22 Nov 2010MichelBuffaActions
Images (9)
Voir 1 - 6 sur 9 images | Voir tout
Aucune description
Snap11.jpg  Actions
Aucune description
Snap7.jpg  Actions
Aucune description
Snap8.jpg  Actions
Aucune description
Snap6.jpg  Actions
Aucune description
Snap5.jpg  Actions
Aucune description
Snap4.jpg  Actions
Commentaires (0)
Vous devez être connecté pour poster un commentaire.