• Aucun résultat trouvé

à la compilation des abstractions du langage, alors qu'une troisième dénit le proces-sus de génération de code qui est spécique au programme en lui-même. Des outils de génération de programme sont alors utilisés pour dénir la compilation de ces facettes du DSL en termes de processus de génération. Cette approche permet de modulariser le processus global de compilation. Chaque module est responsable d'une partie bien précise de code à générer an de compiler un DSL. De plus, chaque technique générative fournit un paradigme, des abstractions et des outils qui lui sont propres. Le développeur du compilateur peut alors choisir l'approche de programmation la plus appropriée pour une facette donnée.

Les sections suivantes proposent une illustration de notre méthodologie. Pour cela, nous reprenons l'exemple du compteur exposé dans le chapitre précédent. Le service SPL associé est rappelé au niveau de la gure 8.2.

1. service Counter { 2. processing {

3. local void log (int);

4.

5. registration {

6. int count;

7.

8. response outgoing REGISTERREGISTER() {

9. count = 0;

10. return forward ();

11. }

12.

13. void unregister() {unregister

14. log (count);

15. }

16.

17. dialog {

18. response incoming INVITE()INVITE { 19. response resp = forward ();

20. if (resp != /SUCCESS) {

21. count++;

22. return forward 'sip:secretary@company.com';

23. } else {

24. return resp;

25. }

26. }

27. }

28. }

29. } 30. }

Fig. 8.2 Le service Compteur écrit en SPL

contrairement à la version complète, la logique du programme est clairement visible et compréhensible, puisque tous les détails d'implémentation ont été omis. De plus, confor-mément à notre méthodologie, cette représentation abstraite ne présente pas la même structure de programme (e.g., insertion de la méthode handler REGISTER, ligne 4), ni le même type de requêtes (e.g., insertion d'un type booléen à la requête sendRequest, ligne 6 ou 13). En eet, cela résulte de la structuration de programme et de l'encodage de l'information an de faciliter une interprétation ultérieure. Ce faisant, cette repré-sentation abstraite se trouve très proche du service SPL. La gure 8.4 illustre ce point et montre que les deux versions du service sont fortement semblables rendant ainsi le processus de compilation simple et direct.

Comme nous pouvons le voir sur la gure 8.4 avec les blocs handler REGISTER ou handler INVITE (lignes 3-6 et 8-10 de la représentation abstraite), la représentation abstraite est une traduction directe de la logique du service. L'organisation de la re-présentation reète parfaitement la structure du programme SPL. Ainsi, il est tout à fait possible d'identier les diérents blocs de code où les messages du protocole SIP sont traités et de les mettre en correspondance avec les gestionnaires SPL associés. De même, les expressions manipulant les variables sont reproduites mot pour mot (e.g., la variable count).

L'utilisation de l'encodage d'informations sous la forme d'arguments additionnels est également illustré. Comme indiqué précédemment, la gestion de l'état, notamment sa restauration au niveau d'une transaction, est une notion importante d'un service de téléphonie. L'un des avantages de SPL réside dans l'abstraction faite au niveau de l'ex-pression forward, qui se compile en une transition avec ou sans état selon le contexte.

Ce point est illustré par l'interprétation présentée dans la gure 8.4. Alors que le rou-tage des messages dans SPL ne se soucie pas de la gestion de l'état, cette propriété est explicitement encodée dans la version GPL abstraite. En eet, l'encodage de cette information sous la forme d'un booléen additionnel à la méthode sendRequest (lignes 5 et 9 de la représentation abstraite) permet de diérencier les contextes d'utilisation et donc leurs traductions. L'un des avantages est ici de séparer la production de l'infor-mation de son exploitation, laissant son interprétation à d'autres phases de compilation.

Une fois que la représentation abstraite est générée, il s'agit d'utiliser les approches génératives an de compléter la génération de code ; il devient alors possible d'introduire les détails d'implémentation manquants entre la représentation abstraite du programme et sa représentation complète (présentées repectivement aux gures 8.3 et 7.1). An d'illustrer notre approche, nous avons choisi trois techniques diérentes de génération de programme : la programmation orientée aspect, les annotations et la spécialisation de programme. Toutefois, conceptuellement, d'autres approches pourraient être choi-sies. Dans notre cas, l'AOP nous permet d'introduire des comportements transverses dans la représentation abstraite (e.g., du code en prologue et épilogue d'invocations de l'API JAIN SIP). Les annotations facilitent l'introduction de préoccupations non-fonctionnelles dans le processus de compilation (e.g., consommation de ressources). En-n, la spécialisation de programme se charge de la génération de code optimisé (e.g., génération de composants logiciels sur mesure). Les sections suivantes présentent

