février
2009
Dans ce billet nous allons expliquer le code du projet DOM-SSE Shapes. Plus exactement nous détaillerons le code de la classe ShapesEditor qui est un EditortPart Eclipse qui utilise un DOM-SSE. Il est conseillé de bien lire le billet précedant pour comprendre comment fonctionne DOM-SSE.
Vous pouvez télécharger le projet org.eclipse.wst.xml.examples.shapes_1.0.0.zip expliqué dans ce billet.
Structure du projet
Ce projet est constitué des classes suivantes :
- ShapesDOMSSEPlugin : classe plugin du projet.
- ShapesCreationWizard : classe qui permet de gérer le wizard de création d’un fichier XML Shapes shapesDiagram*.xml.
- ShapesEditor EditorPart eclipse qui utilise le DOM-SSE pour gérer le fichier XML shapesDiagram*.xml.
- DOMUtils : classe utilitaire de gestion d’un DOM w3c.
- ShapesDiagramUtils : classe utilitaire de gestion du DOM w3c qui contient les informations (titre du diagramme, …) d’un fichier XML shapesDiagram*.xml.
ShapesEditor
La classe ShapesEditor est un éditor Eclipse classique qui éténd la classe org.eclipse.ui.EditorPart.
Chargement DOM-SSE – IDOMModel
La classe ShapesEditor contient une variable model qui est le DOM-SSE :
private IDOMModel model;
Cette variable doit être intialisée à l’aide du fichier sélectionné IFile lors de l’ouverture de l’éditor. Lorsqu’un fichier XML shapesDiagram*.xml est édité avec ShapesEditor, la méthode EditorPart#init(IEditorSite site, IEditorInput input) est appelée et vérifie que IEditorInput est un IFileEditorInput (qui permet de récupérer un IFile). Cette méthode appelle dans notre cas la méthode EditorPart#setInput(IEditorInput input) :
throws PartInitException {
if (!(input instanceof IFileEditorInput))
throw new PartInitException("Invalid Input: Must be IFileEditorInput");
setSite(site);
setInput(input);
setPartName(input.getName());
}
}
C’est dans la méthode ShapesEditor#setInput(IEditorInput input) que nous allons récupérer le DOM-SSE à partir du IFile récupéré du IFileEditorInput et initiliser la variable IDOMModel model :
super.setInput(input);
// Force the load of IDOMMOdel on Editor load
getDOMModel();
}
private IDOMModel getDOMModel() {
if (model == null) {
IFile file = ((IFileEditorInput) super.getEditorInput()).getFile();
try {
model = getDOMModel(file);
} catch (Exception e) {
throw new RuntimeException("Invalid Input: Must be DOM", e);
}
}
return model;
}
Cette méthode appelle la méthode privée ShapesEditor#getDOMModel(IFile file) qui retourne le DOM-SSE à partir du file IFile. :
IModelManager manager = StructuredModelManager.getModelManager();
IStructuredModel model = manager.getExistingModelForEdit(file);
if (model == null) {
model = manager.getModelForEdit(file);
}
if (model == null) {
throw new Exception(
"DOM Model is null, check the content type of your file (it seems that it's not *.xml file)");
}
if (!(model instanceof IDOMModel)) {
throw new Exception("Model getted is not DOM Model!!!");
}
// Add listener to observe change of DOM (change is done with another editor).
model.addModelStateListener(listener);
return (IDOMModel) model;
}
Cette méthode récupère dans un premier temps le modèle SSE en mode édition :
IStructuredModel model = manager.getExistingModelForEdit(file);
if (model == null) {
model = manager.getModelForEdit(file);
}
Dans un premier temps on tente de récupérer un modèle DOM-SSE existant (getExistingModelForEdit) (récupéré par un un autre éditeur (ex : Editeur XML)) en mode édition. Si le modèle est null, on appelle la méthode getModelForEdit. Pour plus d’information sur le mode Lecture/Edition, veuillez consulter la section getModel*ForRead OU getModel*ForEdit?.
puis teste si le DOM-SSE n’est pas null (dans le cas ou le content type est inconnu) :
throw new Exception(
"DOM Model is null, check the content type of your file (it seems that it's not *.xml file)");
}
puis vérifie que le modèle SSE est bien un DOM-SSE :
throw new Exception("Model getted is not DOM Model!!!");
}
Observation DOM- IModelStateListener
Dans le cas ou le DOM-SSE a bien pu être récupére, on ajoute un listener IModelStateListener pour observer les changements du DOM. Ce listener permettra de synchronizer l’interface SWT qui utilise le DOM avec n’importe quel changement du DOM qui sont peuve être effectué via cet éditeur mais via d’autres editeurs comme celui de l’editeur XML.
model.addModelStateListener(listener);
La variable listener est de type IModelStateListener et est initialisé comme suit :
..
public void modelChanged(IStructuredModel model) {
// UPdate UI From DOM Model which have changed
updateUIFromDOMModel();
}
public void modelDirtyStateChanged(IStructuredModel model,
boolean isDirty) {
// dirty from DOM Model has changed (the XML content was changed
// with anothher editor), fire the dirty property change to
// indicate to the editor that dirty has changed.
firePropertyChange(IEditorPart.PROP_DIRTY);
}
...
};
Les 2 méthodes qui nous intéressent sont :
- IModelStateListener#modelChanged(IStructuredModel model) : pour détecter que le model SSE est modifié, autrement dit que le DOM change. Cette méthode appele ShapesEditor#updateUIFromDOMModel() qui permet de modifier l’UI SWT à partir du DOM.
- IModelStateListener#modelDirtyStateChanged(IStructuredModel model, boolean isDirty) : pour détecter que le model SSE est dirty. Cette méthode permet de gérer l’état dirty de l’editor ShapesEditor.
Il est important de supprimer ce listener du modèle SSE lorsque l’editor ShapesEditor est fermé. Pour cela on supprime ce listener dans la méthode EditorPart#dispose() :
...
this.model.removeModelStateListener(listener);
...
}
ShapesEditor – dirty
L’état dirty de l’editor utilise l’état dirty du DOM-SSE, en implémentant la méthode EditorPart#isDirty() comme ceci :
return model.isDirty();
}
Le contenu XML du DOM peut être modifié via un autre editeur (comme l’editeur XML) et doit donc mettre à jour l’état dirty de ShapesEditor. Le listener IModelStateListener permet de gérer ce cas-ci. La méthode IModelStateListener#modelDirtyStateChanged est déclenché lorsque le DOM est modifié. Son implémentation appele EditorPart#firePropertyChange(IEditorPart.PROP_DIRTY) de de ShapesEditor qui permet de notifier l’editeur que son état dirty doit être modifié. L’editeur ShapesEditor appelle ensuite sa méthode EditorPart#isDirty() pour vérifier que l’editor est en dirty ou non.
DOM To UI – ShapesEditor#updateUIFromDOMModel()
L’interface UI SWT doit être mise à jour à en utilisant le DOM à l’initialisation de l’editor et lorsque le DOM se modifie. Cette synchronisation DOM -> UI s’effectue dans la méthode updateUIFromDOMModel qui est appelé dans :
- ShapesEditor#createPartControl(Composite parent) : qui permet de mettre à jour l’UI SWT à partir du DOM chargé lors de l’ouverture de l’editor (initialisation).
- IModelStateListener#modelChanged(IStructuredModel model) : qui permet de mettre à jour l’UI SWT à partir du DOM modifié.
Les 2 widgets utilisés dans ShapesEditor sont :
- une widget SWT Text : cette widget est lié à l’attribut title de l »element racine di DOM
- une widget SWT Table : cette widget est lié à la liste d’elements shapes
Nous allons nous intéresser au cas de la widget SWT Text. Voici le code de updateUIFromDOMModel qui permet de mettre à jour la widget SWT Text (titleText) avec l’attribut title :
...
String titleFromUI = titleText.getText();
String titleFromDOM = ShapesDiagramUtils.getDiagram_Title(document);
if (!titleFromUI.equals(titleFromDOM)) {
titleText.setText(titleFromDOM);
}
ShapesDiagramUtils.getDiagram_Title(document) est simplement une méthode utilitaire qui permet de naviguer dans le DOM w3c. Son code est le suivant :
if (root == null)
return "";
String title = root.getAttribute("title");
return (title != null ? title : "");
UI To DOM
Lorsque l’UI se modifie, elle doit mettre à jour le DOM. Par exemple la widget Text (titleText) doit mettre à jour le DOM lorsque l’utilisateur saisit du texte. Ceci s’effecue en ajoutant un listener ModifyListener à la widget :
public void modifyText(ModifyEvent e) {
// Get DOM Document from DOM Model.
Document document = model.getDocument();
// 1. Set that DOM will be change (no events will be fire although DOM content change)
model.aboutToChangeModel();
// 2. Modify DOM atrtribute value
ShapesDiagramUtils.setDiagram_Title(document, titleText.getText());
// 3. Set that DOM has been changed
model.changedModel();
}
});
La méthode ShapesDiagramUtils#setDiagram_Title est simplemenent une méthode utilitaire qui permet de mettre à jour le DOM :
if (root != null) {
root.setAttribute("title", title);
}
Comme vous pouvez le constater les méthodes IStructuredModel#aboutToChangeModel et IStructuredModel#changedModel ont été utilisées. Mais dans ce cas-ci elle ne sont pas obligatoires car UNE seule modification du DOM est effectuée (modification de l’attribut title).
ShapesEditor#doSave*
La sauvegarde de l’editor ShapesEditor s’effecte en implémentatnt les méthodes doSave et doSaveAs. Ces méthodes utilisent les méthodes save de IStructuredModel. Voici la méthode doSave :
try {
model.save();
} catch (Exception e) {
e.printStackTrace();
}
}
Le code reste tres simple. Après avoir sauvegardé l’editor ShapesEditor, son état dirty doit passer à false. Cette gestion du dirty n’est pas effectuée dans la méthode doSave. En effet c’est le DOM model qui s’occupe de ca, car la méthode IDOMModel#save() s’occupe de sauvegarder le DOM, mais s’occupe aussi de gérer l’état dirty (en déclanchant un evenemement).
ShapesEditor#dispose
La méthode ShapesEditor#dispose est appelé lorsque l’éditeur est fermé. Son implémentation permet de réinitialiser le modèle DOM-SSE correctement. Voici son code :
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
IStructuredModel model = getDOMModel();
// Remove listener
model.removeModelStateListener(listener);
if (model.isDirty()) {
// The DOM is changed and this editor was closed without save it
model.releaseFromEdit();
}
super.dispose();
}
Le code :
model.removeModelStateListener(listener);
permet de supprimer le listener IModelStateListener lorsque l’éditeur ShapesEditor n’est plus utilisé (fermeture de l’éditeur). Ce code est très important, car la plupart des éditeur de WST récupère le modèle SSE existant à l’aide des méthodes getExistingModel*. Dans ce cas-ci, on peut considérer que le modèle SSE est un singleton partagé par plusieurs éditeurs. Il faut donc s’assurer que notre listener soit supprimé lorsque l’on en plus besoin.
Le code :
// The DOM is changed and this editor was closed without save it
model.releaseFromEdit();
}
permet de gérer le cas ou le DOM-SSE est en dirty (il a été modifié) et que l’utilisateur quitte l’éditeur sans sauvegarde. Dans ce cas-ci il faut annuler les modifications du DOM en appelant la méthode IStructuredModel#releaseFromEdit() qui permet d’annuler toutes les modifications du modèle SSE qui a été récupéré en mode édition.
getModel*ForRead OU getModel*ForEdit?
Dans notre cas les méthodes getModel*ForEdit qui permettent de récupérer un modèle SSE en mode édition sont utilisées et pas getModel*ForRead qui permettent de récupérer un modèle SSE en mode lecture car nous souhaitons modifier le DOM. J’ai beaucoup bataillé sur ces méthodes, car il faut savoir que si vous récupérez un modèle SSE en mode lecture (getModel*ForRead), il est quand même possible de sauvegarder le DOM à l’aide de la méthode IStructuredModel#save().
Donc pourquoi ne pas utiliser les méthodes getModel*ForRead dans le cas de ShapesEditor? Après avoir effectué de nombreux tests, la réponse se trouve dans la méthode dispose de ShapesEditor (voir explication ci-dessus) :
...
if (model.isDirty()) {
// The DOM is changed and this editor was closed without save it
model.releaseFromEdit();
}
super.dispose();
}
Il existe effectivement la méthode IStructuredModel#releaseFromRead(), mais qui n’a aucun effet si le modèle SSE n’a pas été récupéré en mode édition? La je ne saurrais pas vous donner de réponse à ce comportement.
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