Tutoriel : À la découverte d'EMF.Edit

Avec EMF, l'Eclipse Modeling Framework, il est possible de générer du code rapidement pour un modèle donné. EMF ne se contente pas seulement de générer le code des classes, mais aussi d'instrumenter ce code. Grâce à cette instrumentation, il est possible de construire des frameworks de plus haut niveau, indépendants de la logique métier derrière un modèle. Parmi ces frameworks, EMF.Edit permet de générer et d'utiliser un ensemble de classes pour la visualisation et l'édition des modèles EMF. Cet article se propose de donner un aperçu des mécanismes de base de ce framework. Les sources de cet exemple sont disponibles à l'adresse suivante : FTPftp-sources ou HTTPhttp-sources.

Pour toute remarque ou question sur ce tutoriel, profitez de cette discussion : Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

EMF, Eclipse Modeling Framework, permet de générer, un peu comme le ferait un générateur de code UML, une architecture de classes qui représentent un modèle métier. EMF ne se contente pas de générer seulement les classes Java, mais aussi toute une infrastructure associée. Ainsi, on bénéficiera par exemple de la persistance du modèle au format XMI, mais aussi d'un ensemble d'outils pour interroger le modèle de manière totalement indépendante des objets qu'il contient. Cette infrastructure permet de construire des outils de plus haut niveau pour traiter les modèles créés avec EMF. Au sein de ce framework, une des fonctionnalités est notamment la visualisation et l'édition de modèles grâce au framework EMF.Edit. Cet article se propose d'expliquer les mécanismes de base d'EMF.Edit et comment les adapter à des besoins propres. Nous verrons ainsi comment visualiser très rapidement l'intégralité d'un modèle et comment le modifier de manière simple.
Pour suivre cet article, il est indispensable de connaître comment créer et modifier un modèle EMF. Vous pouvez pour cela consulter les articles suivants :

Dans cet article, j'ai utilisé Eclipse Luna équipé des outils « Eclipse Modeling Framework SDK ».
Nous allons suivre les étapes suivantes :

  • Création du modèle EMF et options de génération ;
  • Explication des mécanismes d'EMF.Edit pour la visualisation du modèle ;
  • Explication des mécanismes d'EMF.Edit pour l'édition du modèle ;
  • Databinding sur les propriétés des objets pour la construction d'IHM.

II. Création de l'infrastructure

II-A. Création du modèle EMF

La première étape est évidemment de créer un modèle EMF. Nous allons nous baser dans cet article sur un exemple simple, déjà utilisé dans l'article sur Sirius. Ce modèle représente les liaisons aériennes qui existent entre différents aéroports. Le modèle contiendra donc un certain nombre d'aéroports définis par un nom, une ville et un pays. Ces aéroports ont un ensemble de portes identifiées par un numéro. Ces portes peuvent référencer une porte d'un autre aéroport, représentant ainsi une liaison entre deux aéroports.
Créez un nouveau projet de type « Ecore Modeling Project » avec l'ID « com.abernard.aiports ». Sur la deuxième page, le nom du package créé doit être « airports » et enfin sur la troisième page choisissez le viewpoint « Design » pour la création du diagramme. Cela va créer un fichier .ecore, un fichier .genmodel et ouvrir l'éditeur de diagrammes EMF. Créez ensuite chacun des éléments du modèle pour obtenir le diagramme suivant :

Image non disponible
Diagramme du modèle EMF

II-B. Première génération

À partir du fichier *.genmodel, lancez la génération du plugin de modèle, ainsi que des plugins « Edit » et « Editor ». Eclipse va générer le code de toutes les classes ainsi que les plugins « com.abernard.airports.edit » et « com.abernard.airports.editor ». À partir de ce moment, vous pouvez lancer une instance d'Eclipse via le plugin « *.editor » et dès à présent créer un petit projet contenant un exemple d'instance de votre modèle

II-C. Création de l'infrastructure

