Improve Foudations Improve Technologies Improve Community Improve Institute

Voici une liste de notes techniques qui peuvent s’avérer utiles pour répondre à un problème donné.

Atelier Moni - Trucs et astuces



Eclipse Buddy Class Loading

Une petite synthèse de ce qu’il faut savoir / a brief synthesis of what you must know


Principes

Chaque plugin a son propre Class Loader.

Celui-ci peut accéder aux classes du plugin et à celles des packages exportés explicitement dans les plugins dont il dépend.

Problème

Si un plugin doit charger une classe quelconque, d’un plugin dont il ne dépend pas, ça ne fonctionne pas. Ce cas survient pourtant de temps en temps.

Exemple : un plugin encapsulant Log4j veut accéder à une classe d’un plugin P1 qui utilise Log4j, mais la dépendance est déclarée de P1 vers Log4j et pas dans l’autre sens. Déclarer la dépendance dans l’autre sens n’aurait pas de sens justement, car il n’est pas raisonnable d’ajouter au plugin Log4j tous les plugins susceptibles de l’utiliser... Celui-ci doit rester indépendant et ne doit pas être modifié sous prétexte qu’un nouveau plugin l’utilise.

La solution

La solution s’appelle “Buddy class loading”.

Il suffit de déclarer ce mécanisme dans les fichiers MANIFEST.MF :

  • dans le plugin partagé qui a besoin d’accéder aux classes externes (Log4j) :
Eclipse-BuddyPolicy: registered
  • dans les plugins qui utilisent le premier (P1) :
Eclipse-RegisterBuddy: <nom du plugin partagé>

C’est une manière implicite de déclarer la relation dans l’autre sens.


Principles

Each plugin has its own Class Loader.

This one can reach all the plugin classes and those from the packages explicitly exported in the plugins on which it depends.

Problem

If a plugin must load som class from a plugin on which it does not depend, you will get an exception. However it could be useful in some cases.

Example: a plugin encapsulating Log4j wants to access a class from a P1 plugin which uses Log4j, but dependence is declared from P1 towards Log4j and not in the other way. Declaring the dependence in the other way would not have sense, because adding to Log4j a dependance towards every plugin would not be satisfying... Indeed, this one must remain independent and does not have to be modified just because a new plugin needs to use it.

Solution

The solution is called “Buddy class loading”.

You have to declare it in Manifest.MF files :

  • in the shared plugin needing to access external classes (Log4j) :
Eclipse-BuddyPolicy: registered
  • in the other plugins unsing this first one (P1) :
Eclipse-RegisterBuddy: <name of shared plugin>

Thus you implicitly declare a sort of “dependency” in the other way round.

Bundle of JRE with Eclipse RCP

If you put the bundled jre into the jre sub-directory of your rcp app, Eclipse will guarantee to use this without extra options.

How to write an Eclipse installer

Eclipse RCP and OneNote

Eclipse is able to talk with OneNote...


Demonstration

Snippet

Here is a snippet to connect SWT and OneNote :

import java.io.IOException;
 
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.HTMLTransfer;
import org.eclipse.swt.dnd.RTFTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
 
 
public class OneNoteCopyPaste {
	private static final String PASTE_CMD = " /paste";
	private static final String ONENOTE_PATH = "C:/Program Files/Microsoft Office/OFFICE11/onenote.exe";
 
	public static void main (String [] args) {
		final Display display = new Display ();
		Shell shell = new Shell (display);
		Label label = new Label (shell, SWT.NONE);
		label.setText ("Enter your name:");
		final Text text = new Text (shell, SWT.BORDER);
		text.setLayoutData (new RowData (100, SWT.DEFAULT));
		Button ok = new Button (shell, SWT.PUSH);
		ok.setText ("OK");
		ok.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				Clipboard cb = new Clipboard(display);
 
				String textData = text.getText();
			 	String rtfData = "{\\rtf1\\b\\i " + textData + "}";
			 	String htmlData = "<html><body>" + textData+  "</body></html>";
 
			 	TextTransfer textTransfer = TextTransfer.getInstance();
			 	RTFTransfer rtfTransfer = RTFTransfer.getInstance();
			 	HTMLTransfer htmlTransfer = HTMLTransfer.getInstance();
			 	Transfer[] transfers = new Transfer[]{textTransfer, rtfTransfer, htmlTransfer};
			 	Object[] data = new Object[]{textData, rtfData, htmlData};
			 	cb.setContents(data, transfers);
			try {
				Runtime.getRuntime().exec(ONENOTE_PATH + PASTE_CMD);
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
			finally {
				cb.dispose();
			}
 
			}
		});
		Button cancel = new Button (shell, SWT.PUSH);
		cancel.setText ("Cancel");
		cancel.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				System.out.println("Cancel");
			}
		});
		shell.setDefaultButton (cancel);
		shell.setLayout (new RowLayout ());
		shell.pack ();
		shell.open ();
		while (!shell.isDisposed ()) {
			if (!display.readAndDispatch ()) display.sleep ();
		}
		display.dispose ();
	}
}
 