briève-1. public class Counter implements SipListener {

2. Lib lib;

3. [...]

4. private void handler_REGISTER(Request rq_request, String method) { 5. count = 0;

6. sendRequest (false, rq_request); /** SPL, ligne 10 **/

7. }

8. private void handler_unregister(Request rq_request, String method) { 9. lib.local.log(getCounter()); /** SPL, ligne 14 **/

10. sendRequest (false, rq_request); /** SPL, comportement par défaut **/

11. }

12. private void handler_INVITE(Request rq_request, String method) { 13. sendRequest (true, rq_request); /** SPL, ligne 19 **/

14. }

15.

16. public void processRequest (RequestEvent requestEvent) { 17. String method = rq_request.getMethod();

18. if (method.equals (Request.REGISTER)) { /** SPL, ligne 5 **/

19. if (!lib.registrar.hasExpiresZero (rq_request)) {

20. if (!lib.registrar.hasRegistration (rq_request)) { /** SPL, ligne 8 **/

21. handler_REGISTER(rq_request, method);

22. } else { /** SPL, comportement par défaut **/

23. sendRequest (false,rq_request);

24. }

25. } else if (lib.registrar.hasRegistration(rq_request)){ /** SPL, ligne 13 **/

26. handler_unregister(rq_request, method);

27. } else { /** SPL, comportement par défaut **/

28. sendRequest (false,rq_request);

29. }

30. }

31. if (method.equals (Request.INVITE)) { /** SPL, ligne 18 **/

32. if (lib.registrar.hasRegistration (rq_request)) { 33. handler_INVITE(rq_request, method);

34. } else { /** SPL, comportement par défaut **/

35. sendRequest (false,rq_request);

36. }

37. }

38. [...]

39. }

40.

41. public void processResponse (ResponseEvent responseEvent) { 42. String method = rs_request.getMethod();

43. int rs_responseCode = rs_response.getStatusCode();

44. if (method.equals (Request.INVITE)) { /** SPL, ligne 18 **/

45. if (rs_responseCode >= 300) { /** SPL, ligne 20 **/

46. count++ ; /** SPL, ligne 21 **/

47. AddressFactory addressFactory = lib.getAddressFactory();

48. SipURI sipURI = addressFactory.createSipURI ("secretary", "company.com");

49. rs_request.setRequestURI (sipURI);

50. sendRequest (false, rs_request); /** SPL, ligne 22 **/

51. } else {

52. sendResponse (true, rs_response); /** SPL, ligne 24 **/

53. }

54. } else { /** SPL, comportement par défaut **/

55. sendResponse (false, rs_response);

56. }

57. [...]

58. }

59.

60. public void processTimeout (TimeoutEvent timeOutEvent) {[...]}

61. }

Fig. 8.3 La représentation abstraite du service Compteur écrite en JAIN SIP

1. service Counter{ 2. processing{

3. localvoidlog (int);

4.

5. registration{ 6. intcount;

7.

8. responseoutgoingREGISTER() {REGISTER

9. count = 0;

10. return forward ();

11. }

12.

13. voidunregisterunregister() { 14. log (count);

15. }

16.

17. dialog{

18. responseincomingINVITE()INVITE { 19. responseresp = forward ();

20. if (resp != /SUCCESS) {

21. count++;

22. return forward'sip:secretary@company.com';

23. } else {

24. return resp;

25. }

26. }}}}}

8. responseoutgoingREGISTER() {REGISTER

9. count = 0;

10. return forward ();

11. }