Afin de tester les fonctionnalités d'EMF.Edit, nous allons créer une interface toute simple pour afficher notre modèle et l'éditer dans le plugin « com.abernard.airports.editor ». Afin de tirer parti des mécanismes Eclipse, nous allons utiliser un éditeur, plus spécifiquement un « FormEditor ». Ce type d'éditeur affiche des pages de type « Form », à la manière de l'éditeur de fichier « plugin.xml ». Il définit aussi un certain nombre d'implémentations par défaut afin d'éviter d'avoir à implémenter toutes les méthodes de l'interface « IEditorPart ». Pour cela, n'oubliez pas la dépendance au bundle « org.eclipse.ui.forms » dans le MANIFEST.MF :

Image non disponible

Puis créez un éditeur qui hérite de « FormEditor » dans le point d'extension « org.eclipse.ui.editors ».

Image non disponible

Dans cet éditeur, créez une unique page de type « FormPage » qui contiendra notre IHM. Vous pouvez utiliser les codes suivants afin de mettre en place ces interfaces vides.

AirportsFormEditor.java
CacherSélectionnez
AirportsFormEditorPage.java
CacherSélectionnez

Si vous lancez votre application et ouvrez votre modèle avec ce nouvel éditeur, vous verrez une interface vide que nous allons utiliser par la suite.

Notre éditeur, vide
Notre éditeur, vide

III. Visualisation de notre modèle

III-A. Visualisation « par défaut »

Nous allons maintenant visualiser notre modèle avec les outils de visualisation d'EMF.Edit par défaut. Regardons de plus près le code présent dans notre plugin « *.edit ». Une des classes se nomme « AirportsEditPlugin », elle ne présente pas d'intérêt intrinsèque : il s'agit de l'Activator du plugin.
Un ensemble de classes nommées « *ItemProvider » sont aussi générées. Ces classes peuvent être utilisées pour afficher les éléments du modèle dans des composants JFace via un mécanisme de délégation : les adapters. Un adapter permet de « faire comme si » un objet de type A était un objet de type B. Pour regrouper toutes ces classes et fournir la bonne implémentation à la demander, une dernière classe est générée : « AirportsItemProviderAdapterFactory ». Elle génère les classes mentionnées précédemment au besoin.
Toutes ces classes contiennent du code généré, qui doit être considéré comme tel : il est inutile de le modifier et nous verrons plus tard comment le faire de manière adaptée. Ce qu'il faut retenir, c'est que ces classes vont nous éviter beaucoup de codage manuel ! Voyons ça dans un exemple.

Nous voulons visualiser notre modèle dans un arbre, donc un TreeViewer. La première étape est de charger notre modèle à partir de notre fichier .airports. Dans ce premier exemple, nous allons utiliser des mécanismes bas niveau d'EMF. Enrichissez la classe « AirportsFormEditor » comme suit :

AirportsFormEditor.java
CacherSélectionnez

Dans notre page, il suffit ensuite de créer un TreeViewer qui va nécessiter un « IContentProvider » ainsi qu'un « IBaseLabelProvider ». Si nous travaillions sur des objets classiques, nous devrions écrire à la main toutes les méthodes pour parcourir de manière hiérarchique notre modèle ainsi que la manière d'afficher les éléments. EMF.Edit va ici entrer en jeu. La première étape est d'instancier la classe conteneur « AirportsItemProviderAdapterFactory », par exemple dans le constructeur de la classe. Puis nous pouvons utiliser cet objet pour instancier deux classes : « AdapterFactoryContentProvider » et « AdapterFactoryLabelProvider ». Ces classes utilisent notre « adapter factory » afin d'indiquer à notre composant comment afficher notre modèle. C'est tout. Modifiez le code de la classe « AirportsFormEditorPage » de la manière suivante et observez le résultat!

AirportsFormEditorPage.java
CacherSélectionnez
Visualisation de notre modèle
Visualisation de notre modèle

Remarquez qu'en instanciant simplement notre « adapter factory », nous avons pu l'utiliser pour afficher notre modèle très simplement dans l'arbre !

III-B. Les options de génération

Tous les éléments que nous avons utilisés dans ce paragraphe ont été générés automatiquement dans le plugin *.edit de notre application. Vous pouvez évidemment modifier les options de génération pour personnaliser ces éléments. Ces options doivent être modifiées dans le fichier « airports.genmodel ». Les options plus générales sont définies dans l'élément racine du fichier, comme le montre la capture d'écran ci-dessous :

