février
2009
Dans ce billet nous allons enfin commencer a développer avec WST. Nous allons apprendre a sérialiser le fichier XML (format EMF) en un XML plus simple :
<diagram>
<shapes>
<ellipse height="65" width="101" x="32" y="29"/>
<rectangle height="79" width="101" x="86" y="138"/>
<ellipse height="73" width="113" x="234" y="34"/>
</shapes>
</diagram>
Pour effectuer cette tâche, il faudra décrire un mapping entre le DOM XML et l’instance EMF ShapesDiagram en utilisant les Translators de WST. L’article Persisting EMF models with WTP traite l’utilisation des Translators WST avec EMF.
Vous pouvez télécharger le projet org.eclipse.gef.examples.shapes.emfwst_1.0.0.zip expliqué dans ce billet.
Rappel Sérialisation EMF
Pour rappel, la sérialisation d’une instance EMF, s’effectue à l’aide d’une implémentation org.eclipse.emf.ecore.resource.Resource. Dans le cas de Shapes EMF, le chargement de l’instance EMF ShapesDiagram à partir d’un fichier *shapesemfwst s’effectue dans la méthode ShapesEditor#setInput(IEditorInput input) :
URI uri = URI.createPlatformResourceURI(file.getFullPath().toString());
resource.setURI(uri);
...
resource.load(Collections.EMPTY_MAP);
...
diagram = (ShapesDiagram)resource.getContents().get(0);
...
Le code de sauvegarde de l’instance EMF ShapesDiagram s’effectue dans la méthode ShapesEditor#doSave(IProgressMonitor monitor)
resource.save(Collections.EMPTY_MAP);
...
La variable resource est initialisée comme suit :
XMLResourceImpl est une implémentation XML de l’interface Resource et permet de sérializer une instance EMF en un XML. EMF fournit aussi une sérialisation en un fichier XMI à l’aide de la classe XMIResourceImpl . Pour tester cela, modifier l’initialisation de la variable resource comme ceci :
private final Resource resource = new XMIResourceImpl();
Lorsque vous sauvegarderez le fichier *shapesemfwst et que vous l’éditerez vous pourrez remarquer que le contenu XML ne diffère pas excepté la déclaration du namespace XMI :
xmi:version="2.0"
xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:org.eclipse.gef.examples.shapes.emf.model="http:///org/eclipse/gef/examples/shapes/emf/model.ecore">
....
Sérialisation EMF avec Translator
Pour sérialiser dans un XML formé comme on le souhaite, il faut implémenter sa propre interface Resource. WST propose l’utilisation des Translators qui permettent de décrire un mapping entre le méta object (EClass) d’une instance EMF et le DOM XML a obtenir.
WST fournit une classe abstraite org.eclipse.wst.common.internal.emf.resource.TranslatorResourceImpl qui implémente Resource et qui fonctionne avec les Translators.
Dépendances du projet
Pour utiliser les Translators, vous devez ajouter la dépendance org.eclipse.wst.common.emf dans le plugin. Pour effectuer cela, ouvrez le fichier MANIFEST.MF, cliquer sur l’onglet Dependencies, puis sur le bouton Add de la section Required Plug-ins.
Cette action ouvre la fenêtre de dialogue Plug-in Selection. Saisissez org.eclipse.wst.common.emf dans le champs Texte, puis Ok :
ShapesTranslator
Chaque élément XML (diagram, rectangle, ellipse) devront être mappés à un méta objet de ShapesDiagram (ShapesDiagram, RectangularShape, EllipticalShape) à l’aide d’un Translator :
- l’élement XML diagram sera mappé avec le méta objet EMF ShapesDiagram .
- l’élement XML rectangle sera mappé avec le méta objet EMF RectangularShape.
- l’élement XML rectangle sera mappé avec le méta objet EMF EllipticalShape
Les méta objets en EMF sont obtenus à l’aide de l’interface org.eclipse.gef.examples.shapes.emf.model.ModelPackage généré par EMF. Pour récupérer le méta objet ShapesDiagram par exemple, il suffit d’appeler le code suivant :
Dans cette section, nous coderons uniquement le mapping entre l’élément diagram et le méta objet ShapesDiagram. Pour cela créez la classe org.eclipse.gef.examples.shapes.emf.translator.ShapesTranslator
qui étend org.eclipse.wst.common.internal.emf.resource.RootTranslator comme suit :
import org.eclipse.gef.examples.shapes.emf.model.ModelPackage;
import org.eclipse.wst.common.internal.emf.resource.RootTranslator;
import org.eclipse.wst.common.internal.emf.resource.Translator;
public class ShapesTranslator extends RootTranslator {
public static RootTranslator INSTANCE = new ShapesTranslator();
public ShapesTranslator() {
super("diagram", ModelPackage.eINSTANCE.getShapesDiagram());
}
protected Translator[] getChildren() {
return new Translator[] {};
}
}
Le constructeur de ShapesTranslator indique que l’élément XML diagram est mappé avec le meta object EClass EMF ShapesDiagram.
Il est important de définir la méthode Translator#getChildren() qui retourne un tableau de Translator vide, sinon on obtient un NullPointerException.
Ce code ne gère donc pas pour le moment la sérialisation des formes rectangle, ellipse qui ne seront donc pas sauvegardées. La sérialisation des formes rectangle, ellipse sera expliquée dans la suite de ce billet.
ShapesResourceImpl
ShapesResourceImpl est notre implémentation EMF de Resource qui va utiliser notre Translator ShapesTranslator. Pour cela créer la classe org.eclipse.gef.examples.shapes.emf.translator.ShapesResourceImpl
qui étend l’implémentation WST de EMF Resource org.eclipse.wst.common.internal.emf.resource.TranslatorResourceImpl :
import org.eclipse.wst.common.internal.emf.resource.EMF2DOMRendererFactoryDefaultHandler;
import org.eclipse.wst.common.internal.emf.resource.Translator;
import org.eclipse.wst.common.internal.emf.resource.TranslatorResourceImpl;
public class ShapesResourceImpl extends TranslatorResourceImpl {
public ShapesResourceImpl() {
super(EMF2DOMRendererFactoryDefaultHandler.INSTANCE
.getDefaultRendererFactory().createRenderer());
}
public Translator getRootTranslator() {
return ShapesTranslator.INSTANCE;
}
protected String getDefaultPublicId() {
return null;
}
protected String getDefaultSystemId() {
return null;
}
protected int getDefaultVersionID() {
return 0;
}
public String getDoctype() {
return null;
}
}
Voici quelques explications de ShapesResourceImpl :
- TranslatorResource#getRootTranslator() : retourne le Translator racine à utiliser. Dans notre cas on retourne une instance de ShapesTranslator, plus exactement un singleton.
-
le constructeur ShapesResourceImpl renseigne l’instance Renderer WST à utiliser à l’aide du code
EMF2DOMRendererFactoryDefaultHandler.INSTANCE.getDefaultRendererFactory().createRenderer()
La factory EMF2DOMRendererFactoryDefaultHandler.INSTANCE.getDefaultRendererFactory() retourne une instance WST Renderer EMF2DOMRenderer. L’interface Renderer définit comment un objet EMF doit être rendu en XML et inversement.
Utilisation de ShapesResourceImpl
Le projet Shapes EMF utilise une instance Resource EMF à deux endroits :
- ShapesEditor : dans l’editeur Shapes au moment de charger/sauvegarder une instance EMF ShapesDiagram
- ShapesCreationWizard : dans le wizard de création d’un fichier *.shapesemfwst.
Il faut donc modifier le code du projet pour utiliser notre ShapesResourceImpl et plus XMLResources.
Pour cela,
Modifier ShapesCreationWizard#getInitialContents() comme suit :
ShapesDiagram diagram = (ShapesDiagram) createDefaultContent();
Resource resource = new ShapesResourceImpl()
...
Initialiser la variable resource de ShapesEditor comme suit :
Relancer le plugin EMF Shapes, et regénérer un diagramme à l’aide du wizard Shapes EMF-WST Diagram.
Si vous éditer le fichier *shapesemfwst à l’aide d’un éditeur de Texte, vous verrez le contenu XML suivant :
<diagram/>
L’instance EMF ShapesDiagram a été sérializé correctement.
Translator enfants (rectangle,ellipse)
A cette étape, si vous ajouter des formes rectangles ou ellipses dans l’editeur GEF et que vous sauvegardez, ces formes ne seront pas sauvegardées. En effet aucun Translator n’a été défini pour les formes rectangle et ellipse.
Pour effectuer cela, il faut implémenter la méthode ShapesTranslator#getChildren() correctement.
Modifier la classe ShapesTranslator, comme suit :
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.examples.shapes.emf.model.ModelPackage;
import org.eclipse.wst.common.internal.emf.resource.GenericTranslator;
import org.eclipse.wst.common.internal.emf.resource.IDTranslator;
import org.eclipse.wst.common.internal.emf.resource.MultiObjectTranslator;
import org.eclipse.wst.common.internal.emf.resource.RootTranslator;
import org.eclipse.wst.common.internal.emf.resource.Translator;
public class ShapesTranslator extends RootTranslator {
public static RootTranslator INSTANCE = new ShapesTranslator();
private static final Translator RECTANGLE_TRANSLATOR = createRectangleTranslator();
private static final Translator ELLIPSE_TRANSLATOR = createEllipseTranslator();
public ShapesTranslator() {
// Mapping between XML diagram element and ShapesDiagram EMF Metadata
super("diagram", ModelPackage.eINSTANCE.getShapesDiagram());
}
protected Translator[] getChildren() {
return new Translator[] { IDTranslator.INSTANCE,
createShapesTranslator() };
}
private Translator createShapesTranslator() {
MultiObjectTranslator translator = new MultiObjectTranslator(
"diagram/ellipse,rectangle", ModelPackage.eINSTANCE
.getShapesDiagram_Shapes()) {
public Translator getDelegateFor(EObject o) {
switch (o.eClass().getClassifierID()) {
case ModelPackage.RECTANGULAR_SHAPE:
return RECTANGLE_TRANSLATOR;
case ModelPackage.ELLIPTICAL_SHAPE:
return ELLIPSE_TRANSLATOR;
}
throw new IllegalStateException("Shape type delegate expected"); //$NON-NLS-1$
}
public Translator getDelegateFor(String domName,
String readAheadName) {
if ("rectangle".equals(domName))
return RECTANGLE_TRANSLATOR;
if ("ellipse".equals(domName))
return ELLIPSE_TRANSLATOR;
return null;
}
protected Translator[] getChildren() {
return new Translator[] {};
}
};
return translator;
}
private static Translator createRectangleTranslator() {
GenericTranslator translator = new GenericTranslator("rectangle",
ModelPackage.eINSTANCE.getRectangularShape());
translator.setChildren(new Translator[] {
IDTranslator.INSTANCE,
new Translator("x", ModelPackage.eINSTANCE.getShape_X(),
Translator.DOM_ATTRIBUTE),
new Translator("y", ModelPackage.eINSTANCE.getShape_Y(),
Translator.DOM_ATTRIBUTE),
new Translator("width",
ModelPackage.eINSTANCE.getShape_Width(),
Translator.DOM_ATTRIBUTE),
new Translator("height", ModelPackage.eINSTANCE
.getShape_Height(), Translator.DOM_ATTRIBUTE) });
return translator;
}
private static Translator createEllipseTranslator() {
GenericTranslator translator = new GenericTranslator("ellipse",
ModelPackage.eINSTANCE.getEllipticalShape());
translator.setChildren(new Translator[] {
IDTranslator.INSTANCE,
new Translator("x", ModelPackage.eINSTANCE.getShape_X(),
Translator.DOM_ATTRIBUTE),
new Translator("y", ModelPackage.eINSTANCE.getShape_Y(),
Translator.DOM_ATTRIBUTE),
new Translator("width",
ModelPackage.eINSTANCE.getShape_Width(),
Translator.DOM_ATTRIBUTE),
new Translator("height", ModelPackage.eINSTANCE
.getShape_Height(), Translator.DOM_ATTRIBUTE) });
return translator;
}
}
Voici les explications de ce code :
GenericTranslator
Etudions dans un premier temps le mapping rectangle avec le meta object RectangularShape. Le mapping ellipse fonctionne de la même manière. Le mapping de rectangle est effectué dans la méthode createRectangleTranslator :
GenericTranslator translator = new GenericTranslator("rectangle",
ModelPackage.eINSTANCE.getRectangularShape());
translator.setChildren(new Translator[] {
IDTranslator.INSTANCE,
new Translator("x", ModelPackage.eINSTANCE.getShape_X(),
Translator.DOM_ATTRIBUTE),
new Translator("y", ModelPackage.eINSTANCE.getShape_Y(),
Translator.DOM_ATTRIBUTE),
new Translator("width",
ModelPackage.eINSTANCE.getShape_Width(),
Translator.DOM_ATTRIBUTE),
new Translator("height", ModelPackage.eINSTANCE
.getShape_Height(), Translator.DOM_ATTRIBUTE) });
return translator;
}
GenericTranslator est une implementation de Translator qui gère un mapping simple entre un element XML et un meta object EClass. Le code :
ModelPackage.eINSTANCE.getRectangularShape());
permet de mapper l’element XML rectangle avec le meta object EClass RectangularShape. L’attribut XML width est mappé avec le meta object EAttribute getShape_Width. Ceci s’effectue à l’aide du code :
Translator.DOM_ATTRIBUTE)
Les autres attributs XML sont mappés de la même manière.
MultiObjectTranslator
Un diagrame ShapesDiagram est constitué d’une liste de Shape ou un Shape peut etre un RectangularShape OU EllipticalShape. J’ai passé un temps fou à découvrir comment gérer le cas des listes. Et j’ai miraculeusement trouvé un exemple avec EnterpriseBeansTranslator.
Pour gérer ceci, il faut utiliser MultiObjectTranslator qui permet de déleguer l’instance translator à utiliser :
- en fonction d’un nom d’element XML. Ce cas ci est géré par la méthode MultiObjectTranslator#getDelegateFor(java.lang.String, java.lang.String)
- en fonction d’un meta object EClass géré par la méthode MultiObjectTranslator#getDelegateFor(org.eclipse.emf.ecore.EObject)
Dans notre cas si on reçoit le nom XML rectangle on doit retourner le translator RECTANGLE_TRANSLATOR qui gere les rectangles et si on reçoit le EClass RectangularShape on doit retourner aussi le translator RECTANGLE_TRANSLATOR.
Les methodes delegate se base sur des instances static RECTANGLE_TRANSLATOR et ELLIPSE_TRANSLATOR pour éviter qu’à chaque appel des méthodes delegate, une instanciation d’un translator s’effectue.
Le mapping s’effectue comme suit :
Autrement dit on indique que l’élement XML shapes est mappé avec le méta object ShapesDiagram_Shapes. Cette liste (EList) peut contenir des instances de RectangularShape et de EllipticalShape. Pour gérer ceci, on écrit le path suivant :
Le chemin shapes indique que l’element XML shapes est mappé avec le meta object ShapesDiagram_Shapes. Il contient des element XML ellipse ou rectangle que l’on sépare avec le caractère ,.
Articles récents
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step5]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step4]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step3]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step2]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step1]
Commentaires récents
- Conception d’un Editeur Eclipse de workflow XML [step 0] dans
- Conception d’un Editeur Eclipse de workflow XML [step 19] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans