.
Università degli Studi di Firenze
Facoltà di Scienze Matematiche Fisiche e Naturali
Corso di Laurea in Informatica
Sviluppo di un ambiente per la specifica grafica
e l’interpretazione di termini COWS
Relatore: Rosario Pugliese
Candidato: Matteo Monaco
Correlatore: Francesco Tiezzi
Anno Accademico 2007-2008
ai miei genitori
1
Indice
1 Introduzione
6
2 Concetti principali
2.1 COWS: Calculus for Orchestration of Web Services
2.1.1 Sintassi . . . . . . . . . . . . . . . . . . . .
2.1.2 Semantica operazionale . . . . . . . . . . . .
2.1.3 Esempi di uso del linguaggio . . . . . . . . .
2.2 Eclipse e GMF . . . . . . . . . . . . . . . . . . . .
2.2.1 Eclipse e il concetto di plug-in . . . . . . . .
2.2.2 GMF . . . . . . . . . . . . . . . . . . . . . .
3 Architettura del software
3.1 GMF: sviluppo dell’editor grafico .
3.1.1 Sviluppo dei meta-modelli .
3.1.2 Partenza della nuova istanza
3.2 Inteprete . . . . . . . . . . . . . . .
3.3 Contributo della tesi . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
10
10
12
15
21
23
23
27
.
.
.
.
.
30
31
32
46
48
52
4 Interpretazione del linguaggio
4.1 Azioni . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Oggetti Action . . . . . . . . . . . . . . .
4.1.2 Strutture per manipolare le azioni . . . . .
4.1.3 Sviluppi futuri nella gerarchia delle azioni
4.2 Metodi dell’interprete . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
60
61
61
64
65
65
5 Analisi tecnica del parser XML
5.1 Connubio con GMF . . . . . . . . . . . .
5.1.1 Manipolazione dei file prodotti da
5.1.2 Struttura dei file . . . . . . . . .
5.2 Dalla grafica al testo . . . . . . . . . . .
5.2.1 Tecnologia Sax . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
79
80
80
82
83
84
2
. . . .
GMF
. . . .
. . . .
. . . .
.
.
.
.
.
5.2.2
5.3
Politica di parsing del file XML . . . . . . . . . . . .
5.2.2.1 Factory . . . . . . . . . . . . . . . . . . . .
5.2.2.2 Factory applicato alla lettura del XML . . .
5.2.3 Default Handler 2 . . . . . . . . . . . . . . . . . . . .
5.2.3.1 Metodi del DefaultHandler2 . . . . . . . . .
5.2.3.2 Strutture dati della classe Default handler 2
Dal testo alla grafica . . . . . . . . . . . . . . . . . . . . . .
5.3.1 Il file RiscritturaFileCows . . . . . . . . . . . . . . .
5.3.2 Consistenza dei dati . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
86
86
99
102
103
106
107
108
111
6 Un plugin per Eclipse
113
6.1 Sviluppo del plugin per Eclipse . . . . . . . . . . . . . . . . . 113
6.2 Partenza del plugin . . . . . . . . . . . . . . . . . . . . . . . . 121
7 Caso di studio
7.1 Struttura del progetto . . . . . . . . . . . . . . . . . . . . .
7.2 Esempio . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.2.1 Termine COWS caricato da file . . . . . . . . . . . .
123
. 123
. 127
. 136
8 Considerazioni e conclusioni finali
8.1 Considerazioni iniziali . . . . . . . . . . . . . . . . . . . . .
8.2 Problematiche riscontrate . . . . . . . . . . . . . . . . . . .
8.3 Conclusioni ed eventuali sviluppi futuri . . . . . . . . . . . .
139
. 139
. 141
. 144
A Interprete - Sviluppi implementativi
A.1 Analisi . . . . . . . . . . . . . . . . . . . . . .
A.2 Progettazione . . . . . . . . . . . . . . . . . .
A.2.1 Pattern Visitor . . . . . . . . . . . . .
A.2.2 Pattern Visitor integrato all’interprete
.
.
.
.
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
145
145
150
151
156
Elenco delle figure
2.1
2.2
2.3
Eclipse - esempio di ambiente - 1 . . . . . . . . . . . . . . . . 24
Eclipse - esempio di ambiente - 2 . . . . . . . . . . . . . . . . 25
Eclipse - esempio di ambiente - 3 . . . . . . . . . . . . . . . . 26
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
Il GMF Dashboard . . . . . . . . . . . . . . . . . . . . . . . .
Il modello Domain Model cowsVersione2.ecore . . . . . . . . .
Meta-modello cows.ecore . . . . . . . . . . . . . . . . . . . . .
Meta-modello cowsVersione2.ecore . . . . . . . . . . . . . . . .
Dashboard - particolare - Domain Gen Model . . . . . . . . .
Il Domain Gen Model . . . . . . . . . . . . . . . . . . . . . . .
Dashboard - particolare - Graphical Def Model . . . . . . . . .
Processo di creazione del modello Graphical Def Model particolare . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Il Graphical Def Model . . . . . . . . . . . . . . . . . . . . . .
Graphical Def Model - particolare relativo ai Figure Descriptor
Il Mapping Model . . . . . . . . . . . . . . . . . . . . . . . . .
Rappresentazione dell’editor grafico . . . . . . . . . . . . . . .
Esempio di termine COWS rappresentato graficamente
sull’editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Architettura del framework cowsAnalyser - 1 . . . . . . . . . .
Architettura del framework cowsAnalyser - 2 . . . . . . . . . .
Architettura del framework cowsAnalyser - 3 . . . . . . . . . .
Contributo teorico e pratico della tesi . . . . . . . . . . . . . .
Classi estrapolate dal framework proposto in [7] . . . . . . . .
Processo per la crazione di un’editor grafico . . . . . . . . . .
Diagramma del project Interpretegrafico . . . . . . . . . . . .
Architettura finale del sistema . . . . . . . . . . . . . . . . . .
3.9
3.10
3.11
3.12
3.13
3.14
3.15
3.16
3.17
3.18
3.19
3.20
3.21
5.1
5.2
5.3
33
34
36
37
40
40
41
41
42
43
45
48
49
50
52
53
54
56
56
58
59
Rappresentazione del procedimento di trasformazione specifica
grafica-specifica testuale . . . . . . . . . . . . . . . . . . . . . 81
esempio modello grafico cows . . . . . . . . . . . . . . . . . . 82
Architettura del pattern Factory . . . . . . . . . . . . . . . . . 87
4
5
5.4
5.5
Diagramma UML dell’esempio - parte 1 . . . . . . . . . . . . . 98
Diagramma UML dell’esempio - parte 2 . . . . . . . . . . . . . 98
6.1
6.2
6.3
6.4
6.5
Plugin
Plugin
Plugin
Plugin
Plugin
.
.
.
.
.
.
.
.
.
.
115
115
116
117
122
7.1
7.2
7.3
7.4
7.5
7.6
7.7
7.8
Eclipse - view del workspace . . . . . . . . . . . . . . . . . .
Eclipse - view del workspace - particolare . . . . . . . . . . .
Eclipse - view del workspace - 2 . . . . . . . . . . . . . . . .
Eclipse - funzionamento dell’applicativo - 1 . . . . . . . . . .
Eclipse - funzionamento dell’applicativo - 2 . . . . . . . . . .
Eclipse - funzionamento dell’applicativo - 3 . . . . . . . . . .
Eclipse - funzionamento dell’applicativo - 4 . . . . . . . . . .
Eclipse - funzionamento dell’applicativo - Sincrozizzazione su
due canali . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Eclipse - funzionamento dell’applicativo - 5 - Bank Service .
Eclipse - funzionamento dell’applicativo - 6 - Bank Service .
Eclipse - funzionamento dell’applicativo - 7 - Bank Service .
Eclipse - funzionamento dell’applicativo - 8 - Bank Service .
Eclipse - funzionamento dell’applicativo - 9 - Bank Service .
.
.
.
.
.
.
.
125
125
129
131
132
132
133
.
.
.
.
.
.
134
134
136
137
137
138
7.9
7.10
7.11
7.12
7.13
-
Figura
Figura
Figura
Figura
Figura
1
2
3
4
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
A.1 Politica di sviluppo - (a) Generatori di
(b) Sable CC . . . . . . . . . . . . . .
A.2 Gerarchia di oggetti Switchable . . . .
A.3 Gerarchia di oggetti Switch . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
compilatori
. . . . . . .
. . . . . . .
. . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
classici
. . . .
. . . .
. . . .
. . 149
. . 158
. . 159
Capitolo 1
Introduzione
Gli ultimi anni sono stati caratterizzati da alcune interessanti innovazioni in
ambito informatico. Parlando soprattutto di ambienti di rete viene immediato pensare ai servizi web come esempio di tecnologia che ha preso maggiormente piede nel panorama attuale. I servizi web [11] sono un’istanza di
SOC (Service-Oriented Computing), un paradigma di programmazione in
ambiente distribuito. Nella pratica, si tratta di una filosofia di progettazione
che fa uso del concetto di servizio visto come entità software autonoma e
indipendente.
Un servizio web ha lo scopo di supportare l’interoperabilità tra diversi elaboratori su una medesima rete informatica (grazie alla capacità di cooperare
e scambiare informazioni) e di garantire un alto grado di interazione, offrendo un’interfaccia utilizzabile da altri sistemi mediante la quale è possibile
interagire e attivare le operazioni descritte nell’interfaccia stessa. In pratica
possiamo vedere i servizi web come insiemi di funzionalità che possono essere
richieste (anche da altri servizi web) semplicemente invocandole.
I servizi web fanno uso di SOA (Service-Oriented Architecture), un’architettura in grado di mettere in pratica i concetti relativi al paradigma SOC,
quali integrazione, riutilizzo, composizione e orchestrazione di servizi. In particolare l’orchestrazione consente di aggregare e coordinare, mediante regole
e modelli prestabiliti, i servizi web e le loro componenti.
In questa direzione è andato ad esempio lo sviluppo di WS-BPEL [9], un
6
CAPITOLO 1. INTRODUZIONE
7
linguaggio standard per implementare l’orchestrazione di servizi web, tramite il quale è possibile generare servizi che mantengono tra le loro ulteriori
caratteristiche, modularità e scalabilità.
Progettare questo tipo di applicazioni crea non poche difficoltà. Non esistono infatti al momento strumenti in grado di analizzare e garantire proprietà fondamentali di servizi in ambiente concorrente e distribuito quali ad
esempio correttezza, affidabilità, sicurezza. Come ausilio alla progettazione,
alcuni gruppi di ricerca hanno proposto l’uso di metodi formali, strumenti in
grado di esprimere teorie a riguardo.
I metodi formali sono strumenti teorici che permettono di descrivere sistemi e proprietà di sistemi, inizialmente definendone la struttura mediante formalismi appositi e successivamente analizzando determinati concetti facendo
uso di logiche adeguate e strumenti semi-automatici.
Tra i più usati metodi formali per la descrizione di sistemi concorrenti
e distribuiti vi sono i calcoli di processi. Un calcolo di processo è una notazione per modellizzare sistemi dotata di una sintassi ben precisa e di una
semantica formale. Vari calcoli di processo sono stati proposti per il SOC,
molti di questi sono varianti di calcoli già studiati arricchiti con costrutti
appropriati (ad esempio la variante del π-calcolo con le transazioni [1]). In
[8] viene proposto un approccio differente, un calcolo di processi completamente nuovo ed appositamente progettato per il SOC denominato COWS
(Calculus of Orchestration of Web Services). Lo sviluppo di COWS è stato fortememte influenzato da WS-BPEL, ma anche dai calcoli di processo
più noti in letteratura. Scopo di questa tesi è integrare alcuni strumenti per
COWS contribuendo cosı̀ ad accrescerne l’utilità pratica. Precedenti lavori
hanno sviluppato alcuni strumenti interessanti. In [10] , tramite l’ambiente
di sviluppo Eclipse e un tool denominato GMF, è stato sviluppato un editor
grafico per rappresentare graficamente i termini COWS. In [7] è stato proposto un sistema di tipi per il linguaggio ed è stato implementato uno strumento
automatico che permette l’analisi lessicale e sintattica e l’interpretazione semantica di un termine. Partendo da questo scenario, prima sono state apportate alcune modifiche delle componenti preesistenti dove ritenuto necessario;
successivamente è stata sviluppata un’architettura per la loro integrazione.
CAPITOLO 1. INTRODUZIONE
8
Obbiettivo finale è stato lo sviluppo di uno strumento che fornisca un editor
grafico con cui rappresentare i processi, che sia in grado di estrapolarne la
specifica testuale e che sia al contempo in grado di animare quest’ultima, in
accordo alla semantica operazionale di COWS e generare la rappresentazione
grafica del termine ottenuto dopo un passo di computazione.
Struttura della tesi
La tesi è organizzata come segue:
- Nel Capitolo 2 vengono illustrati i concetti principali del calcolo di processi COWS. Successivamente sono presentati l’ambiente di sviluppo
Eclispe e il framework GMF (Graphical Modelling Framework ) usato
per lo sviluppo dell’editor grafico;
- Nel Capitolo 3 viene mostrata nei dettagli quella che è l’architettura
dell’applicativo sviluppato. Vengono inizialmente descritti il processo di
creazione dell’editor grafico [10] e lo strumento automatico per l’analisi
dei termini COWS [7]. Successivamente viene mostrato come queste
funzionalità vengono integrate e come consentono di mettere in pratica
l’effettiva realizzazione del software;
- Il Capitolo 4 si sofferma maggiormente sull’implementazione dell’analizzatore, dettagliando in modo approfondito la parte che mette in pratica
l’analisi semantica;
- Nel Capitolo 5 vengono studiate le politiche utilizzate per realizzare
l’applicativo e i meccanismi che consentono di passare dalla specifica
grafica alla specifica testuale e viceversa;
- Nei Capitolo 6 e 7 vengono presentate due versioni dello strumento realizzato: una versione mira ad estendere la piattaforma Eclipse e una seconda versione realizzata come applicazione Java da eseguire dalla console di Eclipse. Viene infine mostrato un caso di studio dell’applicativo
(nella versione Java Application);
CAPITOLO 1. INTRODUZIONE
9
- Il Capitolo 8 conclude la tesi con alcune considerazioni sulle
problematiche incontrate e su possibili sviluppi futuri.
La tesi termina con un’appendice dove si descrivono le basi teoriche e pratiche
che hanno portato allo sviluppo dell’analizzatore lessicale e sintattico.
Capitolo 2
Concetti principali
Il capitolo descrive il linguaggio COWS e successivamente illustra l’ambiente
di sviluppo Eclipse e il Graphical Modelling Framework (GMF) il plug-in
grazie al quale sono realizzate le specifiche grafiche.
2.1
COWS: Calculus for Orchestration of
Web Services
Prima di definire formalmente il linguaggio, viene fornito un quadro generale
delle caratteristiche principali.
Il linguaggio è caratterizzato da due elementi base, i partner p e le operation o. Sono utilizzati per individuare un endpoint, una sorta di canale di
comunicazione. Un endpoint è visto non come un elemento atomico ma come
composizione di un partner p e di un’operation o denotato (p.o); può essere
interpretato come una specifica implementazione di o fornita da p. Questo
permette di ottenere un meccanismo di naming piuttosto flessibile che consente allo stesso servizio di essere identificato da nomi logici differenti. Per
esempio, il seguente servizio
p1 .o ? w . s1 + p2 .o ? w . s2
accetta richieste per la stessa operation o attraverso differenti partner con
10
CAPITOLO 2. CONCETTI PRINCIPALI
11
modalità distinte. Il servizio s1 implementa funzionalità fornite quando la
richiesta è elaborata attraverso il partner p1 , mentre s2 implementa funzionalità differenti fornite quando la richiesta arriva attraverso il partner p2 .
Oltretutto, il meccanismo permette di trattare i nomi degli endpoint in modo separato, come nel caso delle interazioni richiesta-risposta dove il fornitore
del servizio conosce il nome dell’operazione di risposta ma non il nome del
partner a cui rispedire i risultati. Ad esempio nel seguente servizio di ping
p.oreq ? < x > . (x.ores ! <′ sono attivo′ >)
il nome del partner a cui spedire i risultati viene scoperto solo in fase di elaborazione. Il meccanismo è sufficientemente espressivo da supportare locazioni
esplicite: un servizio localizzato infatti può essere rappresentato mediante
l’utilizzo del medesimo partner per tutti gli endpoint di ricezione. Inoltre,
considerando problematiche inerenti ad una possibile implementazione del
calcolo, COWS è stato progettato in modo da assicurare che i nomi ricevuti
dinamicamente non possono essere utilizzati per definire nuovi endpoint di
ricezione.
Le entità computazionali di COWS sono chiamate servizi. Tipicamente
un servizio genera una specifica istanza per servire ogni richiesta ricevuta.
Un’istanza è composta da thread concorrenti che possono offrire una scelta
tra attività di ricezione alternative. I servizi possono essere abilitati a ricevere messaggi multipli senza un preciso ordine d’arrivo in modo che il primo
che arriva genera l’istanza del servizio dove poi vengono instradati tutti i
messaggi successivi. Il pattern-matching è il meccanismo usato per garantire
la correlazione di messaggi che formano logicamente la stessa sessione, facendo uso dei loro stessi contenuti. In questo modo si permette di riconoscere
i dati importanti, necessari all’identificazione delle istanze dei servizi e di
conseguenza effettuare il corretto instradamento.
Presentiamo di seguito il linguaggio mostrando la sua sintassi e la sua
semantica.
12
CAPITOLO 2. CONCETTI PRINCIPALI
2.1.1
Sintassi
La sintassi di COWS, data in Tabella 2.1, è parametrizzata da tre insiemi
numerabili e disgiunti a due a due:
′
- l’insieme delle etichette killer, indicate con k, k , ... ;
′
- l’insieme dei valori, indicati con v, v , ... ;
- l’insieme delle variabili, indicate con x, y, ... .
Servizi
s
::=
|
|
|
|
|
|
kill(k)
′
u.u !e
s|s
{s}
[d]s
∗s
g
kill
Invoke
Parallel
Protection
Delimitation
Replication
Guarded Choice
Guardie
g
::= p.o?w.s
|
O
|
g+g
Receive
Nil
Scelta guardata
Tabella 2.1: Sintassi di COWS
Si assume che l’insieme dei valori includa l’insieme dei nomi, indicati con
n, m ... , principalmente usato per rappresentare partner e operation. Il linguaggio è inoltre parametrizzato mediante delle espressioni, indicate con e.
Le espressioni e possono essere espressioni booleane, espressioni numeriche
o espressioni su stringhe (la concatenazione ad esempio). Si assume che contengano almeno valori e variabili (ma non etichette killer). Nel seguito si
′
′
usano le notazioni: p, p ... per indicare nomi di partner, o, o , per indicare
le operation. Inoltre usiamo w per indicare insiemi di valori e variabili, u per
indicare nomi e variabili, d per nomi, variabili e etichette killer.
COWS supporta nove differenti attività:
CAPITOLO 2. CONCETTI PRINCIPALI
13
- Nil identifica il servizio vuoto. Rappresentato sintatticamente mediante
il carattere O;
- Kill identifica la terminazione forzata di tutte le attività eseguite in
parallelo e non protette (mediante l’operatore “Protection”). La sintassi
è kill(k) dove k permette di regolare il raggio di azione dell’attività.
L’operatore Kill ha la precedenza su tutte le altre attività;
- Invoke rappresenta l’invocazione di un’operation fornita da un partner. La Invoke può essere eseguita solo se è possibile valutare la sua
espressione. La sintassi è
′
u.u !e
′
dove u e u sono rispettivamente il nome del partner e dell’operazione
e e è una tupla di espressioni;
- Receive rappresenta la ricezione di una invocazione. La sintassi è
p.o?w.s
dove p e o sono rispettivamente il nome del partner e dell’operazione
che compongono l’endpoint, w rappresenta la tupla di parametri per la
ricezione del messaggio e s è il servizio che viene eseguito dopo l’attività
di ricezione.
La sincronizzazione tra due servizi, quindi una comunicazione (tra
un’attività di Receive e una di Invoke), avviene quando gli endpoint
della Invoke e della Receive sono uguali e i loro argomenti soddisfano
una funzione di matching (che definiremo meglio in seguito). In questo caso si genera una funzione di sostituzione delle varibili della tupla
w con i valori inviati dall’elaborazione di e. Nel caso in cui s sia Nil,
utilizzeremo la notazione semplificata p.o?w;
- Guarded Choice definisce la scelta non deterministica tra due
termini. La sintassi è
g1 + g2
CAPITOLO 2. CONCETTI PRINCIPALI
14
dove g1 e g2 sono guardie, servizi che possono essere esclusivamente Nil,
Receive o Guarded Choice;
- Parallel consente l’esecuzione in parallelo di più servizi. La sua sintassi
è
′
s|s
′
dove s e s sono servizi COWS qualsiasi;
- Protection definisce un servizio protetto dagli effetti di una
terminazione forzata (attività Kill). La sintassi è
{s}
dove s è il servizio che intendiamo proteggere;
- Delimitation delimita la visibilità di un dato elemento. La sintassi è
[d]s
dove d può essere una variabile, un nome o una killer label; s è l’ambito
di validità dell’elemento. In pratica, [d]s vincola “d” all’interno del
termine “s”. L’operatore riveste tre ruoli differenti; può essere usato per
dichiarare l’ambiente di una variabile, il campo d’azione di una kill che
utilizza una certa etichetta o l’ambito di visibilità di un nome privato
La Delimitation è l’unica operazione legante all’interno di COWS;
- Replication permette di creare un numero illimitato di copie di un
servizio. Utilizzata principalmente per implementare servizi persistenti
o ricorsivi. La sintassi è
∗s
dove s è il servizio che viene replicato.
Un’occorrenza di una variabile, di un nome o di un’etichetta viene detta libera se non è sotto la portata di una Delimitation. Chiameremo f d(t)
l’insieme di occorrenze libere di variabili, nomi e label in un termine t e
CAPITOLO 2. CONCETTI PRINCIPALI
15
f k(t) l’insieme di occorrenze libere delle label dell’attività kill nel termine t.
Due termini sono detti alfa-equivalenti se uno può essere ottenuto dall’altro
rinominando le variabili, i nomi e le label sotto la portata di una Delimitation.
2.1.2
Semantica operazionale
La semantica operazionale di COWS è definita solo per servizi chiusi, quindi
servizi privi di variabili ed etichette libere, anche se le regole semantiche
hanno validità anche per servizi non chiusi. La semantica è data in termini
di una relazione di congruenza strutturale e di una relazione di transizione
etichettata.
Congruenza strutturale
La congruenza strutturale, indicata dal simbolo ≡, definisce servizi rappresentati in modo sintatticamente diverso ma il cui comportamento è il
medesimo.
∗O
∗s
{O}
{{s}}
{[d]s}
[d]O
[d1 ][d2 ]s
s1 |[d]s2
≡
O
(repl1 )
≡
s| ∗ s
(repl2 )
≡
O
(prot1 )
≡
{s}
(prot2 )
≡
[d]{s}
(prot3 )
≡
O
(delim1 )
≡ [d2 ][d1 ]s
(delim2 )
≡ [d](s1 |s2 ) if d 6∈ fd (s1 ) ∪ fk (s2 ) (delim3 )
Tabella 2.2: Regole di congruenza strutturale di COWS
In Tabella 2.2 sono riportate le regole riguardanti la Replication, la
Protection e la Delimitation. Alcune regole standard, quali ad esempio
la commutatività dell’operatore Parallel o della Guarded Choise, vengono
omesse.
Si noti che la regola (delim3) estende il campo d’azione della Delimitation,
in modo da consentire la comunicazione di nomi ristretti eccetto quando
16
CAPITOLO 2. CONCETTI PRINCIPALI
l’argomento d della delimitazione è un’etichetta killer libera di s2 (altrimenti
si rischia di coinvolgere nella terminazione forzata anche s1 ).
Transizione etichettata
Definiamo inizialmente alcune funzioni necessarie in seguito:
- [[ ]], funzione per la valutazione delle espressioni chiuse (cioè senza
variabili). Associa un valore ad un’espressione chiusa;
- σ è una funzione di sostituzione rappresentata come una collezione di
associazioni x 7−→ v. Applicare la sostituzione σ al servizio s (indicata
con s · σ) consiste nel sostituire ogni occorrenza libera di x in s con il
valore v, per ciascuna x 7−→ v appartenente a σ, utilizzando eventualmente l’alfa-conversione per evitare che il nuovo nome sia catturato da
una Delimitation all’interno di s.
Con |σ| si denota il numero di coppie variabili-valori in σ. Con σ1 ⊎ σ2
si denota l’unione delle due sostituzioni σ1 , σ2 ;
- M( , ) è la funzione di pattern matching su dati semi-strutturati. Consente di determinare se una comunicazione è possibile su un determinato endpoint, quindi se una Receive e una Invoke possono essere sincronizzate. In Tabella 2.3 sono riportate le regole. Le regole indicano
M(x, v) = {x 7−→ v}
M(v, v) = ∅
M(w1 , v1 ) = σ1
M(w¯2 , v¯2 ) = σ2
M((w1 , w¯2 ), (v1 , v¯2 )) = σ1 ⊎ σ2
Tabella 2.3: Regole per la funzione di pattern matching
che due tuple soddisfano il match se hanno lo stesso numero di campi
e campi corrispondenti hanno variabili/valori che soddisfano il match.
Le variabili soddisfano il match con ogni valore, due valori soddisfano
CAPITOLO 2. CONCETTI PRINCIPALI
17
il match solo se identici. Quando la funzione di pattern matching è applicata a due tuple w e v, restituisce una sostituzione delle variabili in
w altrimenti non è definita;
- halt( ), è una funzione che prende come argomento un servizio s e
restituisce il servizio ottenuto preservando solo le attività protette di
s. In Tabella 2.4 sono mostrate le regole nel dettaglio ;
halt(kill(k)) = halt(u1 .u2 !ē) = halt(g) = O
halt({s}) = ({s})
halt(s1 |s2 ) = halt(s1 ) | halt(s2 )
halt([d]s) = [d] halt(s)
halt(*s) = *halt(s)
Tabella 2.4: Regole per la funzione halt
- noc( , , , ), prende in input un servizio s, un endpoint (p.o), una
tupla di parametri di ricezione w̄ e una tupla di valori v̄, restituendo
in output true se non ci sono conflitti di ricezione all’interno di s (s
non può eseguire immediatamente un’attività di ricezione sull’endpoint
(p.o) che soddisfi il matching con v̄).
Il predicato noc usa la nozione di contesto attivo. Dato un servizio A
e dato un servizio s scriviamo Aksk, per intendere che in un punto
imprecisato del termine COWS “A”, andiamo a porre s in modo da
avere un nuovo termine. Se il nuovo termine Aksk è ancora un servizio
COWS allora A può eseguire immediatamente un’attività di s.
Formalmente il contesto attivo è genearato dalla seguente grammatica:
A ::= k·k | A+g | g+A | A|s | s|A | {A} | [d]A | ∗A
Una definizione del predicato noc è quindi esprimibile in questo modo
noc(s, p.o, w̄, v̄) = true
18
CAPITOLO 2. CONCETTI PRINCIPALI
se vale la seguente implicazione logica
′
(s = Akp.o?w¯1 .s k ∧ M(w¯1 , v̄) = σ) ⇒ |M(w̄, v̄) ≤ |σ|
′
dove con s = Akp.o?w¯1 .s k si indica che s può essere scritto come il
′
contesto attivo di A riempendolo con p.o?w¯1 .s .
Definiamo quindi adesso effettivamente una relazione di transizione etichetα
tata −→ come la più piccola relazione sui servizi indotta dalle regole definite
nella Tabella 2.5 dove l’etichetta α è generata dalla seguente grammatica:
α
::=
† k
| (p.o) ⊳ v̄
| (p.o) ⊲ w̄
| p.o⌊σ⌋w̄v̄
| †
Nel dettaglio, rappresentano le seguenti azioni:
- † k indica l’esecuzione della richiesta di terminazione di un termine,
delimitato all’interno dello delimitazione di [k];
- (p.o) ⊳ v̄ indica l’esecuzione di una Invoke sull’endpoint (p.o);
- (p.o) ⊲ w̄ indica l’esecuzione di una Receive sull’endpoint (p.o);
- p.o⌊σ⌋w̄v̄ con σ 6= ∅ indica l’esecuzione di una comunicazione sull’endpoint (p.o), con i parametri w e v e la funzione di sostituzione σ ancora
da applicare;
- p.o⌊∅⌋w̄v̄ indica una comunicazione sull’endpoint(p.o) senza sostituzioni pendenti;
- † indica l’esecuzione della terminazione forzata.
Una computazione da un servizio chiuso s0 è una sequenza di transizioni
nella forma:
α1
α2
s0 −→
s1 −→
s2 . . .
dove per ogni “i” si ha che αi è o un’azione di terminazione forzata † o una
comunicazione senza sostituzioni pendenti p.o⌊∅⌋w̄v̄ . Ogni servizio si è detto
19
CAPITOLO 2. CONCETTI PRINCIPALI
riduzione di s0 . Le regole semantiche sono riportate nella seguente Tabella
2.5:
(p.o)⊲w̄
† k
kill(k) −→ O (kill)
p.o?w̄.s −−−−→ s (rec)
α
kēk = v̄
g1 −
→ s
(inv)
(p.o)⊳v̄
p.o!ē −−−−→ O
p.o⌊σ⊎{x7−→v}⌋w̄v̄
† k
′
s −−−−−−−−−−→ s
(delsub )
p.o⌊σ⌋w̄v̄
′
s −−→ s
(delkill )
†
[k]s −
→ [k]s′
[x]s −−−−−→ s′ · {x 7−→ v}
α
(choise)
α
→ s
g1 + g2 −
′
s −
→ s d 6∈ d(α)
s = Akkill(d)k =⇒ α = †, † k
α
[d]s −
→ [d]s′
(p.o)⊲w̄
s1 −−−−→ s1
′
(p.o)⊳v̄
s2 −−−−→ s2
α
′
′
s −
→ s
(delpass )
α
{s} −
→ {s′ }
M(w̄, v̄) = σ
noc(s1 |s2 , p.o, w̄, v̄)
p.o⌊σ⌋w̄v̄
(prot)
(com)
s1 |s2 −−−−−→ s1 ′ |s2 ′
p.o⌊σ⌋w̄v̄
† k
′
s1 −−−−−→ s1 noc(s2 , p.o, w̄, v̄)
p.o⌊σ⌋w̄v̄
(parconf )
α
α
(parkill )
† k
α
′
s1 |s2 −
→ s1 ′ |s2
′
s1 | s2 −−→ s1 ′ | halt(s2 )
s1 |s2 −−−−−→ s1 ′ |s2
s1 −
→ s1 α 6= (p.o⌊σ⌋w̄v̄), † k
s1 −−→ s1
(parpass )
′
s ≡ s1 s1 −
→ s2 s2 ≡ s
α
s −
→ s′
Tabella 2.5: Semantica operazionale di COWS
Commenti sulla semantica
- (kill) specifica che l’esecuzione di un termine kill(k) comporta la terminazione forzata per tutti i servizi paralleli al termine stesso, che si
trovano nel campo d’azione della delimitazione [k] e che non risultano
sotto l’effetto di una Protection;
- (rec) indica la ricezione dei parametri specificati dalla tupla w̄;
(cong)
CAPITOLO 2. CONCETTI PRINCIPALI
20
- (inv) è l’attività di invocazione di un servizio che può essere eseguita
solo se, come richiesto nella premessa della regola d’inferenza, tutte le
espressioni contenute nell’argomento sono valutabili;
- (choice) indica che date due alternative, il termine si evolve
nell’alternativa scelta;
- ( delsub ) è la regola semantica che applica una sostituzione di variabile
x delimitata da scope [x]. Per ogni occorrenza di x all’interno del termine, si applica la sostituzione prevista x 7−→ v. Dopo la sostituzione,
l’operatore Delimitation sparisce (le variabili sono write-once);
- ( delkill ) fa si che solo il servizio s argomento della delimitation [k]s
è coinvolto negli effetti della terminazione. In pratica l’effetto della
terminazione forzata viene interrotto trasformando l’etichetta † k in †.
La presenza della delimitazione è inoltre assicurata dall’assunzione che
la semantica è definita per servizi chiusi;
- ( delpass ) ulteriore regola che coinvolge la delimitazione. [d]s si comporta
come s, a meno che d non sia un parametro (nome o variabile qualsiasi)
coinvolta nell’esecuzione dell’azione α o un’etichetta killer. In quel caso
la regola non può essere applicata ed entra in gioco una tra (delsub ) o
(delkill );
- (prot) indica che il codice critico può essere protetto dalla terminazione forzata, se posto all’interno dell’operatore di protection {}. Il
comportamento di s rimane il medesimo ma protetto;
- (com) attiva l’effettiva comunicazione (o indifferentemente diciamo
“sincronizzazione”) tra una Receive e una Invoke con il conseguente
passaggio di parametri sul generico endpoint (sul generico “canale di
comunicazione”) (p.o). Due servizi, processati in parallelo devono generare attività di invocazione e di ricezione tale che i loro argomenti
soddisfano la funzione di matching, evitando conflitti per le attività di
ricezione;
CAPITOLO 2. CONCETTI PRINCIPALI
21
- ( parconf ) indica che se più di un’attività di ricezione è abilitata alla comunicazione solo quella più definita, quindi quella che genera la
sostituzione di cardinalità minore, è coinvolta nella comunicazione;
- ( parkill ) permette l’esecuzione di servizi in parallelo come esecuzione
intervallata di singoli servizi. Quando è attiva una terminazione forzata
tutte le attività parallele devono essere terminate a meno che non siano
protette dall’operatore Protection;
- ( parpass ) ulteriore regola che coinvolge il parallelismo. Ogni servizo in
esecuzione in parallelo può essere eseguito senza problemi a meno che
l’azione α non sia una comunicazione o una terminazione forzata;
- (cong) stabilisce che due servizi strutturalmente congruenti possiedono
le medesime transizioni.
2.1.3
Esempi di uso del linguaggio
Concludiamo la presentazione del linguaggio COWS con alcuni esempi.
Esempio 1
Un’attività di Receive e un’attività di Invoke interagiscono tra loro e possono
farlo solo se entrambe sono sotto la portata delle delimitazioni delle proprie
variabili e dei propri nomi riservati. Per attivare una comunicazione di nomi
privati bisogna pertanto estendere la loro portata ed eventualmente estendere
la portata delle variabili usate nella receive:
22
CAPITOLO 2. CONCETTI PRINCIPALI
′
[x](p.o? < x > .s|s ) | [n](p.o! < n >)
′
[n]( [x](p.o? < x > .s|s ) | p.o! < n >)
′
[n][x](p.o? < x > .s|s ) | (p.o! < n >)
≡(delim3 )
≡(delim3 )
p.o⌊∅⌋<x><n>
−−−−−−−−−→
′
[n](s | s ) · {x 7−→ n}
Esempio 2
L’esempio mostra gli effetti dell’esecuzione di una Kill interna ad una Protection. Vediamo che kill(k) termina tutti i servizi processabili in parallelo e interni nel campo d’azione della delimitazione [k], eccetto quelli protetti allo stesso livello dell’attività di terminazione. Si assume che
halt(s1 ) = halt(s3 ) = O
[k]({s1 | {s2 } | kill(k)} | s3 ) | s4
†
−→
[k]{ {s2 } } | s4
Esempio 3
Mostriamo un servizio persistente (implementato attraverso la Replication):
∗[x] ( p1 .o ? < x > .s1 | p2 .o ? < x > .s2 ) | p1 .o! < v > | p2 .o! < v >
p.o⌊∅⌋<x><v>
−−−−−−−−→
∗[x] ( p1 .o ? < x > .s1 | p2 .o ? < x > .s2 ) | s1 {x 7−→ v} |
| p2 .o ? < v > .s2 {x 7−→ v} | p2 .o ! < v >
CAPITOLO 2. CONCETTI PRINCIPALI
23
Il servizio persistente e l’istanza generata sono entrambi nella condizione
di ricevere sull’endpoint (p2 .o). Competono quindi per la medesima richiesta p2 .o ! < v > . Le regole semantiche (com) e (parconf ) permettono l’evoluzione solo dell’istanza generata, prevenendo pertanto la creazione di una
nuova:
∗[x] ( p1 .o ? < x > .s1 | p2 .o ? < x > .s2 ) | s1 .{x 7−→ v} | s2 {x 7−→ v}
2.2
Eclipse e GMF
La sezione descrive tutti gli aspetti relativi all’ambiente di sviluppo. Introduce Eclipse, una piattaforma software che fornisce alcuni strumenti necessari
alla progettazione di programmi informatici e di seguito introduce il concetto
di plug-in. Successivamente viene fatto un accenno al framework di GMF, il
tool mediante il quale è possibile creare un editor grafico. Vedremo che una
delle possibili versioni del software è come parte integrante della piattaforma, una vera e propria estensione delle funzioni offerte; nei capitoli successivi
vengono descritte le fasi e le modalità per mettere in pratica tale processo,
dando anche dettagli su alcuni problemi riscontrati.
2.2.1
Eclipse e il concetto di plug-in
Eclipse è un IDE, da Integrated Development Environment, in italiano ambiente integrato di sviluppo; quindi un software che fornisce ai programmatori un ambiente per lo sviluppo di codice informatico. Sviluppato in Java,
quindi orientato agli oggetti, consiste in un editor per il codice sorgente, un
compilatore e/o un interprete, delle librerie di supporto (un sottoinsieme di
componenti denominato RCP (Rich Client Platform)) e un debugger. È utilizzato per la produzione di software di vario genere, è incentrato sull’uso
di plug-in, componenti software autonome che interagiscono con altri programmi, ampliandone le funzionalità. È open-source, piuttosto versatile ed è
adatto per lo sviluppo non solo di codice Java, ma grazie a varie estensioni,
anche all’uso di codice Php, Xml, Tex ecc.
CAPITOLO 2. CONCETTI PRINCIPALI
24
Il cuore dell’applicativo è il Platform Runtime. I moduli che lo compongono definiscono l’architettura, caricando all’avvio tutte le varie componenti,
tra cui le due principali: il Workbench (l’interfaccia grafica) e il Workspace
(lo spazio contenente i file e i progetti dell’utente) oltre ad eventuali componenti aggiuntivi installati sulla piattaforma. Strettamente legati al Platform
abbiamo poi il Java Development Tool (JDT), una componente orientata a
gestire lo sviluppo di codice Java e il Plug-in Development Environment che
estende a sua volta il JDT come componente atta allo sviluppo di plug-in in
Java. La Figura 2.1 mostra in dettaglio i tre livelli principali.
Figura 2.1: Eclipse - esempio di ambiente - 1
Entrando maggiormente nei dettagli del Platform, vediamo che è possibile dividerlo in due ulteriori sottoinsiemi di componenti, uno il cuore vero
e proprio dell’applicativo e l’altro mette in pratica tutta la parte relativa
all’interfaccia utente e tutte le politiche per interagire; troviamo oltre ai già
citati Workspace e Workbench anche alcune componenti grafiche. La Figura
2.2 mostra le varie componenti del Platform.
CAPITOLO 2. CONCETTI PRINCIPALI
25
Eclipse si basa sul principio di Contribution. In pratica, l’intera piattaforma (Platform a parte) non ha nessuna funzionalità built-in, ma tutto è
basato sul concetto che i componenti, anche quelli forniti di default, sono di
fatto dei plug-in e il Platform altro non è che un kernel che oltre a fornire
tutte le funzionalità basilari, ha anche il compito di caricare all’avvio tutti i
plug-in installati sulla piattaforma. Pertanto il Platform è stato progettato
oltre che per l’organizzazione generale delle varie parti anche per la successiva gestione. [4] descrive il processo principale come mostrato in Figura 2.3.
Figura 2.2: Eclipse - esempio di ambiente - 2
Parlando di un plug-in invece abbiamo fatto accenno ad una componente
autonoma il cui scopo è quello di interagire con altre componenti ed estenderne le funzionalità. Un plug-in si concretizza fisicamente come una directory
contenente codice Java (spesso raccolto in archivi JAR), risorse di vario tipo
(icone, immagini, gif ... ecc.) e due file manifesto manifest.mf e il plugin.xml
che descrivono il plug-in. Un tool, specialmente se di dimensioni consisten-
CAPITOLO 2. CONCETTI PRINCIPALI
26
Figura 2.3: Eclipse - esempio di ambiente - 3
ti, può essere costituito da più plug-in e un plug-in può a sua volta essere
frammentato in più parti.
Il file plugin.xml definisce i vari aspetti del plug-in. In pratica:
- tiene memoria dell’identificativo (dichiarato come Plugin id) che è
univoco per ogni plug-in di Eclipse;
- implementa, se necessario, le librerie del plug-in;
- mette in pratica il concetto di Extensions. Con “Extensions” si intende
l’azione di estendere un aspetto specifico della piattaforma;
- mette in pratica il concetto di Extensions Point. Con “Extensions
Point” si intende l’effettivo contributo alla piattaforma con nuovi punti
di estensione.
Il file manifest.mf definisce invece le dipendenze a runtime di un plug-in.
Esso:
CAPITOLO 2. CONCETTI PRINCIPALI
27
- permette di dichiarare l’esistenza di un plug-in;
- permette di dichiarare le librerie che del plug-in fanno parte, sia esso
codice raccolto in archivi JAR o meno;
- dichiara le dipendenze;
- dichiara infine i servizi offerti dal plug-in. In pratica, a cosa serve.
Sarà il Plug-in Development Enviroment a gestire principalmente i processi per la creazione di un Plugin-project (sia esso vuoto o meno), istanziando
al momento della creazione i manifesti, le dipendenze, i punti di estensione
e la classe di base del plug-in (la Plug-In Class). Tutti i plug-in vengono
rilevati all’avvio dal Platform Runtime, leggendo i loro manifesti e creando
un cosiddetto Plug-in Registry, cioè un registro di informazioni reso disponibile a runtime tramite API (ovvero l’interfaccia di programmazione), consentendo cosı̀ l’aggiunta o la rimozione di plug-in durante l’esecuzione della
piattaforma.
2.2.2
GMF
In questo paragrafo viene descritto il framework di GMF (il Graphical Modelling Framework). Esso è ottenuto dall’unione di due precedenti framework,
EMF e GEF (che riflettendo sui concetti espressi precedentemente, altro non
sono che plug-in che vengono installati per essere usati sotto Eclipse e nel
nostro caso, dipendenze del nostro plug-in). Vengono descritti gli aspetti
principali; maggiori informazioni sono reperibili da [2].
EMF (Eclipse Modeling Framework) è un plug-in per la generazione di
tool e applicativi basati su un modello strutturato. L’obbiettivo è quello di
sfruttare i concetti di formalismo e di modello per favorire la fase di programmazione. Si sviluppa un modello (sotto forma di diagramma UML, schema
XML o interfaccia Java annotata) e lo si da in pasto al framework. L’output risultante sarà una serie di classi Java completamente implementate che
realizzano i vincoli, le relazioni e le associazioni descritte nel modello di partenza. In questo modo il lavoro più ripetitivo e complicato (come la scrittura
CAPITOLO 2. CONCETTI PRINCIPALI
28
dei metodi getter e setter o la gestione delle relazioni tra oggetti) viene decisamente semplificato dalla creazione automatica messa a disposizione da
EMF. EMF è predisposto per fornire ulteriori funzionalità, quali il supporto
di personalizzazione del codice, una Reflective API per generare dinamicamente modelli, il supporto per serializzare e deserializzare dati e la possibilità
di generare semplici editor grafici ad albero per le applicazioni. È costituito da tre componenti principali: EMF Edit, EMF Codegen e EMF Core.
Quest’ultimo, come spiega il nome, è poi la componente principale.
EMF Edit fornisce classi riutilizzabili di supporto alla visualizzazione di
oggetti del modello e EMF Codegen fornisce un framework estensibile per
l’importazione di modelli occupandosi della generazione vera e propria di
codice.
EMF Core, il cuore del framework, include tutto quello che riguarda lo
sviluppo del metamodello ecore, che è il “formato” in cui vengono convertiti
tutti i modelli utilizzati.
Il secondo framework è GEF (Graphical Editing Framework). GEF fornisce un editor su cui è possibile disegnare figure, collegarle, ridimensionarle
ecc. Nel nostro caso è ideale per la rappresentazione di un formalismo grafico.
In pratica facilita lo sviluppo di rappresentazioni grafiche di modelli esistenti, creando grafici e diagrammi dotati delle tipiche funzionalità di un editor
grafico quali drag e drop, copia/incolla, undo e redo. Non genera codice di
alcun tipo, ma si limita a fornire una serie di classi per semplificare la programmazione di un editor. Si basa sul paradigma Model-View-Controller: il
modello è definito mediante EMF; GEF si occupa di realizzare View e Controller. È costituito da due componenti: Draw2D, la View, una libreria che
si occupa della parte grafica (quali la realizzazione di forme, colori, contorni e layout) e il Graphical Editing Framework effettivo, il Controller, che si
occupa di tutte le restanti funzionalità. In pratica un insieme di classi denominate EditPart che rappresentano i componenti principali dell’applicativo,
mappando gli oggetti del modello con le figure.
GMF è la naturale evoluzione. In pratica abbiamo un tool (EMF) a cui
dato in input un metamodello (realizzato ad esempio mediante UML) dove
si modellizzano costrutti esprimibili in un certo linguaggio (nel nostro caso il
CAPITOLO 2. CONCETTI PRINCIPALI
29
calcolo di processi COWS) sviluppa le classi Java che realizzano tutte le entità
del linguaggio (processi, connessioni ecc). L’output di EMF viene dato in
input ad un altro tool (GEF) che prende ogni entità e l’associa ad una figura
geometrica. Successivamente mette a disposizione un editor dove e possibile
manipolare le figure. GMF unisce i due framework in un unico tool. Quindi
dato in pasto a GMF la modellizzazione di un linguaggio, abbiamo come
risultato finale un editor dove ad ogni elemento del linguaggio è associata
una figura e tali figure sono manipolabili sull’editor. Quindi ad esempio, se io
riesco a modellizzare un’operazione di comunicazione tra due processi posso,
seguendo regole opportune, rappresentare questa cosa come due quadrati
(i due processi) inseriti dentro un rettangolo (che rappresenta l’azione di
comunicazione).
Capitolo 3
Architettura del software
Il capitolo descrive l’architettura del software sviluppato in tesi. È stato analizzato, progettato e infine implementato un sistema in grado di prendere
un termine COWS e trasformarlo in una forma grafica associata. Viceversa
è in grado di prendere una specifica grafica, estrapolare il termine COWS
e renderlo nella sua forma testuale. Nel mezzo si trova uno strumento che
prende in input un termine (in forma testuale) e esegue un’analisi lessicale,
sintattica e semantica, studiando l’evoluzione e le azioni che può compiere,
in grado inoltre di mostrare la specifica grafica associata al termine corrente
in qualsiasi momento. Sono stati progettati dei moduli che mettono in pratica questo processo. I moduli fanno uso e si integrano con alcune tecnologie
sviluppate per lavori precedenti.
Per rappresentare gli operatori del linguaggio in forma grafica viene fatto uso di un editor grafico, creato grazie ad un framework sviluppato come
plug-in per Eclipse, il Graphical Modelling Framework (GMF) [10]. Vengono
progettati una serie di meta-modelli dove si descrivono gli operatori COWS,
le relazioni che esistono tra loro e gli attributi che li caratterizzano. Un’altra serie di meta-modelli dove si descrive l’editor, le forme geometriche che
vogliamo associare ad ogni operatore e le politiche che ogni figura deve rispettare. Il framework prende in input i meta-modelli e genera un insieme di
classi che le mettono in pratica. Viene infine attivata una seconda istanza di
Eclipse dove è possibile aprire l’editor e comporre i termini COWS mediante
30
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
31
l’uso dei tipici strumenti grafici (drag e drop, copia e incolla ecc.).
Per eseguire l’analisi del termine è stato usato un pacchetto Java, parte
di un framework in grado di implementare un sistema di tipo completamente statico [7]. Un sistema che genera un insieme di vincoli durante la fase
di inferenza per eliminare completamente il controllo dinamico, garantendo
cosı̀ la correttezza di un servizio ottenuto in generale come composizione di
altri servizi, rispetto a proprietà quali sicurezza e deadlock. Tutte funzionalità fornite mediante un’interfaccia grafica e affiancate da uno strumento semi-automatico per un’analisi del termine e l’evoluzione delle possibili
computazioni.
Il capitolo descrive inizialmente il procedimento da seguire per la creazione dell’editor grafico in ambiente Eclipse. utilizzando le potenzialità di GMF,
ricalcando in parte quanto descritto in [10]. Vengono descritti i meta-modelli
e i passi necessari per realizzarli. Vengono inoltre mostrate delle modifiche
realizzate su alcuni modelli, dando una spiegazione del perché è stato deciso
di intervenire in un certo modo.
Successivamente mostra quella che è l’architettura del framework sviluppato in [7] evidenziando i moduli di cui è stato fatto un riuso sfruttando la
tecnologia Java.
Infine viene mostrato nei dettagli quello che è stato il contributo di questa
tesi, evidenziando le parti realizzate.
3.1
GMF: sviluppo dell’editor grafico
Mostriamo i passi da seguire nello sviluppo dell’editor. Inizialmente scarichiamo la versione di Eclipse per sviluppatori dal sito www.eclipse.org. Le
versioni nel tempo cambiano. Quella installata per lo sviluppo del software
sviluppato per questa tesi è la Eclipse - Ganymede - 3.4. In [10] si usa la
versione 3.3. Attualmente viene rilasciata la versione 3.4.2.
Anche per installare GMF i passi da seguire non sono tutti uguali. Usando
Ganymede bisogna andare sul menù Help e selezionare Software Updates → Available Software. Si apre una finestra con un elenco di siti. Se
è presente la voce The Eclipse Project Updates selezionarla. Si espande
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
32
in una struttura ad albero dove le foglie rappresentano nomi di plug-in. È
presente la voce Graphical Modelling Framework. Posta come postfisso della voce abbiamo anche la versione del plug-in (nel nostro caso la 2.1)
ma di nuovo, le versioni possono facilmente cambiare nel tempo. Selezionata
la voce, cliccare sul tasto Install alla destra dello schermo. Se la voce The
Eclipse Project Updates non è presente, bisogna cercare il sito. Cliccare il
tasto Manage Sites sulla destra della finestra corrente. Si apre un’ulteriore
finestra, dove cercare il sito da aggiungere. Una volta aggiunto ripetere i passi
descritti precedentemente.
Le modalità di installazione plug-in comunque cambiano da versione
a versione. L’installazione di GMF nella versione 3.3 usata in [10] segue
una procedura leggermente diversa. Per informazioni migliori consultare [4]
visualizzabile selezionando dalla barra in alto Help → Help Contents.
3.1.1
Sviluppo dei meta-modelli
Dopo aver installato il framework di GMF, apriamo un nuovo progetto. Dal
menù File selezioniamo nell’ordine:
New → Project →
Graphical Modelling Framework → New GMF Project.
Il nuovo progetto viene chiamato it.unifi.dsi.cowsVersione2. Il precedente,
elaborato in [10], chiamato semplicemente it.unifi.dsi.cows come accennato in precedenza, ha subito alcune modifiche che verranno maggiormente
dettagliate in seguito.
Successivamente dal menù Window selezioniamo nell’ordine:
Show View → Other → GMF Dashboard.
Il Dashboard (Figura 3.1) è un piano di lavoro e spiega quelli che sono i passi
da seguire. In pratica fa da guida alla creazione dei meta-modelli indicando
quello che va fatto.
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
33
Figura 3.1: Il GMF Dashboard
Dai box del Dashboard è possibile vedere la sequenza di meta-modelli da
generare. Il Domain Model è il modello dove viene rappresentato il linguaggio
COWS; da esso è possibile derivarne tre:
- il Domain Gen Model per generare le classi che sviluppano l’editor e i
test del plug-in;
- il Graphical Def Model per generare le classi di specifica delle
caratteristiche grafiche delle figure associate ai vari operatori COWS;
- il Tooling Def Model per generare le classi che sviluppano la tavolozza
con i vari strumenti dell’editor grafico.
I modelli Domain, Graphical e Tooling, combinati insieme, permettono
di ottenere il Mapping Model, necessario per associare tutte le caratteristiche
precedenti in un unico modello. Infine dal Mapping si ottiene il Diagram
Editor Gen Model, il modello finale per la generazione l’editor.
Sviluppo del modello Domain Model
Il Domain Model è il meta-modello di partenza. Serve per modellizzare il
linguaggio COWS. È pertanto il meta-modello principale; la sua composizione si riflette sui meta-modelli successivi. Utilizziamo il Dashboard per farci
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
34
dirigere nei nostri passi. Clicchiamo create sul box Domain Model. La procedura successiva serve alla creazione di un file cowsVersione2.ecore (Figura
3.2).
Figura 3.2: Il modello Domain Model cowsVersione2.ecore
È un file su cui viene descritto il linguaggio usufruendo di una struttura
ad albero. Associato al file ecore, è fornito un ulteriore file col medesimo nome di estensione .ecore diagram (ottenibile cliccando sul file col tasto destro
del mouse e selezionando dal menù popup che si apre la voce Inizialize
ecore diagram); è un editor su cui l’albero può essere rappresentato graficamente usando come struttura uno standard UML (ci sono altre possibili
strutturazioni che non vengono spiegate; per maggiori informazioni consultare [2]) che descrive il linguaggio. Serve più che altro per fornire maggior
praticità e maggior intuitibilità nello sviluppo del modello.
Le notazioni usate sono le seguenti: ogni operatore è associabile ad una
classe; ogni classe può contenere o meno attributi e le classi possono essere
o classi che specializzano una classe padre (ereditarietà) o collegate tra loro
attraverso aggregazioni. In [10] era stato sviluppato un modello ecore a cui
sono state apportate alcune modifiche (il precedente modello era chiamato
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
35
cows.ecore, il nostro modello è chiamato cowsVersione2.ecore); in particolar
modo è stato eliminato l’operatore Parallel. Questo ha permesso una maggiore usabilità a livello grafico. Nelle Figure 3.3 e 3.4 sono mostrati i due modelli:
cows.ecore diagram e cowsVersione2.ecore diagram . Descriviamo quelli che
sono i punti fondamentali e vediamo quali sono le differenze sostanziali tra
le due versioni.
Modello cows.ecore
- la classe Specification è la classe radice. Ha due aggregazioni: definition
contiene la classe Definition e TLService contiene tutti gli oggetti di
tipo Body;
- la classe Body è una classe fittizia. Raggruppa due tipologie di elementi:
Service e Parallel. La funzionalità offerta è la stesso di un’interfaccia
Java;
- la classe Parallel è un Body. Può contenere due o più Service mediante
l’aggregazione Pcontain;
- la classe Service è un Body. Anche questa classe è fittizia e funziona da
interfaccia. Contiene sei elementi distinti più un’ulteriore classe fittizia
Guard;
- tutte le classi che specializzano Service. Alcune non dispongono di aggregazioni come Invoke o Kill, altre come Delimitation o Replication,
possono contenere ulteriori Service mediante l’uso di aggregazioni;
- la classe Guard classe fittizia che specializza Service, il cui scopo è
raggruppare altri elementi, quali le classi Receive o Nil
Modello cowsVersione2.ecore
- la classe Specification continua a rappresentare la radice del Diagram Model. Ha ancora due aggregazioni: definition contiene la classe
Definition e la nuova aggregazione main contiene gli oggetti Service;
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
Figura 3.3: Meta-modello cows.ecore
36
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
Figura 3.4: Meta-modello cowsVersione2.ecore
37
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
38
- la classe Service. Anche in questo modello è una classe fittizia
contenente sette elementi. Ha un funzionamento ancora simile ad
un’interfaccia;
- le classi che specializzano Service, rimangono inalterate.
Differenze tra i due modelli
Nel meta-modello cowsVersione2 sono assenti la classe Body e soprattutto la
classe Parallel. Infatti a livello grafico l’operatore presentava grossi problemi.
In primo luogo, ogni volta che si voleva rappresentare elementi in parallelo
era necessario inserire gli elementi dentro la figura geometrica rappresentante
il Parallel. Ad esempio: dato il termine p.o? < x > .s1 e in seguito si decideva
di mettere s2 in parallelo con s1 (dove s1 e s2 rappresentano generici termini
COWS, anche termini composti) bisognava eliminare completamente s1, aggiungere come continuazione della Receive la figura geometrica raffigurante il
parallelo e inserire dentro s2 e s1 che quindi necessitava di essere ricostruito
di nuovo.
In secondo luogo, l’effetto inverso presupponeva un problema di gravità
maggiore. Dato infatti il termine p.o? < x > .(s1|s2), cambiare idea e voler
trascinare al di fuori del Parallel la figura rappresentante s2 mettendola in
parallelo con l’operatore di Receive presupponeva da parte dell’applicativo
una interpretazione corretta ma con effetti devastanti. Infatti, ogni nuova
figura portata sull’editor allo stesso livello della Receive comportava la sparizione dall’editor della stessa. Questo perché due figure potevano essere allo
stesso livello se e soltanto se inserite in un Parallel. Non avendo pensato
all’operatore di parallelo dall’inizio, il tool di conseguenza si comportava in
questo modo.
Quindi è stato eliminato, eliminando in tal modo anche la classe Body
che in precedenza poneva la differenza tra Parallel e Service, lasciando quindi
soltando Service. Tutte le aggregazioni che in predenza potevano contenere
un Parallel, diventano aggregazioni 1 − n. Sono:
- main aggregazione della radice Specification;
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
39
- bodyDel, bodyProt, bodyRep e continuation aggregazioni rispettivamente delle classi Delimitation, Protection, Replication e Receive che
aggregano a tali servizi, ulteriori servizi Cows;
- sum aggregazione della Guard Choise.
Essendo Parallel comunque sempre un operatore di COWS, nell’applicativo
sono state implementate delle strategie per trattarlo ugualmente.
Per il resto, ricalcano fedelmente il modello cows.ecore realizzato in [10].
Vedremo in seguito che le aggregazioni saranno fondamentali nell’ottica della
trasformazione da specifica grafica a testuale e viceversa.
Una volta definito il modello Domain possiamo verificarne la validità mediante la voce Validate selezionabile dal menù aperto cliccando con il tasto
destro del mouse sulla radice dell’albero nella rappresentazione ecore. Se non
ci sono errori avremo in risposta il messaggio
Validation completed succesfully
Possiamo passare alle fasi successive, derivando i restanti meta-modelli.
I modelli Domain Gen Model, Graphical Def Model e Tooling Def
Model
Sono i prossimi modelli dopo, tutti derivabili dal Domain Model. Torniamo
sul Dashboard e clicchiamo l’etichetta Derive posta sulla freccia orientata
verso il box relativo. Vediamo nel dettaglio i punti da analizzare.
Domain Gen Model
Genera le classi che implementano gli elementi del linguaggio e le classi relative alla definizione del plug-in. Si ottiene facilmente cliccando sul tasto
Derive (Figura 3.5). Si apre una finestra dove viene richiesto il nome del modello (di estensione genmodel ), la tipologia di modello da derivare e il nome
del modello da derivare. Il risultato è un ulteriore meta-modello strutturato
nella forma di un albero (Figura 3.6) molto simile al modello ecore, da cui
è possibile ottenere il primo codice Java (posizionando il cursore del mouse
sulla radice dell’albero e dal menù selezionare Generate All).
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
Figura 3.5: Dashboard - particolare - Domain Gen Model
Figura 3.6: Il Domain Gen Model
40
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
41
Graphical Def Model
Il Graphical Def Model è il modello principale per la creazione dell’editor
grafico. Serve a specificare le varie caratteristiche delle figure, quali forma,
colore ecc. Comporta un intervendo da parte del progettista. Mediante il
tasto Derive dal Dashboard (Figura 3.7) si apre la finestra dove vengono ri-
Figura 3.7: Dashboard - particolare - Graphical Def Model
chieste le informazioni necessarie: nome del file (di estensione gmfgraph), il
file ecore da cui prelevare gli elementi e la richiesta esplicita di quali elementi
vogliamo disegnare sull’editor (Figura 3.8). Vediamo da cosa è costituito il
Figura 3.8: Processo di creazione del modello Graphical Def Model particolare
file rappresentante il modello. Ancora una volta è una struttura ad albero la
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
42
cui radice è Canvas1 (Figura 3.9). Ci sono cinque tipi di elementi che possono
Figura 3.9: Il Graphical Def Model
essere figli della radice dell’albero (per vedere l’elenco basta cliccare col tasto
destro del mouse sulla radice Canvas e selezionare New Child); a noi ne
interessano quattro: i Figure Descriptor, i Node, i Compartment e i Diagram
Label. Per ognuno di questi quattro elementi l’utente deve intervenire. Alcuni interventi sono semplici aggiunte di rami alla struttura dell’albero; altri
necessitano l’apertura del task Properties, selezionabile, andando su un
qualsiasi elemento dell’albero e cliccando con il tasto destro del mouse alla
voce Show Properties View. Le modalità di intervento sono le seguenti:
1
Canvas è un’estensione dell’HTML standard che permette il rendering dinamico di
immagini bitmap gestibili attraverso un linguaggio di scripting.
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
43
- i Figure Descriptor associano per ogni elemento la sua raffigurazione
grafica. Estendendo ulteriormente dal Graphical la voce Figure Gallery
Default vengono visualizzati i rami Figure Descriptor (Figura 3.10).
Dalla figura è possibile intuire come intervenire su gli elementi dell’al-
Figura 3.10: Graphical Def Model - particolare relativo ai Figure Descriptor
bero. Nel task Properties, alla voce Name deve corrispondere il valore
xxxFigure dove xxx è il nome dell’operatore in questione. Estendendo nel modello ogni Figure Descriptor, al ramo Rectangle, mediante
tasto destro del mouse, si aggiunge un figlio Label per ogni attributo
dell’operatore e al ramo Figure Descriptor stesso, sempre mediante tasto destro del mouse, si aggiunge un Child Access per l’operatore e
altri Child Access tanti quanti gli attributi dell’operatore in questione;
- i Node devono essere associati al Figure Descriptor di riferimento.
Per fare questo bisogna selezionare il Descriptor associato nel task
Properties alla voce Figure;
- i Compartment devono specificare gli elementi che al loro interno possono contenere altri elementi. Per ogni elemento COWS che può contenere al suo interno altri elementi COWS si aggiunge un Compartment.
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
44
Nel task properties, alla voce Figure si associa il Figure Descriptor
associato;
- i Diagram Label sono le etichette di ogni attributo. Per ogni attributo del modello cowsVersione2.ecore bisogna aggiungere un figlio Diagram Label specificandone nel task Properties il Figure Descriptor di
appartenenza alla voce Figure.
Tooling Def Model
Il Tooling Def Model definisce la tavolozza, quindi una tabella posta accanto
all’editor da cui selezionare i vari elementi. Ha un processo di creazione piuttosto semplice che ricalca i processi di creazione precedenti. Dal Dashboard si
clicca il tasto Derive posto sulla freccia orientata verso il box rappresentante
il Tooling e nel medesimo modo del Graphical si da il nome del file (estensione gmftool ), si seleziona il modello ecore, si seleziona l’elemento radice e
si selezionano gli elementi che vogliamo mettere nella tavolozza. Nella seconda istanza di Eclipse, alla destra dell’editor grafico è presente la tavolozza
da cui scegliere l’operatore che vogliamo fare apparire sull’editor nella forma
geometrica associata.
I modelli Mapping Model e Diagram Editor Gen Model
Combinando i precedenti modelli si ricava il Mapping Model per associare
gli elementi alle specifiche dell’editor e infine il Diagram, ultimo passo ed
effettiva generazione del codice.
Mapping Model
Nel Mapping vengono definiti in relazione all’editor i vari elementi e il comportamento che assumono tra di loro. Se un operatore può essere contenuto
all’interno di un altro è in questo punto che deve essere precisato. Nota:
l’elemento Compartment presente nel Graphical Def Model specifica il nome dell’elemento contenitore sull’editor; il Mapping Model associa l’elemento
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
45
all’effettivo operatore COWS. Quindi anche in questo caso è necessario un
intervento consistente da parte del progettista in alcuni punti del modello.
Torniamo sul Dashboard e clicchiamo su Combine. Come in precedenza
le finestre che si aprono servono per specificare alcuni aspetti del file, quali
il nome (con estensione gmfmap) e i modelli da combinare insieme. L’ultima
finestra fornisce un elenco degli elementi che verranno visualizzati e il nome
dell’aggregazione che li lega alla radice del modello più un elenco con i link
presenti tra gli oggetti.
Anche in questo caso è un modello strutturato ad albero (Figura3.11).
Figura 3.11: Il Mapping Model
È il modello che richiede più lavoro da parte dell’utente. La Figura 3.11
aiuta a capire dove bisogna mettere mano. Per ogni elemento del linguaggio abbiamo un Top Node Reference. Ogni Top Node ha per figlio un
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
46
Node Mapping. Andando su un elemento Node Mapping e cliccando con
il tasto destro del mouse si possono aggiungere figli al ramo. Va aggiunto
per ogni elemento, un Compartment Mapping se l’elemento è un elemento contenitore, un Feature Label per ogni attributo che possiede e un
Child Reference per ogni elemento che può essere contenuto specificando
l’aggregazione.
Diagram Editor Gen Model
È l’ultimo modello. Generato, mediante la voce selezionabile dal menù tramite tasto destro oppure facendo uso del tasto Transform del Dashboard.
Dalla nuova finestra si seleziona il Mapping Model appena sviluppato e il
nome del file che avrà estensione gmfgen.
Infine, si apre il menù mediante tasto destro sul file cowsVersione2.gmfgen
appena generato; prima si seleziona validate per validare il processo (la
mancata validazione comporta dover tornare a correggere i modelli precedenti) poi si seleziona Generate diagram code. Il risultato sono tutte le
classi Java che permettono di creare l’editor grafico.
3.1.2
Partenza della nuova istanza
Per far partire una nuova istanza di Eclipse e aprire in tal modo l’editor grafico basta andare nella barra verticale Package Explorer posta
alla propria sinistra, cliccare con il tasto destro del mouse sul project
it.unifi.dsi.cowsVersione2 appena sviluppato, selezionare la voce Run As e
la successiva voce da menù popup Eclipse Application. Automaticamente
viene aperta una nuova istanza dell’ambiente Eclipse.
Adesso il framework permette di creare anche l’editor. In seguito vedremo
che per raggiungere i nostri scopi siamo intervenuti su due file specifici, il file
di estensione cows e il file di estensione cows diagram. Il file cows diagram in
particolare, è proprio il tipo di file visualizzabile sull’editor.
Appena attivata la nuova istanza di Eclipse, la Workbench risulta ovviamente vuota; quindi questi due file vanno creati. Il procedimento è il seguente:
deve essere creato un nuovo progetto, un “Project” (passo iniziale per pro-
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
47
grammare codice utilizzando Eclipse). Per fare questo si seleziona nell’ordine
File → New → Project → New Project. Si apre una finestra dove
viene richiesto il nome del project. a cui verrà dato un nome. Una volta creato il progetto abbiamo una directory dove inserire i nostri file. La directory
risiede fisicamente nel workspace. Essendo questa la modalità tipica per creare directory (Project), va successivamente definita la tipologia di codice da
utilizzare; selezionando la nuova serie di comandi File → New → Example è possibile visualizzare una nuova finestra dove è presente nell’elenco di
scelte, la voce CowsVersione2 Diagram. Chiaramente non è una voce
presente di default; è presente solo perché è stata sviluppata in precedenza,
la sequenza di meta-modelli rappresentante il linguaggio COWS e la voce
CowsVersione2 Diagram è una delle opzioni offerte dal plug-in eseguito in
una nuova istanza di Eclipse (se fosse stata fatta la stessa cosa con il C,
la voce sarebbe C Diagram, oppure Pascal Diagram e cosı̀ via). Selezionando la voce CowsVersione2 Diagram viene aperta una nuova finestra dove è
possibile creare i nostri due file con estensione cows e .cows diagram.
Il file di estensione .cows diagram è visualizzabile solo sull’editor grafico;
quindi la sua apertura coincide con l’apertura dell’editor. Su di esso vengono
rappresentati i processi in modalità grafica. Sulla destra appare una barra
verticale con tutte le voci relative ad operatori COWS; una sorta di tavolozza denominata Palette. Vengono messe a disposizione operazioni di default
tipiche degli editor grafici, quali selezione o zoom. Selezionando una delle voci e successivamente cliccando sull’editor, automaticamente appare la figura
geometrica associata all’operatore. L’estensione .cows invece, è associata ad
un file dove viene rappresentato l’albero sintattico equivalente alla specifica
grafica descritta sull’editor.
Notiamo che entrambi i file, se aperti con un editor di testo, hanno una
struttura XML. Questo vedremo, sarà uno dei punti salienti della tesi. Infatti,
come spiegato in precedenza, uno dei contributi principali che la tesi vuole
offrire è ricavare il termine COWS in forma testuale data la sua specifica
grafica (e fare in modo che tale processo sia reversibile). Avere a disposizione
un file XML, vuol dire essere in grado di scorrerlo interamente, eseguire
un processo di parsing e estrapolare informazioni di interesse. La struttura
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
48
XML è una delle rappresentazioni più potenti dell’informazione. Avere un
file che rappresenta qualcosa da un punto di vista grafico, sapere che tale
file è editabile su un editor di testo e strutturato in XML, vuol dire poter
ricavare facilmente informazioni e seguendo certe politiche, rappresentarle in
un formato a piacere (input da passare all’interprete).
Le Figure 3.12 e 3.13 mostrano l’editor e le sue componenti (palette ad
esempio) e un esempio di un termine COWS rappresentato in modalità grafica
sull’editor.
Figura 3.12: Rappresentazione dell’editor grafico
3.2
Inteprete
In [7] è stato studiato il linguaggio COWS, in quanto calcolo di processi che
modelizza in modo astratto WS-BPEL, mediante il quale è possibile avere a
disposizione un supporto formale per la progettazione di servizi composti. La
problematica generale di cui si occupa è garantire la correttezza di un servizio ottenuto come composizione di ulteriori servizi. In [7] a COWS è stato
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
49
Figura 3.13: Esempio di termine COWS rappresentato graficamente
sull’editor
associato un sistema di tipo per la verifica di alcune politiche di sicurezza
che riguardano lo scambio dei dati tra più componenti. Viene proposto un
sistema di tipo che utilizza controlli effettuati a run-time e elimina i controlli dinamici mediante un’analisi completamente statica di alcuni vincoli che
vengono generati durante la fase di inferenza di tipo.
Affiancato al sistema di inferenza è stato prodotto uno strumento semiautomatico che permette l’analisi sintattica di un termine COWS, l’applicazione del sistema di tipo, la specifica delle politiche di scambio dei dati, la
verifica della soddisfacibilità dei vincoli generati e in base a questo, uno studio
sull’evoluzione del termine dopo aver effettuato una qualsiasi computazione;
funzionalità fornite mediante un’interfaccia grafica.
Lo strumento software realizzato è implementato in linguaggio Java.
Vediamo quale è la sua architettura (Figure 3.14). .
Il framework è costruito su tre pacchetti principali:
- cowsAnalysis, è un package contenitore; contiene tutte le classi che
lavorano sul linguaggio COWS. È suddiviso in ulteriori sottopackage
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
50
Figura 3.14: Architettura del framework cowsAnalyser - 1
e ogni sottopackage si occupa di una propria fase del processo. I pacchetti lexer e parser implementano l’analizzatore lessicale e sintattico costruendo per ogni termine l’albero sintattico astratto associato,
analysis contiene tutte le classi che attraversano l’albero e effettuano
l’analisi semantica e node contiene tutti gli oggetti associati ad ogni
elemento del linguaggio. Nel package typeSystem è implementato il
sistema di tipo, contributo principale di [7]. All’interno del pacchetto sono contenute tutte le classi necessarie per compiere il processo di
inferenza di tipo, in pratica le classi che implementano l’ambiente predisposto, le classi che sviluppano le regioni dei tipi e classi che effettuano
il vero e proprio processo di inferenza. Il pacchetto solver implementa
tutte le classi che realizzano il risolutore dei vincoli. Quindi sono implementati i vincoli, il risolutore dei vincoli e il processo di sostituzione
completo dell’analisi dei risultati (per maggior chiariemnti si rimanda
alla lettura di [7]);
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
51
- expressions come cowsAnalysis è un pacchetto contenitore. Usato da
cowsAnalysis stesso si occupa di implementare tutte le classi necessarie
alla valutazione di un’espressione, sia essa aritmetica, booleana o di
tipo stringa. I sottopacchetti ricalcano una parte della suddivisione dei
pacchetti in cowsAnalysis (entrambi sono stati realizzati sfruttando il
framework SableCC, il generatore di compilatori). Quindi abbiamo di
nuovo i package lexer e parser destinati all’analisi lessicale e sintattica
delle espressioni e alla costruzione dell’AST associato ad ogni espressione, analysis e node rispettivamente gli oggetti per attraversare
l’albero e le rappresentazioni in Java degli elementi di un’espressione;
- interfaceCows contiene tutte le classi che realizzano le varie parti
del framework mettendo in relazione tutti gli oggetti fin qua descritti.
IntGraphics è la classe principale. Attiva l’interfaccia grafica, permette il recupero del termine COWS e gestisce le varie operazioni che l’utente richiede aprendo una schermata diversa a seconda della richiesta.
BuildConstraints e VisualConstraints gestiscono, costruiscono, verificano la soddisfacibilità e infine visualizzano i vincoli raccolti durante
l’inferenza del sistema di tipo. In particolare attivano la risoluzione dei
vincoli durante il controllo e mettono il sistema in grado di prepararsi ad
una eventuale successiva esplorazione del termine per valutare le varie
possibili evoluzioni. Checking gestisce tutte le funzionalità dell’interprete e Visual Action permette di vedere tutte le possibili evoluzioni
e le azioni possibili da uno stato attivo del termine. Infine DLList e
DLLNode sono classi di gestione delle strutture dati a disposizione.
Eliminiamo dalla Figura 3.14 alcuni parti e mettiamo in risalto ulteriori
annotazioni.
Il pacchetto cowsAnalysis oltre ai pacchetti citati in precedenza contiene
una classe InterCows che sfrutta il packege Node per l’analisi semantica del
termine COWS. Intanto in interfaceCows è possibile prendere atto dell’uso
di un oggetto Checking da parte della classe IntGraphics (Figura 3.15).
Se approfondiamo ulteriormente le relazioni presenti tra questi oggetti
vediamo che Checking, gestisce tutte le operazioni di analisi del termine.
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
52
IntGraphics viene attivata e recupera il termine COWS. Checking recupera
a sua volta il termine e gestisce le operazioni di analisi lessicale, sintattica e
infine, mediante l’uso di InterCows, l’analisi semantica (Figura 3.16).
Figura 3.15: Architettura del framework cowsAnalyser - 2
.
Per l’obiettivo che si è voluto raggiungere in fase di tesi sfruttando il riuso
del codice, quale caratterisctica tra le principali dell’Object-Oriented si sono
estrapolati i moduli relativi all’analisi e integrati nel framework sviluppato.
3.3
Contributo della tesi
Fatte le opportune considerazioni entriamo nel dettaglio per quanto riguarda
l’applicativo sviluppato in fase di tesi. L’obbiettivo è proporre una soluzione
software che sfrutti una serie di strumenti grafici per rappresentare processi
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
Figura 3.16: Architettura del framework cowsAnalyser - 3
53
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
54
COWS. Uno strumento in grado di leggere un termine COWS in forma testuale e rappresentarlo graficamente e viceversa, in grado di rappresentare
un termine COWS in una specifica grafica e estrapolarne la forma testuale.
uno strumento che mostri le possibili evoluzioni di un termine COWS integrando il passaggio dall’una e l’altra forma con un interprete (Figura 3.17).
L’interprete permette di effettuare sul termine l’analisi lessicale, sintattica e
infine semantica mostrando come il termine può evolversi.
Specifica grafica
di termini COWS
Specifica testuale
di termini COWS
INTERPRETE
Figura 3.17: Contributo teorico e pratico della tesi
Parte relativa all’interprete
Le Figure 3.14, 3.15 e 3.16 mostrano le componenti del framework sviluppato
in [7] partendo da un livello di astrazione più ampio dove vengono messi in
risalto tutti package che compongono l’applicativo, scendendo in seguito più
in dettaglio arrivando a focalizzare l’attenzione su le classi che implementano
lo strumento di analisi sintattica e semantica. Per mettere in pratica i nostri
scopi è stato estrapolato tutto il codice rappresentante la parte analitica.
Quindi:
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
55
- alcune componenti del pacchetto cowsAnalysis. In particolare i sottopacchetti lexer e parser per implementare la parte di analisi lessicale
e sintattica. Il pacchetto analysis con tutte le classi che riguardano
l’attraversamento dell’albero sintattico astratto, il pacchetto node dove
sono presenti gli oggetti Java rappresentanti gli elementi di COWS, più
alcune classi a se stanti nel pacchetto cowsAnalysis stesso, tra cui la
più importante è InterCows dove vengono implementati i metodi per
eseguire l’analisi semantica;
- tutto il pacchetto expressions dove sono contentue tutte le classi per
la gestione delle espressioni aritmetiche, booleane e su stringhe;
- la classe Checking nel pacchetto interfaceCows, classe di gestione
delle operazioni di analisi più le due classi DLList e DLLNode che
implementato le strutture dati utilizzate in tale contesto.
La Figura 3.18 mostra tutte le componenti citate. Omettiamo per il momento un nome, per il pacchetto contenente le classi Checking, DLList e
DLLNode.
Parte relativa alla costruzione dell’editor grafico
Tutti i passaggi visti nella Sezione 3.1 del capitolo sono stati ripresi. La
Figura 3.19 mostra una semplice astrazione di un processo che realizza un
editor grafico.
Architettura del framework
Vediamo adesso l’architettura del framework implementato in questa
tesi. Viene creato nel workspace di Eclipse un project denominato
InterpreteGrafico e tutti i pacchetti del progetto vengono collocati qua
dentro. Essi sono:
- il pacchetto cowsAnalysis con tutto il suo contenuto (eliminando
solver e typeSystem);
- il pacchetto expressions con tutto il suo contenuto;
56
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
cowsAnalysis
analysis
expressions
lexer
node
analysis
node
lexer
parser
{valuta}
InterCows
parser
{usa}
...
Checking
DLList
DLLNode
Figura 3.18: Classi estrapolate dal framework proposto in [7]
Figura 3.19: Processo per la crazione di un’editor grafico
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
57
- un nuovo pacchetto interpreteCows. Contiene le classi Checking, DLList e DLLNode. In seguito saranno implementate e studiate ulteriori
classi collocate in questo pacchetto;
- un nuovo pacchetto terminiCows contenente oggetti Java che rappresentano gli operatori COWS. Hanno una struttura associata al processo
di trasformazione dei termini;
- un nuovo pacchetto trasformazioneCows contenente tutte le classi che
eseguono il processo di trasformazione di un termine dalla modalità
grafica a quella testuale e viceversa.
La Figura 3.20 mostra tutte le componenti incluse nel project InterpreteGrafico. Quando un pacchetto contiene ulteriori classi, viene contrassegnato
dal simbolo della classe in UML il cui nome sono i tre punti.
La Figura 3.21 compone infine tutte le parti del progetto.
Il box denominato Interprete rappresenta tutte le componenti dell’analizzatore lessicale, sintattico e semantico visto in Figura 3.18, Processo creazione
dell’editor rappresenta tutti i passaggi descritti in Sezione 3.1, il box trasformazione sono tutti i nuovi pacchetti sviluppati. Un attore si interfaccia con
le classi presenti all’interno del box trasformazione. Il risultato ottenuto è
la possibile trasformazione di un termine COWS dalla modalità grafica a
testuale e viceversa dalla modalità testuale a grafica.
58
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
Project InterpreteGrafico
cowsAnalysis
analysis
expressions
InterCows
lexer
analysis
node
{valuta}
parser
...
node
lexer
parser
terminiCows
{usa}
...
{usa}
interpreteCows
DLList
Checking
trasformazioneCows
{usa}
DLLNode
...
...
Figura 3.20: Diagramma del project Interpretegrafico
Processo creazione
dell’editor
{usa}
{usa}
- Editing grafico del termine COWS
interpreteCows
- Trasformazione
specifica grafica
-> specifica testuale
specifica testuale -> specifica grafica
{usa}
terminiCows
{permette}
- Evoluzione del termine
Utente
trasformazioneCows
Figura 3.21: Architettura finale del sistema
59
CAPITOLO 3. ARCHITETTURA DEL SOFTWARE
Interprete
Capitolo 4
Interpretazione del linguaggio
Questo capitolo descrive brevemente l’implementazione dell’interprete
sviluppato in [7] e si sofferma su alcuni punti ritenuti essenziali.
Per lo sviluppo dell’interprete è stato utilizzato SableCC [14], un generatore di compilatori object-oriented di discreto successo, i cui dettagli non
vengono descritti in questo capitolo. Per maggiori informazioni è possibile
consultare l’Appendice A della tesi. Per i nostri scopi è sufficiente sapere
che un termine COWS è sottoposto ad un processo che prima effettua un’analisi lessicale e sintattica, quindi trasforma il termine in un oggetto Java
rappresentato dall’albero sintattico astratto associato (AST). Successivamente i nodi dell’albero sono visitati per valutare le azioni attive e l’eventuale
evoluzione del termine iniziale.
Il processo descritto è effettuato tramite l’utilizzo della classe InterCows
del pacchetto cowsAnalysis di Figura 3.20. La classe usa un particolare
insieme di metodi: caseXXX, inXXX e outXXX dove il postfisso “XXX” definisce
l’operatore COWS incontrato durante l’attraversamento. Il metodo caseXXX
è chiamato quando viene incontrato il nodo che rappresenta l’operatore XXX,
inXXX verifica le precondizioni, infine outXXX applica le regole semantiche.
Un ulteriore metodo apply viene richiamato all’interno di ogni caseXXX;
gestisce la procedura di analisi sul nodo corrente.
60
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
4.1
61
Azioni
L’interprete si propone di analizzare il termine; per ogni nodo ricava l’operatore e in base alle regole semantiche formula le azioni attive dallo stato
corrente ricavando il termine evoluto.
4.1.1
Oggetti Action
Le azioni formano una propria gerarchia. A capo della gerarchia è presente
la classe astratta Action. Sostanzialmente un oggetto Action identifica una
transizione intesa come
termine di partenza → termine di arrivo.
La classe dispone di tre attributi principali, due campi di tipo Node i quali
definiscono il termine prima e dopo l’esecuzione dell’azione e un campo booleano che dice se l’azione è attiva o meno. Ogni azione è definita come un
oggetto che estende questa classe.
Oltre ai tipici metodi per la persistenza dei dati, dispone di alcuni
particolari metodi astratti, definiti sotto, che le classi figlie dovranno ridefinire
public abstract boolean dAlpha(ListaDiSostituzioni lista);
public abstract void replace(ListaDiSostituzioni sub);
public abstract String toString();
dAlpha è un metodo che vedremo meglio in seguito, replace viene utilizzato principalmente per la sostituzione di parametri e toString (ridefinito),
permette di stampare a video una stringa ad hoc per visualizzare l’azione.
COWS può effettuare cinque azioni; sono pertanto implementate cinque
classi che ereditano da Action e ogni classe rappresenta un’azione COWS.
Ogni elemento di classe Action ha una serie di campi che memorizzano i
valori coinvolti nell’azione. Ad esempio l’azione di invocazione è un oggetto
Java con tre campi: due di tipo String contenente il partner e l’operation e
uno di tipo List di String contenente l’espressione.
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
62
In Action sono inoltre presenti cinque metodi isXXX dove il postfisso
“XXX” definisce il nome dell’azione; restituiscono un valore booleano. Servono per il riconoscimento dell’azione stessa. Di default restituiscono “false”
ma nella classe “XXX” di appartenenza, il metodo viene ridefinito, sovrascrivendo il metodo ereditato e restituendo il valore booleano “true” quando
l’azione corrente è XXX.
L’interprete (l’oggetto Java InterCows) tratta le azioni tutte nel medesimo modo; a seconda del nodo corrente, quando è necessario applicare le
regole semantiche implementa una serie di costrutti “if-then-else”, dove la
condizione è la chiamata sul metodo “isXXX”, del tipo:
if ( azioneCorrente.isKill() ) {
Applica le regole semantiche associate
alla terminazione forzata
} else if ( azioneCorrente.isInvoke() ) {
Applica le regole semantiche associate
alla invocazione di parametri
} else if ...
...
ecc
...
Ricordiamo di seguito le azioni associate ai rispettivi oggetti Java:
- l’azione † k denota la richiesta di terminazione di un termine all’interno
dello scope di [k]. È implementata attraverso la classe Kill kAction.
La variabile principale è una stringa denominata label e il costruttore
della classe è implementato in modo che il campo rappresentante il
termine di arrivo sia un oggetto Nil. Quando l’oggetto Kill kAction
viene istanziato, l’interprete lo memorizza in una lista di Azioni (a
breve tratteremo la struttura dati per memorizzare le azioni) e grazie
al metodo isKill viene riconosciuto al momento opportuno;
- l’azione (p.o)⊳ v̄ denota una Invoke sull’ endpoint (p.o) . È implementata dalla classe InvokeAction. Dispone dei campi relativi alle stringhe
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
63
associate al partner, all’operation e all’espressione da valutare. È in
questo contesto che l’espressione viene effettivamente valutata. La regola semantica coinvolta è (inv) di Tabella 2.5. Se la valutazione è
positiva l’azione darà un suo contributo altrimenti, non essendo valida
la premessa della regola, non sarà attiva.
La classe InvokeAction dispone di un metodo valExpr chiamato direttamente nel costruttore. Il metodo valExpr istanzia un oggetto
TranslationExpr. In questo modo viene recuperata l’espressione e
creato l’albero sintattico associato all’espressione stessa. Ricordiamo
infatti che ad un’espressione è associabile una propria rappresentazione ad albero. Per il processo di creazione, attraversamento, recupero
informazioni e valutazione dell’albero sintattico (processo identico alla
creazione dell’albero sintattico sviluppato per interpretare il termine
COWS) è stato utilizzato nuovamente SableCC [14], passando come
input una grammatica che produce le espressioni (compresa di operatori matematici, di operatori per le stringhe, ecc). Il pacchetto di classi
expressions è stato mostrato in precedenza in Figura 3.20. Contiene
secondo la politica di sviluppo di SableCC, quattro pacchetti: lexer
relativo all’analisi lessicale di un’espressione, parser per l’analisi sintattica, node e analysis, contenenti i simboli terminali e non, trasformati in oggetti Java e le classi per attraversare l’albero. Quindi
l’oggetto TranslationExpr altro non fa che valutare un’espressione e
memorizzarne il risultato nel campo expr dell’oggetto InvokeAction.
TranslationExpr ha un comportamento identico a InterCows. È un
interprete delle espressioni;
- l’azione (p.o) ⊲ w̄ denota una Receive sull’endpoint (p.o). È implementata dalla classe RequireAction. Contiene i campi rappresentanti
il partner, l’operation e la tupla di valori. Il campo rappresentante la
continuazione del servizio è ereditato di default da Action;
- l’azione p.o⌊σ⌋w̄v̄ denota la comunicazione sull’endpoint (p.o) dove, i
valori inviati sono identificati dalla tupla v̄, i parametri ricevuti dalla
tupla w̄ e dove σ è la funzione di sostituzione. Implementata dalla
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
64
classe subAction. Caratterizzata da un classico costrutto “if-then-else”
sulla condizione σ = ∅ o meno. Una sostituzione dispone di una lista
di oggetti di tipo Substituion, classe di utility per gestire le coppie
parametri-valori sulla sincronizzazione tra Invoke e Receive;
- l’azione † denota il passo computazionale dovuto alla terminazione
forzata. Implementata dalla semplice classe KillAction.
4.1.2
Strutture per manipolare le azioni
Alla fine del processo di analisi, l’interprete crea una lista di azioni attive.
La gestione delle azioni è affidata a due classi generiche1 che implementano
l’interfaccia Serializable: SLLNode e SLList. La prima è la lista di azioni di un
nodo. La seconda è la lista di azioni per tutti i nodi. L’interprete usufruisce
di una variabile list definita nel modo seguente:
SLList<Action> list = new SLList<Action>();
i cui campi
public SLLNode<T> head;
public SLLNode<T> tail;
hanno inizialmente valore “null”. La lista viene riempita per ogni operatore incontrato. Ogni metodo caseXXX mantiene a livello locale una variabile
tempList dello stesso tipo e all’uscita, la concatena a list. Poi ogni metodo
outXXX istanzia un oggetto SLLNode del tipo
SLLNode<Action> listNode = new SLLNode<Action>();
Per ogni nodo vi inserisce le azioni attive e all’uscita dal metodo viene memorizzato nella variabile tempList. Quindi, per ogni passo computazionale le
variabili locali memorizzano le varie azioni attive per ogni nodo, poi di volta
in volta vengono concatenate alla lista di azioni globali.
1
con il termine generiche vogliamo intendere classi definite facendo uso della tecnologia
dei generics in Java
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
4.1.3
65
Sviluppi futuri nella gerarchia delle azioni
Qui entra in gioco la caratteristica principale per cui la scelta del framework
da utilizzare è ricaduta su SableCC [14]. Per ogni nuova azione, basta estendere la classe astratta Action, ridefinendo dove serve i metodi e implementando quelli astratti. Se è necessaria una nuova struttura per le azioni, basta estendere nuovamente Action, con una nuova classe astratta e specializzarla. Se l’interprete deve valutare le nuove azioni, basta creare una classe
NuovoInterprete che estende il precedente aggiungendo semplicemente il
nuovo metodo (ereditando gli altri). A quel punto l’unica modifica sarà nel
metodo “main” dove le due righe di codice
Interprete interprete = new Interprete();
tree.apply(interprete);
devono essere sostituite con
NuovoInterprete nuovoInterprete = new NuovoInterprete();
tree.apply(nuovoInterprete);
4.2
Metodi dell’interprete
L’interpretazione attraversa tutti i nodi dell’albero partendo dal nodo Start
(rappresenta la radice dell’albero). Start ha tra i suoi campi un riferimento
al primo operatore del termine (un riferimento ad un oggetto PService) e un
metodo getPService per recuperare tale riferimento. La tipologia di metodi
implementati si suddivide in:
- metodi caseXXX(XXX node) chiamati quando viene incontrato il nodo
“XXX”. Al suo interno vengono chiamati i metodi inXXX e outXXX.
I primi effettuano l’analisi nei sottonodi stabilendo le precondizioni, i
secondi ne applicano le regole semantiche;
- metodi inXXX e outXXX come sopra;
- i metodi che implementano le funzioni ausiliarie, premesse delle regole
semantiche;
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
66
- alcuni metodi per il trattamento della congruenza strutturale (ulteriori
regole sono implementate di default nei metodi outXXX);
- metodi di utilità per la gestione di nodi e servizi.
Prima di vedere in dettaglio i metodi caseXXX, inXXX e outXXX di maggior
interesse, viene dato uno sguardo alle funzioni ausiliarie.
Funzioni ausiliarie
Le funzioni associate ai relativi metodi sono:
- d(α) implementata dal metodo dAlpha. A dire il vero non è propriamente contenuto nella classe InterCows ma nella classe Action (coinvolge l’azione risultante dalla regola delpass ). Il metodo dAlpha viene
richiamato all’interno del metodo checkDelPass (metodo di utilità il
cui risultato è determinante all’interno del metodo outADelService).
Il metodo dAlpha prende in input una lista di parametri coinvolti nelle
sostituzioni e li confronta mediante una ricerca, se presenti tra quelli
appartenenti all’azione;
- M( , ) = σ la funzione di matching. Implementata dal metodo match.
Esegue un banale algoritmo per il confronto e l’assegnazione di una
lista di valori ad una lista di variabili;
- halt( ) implementata dall’ononimo metodo e coadiuvata dal metodo
haltRic, prende il termine e controlla quale, tra gli operatori coinvolti,
ha provocato l’azione di terminazione. Partendo dal primo operatore
presente li scorre ad uno ad uno controllando l’azione effettuata. Se è
una terminazione forzata, sostituisce il nodo con la sua continuazione
(mediante il metodo replaceBy di default previsto nella classe padre
Node). Per gli operatori rimanenti effettua la terminazione forzata a
meno che non siano sotto gli effetti di una Protection. La ricerca dell’operatore Protection viene eseguita dal metodo haltRic, ricorsivamente
su ogni operatore coinvolto;
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
67
- s = Akkill(d)k invocata da delpass , restituisce true se in s è presente un
kill(d), false altrimenti. Funzione implementata dal metodo context.
Prende in input il nodo corrente e lo scorre ricorsivamente cercando una
kill;
- noc( , , , ) predicato che, come il precedente, restituisce un valore booleano. Quindi restituisce true, se non esiste un conflitto di ricezione. Implementata dal metodo ononimo noc. Il metodo lavora ricorsivamente
cercando operatori Receive. Quando li trova effettua il matching, calcolando la cardinalità dell’insieme di parametri coinvolti nella sostituzione. Se il valore è maggiore della cardinalità totale restituisce false,
altrimenti non ci sono conflitti restituendo true.
Metodi case, in e out
caseStart
È caseStart il primo metodo ad essere richiamato. Rappresenta il caso iniziale; l’analisi parte dal nodo radice Start. Esegue il recupero del riferimento all’oggetto PService dell’albero sintattico astratto sviluppato dal parser.
Applica successivamente il metodo apply(this); questo vuol dire che effettua per la prima volta l’analisi semantica sopra tale nodo passando come
riferiemento se stesso.
Nel caso del nodo Start (come per tutti gli operatori che definiscono
assiomi) non esistono precondizioni (chiamate al metodo in); solo la chiamata
al metodo outStart, mediante il comando
list = outStart(node)
In questo modo si applicano le regole semantiche.
outStart
Recupera la lista di azioni, ma permette una computazione solo se sono
presenti azioni attive equivalenti alla terminazione forzata e alla sostituzione.
Per essere attive, devono essere sotto la portata di una Delimitation.
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
68
caseAInguardreqService
Seleziona la chiamata all’operatore di “guardia” appropiato. L’interprete
si trova sul nodo e riconosce una “guardia”. Recupera il riferimento del
nodo corrente, applica il metodo getInGuard per il recupero dell’operatore
di guardia effettivo (Nil, Receive o Guard Choise). Applica nuovamente la
chimata al metodo apply(this). In pratica effettua una nuova chiamata su
lo specifico oggetto di tipo “guardia”. Il processo viene passato dall’oggetto
generico a quello specifico.
Si può ritenere i metodi caseAKillService, caseAInvokeService e
caseAReqInguard non particolarmente significativi; focalizziamo l’attenzione su le semplici chimate ai metodi “out” associati. Le regole in questione
rappresentano degli assiomi; non sono pertanto previsti metodi “in”.
Ricordiamo che ogni regola semantica implementata fa riferimento alla
Tabella 2.5
outAKillService
Metodo richiamato da caseAKillService. Recupera la variabile list lista
delle azioni aggiungendo in coda un oggetto Kill kAction, oggetto che eredita dalla classe Action e che definisce l’azione di terminazione forzata. L’oggetto Kill kAction aggiunto in coda memorizza nel proprio campo label
l’etichetta, recuperandola mediante una getKiller e nel campo Node, nodo
successivo all’azione, memorizza l’oggetto Nil; quest’ultima operazione avviene direttamente nel costruttore. In questo modo, piuttosto semplice viene
pertanto implementata la regola semantica (kill).
outAInvokeService
Recupera l’espressione e passa la tripla di valori (partner, operation, espressione) ad un istanza appena creata dell’oggetto InvokeAction. I suddetti
parametri vengono salvati e successivamente avviene la chiamata al metodo
valExpr per la valutazione dell’espressione. Viene implementatata la regola
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
69
(inv); se l’espressione è valida, secondo la premessa della regola, l’azione è
attiva.
outAReqInguard
Il comportamento è pressoché identico ad outAInvokeService. L’istanza
creata è un oggetto Java di classe RequireAction. La regola implementata
è (rec).
caseAParService
Il metodo inizia implementando le regole di congruenza strutturale che vedono l’operatore Parallel coinvolto. Una chiamata al metodo congrPar comporta l’eliminazione dei termini Nil. Successivamente la chiamata al metodo
(delim3 ) associato alla regola omonima di Tabella 2.2. L’applicazione di tali
regole può comportare l’eliminazione dell’operatore parallelo: (s1 | O) diventa
semplicemente (s1 ). Vengono effettuati controlli sull’istanza del nodo ottenuto come risultato dai metodi di congruenza. Poi successivamente la chiamata
ai metodi inAParService e outAParService.
inAParService
Applica il metodo apply ai sottonodi. Scorre i figli, partendo dal figlio destro,
passando poi a quello sinistro, per concludere con la lista di figli strutturalmente posti nei nodi centrali (se ve ne sono) applicando su ognuno il metodo
apply. Vediamo sotto il frammento di codice relativo
public List<Action> inAParService(AParService node){
SLList<Action> tempList = new SLList<Action>();
/* INTERPRETE RICHIAMATO SUL SOTTONODO SINISTRO */
node.getLeft().apply(this);
tempList.concatenate(list);
...
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
70
...
ListIterator<PTermPar> iter=node.getList().listIterator();
while(iter.hasNext()){
ATermPar term=((ATermPar)iter.next());
/* ISTANZIAZIONE DI UNA LISTA, EVENTUALMENTE VUOTA
* CHE SCORRE I POSSIBILI SOTTONODI POSTI SINTATTICAMENTE
* IN POSIZIONE CENTRALE NEL TERMINE.
* ALL’INTERNO DEL CICLO WHILE C’E’ LA CHIAMATA
* DELL’ANALIZZATORE SU OGNUNO DEI SOTTONODI CENTRALI
*/
term.getService().apply(this);
tempList.concatenate(list);
...
}
/* INTERPRETE RICHIAMATO SUL SOTTONODO DESTRO */
node.getRight().apply(this);
tempList.concatenate(list);
...
...
return tempList;
}
outAParService
Il metodo scorre tutta la lista di azioni e per ogni caso svolge l’analisi. In
presenza di:
- invocazione (una Invoke); cerca, se esiste, un’azione di ricezione parametri (una Receive associata). Implementa la regola semantica (com).
Secondo le premesse, vengono cercate una Invoke e una Receive. In pre-
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
71
senza di entrambe, confronta i due endpoint e se coincidono, chiama la
funzione match, per la sostituzione dei parametri della Receive e i valori
della Invoke. Chiama infine il predicato noc per valutarne l’eventuale
contesto attivo;
- sostituzione parametri; viene istanziata la classe SubAction e viene implementata la regola parconf . Questo frammento di codice viene
processato quando abbiamo ricorsivamente un termine che include nella sua sintassi il parallelo come sottoservizio di un ulteriore operatore
parallelo. Chiariamo con un esempio. La regola implica nella conclusione, l’evoluzione del solo servizio s1 (o viceversa, data la commutatività
di Parallel, l’evoluzione del solo servizio s2 ). Tra le premesse c’è una
comunicazione attiva tra una Invoke e una Receive. Il semplice termine
[x]( p.o!<e> | p.o?<x>.O )
pone in parallelo una Invoke e una Receive; rispecchia quindi il caso
precedente. Ma il termine
[x, y]( p.o!<e1> | p.o?<x>.O | q.o!<e2> | q.o?<y>.O )
pone il parser nella condizione di costruire un albero sintattico che ha
come padre, un nodo N1 contenente l’operatore parallelo e come figli, due nodi N2 e N3 contenenti a loro volta nuovamente l’operatore
parallelo. Il nodo di sinistra ha a sua volta come figli una Invoke e
una Receive sull’endpoint (p.o) mentre il nodo di destra ha come figli
i medesimi termini sull’endpoint (q.o). N2 e N3 implementano una
comunicazione, quindi la regola (com), N2 all’interno del campo d’azione della delimitation [x] e N3 in quello di [y]. N1 invece implementa
parconf .
Tra le proprie chiamate, secondo le premesse della regola, c’è la
chiamata alla funzione noc per la valutazione del contesto attivo;
- terminazione forzata. Ricordiamo che l’azione kill(k) termina l’esecuzione di tutti i termini eseguiti in parallelo, eccetto quelli protetti
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
72
dall’operatore Protection. Viene quindi implementata la regola parkill
che consiste nella semplice applicazione della funzione halt;
- un’azione diversa dalla sostituzione di parametri o dalla terminazione forzata. Corrisponde alla regola parpass L’implementazione implica
soltanto un focus e un successivo studio sul termine che compie l’azione.
I metodi caseADelService e inADelService non seguono nessuna logica differente da quelle viste fin’ora. Viene focalizzata l’attenzione su
outADelService che è più significativo.
outADelService
Anche in questo caso, il codice è costruito implementando le tre principali
regole semantiche viste nell’insieme delle relazioni di transizione etichettata
che coinvolgono la Delimitation: (delsub ), (delkill ) e (delpass ) di Tabella 2.5.
Quando il metodo è chiamato, vengono inizialmente applicate le regole relative alla congruenza strutturale di Tabella 2.2, in particolare la delim3 .
Una volta eseguite queste operazioni il termine si presenta, come un’unica
Delimitation, con all’interno tutti i parametri trovati nello studio, siano esse
variabili o nomi o label.
Si recupera la lista delle azioni giunte al metodo, la si scorre. Se:
- l’azione è una sostituzione, si implementa (delsub ) Viene istanziato
un oggetto SubAction (classe ereditante da Action, che specializza
la sola azione di sostituzione). Se la sostituzione è coerente (quindi
un valore valutabile da sostituirsi ad una variabile) viene inserita in
una lista temporanea. Il controllo scorre tutti i parametri, verifica la
presenza di ulteriori sostituzioni e le applica dove necessario. Alla fine
viene ricostruito il termine, dove tutte le occorrenze della variabile sono
sostituite con i rispettivi valori. Secondo la conclusione della regola, la
Delimitation sparisce;
- l’azione è una terminazione forzata, si implementa (delkill ) Viene
istanziato l’oggetto specializzato Kill kAction. In questo caso, secon-
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
73
do la conclusione della regola la ricostruzione del termine non implica
la sparizione dell’operatore di Delimitation;
- l’azione non è né una sostituzione né una terminazione forzata. Viene
implementata la regola (delpass ) effettuando il passo computazionale
corrispondente all’azione α secondo premessa.
Il metodo fa uso di tre funzioni di ausilio, ognuna richiamata a seconda
dell’azione coinvolta: checkSub, checkKill, checkDelPass. I primi due prendono come argomenti le variabili e i valori e operano le sostituzioni pendenti;
tra valori-variabili per checkSub, per label-variabili in checkKill.
checkDelPass, effettua le chiamate ai metodi dAlpha e context secondo
le premesse della regola associata (delpass ).
caseAProtService
Il metodo implementa inizialmente le regole semantiche (prot1 ), (prot2 ),
(prot3 ) di Tabella 2.2 dove è coinvolto. Una serie di costrutti “if-then” annidati, dove vengono effettute una serie di chiamate alla funzione replaceBy della
classe Node il cui risultato è proprio restituire il nuovo termine trasformato.
Un frammento di pseudocodice che descrive come il metodo implementa le
regole di congruenza associate è dato di seguito
if ( l’argomento della Protection e’ diverso da Nil ){
if ( l’argomento della Protection e’ diverso da
Delimitation ){
if ( l’argomento della Protection e’ diverso da
Protection ){
...
...
} else {
nodo.replaceBy("elimina Protection esterna");
// applica la trasformazione relativa
// alla regola "prot2"
node.apply(this);
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
74
listaAzioni = outAProtService(node);
} else {
nodo.replaceBy("porta fuori la delimitation");
// applica la trasformazione relativa
// alla regola "prot3"
node.apply(this);
// In tal caso apply e’ applicata
// al nuovo termine [d]{s}
listaAzioni = outAProtService(node);
} else {
node.replaceBy(trasforma nel solo operatore Nil);
// applica la trasformazione relativa
// alla regola "prot1"
}
outAProtService
Implementa la regola prot. Per ogni azione di computazione che vede il termine s coinvolto, viene istanziato un clone applicando una chiamata al metodo
copyAct (per mantenere inalterata la lista d’azioni di partenza) mediante
un assegnamento del proprio riferimento alla dichiarazione statica Action
act. Un oggetto azione ha due campi node identificanti il nodo da cui parte
l’azione e il nodo risultato. Si effettua semplicemente la notifica sull’azione che porta al servizio risultante, inglobandolo in un nuovo nodo di classe
Protection.
outAReplService
Applica le regole di congruenza strutturale associate. In particolare
l’implementazione di (repl2 )
∗s
≡
s | ∗s
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
75
prevede la ricostruzione del termine in un nuovo termine in uscita che include
il parallelo.
Ulteriori metodi relativi alla congruenza strutturale
La congruenza strutturale definisce servizi che possono essere sintatticamente
diversi ma il cui comportamento è il medesimo. È definita mediante il simbolo
≡. Alcune regole sono semplici controlli da effettuare sul nodo coinvolto.
Altre hanno richiesto maggiore attenzione; per quest’ultime è stata preferita
una trattazione separata, implementando metodi Java opportuni. Vediamone
alcuni.
congrPar
Il metodo implementa una delle regole standard della congruenza strutturale, l’eliminazione dell’operatore Nil, elemento neutro dell’operatore parallelo.
Ne gestisce pertanto l’eliminazione e la successiva ricostruzione del termine.
Scrivendo ‘‘O | O’’ s’intende semplicemente scrivere ‘‘O’’; in generale
‘‘O | s’’ è rappresentabile semplicemente con ‘‘s’’. Il metodo basa la propria ricerca recuperando l’oggetto Java rappresentante l’operatore parallelo
e scorrendone i vari campi. Effettua una ricerca per ognuno dei sottoalberi,
interroga il sistema mediante il metodo isIstance controllando se l’istanza
è Nil. In caso affermativo, setta il valore a null (sottointendendo in tal modo l’eliminazione dell’operatore). Al termine della ricerca ricompatta il nodo
eliminandone tutti gli elementi posti a null.
Notiamo che il metodo restituisce un oggetto Node. Questo perché:
- se il nodo rappresenta la forma s1 |s2 |O|s4 elimina il Nil (in qualsiasi
posizione si trovi) e restituisce s1 |s2 |s4 ;
- se il nodo rappresenta la forma s1 |s2 |O oppure s1 |O|s2 elimina il Nil,
(in qualsiasi posizione si trovi) e restituisce sempre s1 |s2 ;
- se il nodo rappresenta la forma s1 |0 restituisce s1 che non è più un
oggetto di classe AParService ma è il generico oggetto Node.
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
76
congrChoise
Anche metodo congrChoise elimina i termini Nil ma dall’operatore di scelta
guardata. Questo perché per congruenza strutturale, Nil è l’elemento neutro
anche della scelta guardata. Non ci soffermeremo ulteriormente sul metodo
in questione. Il funzionamento è medesimo a congrPar.
delim3
Il metodo è associato alla regola semantica omonima (delim3 ) di Tabella 2.2.
Prende come input un oggetto di tipo AParService, un termine rappresentante servizi in parallelo. Vengono estratti ad uno ad uno i vari servizi per
essere inseriti in una lista denominata listService. Scopo del processo è
identificare tutti i termini rappresentanti la delimitazione. Per ogni termine
si estraggono i vari parametri coinvolti nelle Delimitation trovate per poi andarli ad inserire in un unica lista (eccetto le etichette killer, questo perché
necessitano di un’ulteriore verifica per capire se effettivamente l’etichetta è
rimuovibile o meno).
Alla fine del processo di estrazione dei parametri e successivo riempimento della nuova lista, si ricompone il termine tale che, se non sono presenti
delimitazioni, viene restituito il medesimo termine di partenza, altrimenti
viene restituita un’unica Delimitation contenente tutti i parametri estratti
associata al servizio e ai termini coinvolti, inglobati in un unico operatore
parallelo.
Scopo della regola è allargare il campo d’azione della Delimitation, cercando cosı̀ se possibile di attivare nuove comunicazioni. Dicendo d 6∈ f d(s1 ) si
vuole intendere che d non deve essere libera per s1 altrimenti verrebbe coinvolta negli effetti della delimitazione. Dire invece d 6∈ f k(s2 ) fa si che s1 non
venga coinvolto in una terminazione forzata. Le due precedenti condizioni sono entrambe studiate facendo uso di un oggetto java ad Hoc. RenameDelimit
per il controllo della prima e SearchKill per la seconda.
Al momento di verificare se esistono etichette libere, viene istanziato un
oggetto di classe SearchKill passando al costruttore la variabile rappresentante l’etichetta killer da verificare. Tra i propri campi, SearchKill dispone
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
77
di un attributo booleano, isKill, che determina alla fine se l’etichetta su
cui la verifica è effettuata risulta libera o vincolata. Tenendo presente l’etichetta, inizia una fase di scansione sul nodo corrente. Per fare questo basta
riutilizzare le politiche di attraversamento dell’albero già presentate; infatti
la classe SearchKill estende ReverseDepthFirstAdapter, che è la classe
che implementa l’attraversatore dell’albero sintattico. Si cercano nodi, istanza delle classi AKillService e ADelService. Quindi in pratica si cercano i
termini kill(k) e d[s]. Se l’etichetta da verificare è uguale all’etichetta argomento della kill, isKill è impostata a true dal momento che in tal senso
risulta libera. Ma se si incontra un termine del tipo [d]s con l’etichetta presente tra i parametri di d allora risulterebbe vincolata, quindi la regola non
può valere e il campo isKill è impostato a false. Di seguito, il frammento
di codice principale del metodo delim3 che realizza quanto spiegato:
...
...
e = ...etichetta da verificare...;
SearchKill controlKill = new SearchKill(e);
NodoCorrente.apply(controlKill);
if(controlKill.getIsKill()){
// ... Il termine contiene Kill libera ...
listaEtichetteLibere.addToTail(e);
...
} else {
// ... Il termine NON contiene Kill libera ...
}
...
...
Annotazione molto interessante, la chiamata al metodo apply, con argomento
passato controlKill. Il processo è il medesimo mostrato per l’interprete. È
stato semplicemente sviluppato un nuovo oggetto SearchKill che segue le
politiche progettuali dei Pattern utilizzati da SableCC nel medesimo modo.
Per rendere valida la condizione d 6∈ f d(s1 ) invece, il processo si mette
CAPITOLO 4. INTERPRETAZIONE DEL LINGUAGGIO
78
nella condizione di eliminare il rischio. In pratica viene istanziato un oggetto
di classe RenameDelimit al cui interno è presente una tabella hash che mappa
per ogni parametro trovato all’interno dell’operatore Delimitation, un valore
intero. Il meccanismo è sempre il solito, si sviluppa un oggetto che attraversa
l’albero (passando quindi il proprio riferimento al metodo apply). In questo
caso vengono cercati solo i nodi rappresentanti un oggetto ADelService e
per ogni parametro trovato alla fine vi si associa il valore corrispondente in
tabella hash, per quante volte occorre nel termine. A quel punto una chiamata all’oggetto RenameTerm permette di ricostruire il termine correttamente
rinominato.
Quindi se si passa il seguente termine all’interprete:
q.o?<x>.O | [x](p.o!<v> | p.o?<x>.O)
se non fosse utilizzato l’oggetto RenameDelimit sarebbe applicata la sostituzione del parametro x con v anche al sottotermine (q.o?<x>.O trasformandolo in (q.o?<v>.O). Infatti l’inteprete, applicando la regola della congruenza
strutturale (delim3) in modo errato, lavorerebbe sul termine
[x](q.o?<x>.O | p.o!<v> | p.o?<x>.O)
Viceversa, applicando prima dell’interpretazione effettiva, il processo di
rinominazione del termine, si ottiene
q.o?<x>.O | [x1](p.o!<v> | p.o?<x1>.O)
In tal modo viene correttamente interpretato il termine
[x1](q.o?<x>.O | p.o!<v> | p.o?<x1>.O) .
Capitolo 5
Analisi tecnica del parser XML
L’interprete presentato nel capitolo precedente prende in input la specifica
testuale di un termine COWS. L’interprete esegue l’analisi sintattica e semantica, mostra eventuali azioni attive e consente all’utente di far evolvere
il termine. Uno degli obbiettivi di questa tesi è permettere di specificare
un termine COWS tramite una specifa grafica. Spesso infatti una specifica
grafica permette una modellizzazione dei sistemi maggiormente intuitiva e
maggiormente usabile da parte dell’utente finale.
Lo studio di [10] ha permesso di conoscere il Graphical Modelling Framework (GMF) dando una panoramica generale e spiegando come usare alcuni strumenti messi a disposizione dal tool. Come descritto in Sezione 3.1
è un framework che permette dopo una serie di passaggi di creare un editor grafico per un dato linguaggio. Nel caso di COWS, tale editor consente
di modellizzare termini del linguaggio COWS e in seguito manipolarli facilmente. Per mettere in pratica gli scopi proposti siamo intervenuti su due file
specifici, risultato della produzione di GMF, il file di estensione cows e il file
di estensione cows diagram.
Una volta installato il framework GMF, devono essere sviluppati una serie di meta-modelli le cui specifiche sono l’input per il processo di creazione
di un insieme di classi Java che permettono di sviluppare l’editor che successivamente deve essere attivato eseguendo il project it.unifi.dsi.cowsVersione2
come plugin di sviluppo di Eclipse. Viene pertanto attivata una seconda
79
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
80
istanza dell’ambiente di sviluppo.
5.1
Connubio con GMF
Quando l’utente vuole lavorare sull’editor, GMF chiede la creazione di due
file, un file di estensione cows e un file di estensione cows diagram; una volta
creati e aperti sul Workbench, questi permettono all’utente di rappresentare
i termini. Entrambi i file, se aperti con un editor di testo, hanno una struttura XML. Quest’ultimo punto è risultato fondamentale. La sezione corrente
prende in esame le rappresentazioni XML.
5.1.1
Manipolazione dei file prodotti da GMF
Un file strutturato in linguaggio XML, può subire un processo di parsing
mediante l’uso di opportune tecnologie. È possibile estrapolare informazioni
di interesse in modo piuttosto agevole.
Per raggiungere gli obbiettivi preposti è stato costruito un package di
classi Java in grado di prelevare un termine COWS dalla rappresentazione
grafica, analizzando la forma XML associata. Si effettua un passaggio dalla
rappresentazione grafica alla rappresentazione testuale; quest’ultima può essere successivamente prelevata da chiunque richieda il termine in tale forma
e necessiti dell’uso; nel nostro caso un’analisi lessicale, sintattica e semantica
del termine. In seguito, intuita la struttura dei file XML, viene immediato
come rendere il processo reversibile. Mediante lo sviluppo di un pacchetto
di librerie I/O è possibile processare il percorso inverso, attraversando l’albero sintattico del termine COWS e per ogni operatore stampare sul file il
tag XML associato trasformando quindi in tale modo la modalità testuale in
una nuova forma grafica. La Figura 5.1 dettaglia tutti i passaggi. Notiamo
che estende la Figura 3.17, mostrando maggiori dettagli del funzionamento
dell’applicativo.
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
81
Specifica grafica
di termini COWS
Rappresentazione in XML
della specifica grafica
Processo di parsing
Processo I/O
di riscrittura
Specifica testuale
di termini COWS
INTERPRETE
Figura 5.1: Rappresentazione del procedimento di trasformazione specifica
grafica-specifica testuale
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
5.1.2
82
Struttura dei file
La struttura dei due file di estensione cows e cows diagram è totalmente
legata alla struttura dei file ecore ed ecore diagram. I modelli ecore sono
i primi due meta-modelli del processo di creazione dell’editor grafico, da
parte di GMF. Nel seguito della tesi faremo riferimento ai due file cows e
cows diagram come rappresentazione XML e rappresentazione XML estesa
(dove l’estensione consiste nelle informazioni per la visualizzazione grafica
del termine quali i colori, la posizione ecc). Data la rappresentazione XML,
ogni tag è identificato dal nome delle aggregazione che rappresenta. Dato il
modello in formato ecore diagram di figura 3.4 e dato un semplice termine
COWS, quale la comunicazione tra due termini all’interno dello scope della
variabile interessata x
[x]( p.o!<e> | p.o?<x>.O )
la rappresentazione grafica è mostrata in Fig. 5.2.
Figura 5.2: esempio modello grafico cows
La forma XML associata è la seguente
<?xml version="1.0" encoding="UTF-8"?>
<cowsVersione2:Specification xmi:version="2.0"
xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cowsVersione2="it.unifi.dsi.cowsVersione2">
<main xsi:type="cowsVersione2:Delimitation" item="x">
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
83
<bodyDel xsi:type="cowsVersione2:Receive" rpartner="p"
roperation="o"
parameters="x">
<continuation xsi:type="cowsVersione2:Nil">
</continuation>
</bodyDel>
<bodyDel xsi:type="cowsVersione2:Invoke" ipartner="p"
ioperation="o"
expression="e">
</bodyDel>
</main>
</cowsVersione2:Specification>
Il primo tag è cowsVersione2:Specification. È la radice della struttura
XML. Successivamente viene incontrato il tag main. Il tag ha due attributi: xsi:type e item, che hanno valore uguale a cowsVersione2:Delimitation e
x. Una corretta procedura di parsing ricostruisce la stringa [x] che identifica l’operatore Delimitation. Il tag main racchiude due tag bodyDel. Il
secondo tag bodyDel ha gli attributi xsi:type, ipartner, ioperation ed expression uguali a cowsVersione2:Invoke, p, o ed e. Un’opportuna manipolazione permette di ricavare la stringa p.o ! <e>; una Invoke su l’endpoint (p.o) e il dato e. Inoltre la sequenza di tag apertura-chiusura
< bodyDel > · · · < /bodyDel >< bodyDel > · · · < /bodyDel >, tag figli del
main-tag principale, fornisce un’informazione da cui è possibile ricavare il
fatto che gli operatori che rappresentano sono in parallelo tra di loro.
Tutte le altre informazioni, nome operatore, parametri, variabili, sono
valori ricavabili dagli attributi dei tag. Vedremo di seguito le politiche e la
tecnologia utilizzata per mettere in pratica i nostri obiettivi.
5.2
Dalla grafica al testo
La sezione si occupa di fornire una trattazione completa sulle politiche usate
per analizzare il file XML e la successiva estrapolazione delle informazio-
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
84
ni utili, sui concetti Java e in generale di programmazione Object-Oriented
utilizzati e le tecnologie usate, spiegando in modo opportuno tutte le scelte
progettuali usate.
5.2.1
Tecnologia Sax
La tecnologia usata è la tecnologia SAX. Ne esistono diverse per effettuare il
parsing di un file in formato XML. Tra le più conosciute ed usate abbiamo
le tecnologie SAX e DOM. È stata preferita la prima perché concepita in
modo tale da leggere ed interpretare il file una riga per volta. DOM invece è
un’implementazione che richiede che l’intero contenuto del documento venga
analizzato e salvato in memoria. Di solito DOM è utilizzato principalmente
per il recupero di informazioni da documenti con una strutturazione non
standard, cioè dove gli elementi devono essere trovati in modo casuale. Per le
applicazioni basate su XML che usano un processo di lettura e scrittura per
l’analisi, DOM rappresenta un grande spreco di memoria; da qui la preferenza
per SAX.
Il frammento di codice da cui partire è il seguente
File file = new File(’nome del file’);
Default_Handler_2 dh = new Default_Handler_2();
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(file, dh);
Gli oggetti SAXParserFactory e SAXParser istanziano un oggetto parser. Le
specifiche vengono fornite mediante un oggetto DefaultHandler. Dal frammento di codice possiamo vedere che viene istanziato un oggetto dh di tipo
Default Handler 2. È un oggetto che estende e specializza DefaultHandler.
Verrà studiato in seguito dandone maggiori dettagli; al momento basti sapere
che definisce le politiche di parsing da attuare sul file. Il punto principale si
basa comunque proprio su tale oggetto Handler passato al metodo parse.
Interpretando questi comandi, il framework SAX recupera il file e partendo
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
85
dalla prima riga inizia a scorrerlo; per ogni elemento trovato scatena un’evento in base alle politiche impostate. Quindi per ogni tag di apertura, per ogni
tag di chiusura o per ogni attributo trovato mette il sistema nella condizione
di dover fare qualcosa. Sarà nel file Default Handler 2 che vengono date
le direttive su quello che deve essere fatto, come conseguenza dell’elemento
incontrato. I metodi principali di un file di tipo Handler sono
public void startElement(String namespaceURI,
String localName,
String elemento,
Attributes attributi)
throws SAXException {
...
...
}
public void endElement(String namespaceURI,
String localName,
String elemento)
throws SAXException {
...
...
}
Ve ne sono altri ovviamente, ma a noi interessano in particolar modo questi
due. startElement viene richiamato ogni volta che il parser incontra un tag
di apertura, endElement ogni volta che incontra un tag di chiusura.
Questi due metodi saranno sovrascritti nell’oggetto Default Handler 2.
Dalle chiamate a startElement e endElement partiranno altri metodi che
servono alla gestione del file XML. Ma entriamo maggiormente nei dettagli.
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
5.2.2
86
Politica di parsing del file XML
L’approccio da seguire è il seguente: sul file XML viene eseguito il processo
di parsing; ogni tag scatena un evento e in base al nome e al valore dei parametri estrapolati dai suoi attributi, il sistema risponde in modo appropiato.
Quindi la rappresentazione XML viene letta riga per riga e se ne estrapolano
delle stringhe. In base al valore di tali stringhe, il framework effettua certe
operazioni.
Per mettere in pratica questo processo, la progettazione del sistema fa uso
di una certa tipologia di pattern Java: il pattern Factory. Nel caso in questione un connubio tra il Factory Method e l’Abstract Factory. Questa tipologia
di pattern creazionali è molto gettonata nella pratica quotidiana. Scopo di
tali pattern è astrarre la costruzione di un oggetto e la sua composizione
garantendo una maggiore flessibilità e riducendo la dipendenza della classe
client, cioè quella che usa l’oggetto, dalla classe richiesta, quella prodotta dal
Factory; in pratica si vuole rendere il più indipendente possibile la costruzione di un costrutto new object() e creare indipendenza tra il codice e una
specifica implementazione. Il Factory Method si basa sull’ereditarietà, usa
cioè le sue sottoclassi specializzate per istanziare l’oggetto richiesto. Motivo
per cui è considerato un pattern creazionale class-based, mentre l’Abstract
Factory delega ad un altro oggetto la creazione dell’istanza dell’oggetto richiesto. In questo senso è detto un pattern creazionale object-based. Grazie
al forte disaccoppiamento che introducono, i factory sono molto usati nella
costruzione di framework (quali Spring [16]) e in ambienti distribuiti come
RMI [13]. Diamo una descrizione del pattern, appoggiandoci fortemente alla
documentazione ufficiale di [5].
5.2.2.1
Factory
L’idea principale del pattern è: definire un’interfaccia per la creazione di
un oggetto, ma delegare ad una classe derivata il tipo di oggetto da istanziare (questo in base ad un parametro che giunge dall’esterno). In Figura 5.3
troviamo la rappresentazione architetturale del pattern.
L’oggetto product definisce l’interfaccia implementata da concreteProduct
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
87
Figura 5.3: Architettura del pattern Factory
e creator definisce il Factory Method che restituisce una interfaccia di tipo
product; concreteCreator ridefinisce il Factory Method per restituire concreteProduct. Ovviamente sia product che creator possono essere a loro volta
interfacce oppure classi concrete da cui si ottiene il subclassing. In questo caso
viene ridefinito il Factory Method nella classe derivata ma rimane disponibile
una versione di default nella classe base. Per illustrare il pattern seguiamo un
approccio evolutivo, illustrando un esempio ad hoc, in stile “prima e dopo la
cura”, perché aiuta a comprenderne il funzionamento. Ne diamo una buona
panoramica; per maggiori informazioni si rimanda a [6] da cui l’esempio è
stato tratto o a [5].
Supponiamo di avere una applicazione che legge dei dati da un file di testo
contenente informazioni relative a una serie di letture di contatori per acqua
e gas, che saranno successivamente trasformate in un consumo e addebitate
in bolletta. Nel nostro codice abbiamo una classe dedicata a questo scopo,
che legge i vari formati dei file e in base ad un parametro di tipo stringa
istanzia l’oggetto associato al parametro stesso:
public class AcquisizioneLetture {
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
88
public AcquisizioneLetture() {} // Costruttore
public void parseFile(String fileName, String dataType) {
ArrayList lettureArray = new ArrayList();
FileLettureReader fileLettureReader;
/* ** FileLettureReader e’ un’interfaccia ** */
Lettura lettura;
/* ** Lettura e’ un’interfaccia ** */
if (dataType.equals("gas")) {
fileLettureReader = new GasLettureReader(fileName);
}
if (dataType.equals("acqua")) {
fileLettureReader = new AcquaLettureReader(fileName);
}
while (fileLettureReader.hasNextLettura()) {
lettura = fileLettureReader.getNextLettura();
if (lettura.verifica()) {
lettura.calcolaconsumo();
lettura.registra();
} else {
lettura.scarta();
}
}
} // Fine metodo parse
} // Fine classe AcquisizioneLetture
Le classi GasLettureReader e AcquaLettureReader sono classi specializzate nella lettura di file in formato testo; implementano l’interfaccia FileLettureReader e restituiscono un oggetto di tipo Lettura. GasLettureReader elabora tutti i dati relativi alla lettura del gas mentre AcquaLettureReader è
relativa per tutto quello che concerne la lettura del contatore dell’acqua.
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
89
Poniamo di essere interessati anche all’energia elettrica, alla luce. È necessario di conseguenza acquisire le relative letture; è necessario gestire un tipo
aggiuntivo di file. La nostra classe va modificata come di seguito:
public class AcquisizioneLetture {
public AcquisizioneLetture() {} // Fine costruttore
public void parseFile(String fileName, String dataType) {
ArrayList lettureArray = new ArrayList();
FileLettureReader fileLettureReader;
/* ** FileLettureReader e’ un’interfaccia ** */
Lettura lettura;
/* ** Lettura e’ un’interfaccia ** */
if (dataType.equals(’gas’)) {
fileLettureReader = new GasLettureReader(fileName);
}
if (dataType.equals(’acqua’)) {
fileLettureReader = new AcquaLettureReader(fileName);
}
if(dataType.equals(’luce’)){
fileLettureReader = new LuceLettureReader(fileName);
}
while (fileLettureReader.hasNextLettura()) {
lettura = fileLettureReader.getNextLettura();
if (lettura.verifica()) {
lettura.calcolaconsumo();
lettura.registra();
} else {
lettura.scarta();
}
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
90
}
} // Fine metodo parse
} // Fine classe AcquisizioneLetture
È stato necessario toccare il codice e introdurre le modifiche. Non è la scelta
preferibile perché possono esserci altre aggiunte. Quindi la prima idea è separare la parte di codice che tende ad essere spesso modificato dalla parte
di codice che deve rimanere inalterata. Quindi la classe AcquisizioneLetture
può essere scritta come di seguito in modo da non rimettere mano al codice
public class AcquisizioneLetture {
private ReaderFactory factory;
public AcquisizioneLetture(ReaderFactory factory) {
this.factory = factory;
} // Fine costruttore
public void parseFile(String fileName, String dataType) {
ArrayList lettureArray = new ArrayList();
FileLettureReader fileLettureReader;
/* ** FileLettureReader e’ un’interfaccia ** */
Lettura lettura;
/* ** Lettura e’ un’interfaccia ** */
fileLettureReader
= factory.getFileLettureReader(fileName,dataType);
while (fileLettureReader.hasNextLettura()) {
lettura = fileLettureReader.getNextLettura();
if (lettura.verifica()) {
lettura.calcolaconsumo();
lettura.registra();
} else {
lettura.scarta();
}
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
91
}
} // Fine metodo parse
} // Fine classe AcquisizioneLetture
Mentre si incapsula la creazione di FileLettureReader all’interno di una
nuova classe FileReaderFactory:
public class ReaderFactory {
private FileLettureReader fileLettureReader;
public ReaderFactory() {} // fine costruttore
public FileLettureReader getFileLettureReader(String fileName,
String dataType) {
if (dataType.equals("gas")) {
fileLettureReader = new GasLettureReader(fileName);
}
if (dataType.equals("acqua")) {
fileLettureReader = new AcquaLettureReader(fileName);
}
if (dataType.equals("Luce")) {
fileLettureReader = new LuceLettureReader(fileName);
}
return fileLettureReader;
} // Fine metodo getFileLettureReader
} // Fine classe ReaderFactory
In questo modo si separa la logica di creazione del FileLettureReader da
l’utilizzo. AcquisizioneLetture è chiuso a modifiche e ReaderFactory è una
classe riutilizzabile anche altrove, avendo isolato in essa le eventuali modifiche. Questo costrutto, chiamato anche Simple Factory è una applicazione
saggia del basilare principio Object-Oriented di separare il codice soggetto a
cambiamenti da quello che rimane invariato. Inoltre il forte disaccoppiamento tra AcquisizioneLetture da FileLettureReader riduce la dipendenza tra le
classi. ReaderFactory può essere ulteriormente ottimizzata. Pensiamo infatti
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
92
ad un possibile caso reale. Poniamo ad esempio che la classe ReaderFactory
debba essere utilizzata da applicazioni diverse che devono elaborare i dati a loro piacere. È pertanto possibile sviluppare due nuove classi factory ad
hoc, ReaderFactory-Appl1 e ReaderFactory-Appl2, che ereditano dalla nostra
ReaderFactory, come di seguito:
public class ReaderFactory-Appl1 extends ReaderFactory{
public ReaderFactory-Appl1() {}
public FileLettureReader getFileLettureReader(String fileName,
String dataType) {
if (dataType.equals("gas")) {
fileLettureReader = new Appl1-GasLettureReader(fileName);
}
if (dataType.equals("acqua")) {
fileLettureReader = new Appl2-AcquaLettureReader(fileName);
}
return fileLettureReader;
} // Fine metodo getFileLettureReader
}
public class ReaderFactory-Appl2 extends ReaderFactory {
public ReaderFactory-Appl2() {}
public FileLettureReader getFileLettureReader(String fileName,
String dataType) {
if (dataType.equals("gas")) {
fileLettureReader = new Appl2-GasLettureReader(fileName);
}
if (dataType.equals("luce")) {
fileLettureReader = new Appl2-LuceLettureReader(fileName);
}
return fileLettureReader;
}
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
93
}
Vediamo che le due classi sono personalizzate per l’applicazione a cui sono
orientate. In questo modo viene deciso all’esterno di AcquisizioneLetture il
Factory da utilizzare e poi va passato al costruttore per andare a leggere i
files con il metodo di lettura corretto. Due aspetti rilevanti però: AcquisizioneLetture ha sempre bisogno che gli venga passato un factory per funzionare,
tanto che viene passato direttamente nel costruttore. Le operazioni fatte sulla
lettura, come visto all’inizio, sono sempre quelle; sarebbe utile quindi portare il factory direttamente dentro AcquisizioneLetture per rendere la classe
autosufficiente, ma senza perdere la flessibilità ottenuta fino ora.
Torniamo quindi indietro alla prima versione di AcquisizioneLetture, ma
questa volta incapsuliamo la creazione delle classi FileLettureReader all’interno di un metodo astratto e rendiamo quindi astratta tutta la classe; da
questa deriviamo le varie versioni per le varie applicazioni, implementando
in ognuna di esse il metodo che incapsula la creazione delle classi FileLettureReader; se prima avevamo un SimpleFactory per ogni applicazione, ora
abbiamo una AcquisizioneLetture per ogni cliente, ognuna contenente la propria logica di lettura dei file. È importante sottolineare che la classe base non
conosce il FileLettureReader su cui itera e da cui ricava le letture, perché
questo dipende dalle classi derivate, quindi vengono conservati il disaccoppiamento e la flessibilità introdotti con i precedenti SimpleFactory. La nostra
classe astratta sarà quindi come segue:
public abstract class AcquisizioneLetture {
public AcquisizioneLetture() {}
public void parseFile(String fileName, String dataType) {
ArrayList lettureArray = new ArrayList();
FileLettureReader fileLettureReader;
/* ** FileLettureReader e’ un’interfaccia ** */
Lettura lettura;
/* ** Lettura e’ un’interfaccia ** */
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
94
fileLettureReader = getFileLettureReader(fileName,
dataType);
while (fileLettureReader.hasNextLettura()) {
lettura = fileLettureReader.getNextLettura();
if (lettura.verifica()) {
lettura.calcolaconsumo();
lettura.registra();
} else {
lettura.scarta();
}
}
} // fine metodo parse
protected abstract
FileLettureReader getFileLettureReader(String fileName,
String fileType);
} // Fine classe AcquisizioneLetture
dove è presente il metodo astratto getFileLettureReader. Il metodo principale su cui si basa il pattern. A questo punto le classi che ci servono per le nostre applicazioni esterne, implementando il metodo astratto
getFileLettureReader in maniera opportuna, sono le seguenti:
public class AcquisizioneLettureConcreta
extends AcquisizioneLetture {
public AcquisizioneLettureConcreta() {}
/* Fine costruttore
*/
protected FileLettureReader
getFileLettureReader(String fileName,
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
String fileType) {
FileLettureReader fileLettureReader;
if (fileType.equals("gas")) {
fileLettureReader = new GasLettureReader(fileName);
}
if (fileType.equals("acqua")) {
fileLettureReader = new AcquaLettureReader(fileName);
}
if (fileType.equals("luce")) {
fileLettureReader = new LuceLettureReader(fileName);
}
return fileLettureReader;
} // Fine metodo getFileLettureReader
} // Fine classe AcquisizioneLetturaConcreta
public class AcquisizioneLettureAppl1
extends AcquisizioneLetture {
public AcquisizioneLettureAppl1() {}
protected FileLettureReader
getFileLettureReader(String fileName,
String fileType) {
FileLettureReader fileLettureReader;
if (fileType.equals("gas")) {
fileLettureReader =
95
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
96
new Appl1-GasLettureReader(fileName);
}
if (fileType.equals("acqua")) {
fileLettureReader =
new Appl1-AcquaLettureReader(fileName);
}
return fileLettureReader;
} Fine metodo getFileLettureReader
} // Fine classe AcquisizioneLettureAppl1
public class AcquisizioneLettureAppl2
extends AcquisizioneLetture {
public AcquisizioneLettureAppl2() {}
protected FileLettureReader
getFileLettureReader(String fileName,
String fileType) {
FileLettureReader fileLettureReader;
if (fileType.equals("gas")) {
fileLettureReader = new Appl2-GasLettureReader(fileName);
}
if (fileType.equals("acqua")) {
fileLettureReader = new Appl2-AcquaLettureReader(fileName);
}
return fileLettureReader;
} // Fine metodo getFileLettureReader
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
97
} // Fine classe AcquisizioneLettureAppl2
Nelle Figure 5.4 e 5.5 il diagramma UML che descrive il pattern; in Tabella
5.1 viene mostrato quelle che sono gli elementi del pattern e le classi che
lo rappresentano nell’esempio precedente. Si osservi inoltre come un Factory
Method non restituisca necessariamente un solo tipo di concreteProduct, ma
possa restituirne diversi e possa anche essere parametrico, senza alterare la
sostanza della architettura del pattern.
creator
AcquisizioneLetture
product
FileLettureReader
concreteCreator
AcquisizioneLettureConcreta
AcquisizioneLettureAppl1
AcquisizioneLettureAppl2
concreteProduct
GasLettureReader
AcquaLettureReader
LuceLettureReader
Appl1-GasLettureReader
Appl1-AcquaLettureReader
Appl2-GasLettureReader
Appl2-LuceLettureReader
Factory Method
getFileLettureReader()
Tabella 5.1: Elementi del pattern Factory e classi che li rappresentano
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
Figura 5.4: Diagramma UML dell’esempio - parte 1
Figura 5.5: Diagramma UML dell’esempio - parte 2
98
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
5.2.2.2
99
Factory applicato alla lettura del XML
Vediamo come è stato concepito il sistema in modo da leggere il file XML e
per ogni tag scatenare l’evento associato.
Come nell’esempio precedente, dobbiamo scorrere e leggere riga per riga
un file di testo (nel nostro caso strutturato in XML). Ogni tag è una stringa
di testo e in base al valore deve essere istanziato l’oggetto corretto. Quindi, prima venivano istanziati gli oggetti GasLettureReader, AcquaLettureReader o LuceLettureReader in base al valore di ritorno “gas”, “acqua” o
“luce”. Adesso bisogna istanziare degli oggetti ad hoc, in base al nome del
tag incontrato: main, bodyProt, bodyDel, bodyRepl ecc. Partendo quindi
da oggetti che nel framework rappresentano l’interfaccia “product” e le classi derivate “concreteProduct” viene allocato un package. Un pacchetto Java
chiamato terminiCows. L’interfaccia rappresentata da product è denominata
TerminiCows; le classi concreteProduct sono tutte le altre classi contenute
nel pacchetto. Per ogni operatore di COWS abbiamo una classe Java che
implementa TerminiCows il cui nome è
TermineCows nomeOperatore
L’interfaccia TerminiCows ha solo due metodi. Vediamo di seguito il
frammento di codice
package terminiCows;
public interface TerminiCows {
public String getTermineInterpreteCows();
public void creaTermine();
}
Per ogni classe derivata, getTermineInterpreteCows() è il classico metodo
get per il recupero di un dato mentre creaTermine() è il metodo mediante
il quale, una volta istanziato l’oggetto, viene costruita la stringa di testo
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
100
che rappresenta l’operatore in questione. Facciamo un esempio che permette di illustrare meglio il funzionamento. Posto che il parser incontri il tag
d’apertura
<bodyDel xsi:type="cowsVersione2:Invoke" ipartner="p"
ioperation="o"
expression="e">
viene chiamato il metodo startElement sul tag denominato bodyDel. Vengono estrapolati i valori degli attributi. L’attributo “xsi:type” ha valore “cowsVersione2:Invoke”. La sottostringa “cowsVersione2:” viene eliminata lasciando come valore di xsi:type la sola stringa Invoke. Questo è il valore che
viene passato al framework, che sarà processato in modo equivalente a come
venivano processate le stringhe “gas”, “acqua” o “luce”.
Quindi viene eseguita una serie di costrutti if-then-else dove la stringa passata è “Invoke” e in base a questo valore viene chiamato l’oggetto associato.
Il frammento di codice coinvolto sarà:
if (tagTermineCows.equals("Invoke")){
tc = new TermineCows_Invoke(attributi);
tc.creaTermine();
OggettoXmlCows oxc = new OggettoXmlCows();
oxc.setStringaServizioCows(tc.getTermineInterpreteCows());
...
...
}
Viene istanziato l’oggetto TermineCows Invoke. Vengono passati al costruttore i valori p dell’attributo “ipartner” e o ed e rispettivamente valori degli attributi “ioperation” e “expression”. La successiva chiamata al metodo
creaTermine, crea la stringa p.o ! <’e’>. Notiamo il successivo oggetto
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
101
OggettoXmlCows. È un oggetto progettato per utilità che vedremo meglio in
seguito. Adesso basti dire che posside un campo di tipo stringa dove viene
recuperata (mediante la chiamata a getTermineInterpreteCows) la stringa
rappresentante la Invoke appena costruita. Seguendo alcune politiche e utilizzando particolari strutture dati la stringa viene concatenata a tutte le altre
ricavate dal processo di parsing, formando il termine COWS finale, input in
seguito per l’interprete.
Per l’interfaccia “creator” e “concretCreator” la politica adottata è la
seguente. Vista la tecnologia SAX, un interfaccia di tipo creator altro non è
che il DefaultHandler da passare al metodo parse. Ricordando il comando
precedente visto nel frammento di codice SAX in Sezione 5.2.1
Default_Handler_2 dh = new Default_Handler_2();
...
saxParser.parse(file, dh);
l’interfaccia e le classi coinvolte, coincidono con l’oggetto
Default Handler 2. Il valore 2 indica un caso particolare. La tecnologia SAX dispone di una classe di base per i vari Handler denominata
appunto DefaultHandler (in passato esisteva HandlerBase adesso comunque deprecata). In generale per effettuare un parsing basta estendere
questa classe. In questo caso però dobbiamo avere un classe astratta, la
nostra creator, da cui fare estendere i concreteCreator. Per tale motivo, alla
gerarchia di classi della tecnologia SAX, è stato aggiunto un ulteriore ramo,
un sottoalbero alla cui radice c’è la classe astratta Default Handler il cui
codice è mostrato di seguito
public abstract class Default_Handler
extends DefaultHandler{
public Default_Handler() {} // Fine costruttore
protected abstract TerminiCows
getTermineCows(String tagTermineCows,
Attributes attributi);
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
102
public void startElement(String namespaceURI,
String localName,
String elemento,
Attributes attributi)
throws SAXException {}
public void endElement(String namespaceURI,
String localName,
String elemento)
throws SAXException {}
} // Fine Default_Handler
dove vediamo essere presenti i due metodi startElement e endElement
al momento ancora da implementare e soprattutto il metodo astratto
getTermineCows. Per la struttura del pattern Factory vista prima riconosciamo in Default Handler la nostra classe “creator” e nel metodo getTermineCows il “Factory Method” corrispondende al precedente
metodo astratto “getFileLettureReader()” Adesso è possibile identificare
Default Handler 2 (da qui il postfisso 2) come il nostro concreteCreator.
5.2.3
Default Handler 2
Vediamo come è stata progettata la classe Default Handler 2. Vengono descritti i metodi principali. Successivamente vengono fornite ulteriori informazioni quali la struttura dati e alcune utility coinvolte. Ricordiamo che
un oggetto di questa classe viene richiamato per parsare la rappresentazione
XML associata ad un termine COWS rappresentato graficamente. L’output
fornito è il termine COWS associato in forma testuale. Quindi rappresenta
la trasformazione di un termine dalla modalità grafica alla modalità testo.
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
5.2.3.1
103
Metodi del DefaultHandler2
startElement
Sovrascrive il metodo startElement della classe Default Handler (quindi il
metodo appartenente al generico DefaultHandler); scatena pertanto l’evento
principale in base all’identificazione del tag. Tiene il conto di quanti elementi
è formato il termine e il livello di profondità raggiunto nel file XML. Effettua
il controllo sul nome del tag e in base al nome (corrispondente come visto ad
una delle aggregazioni del file .ecore) richiama il metodo per l’estrapolazione
degli attributi. I confronti sul valore del tag sono effettuati mediante una
serie di costrutti di scelta annidati tra loro.
Particolarmente importante il primo tra i suddetti costrutti if-then-else.
Il primo tra questi costrutti infatti esegue un controllo sulla tipologia di tag
d’apertura che il processo di parsing incontra.
Infatti un tag d’apertura può essere preceduto da un ulteriore tag d’apertura; questo caso indica degli elementi autocontenuti (quindi la discesa ad
un ulteriore livello di profondità dell’albero sintattico rappresentante il file
XML). Un tag d’apertura può allo stesso tempo essere, preceduto da un tag
di chiusura. In questo caso gli elementi sono allo stesso livello di profondità.
Questo secondo caso è la strategia scelta per trattare l’operatore parallelo.
Come accennato in Sezione 3.1 il file .ecore, punto di partenza della catena di meta-modelli grazie al quale s’arriva alla definizione dell’editor grafico
utilizzato da GMF, ha subito delle modifiche in base alle versioni precedenti
realizzate in [10]. In particolare ricordiamo l’eliminazione della classe UML
rappresentante l’operatore Parallel, permettendo in questo modo una maggiore usabilità del tool; questo ha comportato la scelta di una strategia di
sviluppo che dovesse in ogni caso riconoscerne la presenza. Il fatto d’avere un
tag d’apertura immediatamente preceduto da un tag di chiusura indica un
nuovo elemento allo stesso livello di un altro, quindi due termini in parallelo
tra loro. Lo pseudo-codice seguente dettaglia maggiormente la strategia
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
104
...
...
<elem1>
<elem2>
...
...
</elem2>
</elem1>
<elem3>
...
...
</elem3>
...
...
Vediamo che elem2 è contenuto in elem1, ma elem3 è parallelo a elem1
Pertano il primo costrutto if-then-else del metodo startElement è effettuato
su una variabile booleana varTag che se assume valore true, indica che il tag
precedente era di chiusura. In questo modo secondo la sintassi del linguaggio
COWS, viene notificato al sistema di trattare il parallelismo.
endElement
Chiamato ad ogni tag di chiusura. Se il tag di chiusura è cowsVersione2:Specification il file XML è concluso, terminando in tal modo il processo di parsing; vengono recuperate tutte le sottostringhe e concatenate nell’unica stringa rappresentante il termine COWS. Altrimenti per ogni altro valore vanno
gestite le variabili booleane principali sul controllo dei tag. Ne abbiamo due
piuttosto rilevanti varTag e varTagSum. varTag, come accennato in precedenza, se posto uguale a true indica che il tag precedente era un tag di chisura;
di conseguenza siamo di fronte alla chiusura in serie di due elementi annidati. Questo implica la diminuzione di uno del livello di profondità dell’albero
e la mancata presenza di parallelismo tra elementi. La variabile varTagSum
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
105
gestisce soltanto la presenza del tag sum, quindi la presenza di una scelta
guardata.
lavoroSuAttributi
Metodo richiamato per l’estrapolazione degli attributi dei vari tag. Ricordiamo infatti che i parametri di interesse sono tutti strutturati nel file XML
sotto forma di attributi. Di nuovo col solito esempio, dato il tag
<bodyDel xsi:type="cowsVersione2:Invoke" ipartner="p"
ioperation="o"
expression="e">
bodyDel è il nome del tag, ma i valori di interesse (l’operatore Invoke, l’endpoint ((p.o)) e l’espressione inviata e) sono ricavabili solo dagli attributi. La
tecnologia SAX ci viene in aiuto predisponendo una classe Attributes il cui
campo principale è un lista dove memorizzare i vari attributi e che mette a
disposizione una serie di metodi per l’estrapolazione, il confronto tra valori,
in generale il loro trattamento.
getTermineCows
Il Factory Method, il metodo astratto del pattern Factory qua implementato.
Prende in input la stringa e la lista degli attributi. Dato x il valore della
stringa viene istanziato l’oggetto TermineCows x dove il postfisso x indica
il nome dell’operatore. Come già visto in precedenza il codice successivo è
composto da i due comandi
tc = new TermineCows_x(attributi);
tc.creaTermine();
Una volta composto il termine (allo stato delle cose ancora un sottotermine) viene memorizzato in un oggetto OggettoXmlCows. Vedremo meglio la
struttura di tale oggetto Java tra poco. Al momento basti semplicemente sapere che contiene un campo String dove viene salvata la sottostringa appena
sviluppata (sfruttando il metodo getTermineInterpreteCows) e un campo
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
106
booleano che indica se l’elemento ne ha annidati altri contenuti in se stesso; Il
valore false di questo campo presuppone la possibilità di trattare la chiusura
del sottotermine e quindi l’effettiva concatenazione al termine COWS finale.
posizioneElemento
Gestisce le strutture dati sviluppate per la classe Default Handler 2. Il compito principale è il posizionamento nel punto corretto, delle varie sottostringhe, in modo tale che la concatenazione finale altro non fa che scorrere la
lista dei sottotermini nell’ordine in cui l’incontra. Prende in input un oggetto di classe OggettoXmlCows, l’oggetto Java dove viene memorizzato il
sottotermine corrente.
5.2.3.2
Strutture dati della classe Default handler 2
Nel costruttore della classe vengono istanziate delle strutture progettate per
il trattamento dei dati. In particolare abbiamo una variabile denominata
localTermineCows che memorizza una lista di oggetti OggettoXmlCows.
Questa sezione lo descrive in dettaglio.
Un oggetto di classe OggettoXmlCows viene istanziato nel metodo
getTermineCows. Il metodo è l’implementazione del Factory Method principale; ogni volta che viene creato il termine COWS viene memorizzato in
questo oggetto, creato ad hoc per gestire tutti i sottotermini, sfruttandolo
infine per la concatenazione conclusiva. Presenta tre campi, un campo String
dove il sottotermine è salvato, una flag booleana dove si indica se il termine
ha o meno ulteriori sottoservizi annidati in esso e un terzo campo contenente
a sua volta una lista di OggettoXmlCows. Chiaro lo scopo di tale terzo campo. Se il termine corrente è un contenitore, la lista contiene i riferimenti ai
termini in esso contenuti. Quando l’oggetto è creato e i campi riempiti, viene passato al metodo posizioneElemento. Inoltre quando la varTag risulta
true, in posizione opportuna, viene memorizzato un nuovo elemento OggettoXmlCows, contente il carattere speciale “(” (parentesi) per identificare la
presenza di parallelismo.
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
5.3
107
Dal testo alla grafica
Il processo di trasformazione del termine COWS è un processo reversibile. Il
framework è stato sviluppato in modo da poter elaborare entrambe le modalità, grafica e testuale, permettendo il passaggio senza problemi da una
all’altra forma. Abbiamo visto come passare dalla specifica grafica alla specifica testuale. La politica di sviluppo in grado di realizzare il processo inverso
è stata quasi immediata. La specifica grafica è associabile ad un file di testo
strutturato in XML; una volta capite le politiche che GMF usa per creare
tale file, basta realizzare un insieme di moduli che le mettono in pratica.
L’idea è quindi quella di ricreare un nuovo file di estensione cows, una nuova
rappresentazione XML che sovrascriva la precedente.
Per fare questo viene nuovamente in aiuto la filosofia progettuale del
pattern Visitor utilizzata da SableCC (vedi [14] o [5] o semplicemente
l’Appendice A di questa tesi).
Dato un termine COWS in forma di testo da passare ad un interprete, l’applicativo crea un oggetto Java che formalmente rappresenta l’albero
sintattico associato. In questo modo si ottiene una struttura dati su cui è
possibile eseguire un attraversamento e la visita dei nodi. Attraversamento
dell’albero, visita del nodo corrente e accesso al nodo successivo. Su queste
basi si sviluppa concettualmente il pattern Visitor. L’idea principale ripresa
dal pattern è quella di creare due gerarchie, una di oggetti su cui è possibile
effettuare una scelta e una di oggetti che scelgono. Per realizzare i nostri
scopi decidiamo di tenere fisso l’insieme da cui si può scegliere e cambiando
le politiche di sviluppo successive alla scelta, è possibile modificare l’oggetto
che compie delle azioni in base alla scelta effettuata.
L’idea da mettere in pratica è quindi quella di sviluppare un nuovo oggetto
che “sceglie” sulla base dell’operatore COWS che incontra e successivamente
compie certe azioni.
Questa idea riprende esattamente le politiche di sviluppo dell’interprete,
in particolare della classe InterCows. La realizzazione dell’interprete ha infatti portato alla costruzione di un oggetto Java che attraversando l’albero,
per ogni nodo, riconosce l’operatore in questione, applica su di esso il meto-
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
108
do apply in modo opportuno passando il riferimento a se stesso e per ogni
o- peratore richiama il metodo caseXXX associato (quindi “sceglie” in base
all’operatore “XXX” incontrato). All’interno dei metodi caseXXX vengono
implementate le regole della semantica operazionale di COWS (mediante la
chiamata di metodi outXXX associati).
In questa ottica invece adottando lo stesso meccanismo, è possibile sviluppare un nuovo oggetto cambiando semplicemente le azioni che i metodi
caseXXX mettono in pratica.
Viene pertanto istanziato un oggetto denominato RiscritturaFileCows
che prende in input l’albero sintattico associato al termine COWS e per ogni
operatore incontrato trascrive sul file di testo, il tag XML associato. Per ogni
operatore appende in coda al file XML la corretta stringa che lo identifica
secondo lo schema che GMF usa per strutturare la rappresentazione XML.
Vediamo come è stato sviluppato l’oggetto RiscritturaFileCows.
5.3.1
Il file RiscritturaFileCows
L’oggetto estende la classe AnalysisAdapter (esattamente come l’oggetto
interprete InterCows) seguendo la politica del pattern Visitor (maggiori dettagli in Appendice A). Quando viene istanziato, viene passato al costruttore
il nome del file da modificare. Dopodiché non si deve far altro che riscrivere
i metodi caseXXX in modo che, per ogni nodo dell’albero, l’attraversatore
riconosce l’operatore associato, richiama il metodo caseXXX relativo e attiva
cosı̀ una serie di operazioni print su file.
Vedremo che i file da modificare sono entrambi i file creati con GMF: la
rappresentazione XML e la rappresentazione XML estesa. La cosa potrebbe
lasciare in parte interdetti. Infatti fino ad ora abbiamo estrapolato informazioni solo dal file di estensione cows, quindi l’estensione cows diagram sembrerebbe ininfluente. C’è un motivo per questo, che verrà spiegato meglio in
seguito.
Il primo metodo che viene eseguito è quello associato alla radice, il metodo caseStart. Esegue le prime operazioni di append sul file, stampando
l’intestazione di un file XML
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
109
<?xml version="1.0" encoding="UTF-8"?>
<cowsVersione2:Specification xmi:version="2.0"
xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cowsVersione2="it.unifi.dsi.cowsVersione2">
e successivamente il tag radice
<main xsi:type="cowsVersione2:
Queste sono operazioni piuttosto semplici da implementare con Java. Bisogna istanziare (che è poi quello che viene fatto nel costruttore) un oggetto
PrintStream a cui viene passato l’oggetto File (la rappresentazione XML).
Per ogni tag viene chiamato il metodo “print” dell’oggetto PrintStream.
Per una versione più dettagliata rimandiamo a [3] dove è presente un ampia
sezione dedicata all’I/O in Java.
Notiamo che la stringa nell’esempio precedente, non è un tag completo,
ma una stringa che termina con i due punti. Questo ovviamente accade perché
possiamo conoscere il nuovo operatore solo continuando a visitare i sottonodi
dell’albero (se ve ne sono).
Vengono pertanto riscritti i vari metodi caseXXX e per ogni case, viene
stampata sul file, la stringa che specifica il nome dell’operatore e se è un
operatore che può contenere ulteriori termini, vengono richiamati ulteriori
metodi che permettono una gestione dei nomi dei tag. Ad esempio, se nell’attraversamento dell’albero il nodo corrente è un nodo rappresentante l’operatore Invoke, mediante una serie di metodi get vengono prelevati i parametri
coinvolti (l’endpoint (p.o) e l’espressione invocata e e successivamente viene
eseguito un append al file cows della stringa
Invoke" ipartner="p" ioperation="o" expression="e">
che concatenata alla precedente diventa
<main xsi:type="cowsVersione2:Invoke" ipartner="p"
ioperation="o"
expression="e">
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
110
Se invece il nodo rappresenta la Delimitation, viene recuperato
l’argomento x e il comando successivo è la stampa su file di
Delimitation item="x">
che concatenata al tag main diventa correttamente
<main xsi:type="cowsVersione2:Delimitation item="x">
Poi, siccome la Delimitation è un operatore COWS che è applicato ad ulteriori
termini viene stampata su file la stringa
<bodyDel xsi:type="cowsVersione2:
e successivamente inizia la visita dei figli del nodo. All’uscita dal metodo
apply viene stampata in coda al file la stringa
</bodyDel>
rappresentante il tag di chiusura.
Ricapitolando, ogni metodo caseXXX stampa i tag ad esso associati immediatamente prima dell’entrata in un metodo apply e immediatamente dopo.
Il primo comando del metodo è sempre la stampa del nome dell’operatore
associato al metodo completato dai propri parametri.
Infine l’uscita dal metodo apply, nel metodo caseStart, permette la
stampa
</main>
</cowsVersione2:Specification>
e quindi la chiusura della rappresentazione XML.
A questo punto GMF si accorge delle modifiche apportate ai suoi file e
chiede l’immediato salvataggio. Basta salvare il file, eseguire un rearrange all
(un riposizionamento degli elementi) e la procedura è conclusa.
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
5.3.2
111
Consistenza dei dati
Nella sezione precedente è stato accennato al fatto che i moduli per la riscrittura dei file toccano in qualche modo anche la rappresentazione XML
estesa, fatto questo piuttosto strano visto che le classi per l’estrapolazione
delle informazioni non vi prelevano nessun dato. Si parlava di un processo
reversibile, ma da questo punto di vista sembra in realtà che venga fatto
qualcosa in più. In effetti per mettere in pratica un processo di trasformazione effettivo da testo a grafica, basta ricostruire la rappresentazione XML,
ma la politica di riscrittura scelta potrebbe minare la consistenza dei dati.
Infatti sono file creati da GMF, quindi il framework può avere delle politiche
di gestione personali.
Per tale motivo entra in gioco la rappresentazione XML estesa. Questo
file (aperto come file di testo) ad un primo sguardo risulta incomprensibile.
Ci sono vari tag e ognuno dispone di un numero cospicuo di attributi. Molti valori sono numerici. Infatti il cows diagram dettaglia la composizione, le
coordinate, le forme e i colori delle figure geometriche sull’editor e l’informazione che viene strutturata è orientata a questo scopo. Basta un semplice
servizio, come la sincronizzazione tra una Invoke e una Receive, per renderlo
voluminoso e illeggibile.
Studiandolo meglio però, è presente un dato significativo, un identificativo
del file. Tale identificativo è l’unico valore che appare quando il file è vuoto. Si
è voluto vedere come GMF gestisce l’identificativo. L’approccio è il seguente:
GMF quando manipola i file, recupera per prima cosa l’identificativo e lo
tiene da parte. Quando viene compiuta una qualsiasi operazione, associa
ogni modifica all’identificativo. Al momento del salvataggio, guarda quale è
l’identificativo del file da salvare quindi recupera tutte le modifiche e le mette
in pratica, riscrivendo il file .cows diagram.
Quindi quello che è stato fatto è una simulazione di questo processo. Prima di processare qualsiasi modifica o riscrittura sulla rappresentazione XML,
viene recuperato l’identificatore da .cows diagram. Con il sistema I/O Java
viene cancellato tutto il suo contenuto, lasciando inalterato il solo identificativo. In questo stato del processo, è come se avessimo un file cows diagram
CAPITOLO 5. ANALISI TECNICA DEL PARSER XML
112
vuoto. Adesso sono possibili tutte le modifiche che vogliamo. Quando andiamo ad eseguire il salvataggio del .cows, GMF vede che è stato toccato un file
che ha assegnato un identificativo quindi guarda tutte le modifiche effettuate
sul .cows, le associa all’identificativo è ricostruisce il file. Reinterpreta i due
file e li ricostruisce.
In questo modo i due file, possono essere modificati dall’esterno senza
problemi.
Capitolo 6
Un plugin per Eclipse
L’applicativo viene fornito in due versioni differenti: come semplice Java Application (una classe Java dove il banale metodo “main” permette di e- seguire l’applicativo sfruttando la console di Eclipse) o come estensione alla
piattaforma, un processo senz’altro più delicato che ha portato a scontrarsi
con determinati problemi e a determinate scelte progettuali.
Il capitolo descrive tutte le fasi necessarie alla progettazione di un plugin
per Eclipse in fase di sviluppo. Nei capitoli successivi viene mostrato un
esempio di uso nella versione Java Application, la cui directory principale
può essere collocata tranquillamente dentro la directory sviluppata per il
plugin.
6.1
Sviluppo del plugin per Eclipse
Eclipse è un IDE (Integrated Development Environment) un ambiente per lo
sviluppo di codice informatico. È costituito da due principali componenti: un
motore che gestisce tutte le funzionalità (il Platform Runtime) e un insieme
di caratteristiche aggiuntive installate come plugin. Il contributo di un plugin
altro non è che un estensione delle funzioni della piattaforma fornite all’utente
finale. Eclipse non è composto da singoli programmi Java. Quando parte
il Workbench, in realtà il Platform runtime è andato nel Plugin registry,
recupera tutta la lista dei plugin registrati e li attiva dinamicamente. Tra
113
114
CAPITOLO 6. UN PLUGIN PER ECLIPSE
questi è presente anche il Workbench. Quindi il Workbench altro non è che un
plugin che segue la medesima politica di sviluppo, di rilascio e di attivazione
di ogni altro plugin che è possibile sviluppare sotto Eclipse.
Quando un utente è intenzionato a sviluppare codice per estendere la
piattaforma, inizialmente vanno definiti quali aspetti estendere (extensions
point). Una nuova finestra dove far apparire dei risultati, un nuovo bottone
della toolbar che scateni un certo evento o come nel nostro caso una nuova
voce da un menù a popup, che se selezionata attiva delle librerie Java che
interpretano codice COWS. Quindi per come abbiamo descritto un plugin
(Capitolo 2) è necessario definire un file manifesto, il manifesto del plugin.
Eclipse è ovviamente predisposto per questo ed è possibile mediante una
serie di passi mettere in pratica ciò che è stato appena detto (creare un plugin,
definire un manifesto ecc).
Vengono descritti di seguito i vari passaggi:
1. Istanziamo un nuovo progetto. Dalla barra orizzontale in alto selezioniamo la voce File; di seguito, dai menù che vengono aperti
successivamente, selezioniamo nell’ordine
New
→
Project
→
Plug-in Project
Una volta eseguite queste operazioni andiamo avanti premendo Next;
2. Sullo schermo appare una nuova finestra denominata Plug-in Project (Figura 6.1) Sono presenti tre text-box dove è necessario effettuare
modifiche. Un box serve per dare un nome al plugin; due ulteriori box
indicano le directory di destinazione dei sorgenti, i file di estensione java e i file di estensione class. Il campo associato al nome è impostabile
a piacere; scriviamo InterpreteGrafico. Lasciamo i- nalterati gli altri
campi. Per default Eclipse considera come directory di destinazione src
e bin. Eseguite queste operazioni andiamo avanti premendo Next;
3. La nuova finestra è denominata Plug-in Content (Figura 6.2) .
.
CAPITOLO 6. UN PLUGIN PER ECLIPSE
Figura 6.1: Plugin - Figura 1
Figura 6.2: Plugin - Figura 2
115
CAPITOLO 6. UN PLUGIN PER ECLIPSE
116
Alcuni campi hanno un valore di di default. Le informazioni di maggior
interesse sono l’identificativo del plugin, il nome e la classe Activator.
La classe Activator è la classe di partenza del plugin. Il Platform
runtime, cerca nel registry i vari plugin da attivare scorrendo gli identificativi. Per ogni identificativo, istanzia mediante un singleton la classe
Activator associata. La classe Activsator fornisce i metodi principali
del plugin, tra cui i metodi start e stop. È possibile cambiare i valori
di default altrimenti andiamo avanti premendo Next;
4. La nuova finestra è denominata Templates (Figura 6.3) . Permette di
definire effettivamente il punto di estensione della piattaforma. Nel box
Figura 6.3: Plugin - Figura 3
di sinistra deve essere selezionata l’estensione che vogliamo definire; nel
box di destra è presente una descrizione e i pacchetti utilizzati. Il plugin
sviluppato per questa tesi viene attivato mediante menù a popup. Per
andare avanti premere Next;
CAPITOLO 6. UN PLUGIN PER ECLIPSE
117
5. Le finestre successive presentano una serie di informazioni riguardanti
l’estensione scelta. Nel nostro caso informazioni relative ad un plugin
che estende la piattaforma mediante un menù popup (Figura 6.4) Il
Figura 6.4: Plugin - Figura 4
tasto Finish consente di terminare il processo di creazione del plugin.
Nel Workspace di Eclipse, automaticamente viene creata una directory
rappresentante il project InterpreteGrafico. Il nuovo project è visualizzabile nella parte sinistra di Eclipse nella finestra denominata “Package explorer”. Cliccandovi sopra con il mouse, il project viene esteso nella sua
struttura ad albero visualizzandone il contenuto. Abbiamo a disposizione
due insiemi di archivi Jar: JRE System Library, un insieme di componenti
necessarie alla piattaforma per attivare il plugin e Plug-in Dependencies, un
insieme di componenti Java per implementare il plugin stesso (librerie grafiche Swt e JFace, il pacchetto org.eclipse.ui.workbench più altre componenti);
la directory src dove vengono messi tutti i file Java che verranno prodotti
CAPITOLO 6. UN PLUGIN PER ECLIPSE
118
(comprese le classi per far eseguire l’applicativo come Java Application) e la
directory META-INF al cui interno è presente il manifesto del plugin.
Di seguito possiamo vedere parte del codice che viene creato, sia il codice
Java sia il file manifesto
Codice della classe che implementa l’azione
package InterpreteGrafico.popup.actions;
import
import
import
import
import
import
org.eclipse.jface.action.IAction;
org.eclipse.jface.dialogs.MessageDialog;
org.eclipse.jface.viewers.ISelection;
org.eclipse.swt.widgets.Shell;
org.eclipse.ui.IObjectActionDelegate;
org.eclipse.ui.IWorkbenchPart;
public class NewAction implements IObjectActionDelegate {
private Shell shell;
/**
* Constructor for Action1.
*/
public NewAction() {
super();
}
/**
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
*/
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
CAPITOLO 6. UN PLUGIN PER ECLIPSE
119
shell = targetPart.getSite().getShell();
}
/**
* @see IActionDelegate#run(IAction)
*/
public void run(IAction action) {
/*
* QUALSIASI AZIONE CHE IL PLUGIN DEVE SVOLGERE VIENE IMPLEMENTATA
* PARTENDO DA QUESTO METODO.
*/
} // Fine metodo run
}
Codice del file manifest.mf
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: InterpreteGrafico Plug-in
Bundle-SymbolicName: InterpreteGrafico; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: InterpreteGrafico.Activator
Bundle-Vendor: Eclipse.org
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.core.resources
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ActivationPolicy: lazy
CAPITOLO 6. UN PLUGIN PER ECLIPSE
Codice del file plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension point="org.eclipse.ui.popupMenus">
<objectContribution
adaptable="true"
id="PlugInCows.contribution1"
nameFilter="defaultCows.cows_diagram"
objectClass="org.eclipse.core.resources.IFile">
<menu
label="Interprete Termine COWS"
path="additions"
id="PlugInCows.menu1">
<separator
name="group1">
</separator>
</menu>
<action
class="plugin_5.popup.actions.NewAction"
enablesFor="1"
id="PlugIn.newAction"
label="Interpretazione Diagramma Cows"
menubarPath="PlugIn.menu1/group1"
state="true"
style="push">
</action>
</objectContribution>
</extension>
</plugin>
120
CAPITOLO 6. UN PLUGIN PER ECLIPSE
6.2
121
Partenza del plugin
Eclipse mette a disposizione due tipologie di plugin. Il plugin per la fase di
rilascio e il plugin per la fase di sviluppo. Il plugin per la fase di rilascio è
un archivio Java che deve essere installato secondo le politiche di Eclipse [4].
Il plugin per la fase di sviluppo permette di lanciare a runtime una seconda istanza dell’ambiente Eclipse che simula al suo interno il plugin appena
sviluppato.
Per attivare il plugin, si sceglie la seconda modalità, un plugin per
la fase di sviluppo. Si posiziona il cursore del mouse su il project
InterpreteGrafico, tasto destro e dal menù successivo selezionare
Run As → Eclipse Application
Automaticamente viene aperta la seconda istanza di Eclipse. Notiamo che
nel codice precedente relativo al file plugin.xml è presente una voce particolare
nameFilter il cui valore è defaultCows.cows diagram.
Quando nella nuova istanza di Eclipse, procediamo posizionando il cursore del mouse premendo successivamente il tasto destro, su un file denominato
defaultCows.cows digram, automaticamente tra le varie voci è presente la label Interprete Termine COWS (Figura 6.5). Selezionando la label, viene
attivato il metodo run della classe NewAction. In questo modo il plugin viene
eseguito estrapolando la specifica testuale dal termine grafico rappresentato
sul file defaultCows.cows digram.
CAPITOLO 6. UN PLUGIN PER ECLIPSE
Figura 6.5: Plugin - Figura 5
122
Capitolo 7
Caso di studio
Il capitolo spiega come interagire con il software prodotto. Vengono rilasciate
due versioni dell’applicativo: come estensione alla piattaforma Eclipse (nel
capitolo precedente è stato spiegato il procedimento per creare il plugin) e
come Java Application (come un effettivo programma Java, eseguibile da
linea di comando).
7.1
Struttura del progetto
La sezione mostra nei dettagli la struttura del progetto, i pacchetti e le classi
che ne fanno parte. Viene creato un Plug-in Project (per i passaggi necessari
rimandiamo al Capitolo 6). Una volta terminata la procedura di creazione,
guardiamo come si presenta la piattaforma. Sul lato sinistro dell’ambiente
Eclipse sono posizionate due view (due viste, quindi due finestre cosı̀ come
sono identificate nella terminologia di Eclipse) in posizione verticale: Hierarchy e Package explorer. Package explorer è di nostro interesse; rappresenta il workspace, con tutti i progetti presenti, incluso il Plug-in Project
InterpreteGrafico.
Il Plug-in Project, è stato ottenuto grazie all’ambiente, predisposto alla
creazione di plugin. Per essere in grado di eseguire il codice come Java Application è necessario copiare le cartelle contenenti il software prodotto in fase
di tesi, all’interno di InterpreteGrafico.
123
CAPITOLO 7. CASO DI STUDIO
124
Cliccando su InterpreteGrafico con il mouse, la directory si estende in
una struttura ad albero; sono presenti due insiemi di archivi Jar, JRE System
Library e Plug-in Dependencies, le directory META-INF e src (source). È la
directory src, la cartella di interesse. Cliccando sopra con il tasto destro del
mouse si apre un nuovo menù da cui selezionare la voce import; serve per
importare nella directory le classi necessarie. Finita l’importazione delle classi
basta cliccare nuovamente su src; la struttura ad albero si allarga mostrando
tutti i package del nostro progetto.
Al suo interno sono già presenti i package InterpreteGrafico e
InterpreteGrafico.popup.action. Questi contengono il codice e le classi relative al plugin viste nel Capitolo 6 e vengono automaticamente create
da Eclipse al momento di creare il plugin.
Tra i package da importare all’interno di src abbiamo cowsAnalyzer e
expressions contenenti tutte le classi dell’interprete e quelle relative a l’analisi delle espressioni. Al loro interno la suddivisione dei sotto-package ha
una struttura direttamente acquisita da SableCC [14]. Entrambi contengono
i pacchetti lexer e parser per l’analisi lessicale e sintattica, il pacchetto
analysis con le classi che implementano i metodi per attraversare l’AST e il
pacchetto node contenente tutti gli oggetti Java associati alle entità coinvolte. cowsAnalysis inoltre contiene tutte le classi sviluppate per implementare
l’analizzatore semantico descritte nel Capitolo 4.
Sono da importare tre ulteriori package: terminiCows e
trasformazioneCows al cui interno sono presenti le classi Java rappresentanti gli operatori COWS utilizzati dal Factory Method e tutte le
classi che processano la trasformazione da grafica a testo e viceversa e
intepreteCows dove sono contenute le classi per la gestione dell’interprete
e la classe di partenza dell’applicativo. Le Figure 7.1 e 7.2 mostrano in
dettaglio la parte descritta:
Di seguito una breve descrizione delle classi contenute nel pacchetto
interpreteCows:
- PartenzaProcesso è la classe di partenza del plugin. È presente il metodo main, peraltro unico metodo della classe. L’applicativo deve essere
eseguito da questa classe per la modalità Java Application. Vengono
CAPITOLO 7. CASO DI STUDIO
Figura 7.1: Eclipse - view del workspace
Figura 7.2: Eclipse - view del workspace - particolare
125
CAPITOLO 7. CASO DI STUDIO
126
mostrate in seguito alcune fasi del processo correlato di screenshot. Al
momento diciamo che l’utente è messo in grado di interpretare un termine COWS recuperandolo da una specifica grafica o immettendolo
direttamente da console. Istanzia l’oggetto InterpreteProcessiCows
passando lo stato del processo;
- InterpreteProcessiCows è la classe destinata alla gestione delle principali operazioni dell’applicativo. Implementa il recupero del termine,
le chiamate all’interprete, il passaggio del termine attivando la fase di riscrittura del file XML. Viene istanziato nel metodo main di
PartenzaProcesso. Prende in input il termine COWS o a seconda delle chiamate, il nome del file di estensione cows e il path dove GMF salva
i propri elaborati. Tra i metodi forniti abbiamo: rinominaTerm per rinominare le variabili non libere e applicare successivamente in modo
semplice la regola semantica (delim3 ), actionPerformed che innesca i
processi principali che mettono in pratica l’analisi lessicale e sintattica
e la costruzione dell’AST e evolutionTerm che attiva un oggetto per
la visualizzazione delle azioni attive del termine;
- Checking è l’oggetto Java principale; al suo interno vengono effettuate
le operazioni di analisi lessicale e sintattica. Viene fornito il metodo
check dove viene istanziato l’oggetto InterCows che implementa le
regole semantiche e secondo la politica di sviluppo del pattern Visitor
(vedere Appendice A) viene passato al metodo apply dell’oggetto tree
...
InterCows trCows = new InterCows();
tree = p.parse();
...
tree.apply(trCows);
...
È presente anche un metodo checkNewXmlFile incaricato di una prima
gestione del parser XML e successiva ricostruzione;
CAPITOLO 7. CASO DI STUDIO
127
- VisualizzazioneAzioni è l’oggetto che permette di visualizzare le
azioni attive.
Di seguito mostriamo un esempio del funzionamento dell’applicativo.
7.2
Esempio
Mostriamo un esempio per descrivere il framework sviluppato. Vengono eseguite tutte le fasi del processo nell’intento di chiarirne l’utilizzo. A tale scopo
forniamo un termine di esempio. Poniamo di voler mettere in pratica il funzionamento di un semplice servizio bancario dove un client vuole prelevare
denaro e la banca una volta ricevute le generalità e l’importo risponde in
modo positivo acconsentendo all’operazione. Si presuppone pertanto di avere a che fare con due entità, un bank service e un client service. Un canale
di comunicazione attiva una prima sincronia tra le parti implementando la
richiesta di prelievo e un secondo canale è fatto uso per la risposta positiva
della banca (viene posta un assenza di fallimento nell’operazione; la risposta
del bank service è affermativa). Vediamo il bank service in questa forma
BankService =
[xuser , xvalue ](bank.withdraw? < xuser , xvalue > .(xuser .resp! <′ ok′ >)).
Il bank service riceve il nome dell’utente che effettua il prelievo e l’importo
sul canale (bank, withdraw); una volta ricevuti tali parametri acconsente
all’operazione inviando una stringa positiva. La risposta positiva ha luogo
solo come continuazione di un’azione precedente. La visibilità è fornita solo
all’interno del campo d’azione delle variabili xuser e xvalue .
Il client service presenta invece una forma di questo tipo
ClientService =
(bank.withdraw! <′ utente′ ,′ 100′ >) | ([xresponde ]utente.resp? < xresponde > .O).
CAPITOLO 7. CASO DI STUDIO
128
Compie l’operazione di invio della coppia di parametri (nome utente, importo
da prelevare) e un’azione di attesa della risposta del bank service (interna
allo scope di xresponde ).
È possibile vedere il tutto come un generico servizio S dove bank service
e client service lavorano in parallelo
S = BankService | ClientService
equivalente al termine COWS (nella sintassi richiesta dall’applicativo)
[user :, value :](bank.withdraw? < user :, value :> .user : .resp! <′ ok′ >) |
(bank.withdraw! <′ utente′ ,′ 100′ >) | ([z :]utente.resp? < z :> .O)
Aperta la console di Eclipse bisogna attivare il plugin elaborato dalle teorie espresse in [10] it.unifi.dsi.cowsVersione2 nella sua fase di sviluppo (Il funzionamento è identico per qualsiasi plugin). Nella barra verticale Package Explorer, cliccando con il tasto destro del mouse sul project
it.unifi.dsi.cowsVersione2, selezionare la voce Run As e la successiva
voce Eclipse Application. Parte automaticamente una seconda istanza di
Eclipse. Nella nuova istanza è possibile manipolare l’editor grafico.
L’insieme delle operazioni iniziali non è ancora completo. È
necessario la prima volta creare i due file defaultCows.cows e
defaultCows.cows diagram. L’applicativo è infatti predisposto per lavorare o su file di estensione cows passati direttamente come input dall’utente,
altrimenti viene cercato il file defaultCows.cows (per i passaggi necessari
alla creazione dei due file, rimandiamo alla Sezione 3.1.2 della corrente tesi).
I- noltre i due file devono essere aperti sull’editor e rimanere aperti ma la
rap- presentazione XML deve essere aperta in modalità testo (per far questo
basta cliccare col tasto destro del mouse sul nome del file e selezionare la voce
Open With e dal successivo menù popup la voce Text Editor). Questa
CAPITOLO 7. CASO DI STUDIO
129
operazione sarà necessaria in seguito.
Ricordiamo inoltre che il file defaultCows.cows diagram è il file su cui
lavora l’interprete quando viene eseguito come plugin di sviluppo. Quindi in
ogni caso i due file defaultCows vanno creati.
A questo punto la seconda istanza della piattaforma rimane la. Non deve
essere più toccata se non per salvare le eventuali modifiche fatte interagendo
con l’editor grafico.
Torniamo sulla prima istanza di Eclipse. Torniamo al project
InterpreteGrafico. All’interno del package interpreteCows lanciamo a
runtime il file PartenzaProcesso. Si esegue la classica sequenza di comandi per runnare una applicativo Java: tasto destro del mouse sulla classe in
questione, dal menù a popup selezionare Run As → Java Application. A
questo punto il software parte nella sua esecuzione.
È possibile recuperare un termine COWS mediante due modalità. Passandolo come input direttamente da console o richiamando un file di estensione
“.cows”. La Console di Eclipse è solitamente posizionata orizzontalmente,
nella parte bassa dello schermo come in Figura 7.3 .
Figura 7.3: Eclipse - view del workspace - 2
CAPITOLO 7. CASO DI STUDIO
130
Se non è presente, basta richiamarla mediante la serie di voci selezionate
Window → Show View → Console
.
In ogni caso dovrebbe automaticamente apparire dopo aver fatto partire
l’applicativo.
Una volta che abbiamo sotto gli occhi la console, per maggior comodità
la possiamo allargare completamente, cliccando sopra il nome della view che
la definisce.
L’applicativo interroga l’utente su quale sia la sorgente da cui recuperare
il termine COWS, se un file grafico “.cows” o un termine passato da console. Se viene scelto un file grafico l’applicativo ne chiede il nome, altrimenti
se il termine è passato direttamente da tastiera di comandi, fornisce una
descrizione della sintassi richiesta.
Forniamo il nostro termine S da tastiera. Diamo in pasto all’applicativo
la modellazione del bank service di cui sopra e che descrive l’interazione di
un client con un servizio bancario di prelievo secondo la sintassi rischiesta
[user :, value :](bank.withdraw? < user :, value :> .user : .resp! <′ ok′ >) |
(bank.withdraw! <′ utente′ ,′ 100′ >) | ([z :]utente.resp? < z :> .O)
La Figura 7.4 mostra come si presenta la schermata .
Successivamente viene richiesto esplicitamente se il termine dato da tastiera lo si vuole anche visualizzare graficamente altrimenti è possibile continuare
nell’elaborazione. In entrambi i casi parte l’interpretazione del termine; la prima opzione però è tipica dell’applicativo. Ogni volta che si trova di fronte ad
un nuovo termine richiede se c’è o meno da parte dell’utente l’intenzione di
rappresentarlo graficamente. Diamo l’okkey all’operazione.
Parte l’interpretazione del termine, ma ogni volta che viene richiesta la visualizzazione grafica, l’applicativo modifica il file “defaultCows”. È necessario
CAPITOLO 7. CASO DI STUDIO
131
Figura 7.4: Eclipse - funzionamento dell’applicativo - 1
spostarsi sulla seconda istanza di Eclipse. Posizionandosi sul file di estensione cows aperto in modalità testuale automaticamente appare il messaggio
mostrato in Figura 7.5
Premendo “Yes” il file grafico viene automaticamente salvato. Spostiamoci adesso sull’omonimo file di estensione cows diagram. La figura che appare
è la rappresentazione grafica del termine. L’utente può intervenire per avere
una visualizzazione migliore. Premendo il tasto Rearrange all fornito direttamente da GMF (selezionando con il mouse quale parte del grafico si vuol
coinvolgere nel rearrange) la figura subisce un riposizionamento sull’editor
dell’elemento selezionato (GMF chiede di salvare anche queste modifiche). Le
Figure 7.6 e 7.7 mostrano un particolare del tasto rearrange all e la successiva visualizzazione del termine ottenuta dopo una serie di riposizionamenti
(rearrange) delle varie parti.
Intanto la prima istanza della piattaforma ha effettuato l’interpretazione
del termine. Viene pertanto mostrato l’elenco delle azioni attive. In questo
caso abbiamo una sola azione. Ma alcuni termini COWS, possono fornire
chiaramente un elenco più ampio. Ad esempio, il banale termine
CAPITOLO 7. CASO DI STUDIO
Figura 7.5: Eclipse - funzionamento dell’applicativo - 2
Figura 7.6: Eclipse - funzionamento dell’applicativo - 3
132
CAPITOLO 7. CASO DI STUDIO
133
Figura 7.7: Eclipse - funzionamento dell’applicativo - 4
[x,y](p.o!<e> | p.o?<x>.O | q.o!<f> | q.o?<y>.O)
due possibili sincronizzazioni tra Invoke e Receive una sull’endpoint (p.o) e
una su (q.o) permette un’evoluzione del termine in due diverse situazioni.
Le Figure 7.8 e 7.9 mostrano come l’applicativo risponde all’interpretazione
della sincronizzazione su i due canali (mostrando le azioni attive) e come
risponde nell’interpretazione del nostro esempio di servizio bancario.
Rifocalizziamo l’attensione sul servizo bancario. Il sistema mette l’utente
nella condizione di visualizzare l’elenco di azioni attive. È un elenco numerato
che fa uso di un numero progressivo. Vengono visualizzate nell’ordine il numero associato all’azione elencata, il termine corrente, l’azione e l’eventuale
evoluzione del termine una volta processata tale azione. Nel nostro caso, la
richiesta da parte del client verso la banca, di un accesso al servizio bancomat, la sincronizzazione quindi sul canale di comunicazione (bank, withdraw)
dove vengono richiesti i parametri user e value che il client service trasmette.
Notiamo che l’interprete ha eseguito correttamente il processo di analisi
lessicale, sintattica e semantica. Le variabili legate hanno subito un processo
di rinominazione (user è diventato user1) e le Delimitation sono state portate
CAPITOLO 7. CASO DI STUDIO
134
Figura 7.8: Eclipse - funzionamento dell’applicativo - Sincrozizzazione su due
canali
Figura 7.9: Eclipse - funzionamento dell’applicativo - 5 - Bank Service
CAPITOLO 7. CASO DI STUDIO
135
tutte all’inizio del termine applicando la regola di congruenza strutturale
(delim3 ).
Quindi, dato il termine corrente
[user :, value :](bank.withdraw? < user :, value :> .user : .resp! <′ ok′ >) |
(bank.withdraw! <′ utente′ ,′ 100′ >) | ([z :]utente.resp? < z :> .O)
se eseguita l’azione corrente
bank.withdraw[value1 :→ 100, user1 :→ utente]
il termine si evolve nel nuovo termine
[z :](utente.resp! <′ ok′ >) | (utente.resp? < z :> .O)
Il sistema come al solito chiede se l’utente è intenzionato a visualizzare il nuovo termine in modalità grafica o se preferisce continuarne l’interpretazione.
Se viene digitato il valore 1, la richiesta di visualizzazione grafica, devono essere ripetuti i passaggi precedenti. Tornare sulla seconda instanza di Eclipse,
posizionarsi sul file cows, di nuovo appare la richiesta (Figura. 7.5) da parte di
GMF di salvare uno dei suoi file appena modificato, successivamente andare
sul file cows diagram per eseguire il rearrange all, il riposizionamento degli
elementi sull’editor, il successivo nuovo salvataggio della specifica grafica. Appare pertanto un termine come in Figura 7.10, correttamente rivisualizzato
graficamente.
L’interpretazione del termine nel frattempo prosegue, sulla prima istanza di Eclipse è possibile vedere nuovamente il termine corrente, la possibile
azione e l’evoluzione seguente. Questa volta il processo di Bank Service, ricevendo i valori richiesti, acconsente all’operazione (invio della stringa okkey).
Il nuovo termine è il risultato di una ulteriore Receive presente in parallelo
CAPITOLO 7. CASO DI STUDIO
136
Figura 7.10: Eclipse - funzionamento dell’applicativo - 6 - Bank Service
con i vecchi termini, che adesso può sincronizzarsi con la Invoke, continuzione ottenuta dalla precedente Receive sul canale (bank, withdraw). Le Figure
7.11 e 7.12 mostrano la schermata dell’interprete con la nuova richiesta di
visualizzazione grafica e il termine finale evoluto in un Nil conclusivo. Ancora
una volta, la possibilità di visualizzare il termine Nil in modalità grafica è
medesima nei passaggi come visto in precedenza.
Importante annotazione: ogni schermata mette a disposizione due valori di default. Il valore 0 che permette di ritornare allo stato precedente
e il valore 1001, valore di default per chiudere l’esecuzione dell’applicativo.
Naturalmente se l’utente è intenzionato a ritornare allo stato precedente del
termine, l’applicativo non si pone problemi e chiede sempre se anche nel
percorso inverso c’è la volontà di visualizzare il termine graficamente.
7.2.1
Termine COWS caricato da file
Abbiamo detto che è possibile caricare un termine direttamente dalla sua
rappresentazione XML. Alla partenza dell’applicativo digitando il valore 2
CAPITOLO 7. CASO DI STUDIO
137
Figura 7.11: Eclipse - funzionamento dell’applicativo - 7 - Bank Service
Figura 7.12: Eclipse - funzionamento dell’applicativo - 8 - Bank Service
CAPITOLO 7. CASO DI STUDIO
138
da tastiera, il sistema viene messo nella condizione di aspettare che venga
inserito il nome del file rappresentante una specifica grafica, da cui estrapolare
il termine. Poniamo, visto che l’abbiamo appena creato di caricare il termine
dal file defaultCows (va dato solo il nome, nessuna estensione) come in figura
7.13 .
Figura 7.13: Eclipse - funzionamento dell’applicativo - 9 - Bank Service
Vediamo che il sistema recupera proprio il servizio rappresentante il nostro processo bancario. Adesso è possibile interpretare il termine, in generale
ripetere le operazioni viste precedentemente.
Capitolo 8
Considerazioni e conclusioni
finali
Il capitolo descrive alcune considerazioni di carattere generale. Inizialmente
spiega come è stato possibile mettere in pratica le scelte progettuali assunte
durante la fase di sviluppo del plugin. Successivamente descrive alcune problematiche riscontrate. Infine sulla base del lavoro svolto, mette in risalto
possibili sviluppi futuri.
La tesi ha alternato varie fasi. Una politica di sviluppo messa in pratica in
un certo momento è stata abbandonata a favore di un’altra ritenuta migliore.
Il capitolo spiega le politiche adottate e le motivazioni rilevanti che ne hanno
determinato la scelta. Il capitolo vuole infine essere di aiuto per chi in futuro
continuerà a lavorare su gli argomenti trattati cercando di evitare il riproporsi
di certe situazioni e favorendone invece altre.
8.1
Considerazioni iniziali
L’obiettivo principale era lo sviluppo di un applicativo che, preso un termine
COWS, potesse fare da ponte da una sua specifica grafica ed una testuale e
viceversa, ponendo nel mezzo un ulteriore strumento in grado di interpretare i
termini analizzandone lessicalmente, sintatticamente ed infine semanticamen-
139
CAPITOLO 8. CONSIDERAZIONI E CONCLUSIONI FINALI
140
te la struttura. Dopo uno studio sull’argomento e i servizi web in generale,
sono stati tracciati e analizzati possibili modi per raggiungerlo.
Grazie a [10] siamo venuti a conoscenza di un framework grafico molto
potente quale GMF e successivamente di un ulteriore tool di nome KermetaSintaks. Kermeta è un ulteriore plugin per Eclipse. Si basa sul presupposto
di essere strettamente correlato ad un modello astratto sviluppato con GMF.
Vuole infatti fornire un collegamento tra le possibili rappresentazioni di termini estrapolabili dal modello, nel nostro caso da una rappresentazione grafica
ad una rappresentazione testuale associata. Partendo da un modello proprio
(rappresentato da un file di estensione “.sts”), recupera l’albero sintattico del
meta-modello dato e per ogni elemento vi associa la specifica sintattica.
Nel nostro caso, dato un meta-modello di estensione “.ecore” che rappresenta operatori COWS, nel file di estensione “.sts” ad ogni operatore viene
associata la struttura sintattica (i simboli terminali della grammatica) che lo
rappresenta.
Quando questa tesi è cominicata, il plugin Kermeta-Sintaks fu installato
ma i passaggi da seguire non sono stati di facile interpretazione. Dopo vari
studi è stato possibile mettere in pratica una parte del processo ottenendo
la specifica testuale da quella grafica. Ma sono stati riscontrati numerosi
problemi. Un plugin ancora in fase di sviluppo, linee guida incomplete e
incompatibili con le varie versioni di Eclipse ma soprattutto il rilevamento
di numerosi bug nel processo inverso (da specifica testuale a grafica) e una
mancanza pressoché totale di documentazione del software, motivi per cui
dopo vani tentativi che non hanno portato frutti è stato deciso di provare
a percorrere altre strade. Allo stato delle cose comunque va detto che il
progetto Kermeta ha compiuto passi avanti e varie informazioni in merito
possono essere raccolte al sito www.kermeta.org/sintaks.
Successivamente si è provato a sfruttare le caratteristiche di GMF. Anche in questo caso però la mancanza di una buona documentazione non ha
permesso di sfruttarne appieno le capacità. GMF è infatti un progetto particolarmente ampio. Offre la possibilità di modellizzare una serie cospicua
di modelli ma, come per Kermeta, quando questa tesi è cominciata non era
presente materiale che spiegasse come approcciarvi in fase di sviluppo, ma ne
CAPITOLO 8. CONSIDERAZIONI E CONCLUSIONI FINALI
141
dava solo un buon resoconto per le modalità d’uso da utente finale. In pratica
GMF prende in pasto un certo input ed è in grado come abbiamo visto di
generare un insieme di classi ad hoc per visualizzare un editor grafico. Questa parte è esauriente e ampiamente dettagliata anche in [10]. Il problema
viene a crearsi quando ci si chiede come può un progettista implementare un
processo che apra a proprio piacere un editor grafico e utilizzi le classi Java
messe a disposizione dal plugin per i propri scopi.
È stata fatta un ampia interazione con i gruppi di sviluppo del Graphical
Modelling Framework mediante mailing list e newsgroups. Ma le scarse risposte ricevute (alcune domande non hanno addirittura avuto risposta) hanno
nuovamente fatto preferire idee alternative.
Lo studio delle classi Java messe a disposizione da GMF, del codice ma
più in generale dei modelli utilizzati ha messo in luce la struttura XML, facilmente editabile, dei file di estensione cows e di estensione cows diagram e lo
studio di alcuni package del framework con suffisso “parser” hanno permesso
di imbattersi nell’identificativo. A questo punto sempre tramite i newsgroups
e qualche mail scambiata con alcuni sviluppatori del gruppo di ricerca di
GMF, sono state intuite le politiche di sviluppo per la manipolazione dell’identificativo in fase di parsing e in generale del processo da mettere in
pratica per replicarne il modus operandi adeguandolo cosı̀ ai nostri scopi,
convincendoci nel proseguire questa strada.
8.2
Problematiche riscontrate
Eclipse possiede due diverse modalità per la creazione di un plugin. È possibile infatti creare un plugin per la fase di sviluppo oppure per quella di
rilascio. La fase di rilascio è quella classica; un archivio Jar da installare nel
workspace secondo le politiche fornite da Eclispe [4]. Per la fase di sviluppo
viene aperta un’ulteriore istanza a runtime dove eseguire il plugin.
Abbiamo più volte fatto riferimento a due diverse versioni dell’applicativo
fornito. Sono state predisposte due possibili modalità da eseguire. L’applicativo fornito come estensione della piattaforma Eclipse o fornito come semplice
Java Application. Sono state rese disponibili entrambe. Soprattutto la prima
CAPITOLO 8. CONSIDERAZIONI E CONCLUSIONI FINALI
142
ha delle proprietà da sfruttare ma presenta anche degli incovenienti da tenere in considerazione. L’applicativo è stato realizzato nella semplice versione
Java Application anche per cercare di superare ugualmente alcune difficoltà
che la progettazione ha dovuto affrontare.
Quella principale è dovuta al fatto che in fase di sviluppo, quando un
plugin viene eseguito aprendo una seconda istanza di Eclipse, mantiene un
suo flusso di esecuzione. Un flusso che termina ad azione finita, ripassando
l’esecuzione al flusso dell’istanza principale. Abbiamo pertanto in esecuzione
due flussi distinti: uno per la prima e uno per la seconda istanza.
Per come è stato progettato l’applicativo (Capitolo 7), mentre il flusso di
esecuzione che coinvolge l’interpretazione del termine, viene mantenuto nella
prima istanza, la seconda rimane in attesa, richiedendo di salvare eventuali
modifiche apportate nella specifica grafica. Successivamente l’interpretazione
del termine si conclude, concludendo in questo modo l’esecuzione del flusso
nella prima istanza. A questo punto, la prima istanza è ferma ed è possibile
andare a salvare le modifiche nella seconda. In realtà l’esecuzione del flusso
nella prima istanza non si conclude effettivamente, ma viene messo in pausa.
In pratica viene eseguita l’interpretazione del termine, vengono mostrate le
azioni attive e le eventuali evoluzioni del termine; il flusso non riparte fino
a che l’utente non sceglie l’azione con cui continuare. In questo momento
di attesa è possibile tornare sulla seconda istanza e salvare le modifiche.
Quindi abbiamo effettivamente due flussi distinti che lavorano ognuno per
conto proprio.
Ma se rendiamo attiva una seconda istanza e quindi un secondo flusso di
esecuzione e questo per un qualche motivo deve appoggiarsi alla prima istanza, viene a crearsi una situazione in cui la prima istanza riprende il flusso in
modo continuato e non lo rende più. Nel nostro caso, se dalla seconda istanza
selezioniamo dal menù popup del plugin la voce Interpreta Termine Cows ma l’interpretazione viene elaborata nella prima istanza, a questo punto
la prima istanza riprende il flusso in modo continuo e non lo rende più. La
seconda istanza rimane pertanto in uno stato di attesa indefinita, non permettendo nessuna interazione con l’esterno (nel gergo informatico si identifica un fatto simile dicendo che la seconda istanza si è “piantata”). Quindi non
CAPITOLO 8. CONSIDERAZIONI E CONCLUSIONI FINALI
143
darebbe più modo di salvare le modifiche effettuate sulle specifiche grafiche.
Questo è possibile solo se il flusso di esecuzione nella prima istanza si
ferma, lasciando il tempo all’utente di inserire input nella seconda istanza
(salvataggio delle modifiche) e continua quando l’utente ha processato in tale
direzione. Proprio quello che succede nella versione Java Application dove
il flusso di esecuzione rimane fisso sulla prima istanza, ma l’utente accede
alla seconda solo quando il primo flusso si ferma temporaneamente (e solo
perchéil sistema lo chiede esplicitamente mediante il messaggio di Figura 7.5
altrimenti la seconda istanza uan volta attivata rimane ferma e l’utente non
vi accede mai).
Quindi anche nella versione plugin, l’unico modo per vedere le modifiche
e salvarle è simulare in qualche modo il funzionamento dell’applicativo come
Java Application interrompendo ad un certo punto il flusso della prima istanza e farlo ripartire in seguito. L’interpretazione del termine mediante plugin
agisce in questo modo. Richiede da parte dell’utente l’interpretazione di un
termine andando sul file defaultCows con il tasto destro e selezionando la voce Interpreta Termine Cows dal successivo menù popup (vedi Capitolo
6). Questo avvia l’esecuzione. Il termine evolve e successivamente l’esecuzione viene fermata in modo forzato per permettere le modifiche. Rimangono
ugualmente due problemi. Il primo è che per interpretare i sottotermini è
necessario ripetere i passi precedenti. Bisogna nuovamente selezionare la voce Interpreta Termine Cows e ripetere il processo di estrapolazione del
termine (che stavolta è comunque il termine evoluto proprio perché siamo
stati in grado di salvare le modifiche precedenti). Il secondo problema però è
che lavorando in questo modo non è possibile mantenere lo stato del termine
e le sue possibili esecuzioni. In pratica è possibile evolvere il termine ma non
è possibile tornare indietro e risalire l’albero sintattico atratto associato al
termine. Nella Java Application questo è possibile perché, usufruendo di un
flusso di esecuzione continuato (che solo l’utente può decidere di interrompere inserendo il valore 1001) gli stati del termine vengono salvati in una
struttura dati lista che è uno stack di memoria volatile, non riproponibile
ovviamente nella modalità plugin dove per far funzionare il plugin si fa uso
di un flusso di esecuzione che si interrompe indipendentemente dall’utente.
CAPITOLO 8. CONSIDERAZIONI E CONCLUSIONI FINALI
144
Si è ovviato a questo problema, facendo uso di un file di testo esterno, dove
ad ogni esecuzione si salvano o si prelevano i termini. Invece di salvare in una
lista di memoria, si salvano i termini sequenzialmente in un file di testo. Per
ritornare allo stato precedente si recupera l’ultimo termine memorizzato.
8.3
Conclusioni ed eventuali sviluppi futuri
Il software qua prodotto può tranquillamente essere visto come un modulo a
se stante da integrarsi con qualcosa di più ampio. Uno degli obbiettivi principali del gruppo di ricerca che lavora su COWS e tirare su un framework in
grado di lavorare con tale algebra di processi e sfruttarne appieno le capacità,
quindi un uso destinato al modellizzare strutture di rete, ambienti distribuiti
e processi che lavorano concorrenzialmente; più nello specifico, un ambiente
destinato ai servizi web, che possano interagire e collaborare fra loro. Modellizzare strutture simili e analizzarne le proprietà cercando di garantire
caratteristiche essenziali quali la correttezza o la sicurezza dei servizi.
Interessante sarebbe nel futuro, permettere a moduli indipendenti di integrarsi tra loro dando vita ad un framework totalmente orientato a questi
scopi. Contributo della tesi è stato proprio il cercare di intraprendere questra
strada. In futuro può essere di grande interesse integrare il tutto con ulteriori componenti quali un potente sistema di typechecking, un’implementazione
java basata sul framework ICM, un compilatore WS-BPEL integrato con la
compilazione COWS, tutti moduli aggiuntivi da sviluppare in futuro per aumentare le potenzialità del framework e per aumentare quelle che sono le
prospettive riversate verso COWS.
Appendice A
Interprete - Sviluppi
implementativi
Questa appendice vuole descrivere alcuni aspetti dell’interprete di termini
COWS. Per il suo sviluppo in [7] viene fatto uso di un framework, un generatore di compilatori: SableCC. Un progetto sviluppato da Etienne Gagnon
presso la università di Informatica di Montreal, il cui sviluppo ha poi avuto
fasi successive. Viene fornita una panoramica completa su quelle che sono
state le scelte in fase di analisi e di progettazione e come tali scelte si sono integrate a SableCC. È possibile trovare una pagina su Internet relativa
al progetto SableCC all’indirizzo www.sablecc.org. Questa appendice fornisce
una panoramica; maggiori informazioni sono reperibili mediante la documentazione del progetto [14], a disposizione in formato elettronico e scaricabile
al sito di cui sopra.
A.1
Analisi
Volendo sviluppare software in Java è stata fatta inizialmente una ricerca per
vedere se esistevano framework che consentissero di sviluppare un interprete
di linguaggi informatici (nel nostro caso il calcolo di processi COWS). La
scelta è caduta su SableCC.
145
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
146
SableCC è un generatore di compilatori open source fornito con licenza
GNU, che include le seguenti caratteristiche:
- genera un automa a stati finiti deterministico per l’implementazione
dell’analizzatore lessicale;
- supporta la forma Backus-Naur Form estesa per le grammatiche;
- genera un parser di tipo LALR1 ;
- effettua una generazione automatica dell’albero sintattico astratto
(AST) e genera altrettanto automaticamente le classi dell’attraversatore dell’albero, per l’analisi dei suoi nodi.
I passi principali per la generazione delle classi sono:
1. lo sviluppo della grammatica del linguaggio; nel nostro caso la scrittura
di un file di testo, secondo le specifiche descritte da SableCC stesso. In
pratica deve essere prodotto un file con estensione “.grammar”, dove
vengono trascritte le regole grammaticali del linguaggio, riportando i
simboli terminali e non, le produzioni e le varie alternative (le modalità
da seguire non sono argomento di questa tesi, per le quali rimandiamo
a [14]);
2. passare il suddetto file come input al framework. SableCC genera completamente le classi che implementano il lexer e il parser da utilizzare
per svolgere l’analisi lessicale e sintattica di un termine, più una serie
di interfacce per consentire l’implementazione delle regole semantiche
1
LALR sta per Lookahead LR. Un parser LR è un parser di tipo Bottom-up per grammatiche libere da contesto, usate molto di frequente nei compilatori dei linguaggi di programmazione (e degli altri strumenti associati). Un Parser LR legge il proprio input partendo
da sinistra (Left) verso destra, producendo una derivazione destra (Rightmost Derivation).
Nell’uso tipico, quando ci riferiamo a un parser LR significa che stiamo parlando di un
particolare parser in grado di riconoscere un linguaggio specifico in una grammatica libera
da contesto. Non è tuttavia insolito che ci riferisca a un parser LR intendendo un programma che, fornendogli una tabella ad hoc, sia in grado di produrre un ampio numero di
LR distinti. Creare un parser LR a mano è parecchio difficile; solitamente essi sono creati
usando dei generatori di parser. In base a come la tabella di parsing viene generata, questi
parser possono essere anche parser SLR o appunto LALR. Il parser LALR è in grado di
riconoscere un numero maggiore di grammatiche rispetto un SLR.
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
147
e l’attraversamento dell’albero sintattico astratto. È compito dell’utente finale (quindi del fruitore di SableCC) estendere tali interfacce allo
scopo di sviluppare l’interprete (nel nostro caso, rimandiamo alla sezione “Implementazione”, dove vengono spiegate in modo dettagliato
le politiche scelte nello sviluppo delle regole semantiche di COWS);
3. una volta generate le classi suddette deve essere generato un ulteriore file Java dove sia presente un metodo “main” da cui far partire il
processo. All’interno del metodo main dovranno essere istanziati l’oggetto “Lexer” e l’oggetto “Parser” messi a disposizione da SableCC
e l’oggetto “Interprete” sviluppato dall’utente. Il metodo main deve
implementare i seguenti comandi:
public static void main(String[] arguments) {
try {
/* **** Viene creato l’oggetto Parser **** */
Parser p = new Parser(
new Lexer(
new PushbackReader(
new InputStreamReader( "termine COWS" ), 1024)));
/* Viene richiamato il metodo parse. Il risultato
* viene assegnato alla variabile "tree" che
* conterra’ l’AST ricavato.
*
* TUTTI QUESTI PASSAGGI VENGONO ESEGUITI DA SableCC
*
*/
Start tree = p.parse();
} catch (LexerException e) {
Stampa ("Errore nell’analisi lessicale");
} catch (ParserException e) {
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
148
Stampa ("Errore nell’analisi sintattica")
}
/* Applicazione dell’interprete sviluppato
* dall’utente che usa SableCC.
*/
try {
Interprete interprete = new Interprete();
tree.apply(interprete);
} catch( Exception e){
Stampa ("Errore nell’analisi semantica");
}
} // Fine metodo main
Per quale motivo tra tanti framework è stato scelto SableCC? Semplicemente perché è risultato il più convincente nella sua struttura. I tradizionali
generatori di compilatori mettono in pratica un processo del tipo seguente: il
programmatore scrive le regole grammaticali e fissa le azioni del linguaggio;
un generatore di compilatori classico, prende queste informazioni come input
e genera il codice conseguente. Se al momento del debug però ci si accorge
che bisogna intervenire nuovamente sulle regole, queste ultime devono essere
riformulate e di conseguenza riformulate le azioni.
SableCC è differente. Nel file di testo viene fornita solo la grammatica.
Le azioni sono classi Java. Quindi ogni nuova aggiunta porta semplicemente
alla creazione di una nuova azione, quindi alla creazione di una nuova classe Java che estende le interfacce già presenti. Questo discorso è dettagliato
maggiormente in figura A.1.
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
149
Figura A.1: Politica di sviluppo - (a) Generatori di compilatori classici - (b)
Sable CC
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
A.2
150
Progettazione
Una volta che SableCC genera le classi necessarie bisogna capire come adattarle ai nostri scopi. L’idea principale è passare il termine come input ad
un oggetto “Lexer”, consentire l’analisi lessicale e successivamente (se superata) passare nuovamente il termine come input ad un oggetto “Parser”,
applicando in tal modo l’analisi sintattica e la creazione dell’albero sintattico associato al termine (processo questo, interamente svolto da SableCC).
L’utente interviene principalmente passando l’albero ottenuto all’interprete
sviluppato per estensione delle classi che SableCC ha prodotto.
SableCC modularizza le classi generate messe a disposizione, fornendo un
insieme di package che comprendono:
- “lexer”, il pacchetto contenente le classi che eseguono l’analisi lessicale e
gestiscono le eventuali eccezioni. Le due classi coinvolte sono Lexer.class
e LexerException.class;
- “parser”, il pacchetto contenente le classi che eseguono l’analisi sintattica compresa la gestione di eventuali eccezioni. Le due classi coinvolte
sono Parser.class e ParserException.class;
- “node”, il pacchetto contenente tutti i nodi dell’albero visti come oggetti Java. Ancora una volta per le politiche di sviluppo dei nodi rimandiamo alla documentazione di SableCC; in quest’ottica diciamo soltanto
che data una grammatica che descrive un linguaggio, per ogni simbolo
terminale e non-terminale, viene associato un nodo, quindi un oggetto
Java che lo descrive;
- “analysis” il pacchetto comprendente le classi che generano
l’attraversatore dell’albero sintattico astratto (AST).
Sta all’utente a questo punto, sviluppare un package aggiuntivo (o più
package) contenenti le classi che implementano l’analisi semantica (quindi
il cuore dell’interpretazione) basandosi su quella che è la scelta progettuale
principale, l’uso del pattern Visitor. Solo in tal modo è possibile mettere in
pratica l’idea generale che presuppone, dato un termine del linguaggio (nel
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
151
nostro caso un termine COWS), recuperare l’AST associato e partire dalla
radice per esplorare il termine. Una volta identificato l’operatore corrente, si
approfondisce l’analisi fino alle foglie, recuperando le informazioni relative alle azioni attive e risultando quindi immediatamente eseguibili per l’operatore
in questione.
A.2.1
Pattern Visitor
In questa sezione viene spiegato in dettaglio il pattern Visitor che è la scelta progettuale più importante, sulle cui direttive viene sviluppato e messo
in pratica il processo di attraversamento dell’albero. Poi vedremo che per
generare le classi per l’attraversamento di un AST che possa essere esteso
mediante le azioni da compiere sugli specifici nodi, il sistema adotta una
versione rivisitata del pattern Visitor. In pratica, il pattern è da considerarsi applicabile agli elementi che costituiscono una struttura. È progettato in
modo tale da consentire la definizione di nuove operazioni senza modificare
gli elementi sui quali opera. Viene descritto mediante un esempio. Supponiamo di avere un oggetto “Forma geometrica” e tre oggetti che ne estendono
la classe dando maggiori informazioni sulla forma in questione: “Cerchio”,
“Quadrato” e “Rettangolo” . Poniamo d’avere un bottone per ognuna delle
tre figure tale che al momento del click viene attivato un ipotetico metodo “selected” a cui come parametro viene passato l’oggetto selezionato. Un
modo per implementare in Java un simile processo è mediante l’uso della
funzione instanceof in un frammento di codice del tipo
public void Selected(Forma obj) {
if(obj instanceof Cerchio)
System.out.println("E’ stato selezionato un cerchio");
else if(obj instanceof Quadrato)
System.out.println("E’ stato selezionato un quadrato");
else
System.out.println("E’ stato selezionato un rettangolo");
}
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
152
ma è un approccio in parte sbagliato perché date 100 forme geometriche
diverse sono necessari 99 confronti, quindi 99 chiamate alla funzione instanceof. Questo comporta una complessità dell’ordine di O(n). Per portare la
complessità in un ordine di grandezza pari a O(1). Seguiamo la seguente
strategia: viene definita un’interfaccia Switch tale che per ogni classe derivata dall’oggetto Forma, si abbia un metodo del tipo caseXXX (dove XXX è
il nome dell’oggetto derivato) come di seguito
interface Switch {
void caseCerchio(Cerchio obj);
void caseQuadrato(Quadrato obj);
void caseRettangolo(Rettangolo obj);
}
e per ogni classe rappresentante una “Forma” si mette a disposizione un
metodo “apply” che richiama il metodo appropiato a seconda dell’oggetto
passato come parametro
abstract class Forma {
...
abstract void apply(Switch sw);
...
}
public class Cerchio extends Forma {
...
public void apply(Switch sw) { sw.caseCerchio(this); }
...
}
public class Quadrato extends Forma {
...
public void apply(Switch sw) { sw.caseQuadrato(this); }
...
}
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
153
public class Rettangolo extends Forma {
...
public void apply(Switch sw) { sw.caseRettangolo(this); }
...
}
Quindi se viene richiamato il comando “Cerchio.apply” in tal caso viene
richiamato il metodo “caseCerchio” passando this come parametro; quindi
Cerchio riesce a richiamare il metodo caseCerchio passando come parametro
un riferimento a se stesso.
A questo punto basta implementare il metodo “selected” in modo tale
che venga effettuato lo switch, come segue
public void Selected(Forma obj) {
obj.apply(new Switch() {
void caseCerchio(Cerchio obj) {
System.out.println("E’ stato selezionato un cerchio");
}
void caseQuadrato(Quadrato obj) {
System.out.println("E’ stato selezionato un quadrato");
}
void caseRettangolo(Rettangolo obj) {
System.out.println("E’ stato selezionato un rettangolo");
}
});
}
Quello mostarto altro non è che il classico pattern Visitor.
Ora, in questo modo abbiamo una complessità nell’ordine di grandezza
pari a O(1) però risulta difficile aggiungere tipi nuovi. In pratica l’approccio
è ottimo da un punto di vista della complessità ma ancora non è adattabile
per strutture che si evolvono. A tale scopo quindi si ridefinisce l’interfaccia
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
154
Switch in modo generico posta come l’antenato di tutte le interfacce Switch
e successivamente un interfaccia Switchable che al suo interno contiene il
metodo apply
interface Switch { }
\\ Interfaccia generica
interface Switchable { void apply(Switch sw); }
Tutte le classi che mettono a disposizione i metodi “caseXXX” estendono
Switch e tutti gli oggetti messi a disposizione nella selezione implementano
Switchable
interface FormaSwitch extends Switch {
void caseCerchio(Cerchio obj);
void caseQuadrato(Quadrato obj);
void caseRettangolo(Rettangolo obj);
}
public abstract class Forma implements Switchable {
...
}
public class Cerchio extends Forma {
...
void apply(Switch sw) {
((FormaSwitch)sw).caseCerchio(this);
}
}
...
ecc ...
Quindi in questo modo è possibile aggiungere nuove forme mediante
l’estensione
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
155
interface EstensioneFormaSwitch extends Switch {
void caseEllisse(Ellisse obj);
}
public class Ellisse extends Forma {
...
void apply(Switch sw) {
((EstensioneFormaSwitch)sw).caseEllisse(this);
}
...
}
e l’oggetto che eredita da Switch e il nuovo metodo “selected”
interface TutteleFormeSwitch extends
FormaSwitch, EstensioneFormaSwitch {}
void Selected(Forma obj) {
obj.apply(new TutteLeFormeSwitch() {
// <---------void caseCerchio(Cerchio obj) {
System.out.println("E’ stato selezionato un cerchio");
}
void caseQuadrato(Quadrato obj) {
System.out.println("E’ stato selezionato un quadrato");
}
void caseRettangolo(Rettangolo obj) {
System.out.println("E’ stato selezionato un rettangolo");
}
void caseEllisse(Ellisse obj) {
System.out.println("E’ stata selezionata un ellisse");
}
});
}
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
A.2.2
156
Pattern Visitor integrato all’interprete
In base a quanto detto, abbiamo appurato quelle che sono le gerarchie di classi messe a disposizione da SableCC. Anzitutto vengono generati una serie di
oggetti che rappresentano i nodi dell’albero sintattico; ogni nodo rappresenta un elemento del linguaggio COWS. Senza entrare nel merito delle scelte
progettuali di SableCC, diamo come esempio, una generica produzione della
grammatica, quale può essere
Service
::=
Del
| P rot | Kill
...ecc
che permetterà a SableCC di generare le seguenti classi:
- PService, classe che implementa il generico servizio;
- ADelService, classe che implementa una delle alternative, la Delimitation, tra le possibili produzioni di Service; è una sottoclasse di
PService;
- AProtService, classe che implementa una delle alternative, la Protection, tra le possibili produzioni di Service; è una sottoclasse di
PService;
- AKillService, classe che implementa una delle alternative, la Kill, tra
le possibili produzioni di Service; è una sottoclasse di PService;
- ecc .
Node è l’antenata di tutte le classi che riguardano l’albero sintattico,
quindi Node implementa Switchable. In questo modo abbiamo una gerarchia
di oggetti “scieglibili”, cioè un insieme di oggetti selezionabili.
Poi abbiamo un’altra gerarchia di interfacce Java; ha per radice l’interfaccia Switch, implementata inizialmente da Analysis, a sua volta implementata
dalla classe AnalysisAdapter. Analysis è un’interfaccia che contiene tutti i
vari metodi caseXXX (nel nostro caso alcuni metodi sono caseADelService,
caseAProtService, ... ecc). AnalysisAdapter implementando Analysis fornisce
un’implementazione di default per i metodi. Quindi a meno che un metodo
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
157
non venga riscritto verrà sempre chiamato un “defaultCase”. In questo modo abbiamo una gerarchia di oggetti “scieglitori”, cioè da AnalysisAdapter
è possibile estendere un oggetto che seleziona tra gli oggetti selezionabili,
sovrascrivendo i metodi che interessano.
Infine, bisogna ricordare che l’albero sintattico va attraversato, quindi
viene implementato un oggetto che implementa una visita dell’albero in profondità. L’interprete deve estendere una classe chiamata DepthFirstAdapter
(che a sua volta estende AnalysisAdapter) dove oltre ai metodi “caseXXX”
sono implementati i metodi “inXXX” e “outXXX” secondo lo schema sotto
class DephtFirstAdapter extends AnalysisAdapter {
...
...
void caseXxx(Xxx node) {
inXxx(node);
node.getYyy.apply(this); // primo figlio di Xxx
node.getZzz.apply(this); // secondo figlio di Xxx
outXxx(node);
}
...
...
}
Il processo si configura nel modo seguente: quando la visita dei nodi dell’AST
giunge sul generico “XXX”, chiamando “inXXX” se ne verificano le precondizioni (visitando pertanto i sottonodi), con “outXXX” vengono applicate
quelle che sono le conseguenze alla visita del nodo (nel nostro caso quindi si
applicano le regole della semantica).
In figura A.2 e A.3 vengono visualizzate le gerarchie di classi.
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
Figura A.2: Gerarchia di oggetti Switchable
158
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
Figura A.3: Gerarchia di oggetti Switch
159
Ringraziamenti
Beh...Eccoci qua!!!!. Questa...è una pagina importante. Ci tengo particolarmente.
In tutti questi anni, di persone ne ho conosciute tante e di personaggi fantastici
da ricordare, ne avrei migliaia. Forse non riuscirò a ricordarli tutti, comunque io
ci provo. Dai!!!!
In primo luogo, un grazie ai miei genitori a cui questa tesi è dedicata. Hanno
fatto un monte di sacrifici. Mamma...Babbo....è stata durissima....ma alla fine ce
l’ho fatta!!!!!! :-)
Poi un bacio gigantesco a Belinda, non solo una sorella ma una seconda
mamma....e un bacione grande a Gennaro, Anthony e Mirko!!!!
Grazie mille infine al Professor Pugliese e al Dottor Tiezzi per avermi seguito
in questa tesi e per la massima disponibilità avuta nei miei confronti.
Ecco...queste poche righe!! e finiscono i ringraziamenti di Matteo.
Adesso....iniziano quelli dello Zibe!!!!
Dunque, in primis, un enorme grazie a Petroio, amico e compagno di mille
avventure. Ci siam divertiti e ci siamo presi per i fondelli, s’è litigato e ci siam
ammazzati di risate, s’è passato grandi momenti e grandi delusioni, un grande
personaggio, a cui auguro tutto il bene possibile. Grazie mille mitiko...soprattutto
quando m’hai sopportato nei momenti duri!!!!
Poi...un enorme grazie a Montorsoli grande grande personaggio con cui, soprattutto i primi anni di università, s’è dato vita a grandissimi numeri. Ci si vede
molto meno, ma per me rimani sempre il solito grandissimo. Un grazie a Massa e
Cozzile (pupppaaaaaaaaaaa mitikoooo :-D), a Via Toscana a Firenze (altro gloriosissimo personaggio), a Campi Bisenzio (ah no!!!..l’Impruneta), a Roma (ma cosa
faccio?!?!..ringrazio Roma dopo che lui m’ha fatto danna’ per un’intera vacanza),
alla grandissima Reggio Emilia (sempre sempre mitiko) e alla grandissima Petricci,
campione eccezionale con cui tuttora si da vita a fantastici pezziiii!!!!
160
APPENDICE A. INTERPRETE - SVILUPPI IMPLEMENTATIVI
161
Un bacione gigante a Vaiano e Biella, amiche da una vita. E poi un grazie...con
tutto il cuore....ad Agliana e tutti i suoi bottini, a San Donato in Collina e al
Raparo, all’Antella e a Reggello, a tutta Prato e a Montemurlo, a Bolzano e a
Pesaro Urbino, a tutta la Calabria (la Puglia, la Sicilia ecc..ecc..). Un grazie al
Pozzale, a Certaldo e a Monsummano, a Contea e Dicomano, a tutta Pistoia e
a tutta Grosseto, ad Arezzo e al Casentino e tutta Montevarchi, Pontassieve, a
i’Ponte ai’ppino e a via di Maragliano.
Un grazie a tutta Firenze e a tutti quelli che a Firenze c’hanno messo piede
(almeno una volta)!!!!! A chi gira per Careggi e a chi gira per gli scout. A chi gira
per Rifredi e a chi gira per Piazza Indipendenza.
Un abbraccio di cuore al Liceo Artistico e ai suoi gloriosi personaggi, compagni
da una vita!!!! Personaggi di una grandezza tale come pochi. Siete bellissimi!!!!! Con
voi, alcune tra le più grandi gag!!!!!!!!!
Un bacione immenso a tutta Grassina e a chi a Grassina s’è aggiunto. Dalle
Scalette a Greve in Chianti da Capannuccia fino a.......uff...e va beh....fino a San
Giorsolè, da Rignano fin lassù in Germania. Personaggi fantastici, amici!!!! Poi,
impossibile dimenticare ringraziando il City Ghey e il fantastico muretto della
chiesina.
Arrivato a questo punto...concludo con un grande grazie a Gavinana (e quel
laccetto per i capelli...cacciatelo nel cu.....) e un grazie a quel “fiocchino” in Via
Sirtori (mi dispiace, fino a qui ho resistito...lo giuro!!...ma con te non ce l’ho proprio
fatta... ..“fiocchino” ...è l’anagramma di che’ttu’ssei te!!!!!).
Eccoci...sono in fondo!...anzi no..... voglio concludere con un grazie e un bacione al Vingone (eh si...dev’essere stata proprio dura sopportarmi negl’ultimi
tempi...un bacione..l’ho meriti).... (un bacetto anche a San Donnino...che già lo
vedo sennò...geloso marcio!!!!).
Un grazie e un bacione a tutti di cuore, davvero!!!!!
...a quelli con cui mi sono ammazzato di risate e a quelli con cui ho litigato!!!
a quelli che m’hanno preso per i’kiu...e a quelli che ho preso per i’kiu!!! D’altronde
anche le prese per i’kiu son necessarie per rimanere con i piedi per terra..che sennò
ci si gasa...e poi un’va miha bene ;-P Ho iniziato che ero un bimbo...e finisco che
forse...spero sono un po’ più adulto!! Grazie di cuore a tutti quelli che in questi
anni mi sono sempre stati accanto.
un bacione
Zibe
Bibliografia
[1] Laura Bocchi, Cosimo Laneve e Gianluigi Zavattaro
“A calculus for long-running transactions”.
In FMOODS, volume 2884 of LNCS, pages 124-138, Springer 2003.
[2] Davide Devescovi
“Sviluppo di tool basati su un metamodello editabile graficamente.
Panoramica e breve guida all’uso di EMF, GEF e GMF”.
Corso di Argomenti Avanzati di Ingegneria del Software - Politecnico di Milano,
2006.
[3] Bruce Eckel
“Thinking in Java”
4th Edition Pearson - 2006.
[4] Eclipse documentation - Current release: Eclipse Ganymede
http://help.eclipse.org/ganymede/index.jsp.
[5] Gamma-Helm-Johnson-Vlissides
“Design Patterns - Elementi per il riuso di software a oggetti”
Prima Edizione Italiana - Ed: Pearson Education Italia - 2002.
[6] www.jugancona.it;
[7] Alessandro Lazzoni
“Un sistema di tipo per regolare lo scambio di dati fra servizi web”,
Master Thesis in Computer Science, supervisor Prof. Rosario Pugliese
Dipartimento di Sistemi e Informatica, Università degli studi di Firenze, 2007;
[8] Alessandro Lapadula, Rosario Pugliese and Francesco Tiezzi
“A Calculus for Orchestration of Web Services”
162
BIBLIOGRAFIA
163
In Proc. of 16th European Symposium on Programming (ESOP’07)
volume 4421 of LNCS (R. De Nicola, Ed.), pages 33-47. Springer - 2007
Dipartimento di Sistemi e Informatica Universita degli Studi di Firenze.
[9] Alexandre Alves et al
“Web Services Business Process Execution Language Version 2.0”
Technical report, WS-BPEL TC OASIS, August 2006. http://www.oasisopen.org
[10] Daniele Persechino
“Sviluppo di un plug-in grafico per il linguaggio COWS tramite GMF e Sintaks”
Bachelor Thesis in Computer Science, supervisor Prof. Rosario Pugliese
Dipartimento di Sistemi e Informatica, Università degli studi di Firenze, 2008;
[11] Barbara Pernici - Pierluigi Plebani
Un’introduzione ragionata al mondo dei Web Service
Mondo Digitale n.1 - 2004;
[12] Laura Rinaldi
BPEL: Business Process Execution Language
MokaByte n.127 - Marzo 2008
http://www2.mokabyte.it/cms/article;
[13] JavaTM Remote Method Invocation (RMI)
Enhancements since version 1.3 of the JavaTM 2 SDK
http://java.sun.com/j2se/1.4.2/docs/guide/rmi/.
[14] Etienne Gagnon
“SableCC, an object-oriented compiler framework”
Thesis submitted to the Faculty of Graduate Studies and Research of the requirements for the degree of Master of Science
Faculty of Montreal - March 1998;
[15] www.kermeta.org/sintaks;
[16] www.springsource.org.