Checkboxes dans une table SWT

Comment mettre des checkboxes dans une table SWT ? Plusieurs solutions se présentent...


En standard

La table SWT n’accepte les checkboxes que dans sa première colonne.

Pour activer ce comportement, on utilise le style SWT.CHECK.

new Table(parent, SWT.CHECK);

Cette checkbox a cependant un comportement particulier et l’action de la cocher n’est qu’une alternative à la sélection de ligne.

En conséquence, c’est un SelectionListener qui pourra intercepter les changements d’état :

table.addSelectionListener(new SelectionAdapter() {
	public void widgetSelected(SelectionEvent e) {
		if (e.detail == SWT.CHECK) {
			...
		}
	}
});

En résumé, cette façon de procéder est assez simple à mettre en oeuvre mais ne permet pas d’avoir des cases à cocher dans plusieurs colonnes distinctes qui, par exemple, correspondraient à des booléens sur des beans.

Avec JFace

On peut simuler la présence de checkboxes dans un TableViewer.

Pour cela, on implémente ICellModifier (voir cet article sur l'utilisation de TableViewer) et on fera en sorte que le LabelProvider retourne une image qui corresponde selon les cas à la case cochée ou décochée.

Cette approche comporte quelques limitations :

  • on n’obtient pas l’aspect habituel d’une checkbox puisque ce ne sont que des images
  • si la table doit afficher d’autres images, celles correspondant aux cases à cocher risquent de ne pas être de la bonne taille (car malheureusement, dans une table SWT, toutes les images sont de la taille de la première image insérée)

TableEditor

Utiliser TableEditor permet d’ajouter une véritable checkbox (bouton de style check) aux cellules de son choix.

TableEditor editor = new TableEditor(table);
editor.grabVertical = false;
editor.minimumHeight = 18;
Button button = new Button(table, SWT.CHECK);
button.pack();
editor.horizontalAlignment = SWT.CENTER;
editor.minimumWidth = button.getSize().x;
editor.setEditor(button, item, colIndex);

où item est le TableItem qui correspond à la ligne et colIndex est l’indice de la colonne.

Pour traiter les changements d’état :

button.addSelectionListener(new SelectionAdapter() {
	public void widgetSelected(SelectionEvent e) {
		Button checkBox = (Button) e.getSource();
		boolean selected = checkBox.getSelection();
		...
	}
});

On peut procéder de la même manière pour mettre en oeuvre des boutons radio.

Utiliser JasperReports avec Eclipse RCP

L’utilisation de JasperReports dans une application RCP provoque des NoClassDefFoundError ...
Cyril HUGER, 2006/01/24


Dans une application Eclipse RCP chaque plugin possède son propre classloader. Par défaut ce classloader ne donne accès qu’aux classes dudit plugin et aux classes exportées par les plugins dont il dépend. Pour fonctionner, JasperReports génère dynamiquement une classe représentant le Report. Puisque cette classe n’est pas connue du plugin, elle n’est pas chargée. Eclipse propose un mécanisme nommé “buddy loading” mais celui-ci n’est pas utilisable dans le cas de JasperReports. La solution consiste à définir un nouveau classloader.

Créer la classe JasperContextFinder

Notre classloader va être créé en dupliquant la classe org.eclipse.core.runtime.internal.adaptor.ContextFinder. Nous nommons cette classe JasperContextFinder et nous remplacons la méthode basicFindClassLoader par le code suivant :

ClassLoader basicFindClassLoader() {
	Class[] stack = contextFinder.getClassContext();
	for (int i = 1; i < stack.length; i++) {
		ClassLoader tmp = stack[i].getClassLoader();
		if (stack[i] != ContextFinder.class && tmp != null
				&& checkClassLoader(tmp)) {
			return tmp;
		}
	}
	return null;
}

Utiliser JasperReports

Il ne reste plus qu’a utiliser JasperReports avec notre classloader.

// Taking backup of the default classloader
ClassLoader systemClassLoader = Thread.currentThread().getContextClassLoader();
try {
	// Loading my custom classloader
	Thread.currentThread().setContextClassLoader(
		new JasperContextFinder(Thread.currentThread()
					.getContextClassLoader()));
 
	// Manipulation de Jasper
	...
	JasperPrint jasperPrint = JasperFillManager.fillReport(...);
	...
 
} finally {
	// Replacing my custom classloader with the original classloader.
	Thread.currentThread().setContextClassLoader(systemClassLoader);
}

SafeRunnable

La plate-forme Eclipse apporte un mécanisme pour gérer les erreurs qui surviennent sur un traitement.


Définition du mécanisme

ISafeRunnable (org.eclipse.core.runtime) permet de définir une portion de code protégée. L’interface se compose de 2 méthodes :

  • run() pour exécuter une tâche,
  • handleException(Exception) qui réagit en cas d’erreur dans run().

D’autre part, ISafeRunnableRunner définit un cadre pour l’exécution de telles portions de code via une méthode run() qui prend en paramètre une implémentation d’ISafeRunnable.

L'implémentation SafeRunnable

SafeRunnable (org.eclipse.jface.util) est une mise en oeuvre de ce mécanisme. En effet, cette classe propose à la fois :

  • une implémentation d’ISafeRunnable qui ouvre une boîte de dialogue en cas d’erreur,
  • une méthode statique pour exécuter un ISafeRunnable.
SafeRunnable.run(new SafeRunnable() {
   public void run() throws Exception {
     ... <traitement> ...
   }
});

Ce genre de code peut donc remplacer un bloc try/catch lorsque l’on souhaite généraliser la gestion des erreurs. Il est évidemment possible de définir un autre traitement d’erreurs que celui par défaut.

Des onglets avec le look Eclipse 3

Par défaut les applications Eclipse RCP montrent des onglets rectangulaires plutôt classiques. Pour avoir des onglets qui ressemblent à ceux d’Eclipse IDE version 3.x...,


...il suffit d’ajouter la ligne de code suivante :

PlatformUI.getPreferenceStore().setValue(
            IWorkbenchPreferenceConstants.SHOW_TRADITIONAL_STYLE_TABS,
            false);

Ce code peut être placé dans ApplicationWorkbenchAdvisor.initialize().

Il permet simplement de passer de ce genre d’onglet :

ongletclassique.jpg

à celui-ci :

ongletv3.jpg

Ce n’est sans doute pas une fonctionnalité indispensable mais un bon moyen de donner du caractère à l’IHM à peu de frais.

Intégrer des JARs à une application RCP

Pour intégrer des JARs à une application Eclipse RCP, il faut...


  1. ajouter les fichiers au projet (dans un répertoire lib/ par exemple).
  2. déclarer les dépendances au niveau du Plugin Manifest :
    • cocher les JARs dans Binary Build (onglet Build)
    • les ajouter aussi dans Extra Classpath Entries
    • dans l’onglet Runtime, ajouter les JARs dans la zone “classpath”
    • attention, dans cette même zone, il faut ajouter “.” (bouton “New...”) [contournement d’un bug d’Eclipse 3.1]

NB : Il n’est pas utile d’ajouter les JARs au classpath du projet via son menu Properties. Si on le fait une erreur de redondance sera signalée.

Si les fonctionnalités apportées par les bibliothèques sont susceptibles d’être utilisées assez souvent, il est conseillé de créer un plugin plutôt que d’intégrer simplement les JARs à l’application. Eclipse propose un assistant pour créer des plugins à partir de JARs existants. ⇒ on bénéficie ainsi de la souplesse et de la puissance de la notion de plugin dans Eclipse (versionning, features, update...)

Manipulation d'images avec RCP

Quelques bouts de code pour manipuler les images avec Eclipse RCP, en particulier accéder aux icônes de la plate-forme.


Récupérer une icône de la plate-forme

PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);

ISharedImages (org.eclipse.ui) porte les identifiants des icônes apportées en standard par la plate-forme (images partagées). Remarques :

  • Les icônes de l’IDE sont définies sur org.eclipse.ui.ide.IDE.ISharedImages
  • Celles du JDT : org.eclipse.jdt.ui.SharedImages
  • Pour voir toutes les icônes d’Eclipse.

Ajouter sa propre image aux images partagées

On déclare l’image au niveau de ApplicationWorkbenchAdvisor.initialize(IWorkbenchConfigurer) de la façon suivante :

URL link = Platform.getBundle("my plugin").getEntry("icons/myicon.gif");
ImageDescriptor icon = ImageDescriptor.createFromURL(link);
configurer.declareImage(MY_ICON_KEY, icon, true);

“my plugin” est le Bundle_SymbolicName du plugin déclaré dans MANIFEST.MF

Accéder à une vue RCP

Comment accéder à une vue RCP depuis n’importe quel endroit du code ?


IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
IViewPart view = page.findView(MyView.ID);

Pour afficher directement la vue :

page.showView(MyView.ID);
 
moni/trucs.txt · Last modified: 2006/02/22 11:42 by fesnault