Options générales d'édition
Options générales d'édition

Ces options vous permettent notamment de définir les différents « providers » JFace à générer. Par exemple, la propriété « Stylde Providers » vous permettra, après génération, d'accéder aux méthodes « getStyledText » afin d'afficher des styles sur vos labels. L'option « Table providers », elle, vous permettra de spécifier le nom des colonnes dans un tableau.

Si vous sélectionnez des éléments du modèle, vous pouvez définir des propriétés pour chacun, comme le fait d'associer une image aux éléments ou l'attribut à utiliser par défaut pour afficher les éléments. Un autre élément intéressant est le type de provider : cette propriété peut être définie comme « singleton » ou « stateful ». Avec un provider de type « singleton », un seul provider sera instancié et utilisé pour toutes les instances d'un objet de votre modèle :

Singleton pattern
Singleton pattern

À l'inverse, avec un provider « stateful », un provider sera instancié pour chaque instance des objets de votre modèle :

Stateful pattern
Stateful pattern

III-C. Modifier la visualisation

Voir son modèle rapidement c'est bien, adapter l'affichage pour le mettre à son goût c'est mieux ! Dans ce paragraphe, nous allons modifier la manière dont les éléments de notre modèle sont affichés : le label pour les aéroports affichera directement la ville et le pays, et lorsque des portes sont reliées à d'autres portes, cette information sera elle aussi directement visible. Une première solution consiste à modifier directement le code des providers afin de modifier la méthode « getText() ». Pour peu qu'on supprime l'annotation « @generated », EMF ne remplacera pas le code écrit à la main. Pour autant, une bonne pratique consiste à traiter le code généré comme s'il s'agissait de bytecode. L'astuce consiste donc à utiliser le design pattern « Decorator ».

Cette méthode est directement issue d'une présentation faite à différentes EclipseCon par Mikaël Barbero dont vous trouverez le lien en fin d'article.

Nous pouvons donc créer un dossier de source « src-dec » dans notre plugin « com.abernard.airports.edit ».

N'oubliez pas de modifier le fichier « build.properties » afin d'ajouter le dossier « src-dec » aux dossiers sources.

Nous allons commencer par créer un décorateur générique pour tous nos éléments, que nous allons appeler « ItemProviderAdapterDecorator » :

ItemProviderAdapterDecorator.java
CacherSélectionnez

Puis nous créons tout d'abord un décorateur « vide » : il servira à rediriger l'appel de toutes les méthodes vers leur implémentation par défaut pour tous les éléments que nous n'allons pas modifier.

ForwardingItemProviderAdapterDecorator.java
CacherSélectionnez

Nous pouvons ensuite créer les deux décorateurs pour nos objets « Airport » et « Gate ». Ces deux classes se contentent de redéfinir l'implémentation de la méthode « getText() ».

AirportItemProviderDecorator.java
CacherSélectionnez
GateItemProviderDecorator.java
CacherSélectionnez

Enfin, il ne reste plus qu'à agréger tous ces éléments au sein d'une « adapter factory » qui réutilisera évidemment la classe par défaut « AirportsItemProviderAdapterFactory » :

WorldMapDecoratorAdapterFactory.java
CacherSélectionnez

Il ne nous reste plus qu'à observer le résultat. Dans la classe « AirportsFormEditorPage », il suffit de modifier l'instanciation de l'« adapter factory » avec notre nouvelle classe (et de modifier le type de l'attribut en conséquence).

 
Sélectionnez
// ...
this.adapterFactory = new WorldMapDecoratorAdapterFactory();
// ...
Nouveaux labels
Nouveaux labels

On l'a vu, le système des « adapter factories » permet d'afficher rapidement les éléments d'un modèle. Mais on peut aller encore plus loin, en combinant plusieurs factories en une seule pour afficher au sein d'un même viewer plusieurs modèles EMF différents. Pour cela on passe par une classe spécifique, la « ComposedAdapterFactory ». Une fois instanciée, on peut lui ajouter toutes celles que l'on veut traiter. Par exemple, dans l'éditeur généré par défaut par EMF, on a le code suivant dans la méthode « initializeEditingDomain() » :

 
Sélectionnez
// ...
protected void initializeEditingDomain() {
    // Create an adapter factory that yields item providers.
    //
    adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);

    adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
    adapterFactory.addAdapterFactory(new AirportsItemProviderAdapterFactory());
    adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
    // ...
}

