Le site francophone consacré au projet Eclipse et à OSGi
 
 

 

 

(publié le 13/6/2007 - Versions utilisées : Eclipse SDK 3.2.2 - JDK 5.0)

 

Tutorial : développer des plug-ins Eclipse (2ème partie)


Que ce soit pour enrichir l'environnement de développement ou bien pour développer des applications Eclipse RCP, la notion de plugin est centrale.

Les plugins sont la concrétisation des deux principaux objectifs des concepteurs d'Eclipse : modularité et extensibilité.

Dans la première partie de ce tutorial nous avons vu comment créer des plugins en utilisant l'outillage intégré (le PDE) et étudié la façon dont se concrétisait la modularité des applications basées sur le framework Eclipse. Pour cette seconde partie, nous allons nous focaliser sur les fonctionnalités d'extensibilité dont peuvent bénéficier tous les plugins Eclipse.

(L'auteur de cet article est le concepteur et l'animateur de notre formation 'Développement d'applications Eclipse RCP')

 

Sommaire


 

 

Eclipse : un framework pour faire des applications extensibles


Le besoin d'extensibilité

Le but initial du projet Eclipse était de fournir un socle, écrit en Java, pour la création d'environnements de développement. Depuis 2004, cet objectif a été étendu en prenant en compte l'utilisation du framework Eclipse pour tous les types d'applications (applications Eclipse RCP).
Pour les concepteurs d'Eclipse, l'utilisation type du framework Eclipse était qu'une équipe, maîtraisant un langage de développement particulier, s'appuie sur le framework Eclipse pour fournir un environnement de développement propre à ce langage et qu'ensuite d'autres développeurs enrichissent cet environnement avec des briques supplémentaires parfaitement intégrées.

Les plugins constituant le framework sont pour la plupart extensibles et tout développeur de plugin peut rendre son plugin extensible.
En résumé, le framework Eclipse propose un mécanisme générique d'extensibilité : tout plugin peut se déclarer extensible et tout autre plugin pour étendre un plugin extensible.

 

Principes d'extensibilité d'Eclipse

L'extensibilité du framework Eclipse se concrétise par deux notions principales : les points d'extension et les extensions.

Un plugin qui veut être extensible doit déclarer un point d'extension dans son fichier plugin.xml, il indiquera notamment le nom d'un fichier au format XML Schema qui décrit la grammaire XML du point d'extension. Les plugins qui veulent se brancher sur ce point d'extension vont déclarer une extension dans leur fichier plugin.xml (en respectant la grammaire associée au point d'extension).
Pour la plupart des points d'extension, le plugin définissant le point d'extension fournit aussi une interface que devront implémenter les plugins contributeurs.

 

Illustration avec un point d'extension d'Eclipse : les pages de préférences

Avant d'aborder les aspects pratiques illustrons les principes de point d'extension et d'extension en étudiant le fonctionnement du point d'extension qui permet d'ajouter des pages de préférences dans la fenêtre des préférences d'Eclipse :

Le plugin 'org.eclipse.ui', fourni par le framework Eclipse, définit un point d'extension nommé 'org.eclipse.ui.preferencePages'. Le fichier 'preferencePages.exsd' contient la grammaire associée à ce point d'extension, en voici un extrait :

<element name="page">
   <complexType>
      <attribute name="id" type="string" use="required" />


      <attribute name="name" type="string" use="required" />

      <attribute name="class" type="string" use="required">
         <annotation>
            <appInfo>
               <meta.attribute kind="java"
                     basedOn="org.eclipse.jface.preference.PreferencePage:
                                             org.eclipse.ui.IWorkbenchPreferencePage"/>
               </appInfo>
            </annotation>
         </attribute>


      <attribute name="category" type="string"/>

   </complexType>
</element>


Cette grammaire définit un élément XML nommé 'page' qui possède quatre attributs : 'id', 'name', 'category' et 'class'.
Pour l'attribut 'class', l'interface à implémenter est indiquée ('org.eclipse.ui.IWorkbenchPreferencePage') et dans ce cas précis une superclasse est aussi proposée ('org.eclipse.jface.preference.PreferencePage').
Les attributs 'id', 'name' et 'class' sont obligatoires.


Pour ajouter une page de préférences, un plugin doit donc définir, dans son fichier plugin.xml, une extension sur le point d'extension 'org.eclipse.ui.preferencePages'

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>

<plugin>
   <extension
      point="org.eclipse.ui.preferencePages">
         <page
               id="com.eclipsetotale.tutorial.pagepreference.page1"
               name="Tutorial développement de plugin"
               class="com.eclipsetotale.tutorial.pagepreference.TutorialPreferencePage"
         />
   </extension>
</plugin>



Le plugin doit aussi fournir la classe indiquée par l'attribut 'class'.

package com.eclipsetotale.tutorial.pagepreference;

import org.eclipse.jface.preference.PreferencePage;
...

public class TutorialPreferencePage extends PreferencePage
       implements IWorkbenchPreferencePage {

       protected Control createContents(Composite parent) {
            Composite composite = new Composite(parent, SWT.NONE);
            Color jaune = parent.getDisplay().getSystemColor(SWT.COLOR_YELLOW);
            composite.setBackground(jaune);
            return composite;
      }

      public void init(IWorkbench workbench) {
      }

}


Cette sous-classe de 'org.eclipse.jface.preference.PreferencePage' a pour responsabilité la gestion du contenu spécifique à la page de préférences (la partie en jaune sur cette capture d'écran) :


Le plugin qui gère les pages de préférences construit le cadre graphique et utilisent des APIs du framework Eclipse pour :

  • connaître la liste des extensions définies sur le point d'extension 'org.eclipse.ui.preferencePages'.
  • récupérer la valeur des attributs (id, name, class et category).
  • instancier la classe associée à chaque page de préférences et appeler la méthode createContents pour déléguer la gestion de la partie en jaune.

 

 

Des deux notions, point d'extension et extension, découlent deux niveaux de mise en oeuvre de l'extensibilité du framework Eclipse :

  • l'utilisation de points d'extension définis par d'autres développeurs. Le cas le plus courant étant de se brancher sur les points d'extensions prédéfinis d'Eclipse (vue, éditeur, perspectives, menus, pages de préférences, assistants, ...).
  • la déclaration de nouveaux points d'extension sur lesquels s'appuieront d'autres développeurs.

Dans la suite de ce tutorial nous allons mettre en pratique ces deux niveaux.

 

 

 

Utiliser un point d'extension

Le framework Eclipse propose de nombreux points d'extension, notamment pour assurer l'extensibilité de son interface graphique. Nous allons étudier les étapes permettant l'ajout d'une nouvelle vue à l'environnement Eclipse.

Cette vue affichera l'heure, pour ce faire nous allons réutiliser les plugins développés dans la première partie de ce tutorial.



Définition d'une extension


Créer un nouveau plugin nommé 'com.eclipsetotale.tutorial.horloge' (ne pas utiliser de template).

Dans l'éditeur de fichiers manifestes de ce plugin, sélectionner l'onglet 'Extensions'. Dans cet onglet cliquer sur le bouton 'Add...' : la liste de tous les points d'extension est présentée. Choisir le point d'extension 'org.eclipse.ui.views' :

 

L'objectif de l'onglet 'Extensions' est de générer la définition XML de l'extension dans le fichier plugin.xml. Sélectionner le point d'extension et utiliser le menu contextuel pour ajouter un des éléments XML déclarés dans la grammaire associée au point d'extension, dans notre cas un élément de type 'view':

 

La partie de gauche de l'éditeur permet de voir les attributs associés à cet élément XML et d'indiquer leurs valeurs :

 

La définition de notre extension est terminée, le code XML correspondant a été généré dans le fichier plugin.xml :

   <extension
               point="org.eclipse.ui.views">
         <view
               id="com.eclipsetotale.tutorial.horloge.vue"
               name="Horloge"
               class="com.eclipsetotale.tutorial.horloge.HorlogeView"
         />
   </extension>

 


Classe associée à l'extension

L'étape suivante consiste à créer la classe indiquée par l'attribut 'class'. Le PDE fournit une aide sympathique : le libellé de l'attribut 'class' est un lien hypertexte capable d'ouvrir l'assistant de création de classe et de pré-remplir les champs (notamment le nom de l'interface à implémenter et/ou de la classe à étendre) :



La classe est créée avec le code suivant :

package com.eclipsetotale.tutorial.horloge;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;

public class HorlogeView extends ViewPart {

   public HorlogeView() {
      // TODO Auto-generated constructor stub
   }

   @Override
   public void createPartControl(Composite parent) {
      // TODO Auto-generated constructor stub
   }

   @Override
   public void setFocus() {
      // TODO Auto-generated constructor stub
   }

}

 

Pour que cette vue affiche l'heure, nous allons réutiliser le plugin 'com.eclipsetotale.tutorial.horloge.texte'. Dans l'éditeur de fichiers manifestes du plugin 'com.eclipsetotale.tutorial.horloge', sélectionner l'onglet 'Dependencies' et ajouter le plugin 'com.eclipsetotale.tutorial.horloge.texte' dans la section 'Required plugins'.

Une fois cette opération effectuée, modifier le code de la classe 'HorlogeView' pour lui faire afficher l'horloge. La méthode à modifier est la méthode createPartControl, cette méthode est appelée par Eclipse à l'ouverture de la vue :

package com.eclipsetotale.tutorial.horloge;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
import com.eclipsetotale.tutorial.horloge.texte.HorlogeTexte;

public class HorlogeView extends ViewPart {

   public HorlogeView() {
   }

   @Override
   public void createPartControl(Composite parent) {
      new HorlogeTexte(parent);
   }

   @Override
   public void setFocus() {
   }

}

 

Tester en lançant l'environnement Eclipse de test. La vue n'est pas ouverte automatiquement, il faut demander son affichage à partir du menu 'Window->Show view->Other ...', sélectionner la catégorie 'Other', la vue 'Horloge' doit s'y trouver :

 

 

 


Créer un nouveau point d'extension


Définition d'un point d'extension

La notion de point d'extension n'est pas réservée aux plugins propres au framework Eclipse : tout plugin peut définir ses propres points d'extension. La définition d'un nouveau point d'extension passe essentiellement par la définition de la grammaire XML associée. Le PDE propose un éditeur spécifique pour créer et éditer ces grammaires qui sont au format XML Schema.
Nous allons définir un point d'extension permettant à des plugins de contribuer de nouveaux types d'horloge. Notre plugin 'com.eclipsetotale.tutorial.horloge' aura donc la responsabilité de gérer la vue 'Horloge' mais délèguera l'affichage à un des plugins contributeurs. La première étape est de définir un point d'extension sur lequel se brancheront les plugins implémentant une Horloge.

 

Déclaration du point d'extension

L'onglet 'Extension points' de l'éditeur de fichiers manifestes aide à la création de nouveaux points d'extension. Ouvrir cet onglet pour le plugin 'com.eclipsetotale.tutorial.horloge'. Le bouton 'Add...' ouvre un assistant permettant d'indiquer l'ID du nouveau point d'extension (l'ID réel sera la concaténation de l'ID du plugin et de la valeur de ce champ), son nom et le nom du fichier qui contiendra la grammaire :



Définition de la grammaire

Notre point d'extension doit permettre à des plugins de contribuer de nouveaux types d'Horloge. Nous proposons donc que la grammaire soit composée d'un élément XML nommé 'Horloge' comprenant trois attributs 'id', 'nom' et 'classe'.

Cette grammaire doit être définie dans un fichier XMLSchema avec une extension .exsd. Le fichier a été créé automatiquement lors de l'étape précédente et l'éditeur de fichiers .exsd a été ouvert sur notre fichier. L'éditeur dispose de plusieurs onglets, se placer dans l'onglet 'Definition' et utiliser le bouton 'Add Element' pour créer un élément XML que nous nommerons 'Horloge' :



Utiliser le bouton 'New Attribute' pour créer les attributs 'id' et 'nom'. Indiquer que ces attributs sont 'required' et de type 'string' :



Créer ensuite l'attribut 'classe'. Spécifier qu'il est 'required' et de type 'java'. Cet attribut sera utilisé par les plugins contributeurs pour indiquer le nom de la classe qui gère l'affichage de l'Horloge. Pour que notre plugin (celui définissant le point d'extension) puisse manipuler les classes fournies par les plugins contributeurs, nous allons leur imposer une interface commune. Le nom de cette interface est à indiquer dans le champ 'Implements' :


Ajouter l'interface dans le plugin 'com.eclipsetotale.tutorial.horloge', son code est le suivant

package com.eclipsetotale.tutorial.horloge;

import org.eclipse.swt.widgets.Composite;

public interface Horloge {
   public void afficher(Composite parent);
}



Cette interface devra être utilisable par les plugins dépendants : dans l'éditeur de fichier manifestes, sélectionner l'onglet 'Runtime' et ajouter le package 'com.eclipsetotale.tutorial.horloge' dans la section 'Exported Packages'.



La grammaire d'un point d'extension se compose systématiquement d'un élément parent nommé 'extension'. Il nous faut indiquer la relation entre l'élément 'extension' et notre élément 'Horloge'. Dans notre cas l'élément 'extension' peut contenir plusieurs sous-éléments 'Horloge' (un plugin peut fournir plusieurs types d'horloge), pour définir ce lien il faut utiliser le menu contextuel sur l'élément extension et sélectionner 'New->Compositor->Sequence' :



Ouvrir ensuite le menu contextuel sur la séquence et sélectionner 'New->Reference->Horloge' :


Sélectionner la référence vers l'élement 'Horloge' et cocher la case 'Unbounded' dans la partie de gauche:

 

 


Utilisation du point d'extension

Le point d'extension 'com.eclipsetotale.tutorial.horloge.horloges' est maintenant accessible aux plugins souhaitant y contribuer. Dans notre cas nous allons déclarer une extension sur ce point dans nos plugins 'com.eclipsetotale.tutorial.horloge.texte' et 'com.eclipsetotale.tutorial.horloge.nebula'.


Pour ces deux plugins, ouvrir l'éditeur de fichiers manifestes, se placer dans l'onglet 'Extensions' et sélectionner le bouton 'Add...'. Dans l'assistant décocher la case 'Show only extension points from required plugins' et sélectionner le point d'extension 'com.eclipsetotale.tutorial.horloge.horloges' :

Répondre oui à la boîte de dialogue proposant d'ajouter automatiquement le plugin 'com.eclipsetotale.tutorial.horloge' comme pré-requis.


Sachant que précédemment nous avions mis le plugin 'com.eclipsetotale.tutorial.horloge.texte' comme pré-requis de 'com.eclipsetotale.tutorial.horloge', nous avons maintenant un cylce dans les dépendances qui empêche la compilation. Dans l'onglet 'Dependencies' de l'éditeur de fichiers manifestes du plugin 'com.eclipsetotale.tutorial.horloge' supprimer le plugin 'com.eclipsetotale.tutorial.horloge.texte' de la liste 'Required plugins'.


Dans l'éditeur de fichiers manifestes des plugins 'com.eclipsetotale.tutorial.horloge.texte' et 'com.eclipsetotale.tutorial.horloge.nebula, utiliser le menu contextuel sur le point d'extension pour ajouter un élément 'Horloge' :

Indiquer la valeur des attributs 'id', 'nom' et 'classe' :

 

Faire créer les classes 'HorlogeImpl' en sélectionnant le lien hypertexte 'classe*'. Compléter le code de ces deux classes :

package com.eclipsetotale.tutorial.horloge.texte;

import org.eclipse.swt.widgets.Composite;
import com.eclipsetotale.tutorial.horloge.Horloge;

public class HorlogeImpl implements Horloge {

   public void afficher(Composite parent) {
      new HorlogeTexte(parent);
   }
}


package com.eclipsetotale.tutorial.horloge.nebula;

import org.eclipse.swt.widgets.Composite;
import com.eclipsetotale.tutorial.horloge.Horloge;

public class HorlogeImpl implements Horloge {

   public void afficher(Composite parent) {
      new HorlogeNebula(parent);
   }
}

NB : pour éviter de modifier les classes HorlogeTexte et HorlogeNebula nous avons créé des classes intermédiaires. La solution la plus standard aurait été de sélectionner directement les classes 'HorlogeTexte' et 'HorlogeNebula' dans les champs 'classe*' puis de modifier le code de ces classes pour leur faire implémenter l'interface 'com.eclipsetotale.tutorial.horloge.Horloge'.

 

 


APIs associées au point d'extension

A ce niveau nous avons défini un point d'extension et deux extensions sur ce point. Le framework Eclipse gère un registre des extensions, accessible par programmation. Le registre est alimenté au lancement d'Eclipse lors de l'analyse des fichiers plugin.xml.

Pour finaliser nos plugins, nous allons compléter le plugin 'com.eclipsetotale.tutorial.horloge' pour lui faire afficher dans une page de préférences la liste des types d'horloges disponibles (liste des extensions sur le point d'extension 'com.eclipsetotale.tutorial.horloge.horloges'). L'Horloge sélectionnée dans la page de préférences sera celle affichée par la vue 'HorlogeView'.

 

Création de la page de préférences

Dans l'éditeur de fichiers manifestes du plugin 'com.eclipsetotale.tutorial.horloge', sélectionner l'onglet 'Extensions' puis ajouter une extension de type 'org.eclipse.ui.preferencePages'. Créer un élément de type 'page' (via le menu contextuel 'New') et renseigner ses attributs :


Le code de la classe associée à la page de préférences est le suivant :

package com.eclipsetotale.tutorial.horloge;

import java.util.Date;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;

public class PrefPage extends org.eclipse.jface.preference.PreferencePage
      implements IWorkbenchPreferencePage {

   private static final String HORLOGE = "horloge";
   private Combo comboHorloges;

   @Override
   protected Control createContents(Composite parent) {
      Composite content = new Composite(parent, SWT.NONE);
      content.setLayout(new GridLayout(2, false));

      (new Label(content, SWT.NONE)).setText("Type d'horloge : ");
      comboHorloges = new Combo(content, SWT.DROP_DOWN);
      comboHorloges.setItems(getNomsHorloges());
      comboHorloges.setText(getHorlogeCourante());

      return content;
   }

   /**
    * Liste des noms d'horloge apparaissant dans le registre des extensions
    */

   static private String[] getNomsHorloges() {
      String extensionPointId = "com.eclipsetotale.tutorial.horloge.horloges";
      IConfigurationElement[] contributions =
         Platform.getExtensionRegistry().getConfigurationElementsFor(extensionPointId);
      String[] nomsHorloges = new String[contributions.length];
      for (int i = 0; i < contributions.length; i++) {
         nomsHorloges[i] = contributions[i].getAttribute("nom");
      }
      return nomsHorloges;
   }

   /**
    * Nom de l'horloge actuellement sélectionnée dans la page de préférences.
    * Si aucun retourne le nom de la première horloge.
    */

   static public String getHorlogeCourante() {
      IPreferenceStore prefStore =
         Activator.getDefault().getPreferenceStore();
      String nomHorloge = prefStore.getString(HORLOGE);
      if (nomHorloge.equals("")) {
         nomHorloge = getNomsHorloges()[0];
         prefStore.setValue(HORLOGE, nomHorloge);
      }
      return nomHorloge;
   }

   @Override
   public boolean performOk() {
      // Stockage du nom de l'horloge dans les préférences du plugin
      IPreferenceStore prefStore =
         Activator.getDefault().getPreferenceStore();
      prefStore.setValue(HORLOGE, comboHorloges.getText());

      return true;
   }

   public void init(IWorkbench workbench) {
   }

}

La partie importante est la méthode 'getNomsHorloges'. Cette méthode consulte le registre des extensions pour trouver les contributions au point d'extension 'com.eclipsetotale.tutorial.horloge.horloges' et récupère la valeur de l'attribut 'nom' de chaque extension.
Le reste du code gère le contenu graphique de la page de préférences et le stockage du nom d'horloge sélectionné par l'utilisateur.

 

La dernière étape est de faire afficher l'horloge sélectionnée par la vue 'Horloge'. Modifier le code de la classe 'HorlogeView' de la façon suivante :

package com.eclipsetotale.tutorial.horloge;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
import com.eclipsetotale.tutorial.horloge.texte.HorlogeTexte;

public class HorlogeView extends ViewPart {

   public HorlogeView() {
   }

   @Override
   public void createPartControl(Composite parent) {


      // Nom d'horloge sélectionné dans la page de préférences
      String nomHorlogeCourante = PrefPage.getHorlogeCourante();

      // Récupération de l'extension associée au nom d'horloge
      String extensionPointId = "com.eclipsetotale.tutorial.horloge.horloges";
      IConfigurationElement[] contributions =
         Platform.getExtensionRegistry().getConfigurationElementsFor(extensionPointId);
      IConfigurationElement extensionHorloge = null;
      for (int i = 0; i < contributions.length; i++) {
         if(contributions[i].getAttribute("nom").equals(nomHorlogeCourante)) {
            extensionHorloge = contributions[i];
            break;
         }
      }

      // Si une extension est disponible, la classe 'Horloge' correspondante
      // est instanciée via la méthode 'createExecutableExtension'
      if(extensionHorloge != null) {
         try {
            Horloge horloge =
               (Horloge)extensionHorloge.createExecutableExtension("classe");
            horloge.afficher(parent);
         } catch (CoreException e) {
            String msg = "Impossible d'afficher l'horloge";
            parent.setLayout(new RowLayout());
            (new Label(parent, SWT.NONE)).setText(msg);
         }
      }
   }

   @Override
   public void setFocus() {
   }

}

 

Tester. Le changement d'horloge n'est pas géré dynamiquement par notre code, donc à chaque modification du type d'horloge dans la page de préférences, il faut fermer et ouvrir de nouveau la vue 'Horloge'.

 

 


Conclusion

La notion de plugin intégrée au framework Eclipse permet le développement et la livraison d'applications modulaires et extensibles.
La première partie de ce tutorial nous avait permis d'apprendre à manipuler l'outillage de développement de plugins (le PDE) et de découvrir comment le framework Eclipse assurait la modularité. Dans cette deuxième partie nous avons étudié comment se concrétisait l'extensibilité du framework Eclipse avec les notions de points d'extension et d'extensions.

(L'auteur de cet article est le concepteur et l'animateur de notre formation 'Développement d'applications Eclipse RCP')

 

 

 


 

 

 


 

 

(c) EclipseTotale - contact(arobase)eclipsetotale.com