18. responseincomingINVITE()INVITE { 19. response resp = forward ();

SPL

1. public class Counter implements SipListener{ 2. [...]

3. private void handler_REGISTER(Request rq, String method){ 4. setCounter(0);

5. sendRequest (false, rq);

6. }} 7. [...]

8. private void handler_INVITE(Request rq, String method){ 9. sendRequest(true, rq);

10. } 11.

12. public void processRequest (RequestEvent requestEvent) { 13. String method = rq_request.getMethod();

14. if (method.equals (Request.REGISTER)) {

15. if(!lib.registrar.hasExpiresZero (rq_request)) { 16. if (!lib.registrar.hasRegistration (rq_request)) { 17. handler_REGISTER(rq_request, method);

18. [...]

19. }else if (method.equals (Request.INVITE)) {

20. if(lib.registrar.hasRegistration (rq_request)) { 21. handler_INVITE(rq_request, method);

22. [...]

3. private void handler_REGISTER(Request rq_request, String method){ 4. count = 0;

5. sendRequest (false, rq_request);

6.

6. }}

17. handler_REGISTER(rq_request, method);

21. handler_INVITE(rq_request, method);

Représentation abstraite

8. private void handler_INVITE(Request rq_request, String method){ 9. sendRequest (true, rq_request);

10. }

Fig. 8.4 Comparaison du service SPL et de sa représentation abstraite

ment chacune de ces techniques avant d'illustrer la compilation des unités fonctionnelles (section 8.4) et non-fonctionnelles (section 8.5).

8.3.1 La programmation orientée aspect

La programmation orientée aspect [KLM+97] est un paradigme de programmation qui permet de réduire fortement les couplages entre les diérents aspects techniques d'un logiciel. L'AOP facilite l'encapsulation d'un besoin transverse (e.g., monitorage de programme, authentication ou encore persistance) dans un seul module, appelé un aspect. Ce dernier étend la fonctionnalité d'un objet en y ajoutant un comportement supplémentaire appelé greon (advice). Pour utiliser un aspect, il faut dénir des points d'exécution (joinpoints), qui correspondent aux endroits spéciques dans le ot d'exé-cution du programme où il est valide d'insérer du code. Ces points sont congurés en spéciant des points de coupure ou d'activation (pointcut). La dénition d'un point de coupure permet de spécier plusieurs points d'exécution dans le programme, c'est-à-dire plusieurs endroits où est inséré un même greon. Un aspect correspond donc à un module dénissant des greons et leurs points de coupure.

Selon notre expérience, les unités fonctionnelles de compilation sont particulièrement bien adaptées à la programmation orientée aspect. Cependant, pour être utilisable, l'ap-proche impose une forte structuration de la représentation abstraite an de correspondre à l'expressivité du langage de points de coupure. En eet, il est très important de bien identier les endroits où insérer le code dans la représentation abstraite. L'identication

de ces points du programme implique le remaniement de la représentation abstraite et l'introduction d'entités langage comme des invocations ou des dénitions de mé-thodes, ou encore des déclarations de variables. Ainsi, il peut arriver qu'une séquence de commandes ait besoin d'être placée à l'intérieur d'une méthode de la représentation abstraite an de permettre d'insérer du code avant, après ou à la place de son exécution.

La partie haute de la représentation abstraite écrite en JAIN SIP (gure 8.3) montre, par exemple, des méthodes privées (e.g., handler REGISTER) qui ont été introduites par le compilateur du DSL pour insérer facilement du code au début et à la n des invocations des gestionnaires de requêtes SPL.

L'un des avantages de cette technique est de permettre au compilateur de DSL d'être conçu et structuré en termes de modules, qui correspondent à des aspects. Chaque aspect dénit un comportement spécique du DSL dont la nature transverse est véritablement gérée par l'AOP.

8.3.2 Les annotations

En programmation, une annotation [CEI+05] est une approche permettant d'ajou-ter des informations à un programme. Ces annotations peuvent ensuite être utilisées à la compilation pour automatiser certaines tâches. De même, elles peuvent permettre de générer du code de sorte à traiter de manière spécique l'exécution du programme.

Ces annotations prennent souvent la forme de commentaires, comme par exemple les annotations JML (Java Modeling Language) [JP01] de Java. Elles n'aectent jamais la logique du programme mais fournissent seulement des informations sur son comporte-ment. Ainsi, elles permettent de dénir des propriétés, des invariants ou des conditions, qui peuvent par la suite être vériés par un prouveur de cohérence du type Krakatoa [MPMU04]. De telles applications permettent par exemple de savoir si le programme respecte les annotations, et donc de savoir s'il est correct vis-à-vis de certaines proprié-tés.

Dans notre cas, les annotations peuvent être incluses dans la représentation abstraite an de rendre certaines informations explicites lors de la traduction du programme DSL.

Comme elles n'inuent pas sur la logique du programme, les annotations sont tradi-tionnellement utilisées pour exprimer des besoins non-fonctionnels. Ainsi, elles peuvent permettre de rendre la consommation de ressources explicite ou de déclencher des véri-cations à des points précis du programme.

Comme les aspects, les annotations permettent de modulariser la compilation du DSL parce qu'elles introduisent des informations qui sont par la suite interprétées selon un processeur d'annotations donné. Elles permettent donc de découpler la dénition de l'information de son exploitation.

8.3.3 La spécialisation de programme

La spécialisation de programme [CD93, JGS93] est une autre technique de program-mation générative. Elle consiste à instancier un programme par rapport à certains de ses paramètres d'entrée, permettant ainsi d'obtenir un code adapté pour un contexte

d'utilisation donné. Ainsi, la spécialisation permet à des composants logiciels génériques d'être personnalisés en fonction du contexte dans lequel ils sont utilisés.

Dans notre cas, la spécialisation peut être utilisée plus particulièrement an d'opti-miser l'implémentation d'un programme DSL. Parce que les unités de compilation sont invoquées par le code généré par le compilateur, les contextes de personnalisation sont déterminés par le compilateur du DSL. En outre, parce que les composants sont xes, ou évoluent lentement, leur spécialisation peut être déterminée de manière précise. Ainsi, cette transformation de programme peut être prévisible, ce qui n'est pas le cas en gé-néral.

Les deux sections suivantes proposent une illustration de l'application de ces ap-proches de programmation générative au sein de notre méthodologie. Elles montrent comment ces techniques permettent de compléter notre représentation abstraite en termes de génération de code et de combler les détails d'implémentation à travers la compilation d'unités fonctionnelles et non-fonctionnelles.