C'est ce mécanisme qui permet entre autres à l'éditeur par défaut de combiner l'affichage de la « Resource » contenant le modèle avec le modèle lui-même. Notez que si vous voulez accéder à une adapter factory capable d'afficher n'importe quel modèle disponible dans votre plateforme courante, vous pouvez utiliser l'instruction suivante :

 
Sélectionnez
ComposedAdapterFactory composedAdapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
Composed Factory
Composed Factory

IV. Édition du modèle

Un des énormes avantages d'EMF.Edit est que le framework introduit des outils pour faciliter la manipulation et l'édition des modèles. Ces outils sont principalement la « CommandStack » et « l'EditingDomain ». Le premier consiste en une « pile de commandes » littéralement qui sont stockées lors de leur exécution, afin d'être éventuellement annulées par la suite. Elles sont principalement constituées de trois catégories principales : les commandes de type « Add » (pour ajouter des éléments à un modèle), les commandes de type « Remove » (pour supprimer des éléments du modèle) et enfin des commandes de type « Set » (pour modifier des attributs du modèle). Ces commandes peuvent être combinées au sein de commandes appelées « CompoundCommand ». L'objet « EditingDomain » quant à lui permet d'accéder au modèle afin de l'éditer. Il permet d'instancier les commandes citées précédemment, de les exécuter et enfin de faciliter le chargement et la sauvegarde du modèle. Nous allons voir comment mettre en place et utiliser ces éléments sur notre modèle. La première étape consiste à initialiser l'EditingDomain et la CommandStack dans notre éditeur, puis à s'en servir pour charger notre modèle. Nous allons remplacer la méthode « loadModel » par deux autres, « initializeEditingDomain » et « loadModelWithDomain ». Toutes ces méthodes sont donc dans la classe « AirportsFormEditor.java ».

AirportsFormEditor.java
CacherSélectionnez

Dans le code que nous avons modifié, plusieurs points importants sont à noter. Intéressons-nous tout d'abord à la méthode « initializeEditingDomain ». Nous utilisons une implémentation par défaut de l'interface « CommandStack », « BasicCommandStack » qui suffira à la plupart des besoins. De la même manière, nous utilisons pour l'interface « EditingDomain » une implémentation standard « AdapterFactoryEditingDomain ». Cette implémentation utilise les adapter factories que nous avons mentionnés dans les paragraphes précédents pour accéder aux éléments du modèle via les « *ItemProvider ». Nous ajoutons aussi un « CommandStackListener » afin d'informer le workbench à chaque fois qu'une commande est exécutée. Cela permettra la sauvegarde du modèle à chaque fois qu'il est modifié.
Lorsque la propriété « PROP_DIRTY » est modifiée, le workbench appelle la méthode « isDirty » sur l'éditeur courant. Il faut donc aussi que nous modifiions cette fonction. Dans un FormEditor, nous pouvons déjà utiliser l'implémentation par défaut qui vérifie si un des onglets de l'éditeur est « dirty ». Nous ajoutons à cela un test sur notre CommandStack qui peut nous indiquer si des commandes ont été exécutées sur notre modèle depuis son état initial.
Enfin lorsque nous effectuons la sauvegarde de notre modèle, il faut aussi penser à informer la CommandStack que la sauvegarde a été effectuée. Cela permet de faire en quelque sorte un « reset » sur cet élément. Une fois que ces mécanismes sont mis en place, il ne nous reste plus qu'à modifier notre interface pour ajouter des commandes. Nous allons tester les trois commandes au sein de trois actions simples. Pour cela, nous devons enrichir notre classe « AirportsEditorFormPage » pour l'ajout des boutons idoines.

AirportsFormEditorPage.java
CacherSélectionnez

