Version 0.0 IRISA / INRIA Model Transformation Cahier des charges Général IRISA / INRIA - EQUIPE TRISKELL Model Transformation : cahier des charges général MTL : Model Transformation language from INRIA Table des matières P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Chapitre 1 Les objectifs Diverses fonctionnalités que ce nouveau langage doit permettre. I ntroduction Pour préciser les objectifs à atteindre, il faut commencer par décrire l’environnement dans lequel on veut créer ce nouveau langage. Cet environnement est basé sur l’approche MDA (Model Driven Architecture) de l’OMG ; y figurent : Quatre niveaux de modélisation Des précisions sémantiques Exprimées par des contraintes OCL, ou du texte ( notes attachées à un modèle). On peut imaginer que d’autres expressions annotent les diagrammes et puissent être vérifiées par des outils. 1 P E R S O N N A L I S A T I O N F D E L A P R E S E N T A T I O N onctionnalités attendues de MTL MTL doit pouvoir se connecter à un dépositaire de modèles de niveau M1, pour naviguer dans un modèle présent dans le dépositaire et éventuellement le modifier. MTL doit pouvoir être utilisé avec des dépositaires différents, et dialogue avec le dépositaire via une API générique. Pour raccorder un nouveau dépositaire, il suffit d’écrire l’implémentation de l’API générique qui lui est propre. Le compilateur MTL doit pouvoir s’écrire en MTL de façon à rester indépendant du langage cible qui le réalise concrètement. Un interprète OCL devrait pouvoir s’écrire facilement en MTL, car MTL dispose des mêmes possibilités de navigation et de test qu’OCL. MTL étend OCL en permettant des créations et modifications. MTL doit permettre d’appeler des « opérations externes » telles que l’appel d’un interprète OCL sur une expression OCL isolée, un autre outil associé aux expressions présentes dans le métamodèle, ou encore des fonctionnalités du langage cible. MTL doit permettre de réaliser des modules ou librairies qui peuvent être réutilisés dans le cadre de plusieurs transformations. Fonctionnalités en termes de modèles : MTL peut vérifier la cohérence d’un modèle vis-à-vis de son métamodèle : récupération de toutes les contraintes OCL issues du métamodèle et attachées aux éléments du modèle, puis vérification de leur validité par appel de l’interprète OCL sur de chacune d’elles. MTL peut transformer un modèle en un autre, même lorsque ceux-ci sont basés sur des métamodèles différents. Exemples : transformation d’un arbre quelconque en arbre binaire, transformation d’un arbre ANTLR en arbre N-aire, transformation d’un modèle en UML1.4 en modèle d’UML2. : MTL permet l’application d’une même transformation à tous les éléments de modèle satisfaisant une propriété. Par exemple, l’application de contrat entre une interface requise et une fournie. MTL recherche dans le modèle toutes les interfaces mises en correspondance et y ajoute systématiquement le type de contrat ainsi que son implémentation. On peut également tisser des aspects sur le modèle. MTL permet de dériver, à partir d’un groupe d’éléments du modèle représentant un produit générique, une ligne de produits par introduction de caractéristiques spécifiques à chaque produit. MTL doit-il être un langage orienté objet autorisant l’héritage multiple ? Avantages : Les objets MTL manipulés représentent des éléments de modèle (objets pouvant avoir un héritage multiple). On peut utiliser la modélisation UML pour décrire des transformations. 2 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Chapitre 2 Connexion au(x) dépositaire(s) Diverses analyses, divers dépositaires existants A pproche « Proxies » Comme le modèle peut avoir une taille importante, et que le dépositaire est supposé assez lent, l’idée était de fournir au compilateur MTL le code source de la transformation ainsi que le ou les métamodèles des modèles manipulés. Le compilateur générait alors le binaire de la transformation et autant de proxies que de métamodèles en entrée afin que l’exécution de la transformation minimise les consultations du dépositaire. 3 P E R S O N N A L I S A T I O N D E SOURCE MTL Transformation arbre ANTLR => arbre N-aire L A P R E S E N T A T I O N METAMODELE arbre ANTLR METAMODELE arbre N-aire COMPILATEUR MTL METAMODELE arbre ANTLR Proxy de modèle d’arbre ANTLR API GENERIQUE DEPOSIRAIRE BINAIRE Transformation ANTLR => N-aire Proxy de modèle d’arbre N-aire API GENERIQUE METAMODELE arbre N-aire DEPOSIRAIRE Avantage : les éléments de modèle manipulés sont accessibles dans le proxy et donc en mémoire, ça va vite. Inconvénients : le compilateur n’est pas facile à construire ; le métamodèle du dépositaire n’est pas toujours connu, et surtout il n’est pas du tout certain que l’on puisse forcer le dépositaire à se baser sur un métamodèle dont on dispose, par exemple celui d’un arbre. On veut pouvoir écrire des transformations de modèles qui sont applicables dans divers métamodèles. Par exemple, un raffinement de diagramme de classes propre à une entreprise et qui resterait valable en UML1.3, 1.4 et 2. On envisage de déposer le métamodèle pour lequel on souhaite générer un proxy (en vue d’améliorer les performances) comme simple modèle dans le dépositaire. 2 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N MTL permet de naviguer dans un modèle ( même si son métamodèle n’est pas accessible ) via l’API générique. Donc MTL peut naviguer dans un métamodèle déposé comme modèle dans le dépositaire. On peut écrire une transformation qui explore complètement un métamodèle et génère un proxy pour naviguer facilement dans des modèles issus de ce métamodèle. SOURCE MTL Exploration de métamodèle et génération d’un proxy d’exploration de ses modèles COMPILATEUR MTL METAMODELE du dépositaire ??? BINAIRE de génération de proxy API GENERIQUE DEPOSIRAIRE METAMODELE arbre N-aire ( MODELE Proxy de modèle d’arbre N-aire utilisable avec l’API générique A pproche « Dépositaire MOF » Dans un dépositaire tel que Netbeans-MDR, on se base sur le MOF pour y déposer un métamodèle (UML ou CWM par exemple). Ensuite, on peut créer une interface d’accès et de modification de modèles basée sur le métamodèle présent dans le dépositaire. 3 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N SOURCE MTL Transformation de modèle basée sur un métamodèle DEPOSIRAIRE MDR COMPILATEUR MTL BINAIRE de la transformation API MOF METAMODELE arbre N-aire ( MODELE MOF) API générée par le dépositaire, basée sur le métamodèle d’arbre N-aire Modèle d’arbre N-aire Avantage : Métamodèle et modèle sont présents dans le même dépositaire Inconvénients : MTL est très lié au dépositaire. Les dépositaires de ce type évoluent : NetBeans-MDR (Sun basé sur MOF 1.4 ) Eclipse-EMF (IBM basé sur MOF 2 ) Il faut faire des échanges basés sur XMI entre « CASE tools » et dépositaire (sauf si l’on fait tout sous Eclipse : EclipseUML + Eclipse + EMF lié à une base de données par exemple ) C ONCLUSION ( ? ) Les règles d’or de l’accès à un dépositaire : Le métamodèle utilisé par le dépositaire pour stocker des modèles n’est pas toujours complètement connu, et évolue au rythme des RFP de l’OMG. => l’API d’accès au dépositaire doit demander et créer des instances de métaélément (métaélément 4 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N indiqué sous forme de chaîne de caractères car on ne connaît pas la structure du dépositaire). Si le dépositaire ne peut satisfaire la requête, l’API le signale. Le dépositaire peut faire des évaluations paresseuses. => lorsque l’API demande la collection des instances d’un métaélément particulier, il n’est pas certain que le dépositaire construise réellement cette collection. Il est souvent plus judicieux qu’il fournisse un nouvel élément de modèle pour chaque collection.at(i), i=1,2,…n en gardant l’uid des instances de métaélément déjà fournies. ( à discuter ? ) Le programmeur de transformation ne doit pas à avoir à connaître par cœur le MOF1.4 ou le MOF 2 pour utiliser l’API de connexion au dépositaire. => l’API n’est basée sur aucun MOF particulier. Le dépositaire risque à terme d’inclure la description de la transformation à effectuer : métamodèle annoté par des expressions MTL et OCL. Les annotations MTL précisent la transformation à réaliser dans les modèles basés sur ce métamodèle => l’API de connexion au dépositaire sera susceptible de servir pour obtenir le source MTL D ESCRIPTION SOMMAIRE DE L4API ENVISAGEE L’API est organisée en une version de base et des modules d’extension. Selon les fonctionnalités du dépositaire, et la sémantique attachée au métamodèle utilisé pour les modèles, une implémentation de l’API de base seule, ou bien de celle-ci avec certains modules sera réalisée pour le dépositaire concerné. Pour condenser, seules les opérations primordiales figurent dans le tableau ci-dessous. API de base Interface d’échange de données entre applications et d’appel fonctionnels extérieurs. Possibilité de communication avec d’autres langages de programmation ( Java, Python, C++, C#) ; Conversion d’objets MTL en « données extérieures » et vis versa. ( IDL, XDE, SOAP ???? ) Exemples : écriture de données dans un fichier ; Appel d’un interprète OCL sur une expression API de base Opérations sur modèles : uidm=NewModelEleme nt (arguments[ ] ) Crée un élément de modèle en utilisant les données passées en arguments, et en renvoyant un uidm ( identificateur unique de l’élément de modèle) 5 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N uidm.deleteModelEleme nt () Enlève du modèle l’élément associé à l’uidm.. uidm.isTypeOf (MetaType) Détermine si l’élément de modèle a été créé avec le métatype fourni uidm.isKindOf(MetaTyp e) Détermine si l’élément de modèle est bien une instance du métatype fourni. uidm.getAttributeValue( MetaAttribut) uidm.setAttributeValue( MetaAttribut,Valeur) Donne ou affecte la valeur d’un attribut de métaélément dont uidm est une instance. uidm.getRelatedConstrai nts() Obtient l’ensemble des contraintes attachées au métaélément dont uidm est une instance. uidm.invokeOperation (MetaOperation,argumen ts[ ]) Fait appel à l’opération définie dans le métaélément dont uidm est une instance. associateModelElements (Name, ModelEndPoint[ ] ) Crée une association de nom (optionnel) « Name » entre divers éléments de modèle. Une spécification de liaison est fournie pour chaque point de connexion à un élément, elle comprend dans l’API de base : - l’élément de modèle à raccorder ; . - un nom éventuel de rôle ; ( elle est étendue dans lez extensions d’API) dissociateModelElements (ModelEndPoint [ ]) Supprime l’association qui existe entre les éléments de modèles, le dépositaire peut retrouver cette association grâce aux specs de liaison.. MetaType.allInstances () Fournit la collection d’instances du MetaType 6 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N présentes dans le modèle. MetaType.selectInstances (critère) Fournit la collection d’instances du MetaType présentes dans le modèle, qui de plus satisfont le critère de sélection. MetaType.getStaticAttrib uteValue(MetaAttribut) MetaType.setStaticAttrib uteValue(MetaAttribut,V aleur API de base Récupération ou modification de la valeur d’un attribut statique. Opérations propres aux métamodèles : MetaType.getAllParents() Fournit la collection des métaéléments qui sont issus d’une généralisation du MetaType considéré. MetaType.getAllChildren () Fournit la collection des métaéléments qui sont issus d’une spécialisation du MetaType considéré MetaType.getAllAssociat edElements() Fournit la collection de collections des métaéléments qui sont en association avec le MetaType considéré. uidm.getMetaType() Fournit le Métaélément qui a permis de créer l’instance de modèle considéré. MetaType.getAllAttribute sNames() Donne la collection de noms de MétaAttributs portés par le Métaélément considéré. Donne la collection de noms de Métaopérations offertes par le Métaélément considéré MetaType.getAllOperatio nsNames() Extension ModelEndPoint contient de plus : - un rang de l’élément à connecter ; - un mode (insert/replace) « lien ordonné » Extension ModelEndPoint contient de plus : un tableau selecteur[ ] chaque selecteur est un couple (nom de qualificatif,valeur) « liens qualifiés » 7 P E R S O N N A L I S A T I O N Extension « Multiplicité des valeurs d’attributs » D E L A P R E S E N T A T I O N Valable pour un attribut dont le type est « Collection » Les manipulations sur la collection attachée à l’attribut sont réalisées par le dépositaire. ou dont le type est un type primitif avec une multiplicité prédéfinie de valeurs. NB : il existe des fonctions similaires pour un attribut statique ayant une multiplicité, par exemple : uidm.getAttributeCollecti onSize(MetaAttribut) MetaType.getStaticAttributeCollectionSize(M etaAttribut) uidm getAttributeCollectionVa lueAt(MetaAttribut,positi on) uidm setAttributeCollectionVal ueAt(MetaAttribut,positi on) … (autres opérations telles que insertAt, removeAt, etc. ) API : basée sur JMI, IDL, … ? Avantage de JMI : devrait s’interfacer directement avec la plupart des « CASE tools » qui importent et exportent du XMI. ; Inconvénients de JMI : Il faut une DTD identique pour relire un fichier xml issu d’un « CASE tool », il faut recréer en mémoire un modèle identique à celui manipulé par le « CASE tool ». Il existe plusieurs versions de XMI. Avantages d’IDL,SOAP : L’API correspond à une interface au travers de laquelle des données simples sont communiquées à d’autres applications; IDL ne présuppose rien sur l’implémentation de l’interface sœur : on peut utiliser un dépositaire écrit dans un langage quelconque et disposant d’un driver compatible avec l’interface IDL correspondante à l’API. 8 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Inconvénients : le bus corba est lent ; IDL3 va remplacer IDL ? SOAP est basé sur un échange de données XML. E xemple d’utilisation de MTL et de son API avec les métamodèles d’arbre ANTLR et d’arbre Naire, ainsi que le modèle de transformation présentés en annexe. COMPILATEUR MTL ANALYSE LEXICALE ET SYNTAXIQUE SOURCE MTL Transformation de modèle basée sur un métamodèle BINAIRE de la transformation TRANSFORMATION API MTL API MTL API MTL DEPOSIRAIRE MDR DEPOSIRAIRE Eclipse-EMF API générée par le dépositaire, basée sur le métamodèle d’arbre N-aire API MOF 2 API de modèles ANTLR METAMODELE d’arbres ANTLR et N-aire annoté avec MTL Modèle d’arbre ANTLR 9 Modèle d’arbre N-aire P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Chapitre 3 Compilation, ou interprétation de MTL ? Après de longues discussions, l’état de l’art détaillé… O n va développer un prototype de langage MTL qu’il va falloir affiner pour arriver au langage définitif. On a besoin d’un interprète OCL2 auquel on pourra éventuellement faire appel depuis MTL. Pour les deux raisons précédentes, on préfère développer dans un premier temps un interprète qu’un compilateur. Celui-ci ne verra le jour que lorsque le langage MTL commencera à se stabiliser. La syntaxe concrète du langage MTL définitif n’est pas encore connue (voir le chapitre 4 sur l’utilisation) . Elle pourra provenir directement d’un « CASE tool », d’un bus BOM via une interface normalisée de ce bus, d’une description textuelle de la transformation. Un parseur spécifique à chaque origine de transformation va produire un arbre concret basé sur la définition de la syntaxe abstraite de MTL. Analyse du source MTL Source MTL Diagramme annoté dans un «CASE tool » Parseur spécifque Parseur de source texte MTL Arbre concret annoté par les éléments de syntaxe abstraite MTL IFexpr Basé sur l’API de connexion à un dépositaire + syntaxe abstraite MTL iCONDexpr Données objets venant du BOM Basé sur l’IDL BOM (IDL3 Corba ? ) + syntaxe abstraite MTL Basé sur la syntaxe abstraite MTL THENclause ELSEclause Fichier texte 10 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N La syntaxe abstraite de MTL va évoluer en fonction de la mise au point du langage. L’arbre concret issu du parseur va être compilé en utilisant un visiteur qui va réexprimer tout l’arbre en fonction d’un langage MTL simplifié, le BasicMTL. Par exemple, on peut exprimer un « FOR var IN collection DO expression » au moyen d’une boucle while de BasicMTL qui va donner à var la valeur de chacun des membres de la collection et évaluer l’expression. Cette transformation d’arbre peut être réalisée complètement par un compilateur MTL=>BasicMTL. Compilation de MTL vers Basic MTL Arbre concret MTL Arbre concret BasicMTL FORexpr BasicWHILEexpr IDENTIFIER BasicNOTEMPTY MTLexpr corps BasicEVALSEQUENCE COLLECTIONexpr IDENTIFIER BasicMTLexpr corps BasicLET IDENTIFIER BasicNEXTELEMENT BasicCOLLECTION La dernière étape consiste à lancer un visiteur sur l’arbre concret basé sur la syntaxe abstraite de BasicMTL. La syntaxe abstraite de BasicMTL est réduite à : La définition d’une classe (éventuellement abstraite) pouvant hériter d’une ou plusieurs autres classes ; 2 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Une classe comprend des attributs dont les types sont soit prédéfinis soit des classes, et des définitions de méthodes (éventuellement abstraites) ; Une définition de méthode comprend une séquence d’instructions. Chaque instruction est soit la création d’un nouvel objet attaché à une variable, soit un appel de méthode d’un objet existant avec d’éventuels paramètres. Une méthode particulière « main » permet d’initialiser et de lancer l’exécution de la transformation. U ne syntaxe possible de BasicMTL pourrait être la suivante : <BasicMTLpgme> ::= <definition_classe>* <main> <{> <decl_attributs>* <corps_méthode>+<}> <definition_classe> ::= [ <abstract>] <class> identifier [ <extends> identifier+ ] <{> <corps_classe> <}> <corps_classe> ::= <decl_attribut>* <definition_methode>+ <decl_attribut> ::= <decl_vars> <decl_vars> ::= { <type> | identifier } identifier < ;> // type var ; <definition_methode> ::= [ <abstract> ] { <type> | identifier} identifier <(> <decl_vars>* <)> <{> <corps_methode> <}> //type nom_methode ( type1 paramètre1 type2 paramètre2 … ) { …} <corps_methode> ::= <New> <(> identifier { <type> | identifier} <)> //New (var classe) | identifier<.>identifier <(> {<valeur> | identifier}* <)> //classe.méthode(…) <type> ::= « integer » | « real » | « boolean » | “string” | « collection » | « tuple » <valeur> ::= <nombre> | <chaine de caractères> | <valeur booléenne> L ’héritage multiple va être réalisé en créant une interface de méthodes par classe parente. Un objet héritant de plusieurs classes est une instance java réalisant toutes les interfaces correspondantes à ses classes parentes. Le visiteur d’arbre BasicMTL produit un fichier java lorsqu’il rencontre une définition de classe, puis le compile avec javac. Il conserve en permanence le graphe des héritages de classes et la table des symboles pour obtenir la correspondance « méthode MTL => méthode d’un fichier java.class généré ». Le fichier java compilé peut faire référence au visiteur pour évaluer des morceaux d’arbres BasicMTL qui lui sont fournis en arguments. Le « while » java fait appel au visiteur sur la partie condition pour savoir si celle- 3 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N ci est satisfaite ou pas : Class BasicMTLinstructions { … BasicMTLwhile( Object condition, Object CorpsBoucle) { boolean continueBoucle ; do { Object condEvaluée = BasicMTL.Interprète( condition } ; BasicMTL.CheckBoolean(condEvaluée); //ExceptionMTL si ! Bool continueBoucle = BasicMtl.ToJava(condEvaluée); if (continueBoucle) BasicMTL.Interprète( CorpsBoucle) ; } while (continueBoucle) ; } Pour l’évaluation d’appels de méthodes, le visiteur d’arbre BasicMTL se contente de faire appel à la méthode de la classe java correspondante. Il trouve cette classe en se basant sur son graphe de dépendances entre classes et la table des symboles. U ne fois le langage stabilisé, on pourra compiler l’arbre BasicMTL et traduire directement le graphe des héritages par des appels directs aux classes et méthodes java adéquates. On prévoit de créer des librairies MTL (voir exemple en annexe) pour faciliter l’écriture modulaire des transformations. Dans un premier temps, les sources MTL de librairies sont compilés pour produire des « arbres concrets BasicMTL » que l’interprète MTL peut aller chercher et interpréter au besoin de façon dynamique. Lorsque le compilateur BasicMTL sera réalisé, les diverses librairies MTL seront alors disponibles sous forme d’archives java (fichiers jar). 4 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Chapitre 4 Comment fera-t-on usage de MTL ? D u modeleur à MTL… M odèles de transformation On annexe, on donne un exemple de transformation d’arbre basée sur deux métamodèles distincts d’arbres. La transformation elle-même peut être décrite dans le modeleur. Elle peut alors servir pour : - constituer le texte de la transformation ; les éléments graphiques du modèle de transformation étant repris un par un sous forme de texte déclarant des classes et des associations d’objets MTL ( voir le texte complet de la transformation). - être annotée par le contenu des méthodes MTL qui sont attachées aux classes. C’est exactement comme pour la conception d’une application Java où le code source d’une méthode figurerait en note attachée. Dans un modeleur, on choisit le langage vers lequel générer le modèle, java ou C++ par exemple. On devrait pouvoir utiliser n’importe quel modeleur tel quel, du moment qu’on peut le connecter via l’API générique. Au lieu d’être un dépositaire de modèles que la transformation va consulter et/ou modifier, le modeleur est alors un « dépositaire de programme » qui contient le source MTL. La « transformation » chargée de naviguer dans un modèle de transformation, de construire les classes d’objets MTL nécessaires et d’y mettre les annotations MTL comme texte de méthodes pourra s’écrire en MTL. Elle reviendra au même qu’une extension de génération MTL – réalisée comme une génération C++ ou java – effectuée par la société fabriquant le modeleur. M odèles d’objets annotés par du MTL On croit que la façon la plus aisée de construire une ligne de produits, d’ajouter des contrats ou de faire du « design pattern » sera sans doute d’annoter le modèle par des expressions MTL et d’y joindre un modèle paramétrique de la transformation à effectuer comme dans l’exemple ci-dessous. 5 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Classe X Classe X {} Op x::xop1 typeA->typeB Op x::xop1 typeA->typeB {MTL : T(x,y)} Classe Y Attrib y::yattrib1 typeB Classe Y -Fin1 Attrib y::yattrib1 typeB 1 -Fin2 Classe Z * Attrib z::zattrib1 typeA Op z::zop1 typeB->typeA Dans cet exemple, la transformation décrite par T s’applique dans tout modèle où l’on peut trouver une portion de modélisation homomorphe à la partie située à gauche de la note MTL. La portion à droite de la note MTL décrit la transformation réalisée pour chaque appariement effectué : une agrégation est ajoutée avec une classe Z ayant un attribut et une opération (ceux-ci pouvant être choisis en fonction des classes x et y et de leurs « features »). MTL doit seulement garantir que tout le modèle cible sera parcouru, et que pour toute portion s’appariant avec le motif de gauche, une et une seule transformation aura lieu. Cette « application de motif » devrait pouvoir s’écrire également en MTL. 2 P E R S O N N A L I S A T I O N D E L A P R E S E N T A T I O N Annexes Arbre ANTLR : l’arbre est parcouru avec FirstChild qui donne le premier fils d’un nœud, et NextSibling qui donne le frère d’un nœud. Un nœud comprend un entier correspondant au symbole non terminal parsé et une chaîne de caractères pour la valeur concrète associée. Arbre générique : au niveau de chaque nœud le père et les fils (dans l’ordre, de la gauche vers la droite) sont conservés 3 Classes et associations (objets MTL) manipulés lors de la transformation d’un modèle d’arbre ANTLR en un modèle d’arbre générique. 2 Texte source MTL de la transformation : arbreANTLR.mtl library ArbreANTLR; model ModelArbreANTLR implements MetamodelArbreANTLR; uses Arbres; import MetamodelArbreANTLR:::*; import Arbres:::*; -- permet de consever la valeur de parsed asscociation ParsedLocal extends ValeursLocales { role: + valeurParsed [valeur] : 0..1 ValeurInteger; unnavigable [arbre] : 0..1 ArbreANTLR; } -- permet de consever la valeur de text asscociation TextLocal extends ValeursLocales { role: + valeurText [valeur] : 0..1 ValeurString; unnavigable [arbre] : 0..1 ArbreANTLR; } class ArbreANTLR specializes ArbreNAire { attribute: - referenceAST : AST; - valeurPourParsed : ValeurInteger; - valeurPourText : ValeurString; - parsedName : String; constructor: # (reference : AST) { if (self.reference = NULL) throw new NullPointerException(); self.reference := reference; self.valeurParsed := new ValeurInteger('parsed', reference.parsed); self.valeurText := new ValeurString('text', reference.text); self.parsedName := findRuleName(self.valeurParsed.valeur); } + () { constructor (newModelElement AST()); } operation: + static arbreANTLRPour (reference : AST) : ArbreANTLR { ArbreANTLR ret; if reference = NULL then ret := NULL; else 3 ArbreANTLR ret := ArbreANTLR.selectInstances(ag| ag.reference = reference)->getOne; if ret = NULL then ret := new ArbreANTLR(reference); endif endif return ret; } # static frerePrecedentAST (ast : AST) : AST { return AST.selectInstances(i| i.nextSibling = ast)>getOne; } # static frereAineAST (ast : AST) : AST { AST frere = ast; AST frerePrecedent = frerePrecedentAST(AST); while frerePrecedent <> NULL do ast := frerePrecedent; frerePrecedent = frerePrecedentAST(AST); done return ast; } # static pereAST (ast : AST) : AST { -- cas ou ast represente un premier fils AST referenceSurPere := AST.selectInstances(i| i.firstChild = ast)->getOne; -- cas ou ast represente un frere cadet if referenceSurPere = NULL then AST frereAine := frereAineAST(ast); if ast <> frereAine -- pour blocquer la recursivite pour un noeud racine then referenceSurPere := pereAST(frereAine); endif endif return referenceSurPere; } # fabriquePere : ArbreSurProxie { return arbreANTLRPour(pereAST(self.reference)); } # fabriqueFils : Sequence(ArbreSurProxie) { AST tmp := self.reference.firstChild; Sequence(AST) filsAST := Sequence{}; while tmp <> NULL do filsAST->alterAppend(tmp); tmp := tmp.nextSibling; done return filsAST->collect(i| arbreANTLRPour(i)); } 4 # insereFilsDistant (fils : ArbreNAireSurProxie, index : Integer) { if fils.oclIsTypeOf(ArbreANTLR).not then throw new IllegalTypeException(); if i > self.fils->size + 1 then self.ajouteFils; elsif i <= 0 then throw new IllegalArgumentException(); elsif index = 1 then fils.reference.nextSibling := self.reference.firstChild; self.reference.firstChild := fils.reference; else -- une facon de faire un for... avantInsert : AST; apresInsert : AST := self.reference.firstChild; for i in Sequence{1..index} do avantInsert := apresInsert; apresInsert := apresInsert.nextSibling; done avantInsert.nextSibling := fils.reference; fils.reference.nextSibling := apresInsert; endif } # static findRuleName (ruleNumber : Integer) : String { return WFR.selectInstances(number = ruleNumber)->getOne; } # static findRuleNumber (ruleName : Integer) : String { return WFR.selectInstances(name = ruleName)->getOne; } + fixeValeur (valeur : Valeur) { if valeur.name = 'text' then if not valeur.oclIsKindOf(ValeurString) then throw new IllegalArgumentException(); endif self.reference.text := valeur.oclAsType(ValeurString).valeur; delete self.valeurText; self.valeurText := valeur.oclAsType(ValeurString); elsif valeur.name = 'parsed' then if valeur.oclIsKindOf(ValeurString) then ruleNumber : Integer := findRuleNumber(valeur.oclAsType(ValeurString).valeur); if ruleNumber = NULL then throw new IllegalArgumentException(); endif self.parsedName := valeur.oclAsType(ValeurString).valeur; valeur := new ValeurInteger('parsed', ruleNumber); elsif valeur.oclIsKindOf(ValeurInteger) then ruleName : String := findRuleName(valeur.oclAsType(ValeurInteger)); if ruleName = NULL then throw new IllegalArgumentException(); endif 5 self.parsedName := ruleName; else throw new IllegalArgumentException(); endif self.reference.parsed := valeur.oclAsType(ValeurInteger).valeur; delete self.valeurParsed; self.valeurParsed := valeur; else throw new IllegalArgumentException(); endif } + = (ArbreANTLR rhs) : Boolean { return rhs <> NULL and self.reference = rhs.reference; } } class ArbreANTLRPratique specializes ArbreANTLR { constructor: # (reference : AST) { superconstructor ArbreANTLR (reference); } + () { constructor (newModelElement AST()); } operation: + parsedRuleNumber : Integer { -- return self.reference.parsed; -- appel au modele return self.valeurParsed.valeur; -- appel proxie } + parsedRuleName : String { return self.parsedName; } + fixeParsedRuleNumber (parsed : Integer) { self.fixeValeur(new ValeurInteger('parsed', parsed)); } + fixeParsedRuleName (parsed : String) { self.fixeValeur(new ValeurString('parsed', parsed)); } + text : String { return self.valeurText.valeur; -- appel proxie } + fixeText (parsed : String) { self.fixeValeur(new ValeurString('text', parsed)); } } 6 arbregénérique.mtl library ArbreGenerique; model ModeleArbreGenerique implements MetamodeleArbreGenerique; uses Arbres; import MetamodeleArbreGenerique:::*; import Arbres:::*; class ArbreGenerique specializes ArbreNAire { attribute: - reference : Noeud; -- reference du proxy (objet reel du modele) constructor: # (reference : Noeud) { if (self.reference = NULL) throw new NullPointerException(); self.reference := reference; self.reference.attributs->collect(att| associate valeur := new ValeurString(att.nom, att.valeur), arbre := self); // self.reference.attributs->collect(att| self.valeur>alterInsert(new ValeurString(att.nom, att.valeur))); -- ancienne version } + () constructor (newModelElement Noeud() in ModelArbreNAire); -- optionnel car ModelArbreNAire est le seul modele implementant le metamodele ArbreNAire } operation: + static arbreGeneriquePour (reference : Noeud) : ArbreGenerique { ArbreGenerique ret; if reference = NULL then ret := NULL; else ArbreGenerique ret := ArbreGenerique.allInstances()->select(ag| ag.reference = reference)>getOne; // exemple en specifiant le modelea explorer a allInstances; ici, c'est optionnel... // ArbreGenerique ret := ArbreGenerique.allInstances(ModeleArbreGenerique)->select(ag| ag.reference = reference)->getOne; if ret = NULL then ret := new ArbreGenerique(reference); endif endif return ret; } # fabriquePere : ArbreSurProxie { arbreGeneriquePour(self.reference.pere); } 7 # fabriqueFils : Sequence(ArbreSurProxie) { reference.fils->collect(i| arbreGeneriquePour(i)); } # insereFilsDistant (fils : ArbreNAireSurProxie, index : Integer) { if (fils.oclIsTypeOf(ArbreGenerique).not) throw new IllegalTypeException(); try { associateModelElement pere := self.reference, fils := fils.reference insertAt index; // self.reference.fils->alterAt(fils.reference, index); -- vieille version } catch (IllegalArgumentException x) { self.ajouteFils; } } + fixeValeur (valeur : Valeur) { attributCorrespondant : Attribut := self.reference.attributs->select(nom = valeur.nom)->getOne; if attributCorrespondant = NULL then attributCorrespondant := newModelElement Attribut(); attributCorrespondant.nom := valeur.getNom; associateModelElement noeud := self.reference, attributs := attributCorrespondant; // self.reference.atributs>alterInsert(attributCorrespondant); -- vieille version endif attributCorrespondant.valeur := valeur.valeurString; self.oclAsType(Arbre).fixeValeur(valeur); } + = (ArbreGenerique rhs) : Boolean { return rhs <> NULL and self.reference = rhs.reference; } } 8 Transformation proprement dite : arbreANTLR2arbregénérique.mtl transformation ANTLR2ArbreGenerique; readonly model MonModeleANTLR implements MetamodelArbreANTLR; model MonModeleArbreGenerique implements MetamodeleArbreGenerique; uses ArbreGenerique with MonModeleANTLR as ModeleArbreGenerique; uses ArbreANTLR with MonModeleArbreGenerique as ModeleArbreGenerique; uses Arbres; import Arbres:::ArbreNAire; main () { nouvelArbre : ArbreGenerique:::ArbreGenerique; for racine in ArbreANTLR:::AST.selectInstances(pere->isEmpty) do nouvelArbre := new ArbreGenerique:::ArbreGenerique(); copyFils(racine, nouvelArbre); copyValeur(racine, nouvelArbre); done } copyValeur (source : ArbreNAire, cible : ArbreNAire) { for valeur in source.valeur do cible.fixeValeur(valeur); done } copyFils (source : ArbreANTLR:::AST, cible : ArbreGenerique:::ArbreGenerique) { ArbreGenerique:::ArbreGenerique nouveauFils; for fils in source.fils do nouveauFils := new ArbreGenerique:::ArbreGenerique(); cible.insereFils(nouveauFils); copyValeur(fils, nouveauFils); copyFils(fils, nouveauFils); done } 9 10