Nous avons ajouté trois boutons pour ajouter un aéroport, en supprimer et remplacer le nom par « Toto ». Tout d'abord la commande « Add ». Si on regarde la syntaxe de la commande, rien de transcendant. On communique l'EditingDomain afin que la commande puisse manipuler ledit modèle, le modèle en question, le nom de la « feature » à laquelle ajouter le nouvel élément ainsi que le nouvel élément créé. On peut s'en douter, cette commande va ajouter le nouvel élément créé à la fin de la liste « airports » de notre modèle. Puis, au lieu d'appeler simplement la méthode « execute » sur la commande, on passe par la CommandStack. Utiliser ces mécanismes a de multiples avantages :

  • on peut annuler l'action en utilisant simplement « editingDomain.getCommandStack().undo() » ;
  • on peut vérifier si un modèle doit être sauvegardé (rappelez-vous la méthode « isSaveNeeded » que nous avons utilisée dans la méthode « isDirty » de notre éditeur) ;
  • on peut enfin utiliser l'instruction « myCommand.canExecute() » pour s'assurer que le contexte courant permet l'exécution d'une commande.

Notez que pour personnaliser le comportement des commandes, on peut écrire des sous-classes de toutes les commandes définies par le framework.

Les deux autres commandes fonctionnent exactement de la même manière et leur fonctionnement est aisément compréhensible. On peut vérifier le bon fonctionnement de ces commandes, et le fait que notre éditeur peut bien être sauvegardé lorsqu'on les a utilisées.

Test de la commande 'Add'
Test de la commande 'Add'

La cerise sur le gâteau est que l'utilisation de ces mécanismes (commandes et EditingDomain) va nous permettre de mettre en place des commandes de type « Undo/Redo » ou « Copy/Paste » simplement. Pour cela, il suffit de créer une barre d'outils d'éditeur qui est une sous-classe de « EditingDomainActionBarContributor » (pour rappel, cela se fait dans le champ « contributorClass » de votre éditeur dans le fichier « plugin.xml »). Cette classe active ses propriétés dès lors que l'éditeur courant implémente l'interface « IEditingDomainProvider ». On peut dès lors profiter de ces mécanismes sans autre action supplémentaire !

Pour que ces commandes fonctionnent correctement, il est impératif que l'arbre soit enregistré comme fournisseur de sélection, grâce à l'instruction « getSite().setSelectionProvider(viewer); ».

Comme d'habitude, on peut modifier le comportement de la classe, par exemple, on peut activer très simplement la validation de notre modèle en ajoutant dans le code la commande de validation. Les contraintes basiques des objets de notre modèle sont alors validées (comme dans l'éditeur Ecore).

FormEditorActionBarContributor.java
CacherSélectionnez

Enfin, si on veut bénéficier de commandes plus avancées, comme le menu contextuel pour la création d'éléments mis en place dans l'éditeur généré, il suffit de réutiliser ou sous-classer la classe générée « AirportsActionBarContributor ».

V. Databinding avec EMF.Edit

V-A. Mise en place

Nous savons maintenant comment afficher les éléments contenus dans notre modèle et éventuellement les modifier. Néanmoins nous aurons vite le besoin ou l'envie de construire des interfaces plus spécifiques pour modifier les propriétés avancées. Nous allons voir dans ce paragraphe comment éditer les propriétés des objets EMF via le databinding. Ce mécanisme consiste à lier directement les valeurs affichées dans l'IHM aux valeurs contenues dans le modèle. Lorsque le modèle est modifié, l'affichage l'est aussi directement et vice-versa. Cela évite de passer par les classiques, mais fastidieux mécanismes de listeners, etc. Le databinding peut être réalisé sur des objets classiques, cf. ce tutorielDatabinding avec SWT. Nous allons créer un élément de type MasterDetails pour afficher les détails des éléments sélectionnés dans l'arbre. Pour cela, il faut de nouveau compléter la vue « AirportsFormEditorPage ».

AirportsFormEditorPage.java
CacherSélectionnez

Ce code supplémentaire crée le composant MasterDetails, il ne nous reste plus qu'à créer une page de détails pour nos objets « Airport ».

AirportDetailsPage.java
CacherSélectionnez

Dans cette classe, intéressons-nous aux méthodes « selectionChanged » et « createDatabinding » (le reste ne constitue que de l'interface pure et ne présente pas d'intérêt). La première encapsule l'élément sélectionné dans l'arbre dans un objet de type « WritableValue » qui va permettre de modifier et lire les propriétés à observer et va déclencher les événements lorsque ces valeurs sont modifiées (soit par le modèle, soit par l'interface). L'objet IObservableValue va notamment éviter des NullPointerException dans tous les sens si jamais l'objet du modèle est « null » et que les champs de l'interface ne peuvent donc rien afficher.
La deuxième méthode va lier chaque champ texte à une propriété EMF. Cela se fait en utilisant les méthodes statiques de la classe EMFEditProperties, qui évidemment va utiliser un objet EditingDomain. Cela va permettre directement de bénéficier de la sauvegarde et de l'« Undo/Redo » ! Notez aussi l'emploi de la méthode « observeDelayed » avec un temps en millisecondes : au lieu de modifier le modèle à chaque fois que le champ texte est modifié, un timer sera utilisé (ici 200 ms). Cela évite un trop grand nombre d'événements lorsque l'utilisateur est en train de taper son texte. Avec ces mécanismes, on peut vite vérifier que notre modèle est bien mis à jour lorsqu'on modifie une des trois valeurs.

Databinding simple
Databinding simple

V-B. Validation

Évidemment, l'intérêt d'une interface est aussi d'empêcher l'utilisateur de rentrer des valeurs abracadabrantes, pour cela on peut évidemment réutiliser les mécanismes de validation du databinding dans Eclipse sur nos éléments. Par exemple nous allons stipuler que chacune des trois données doit commencer par une majuscule. Nous utilisons donc un objet spécifique de type « UpdateValueStrategy » pour valider la donnée entrée par l'utilisateur avant qu'elle ne soit communiquée au modèle.

AirportDetailsPage.java
CacherSélectionnez

Notez l'utilisation très directe des objets « ControlDecorationSupport » qui vont afficher sur les champs le texte d'erreur ainsi qu'un petit marqueur visuel. On peut donc tester notre composant et vérifier que le modèle n'est pas modifié si nos valeurs ne commencent pas par une majuscule. Sur la capture ci-dessous, on voit bien le champ invalide avec la notification, on constate aussi que l'arbre n'est pas modifié (on a rentré « paris », mais le modèle contient toujours la valeur « Paris ») et l'éditeur n'est pas marqué comme « dirty ».

Databinding avec validation
Databinding avec validation

VI. Conclusion et perspectives

Dans cet article nous avons eu une première approche du framework EMF.Edit et j'espère qu'avec ces informations, vous aurez les éléments en main pour construire des interfaces autour de vos modèles EMF. Les mécanismes de ce framework permettent de mettre en œuvre rapidement une IHM avec tous les éléments nécessaires pour l'utilisateur final avec un minimum de code : validation des données, commandes classiques d'« Undo/Redo » ou de gestion du presse-papiers. En utilisant ce framework, on peut aussi aller encore plus loin et directement créer des interfaces personnalisées sans écrire de code, grâce à des outils comme EMFForms, EMF Editing Framework ou encore EMFParsley. Ces outils feront peut-être l'objet de prochains articles ! En attendant, si vous pensez qu'un diagramme représenterait mieux notre modèle, vous pouvez jeter un œil à mon article sur SiriusTutoriel sur Eclipse Sirius. Enfin, l'éditeur généré par défaut ainsi que la barre d'outils associée, même s'ils peuvent être verbeux, sont un bon réservoir d'idées et de bonnes pratiques pour vos propres éditeurs !

VII. Liens utiles

Vous trouverez dans cette section quelques liens qui peuvent être utiles sur EMF.Edit ou les frameworks évoqués en conclusion.

N'oubliez pas que pour ce qui a trait à EMF, le livre « EMF, 2d Edition » reste une référence, bien qu'un peu daté !

VIII. Remerciements

Pour cet article, je remercie Yassine OUHAMMOU, Claude LELOUP et Malick SECK pour leur relecture attentive. Je tiens aussi à remercier Mikaël Barbero pour ses présentations d'EMF.Edit et pour la démarche des décorateurs pour les classes générées.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2014 Alain BERNARD. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.