it
Poste Italiane S.p.ASpedizione in A.P. D.L. 353/2003 (conv.in L.27/02/2004 n.46) art.1 comma 2 DCB ROMA Periodicit mensile APRILE 2007 ANNO XI N.4 (113)
PER ESPERTI E PRINCIPIANTI
RIVISTA+CD E6,90 RIVISTA+LIBRO+CD E9,90
VERSIONE PLUS VERSIONE STANDARD
Poste Italiane S.p.A Spedizione in A.P. D.L. 353/2003 (conv.in L.27/02/2004 n.46) art.1 comma 2 DCB ROMA Periodicit mensile NOVEMBRE 2008 ANNO XII, N.11 (132)
PER ESPERTI E PRINCIPIANTI
SOLUZIONI COME FUNZIONA E QUALI SONO GLI ALGORITMI IMPIEGATI
DALLE COMUNI APPLICAZIONI DI GRAFICA PER IMPLEMENTARE LA FUNZIONE
DI CLIPPING DI UNIMMAGINE
LIBRARY
GUIDA ALLUSO DELLA
LIBRERIA GNU SCIENTIFIC
Simulare il lancio di un dado
e studiarne la distribuzione
VB.NET
COME SI REALIZZA
UN EDITOR DI TESTO?
Sviluppare un software
con funzionalit simili a Word
RUBY
UN RISOLUTORE
DI SUDOKU
Implementare metodi euristici
per risolvere problemi complessi
FACEBOOK:
PROGRAMMALO COS
Creare un'applicazione utilizzando
la libreria Facebooker
JAVA
INTRODUZIONE AI
MESSAGE ORIENTED
MIDDLEWARE
Far parlare le nuove applicazioni
con un vecchio sistema
MOBILE
LA GRAFICA
DI BASSO LIVELLO
Controllo totale dei grafici
da plottare sul display
ECLIPSE
LA PRODUZIONE FINALE
DI UN SOFTWARE
Impacchettare unapplicazione
con tanto di eseguibili
e documentazione
DATABASE
COME FUNZIONA
NHIBERNATE
La libreria per la persistenza
di oggetti .NET su DB relazionali
JAVA
OPEN SOURCE:
JFREECHART
Guida alla generazione
dinamica di grafici
PYTHON
LE PRINCIPALI
NOVIT DI PY3K
Migrazione del codice legacy
e tutte le nuove funzionalit
PHP
ACCOUNT WEB:
COME GESTIRLI
Utilizzare i componenti del Zend
Framework e il paradigma MVC
20
SNIPPET
DI CODICE
da usare subito nei tuoi lavori
CHROME FAI DA TE
Ti insegnamo a realizzare un browser per navigare in Rete
con lo stesso motore impiegato dal software di Google
IL WIIMOTE TI OSSERVA!
Realizzare un rilevatore di movimento
che scatta la foto al momento giusto...
SISTEMA
Cosa si cela dietro il chiacchierato
sistema operativo mobile
TECNICA
Tutte le nozioni per progettare
software compatibile con Android
SOFTWARE
La nostra prima applicazione
per i nuovi cellulari HTC
PROVA IL SIMULATORE
per creare applicazioni Android
senza possedere il cellulare
sul CD-ROM
i
o
P
r
o
g
r
a
m
m
o
A
n
n
o
X
I
I
-
N
1
1
(
1
3
2
)
E
D
I
Z
I
O
N
I
M
A
S
T
E
R
S
V
I
L
U
P
P
A
R
E
P
E
R
A
N
D
R
O
I
D
B
R
O
W
S
E
R
F
A
I
D
A
T
E
U
N
E
D
I
T
O
R
D
I
T
E
S
T
O
S
U
M
I
S
U
R
A
SVILUPPARE APPLICAZIONI
PER IL NUOVO ANDROID
Anno XII - N.ro 11 (132) - Novembre 2008 -
Periodicit: Mensile
Reg. Trib. di CS al n.ro 593 del 11 Febbraio 1997
Cod. ISSN 1128-594X
E-mail: ioprogrammo@edmaster.it
http://www.edmaster.it/ioprogrammo
http://www.ioprogrammo.it
Direttore Editoriale: Massimo Sesti
Direttore Responsabile: Massimo Sesti
Responsabile Editoriale: Gianmarco Bruni
Vice Publisher: Paolo Soldan
Redazione: Raffaele Del Monaco
Collaboratori: E. Bottari, A. Bottoni, M. Buccio, F. Fortino,
F. Grimaldi, A. Leganza, G. Pignalberi, C. Pelliccia,
G.Pennella, F. Tomassetti, G. Valente
Segreteria di Redazione: Emanuela Giordano
Consulenza Redazionale: SET S.r.l.
Gianfranco Forlino
Realizzazione grafica: Cromatika S.r.l.
Art Director: Paolo Cristiano
Responsabile grafico di progetto: Salvatore Vuono
Responsabile area tecnica: Giancarlo Sicilia
Illustrazioni: M. Veltri
Impaginazione elettronica: Francesco Cospite, Lisa Orrico,
Nuccia Marra, Luigi Ferraro
Realizzazione Multimediale: SET S.r.l.
Realizzazione CD-Rom: Paolo Iacona
Pubblicit: Master Advertising s.r.l.
Via Amedei, 15 - 20123 Milano
Tel. 02 83121211 - Fax 02 83121207
e-mail advertising@edmaster.it
Sales Director ITC & Entertainment Magazines:
Max Scortegagna
Editore: Edizioni Master S.p.a.
Sede di Milano: Via Ariberto, 24 - 20123 Milano
Sede di Rende: C.da Lecco, zona ind. - 87036 Rende (CS)
Presidente e Amministratore Delegato: Massimo Sesti
Direttore Generale: Massimo Rizzo
ABBONAMENTO E ARRETRATI
ITALIA: Abbonamento Annuale: IOPROGRAMMO (11 NUMERI)
E59,90 SCONTO 21% SUL PREZZO DI COPERTINA DI E75,90
IOPROGRAMMO CON LIBRO (11 NUMERI) E75,90 SCONTO 30%
SUL PREZZO DI COPERTINA DI E108,90
Abbonamento Biennale: IOPROGRAMMO (22 NUMERI) E75,90
SCONTO 50% SUL PREZZO DI COPERTINA DI E151,80
IOPROGRAMMO CON LIBRO (22 NUMERI) E108,90 SCONTO 50%
SUL PREZZO DI COPERTINA DI E217,80 OFFERTE VALIDE FINO AL
30/11/2008.
Costo arretrati (a copia): il doppio del prezzo di copertina + E
5. 32 spese (spedizione con corriere). Prima di inviare i
pagamenti, verificare la disponibilit delle copie arretrate allo 02
833836.
La richiesta contenente i Vs. dati anagrafici e il nome della riv-
ista, dovr essere inviata via fax allo 02 83383610, oppure via
posta a EDIZIONI MASTER via C. Correnti, 1 - 20123 Milano, dopo
avere effettuato il pagamento, secondo le modalit di seguito
elencate:
} else {
// Form non valido, viene
ripresentato all'utente
echo "ERRORE di VALIDAZIONE
dei campi - controllare quanto inserito";
$this->view->form = $form;
}
} else {
// Accesso iniziale, visualizzazione del form
$this->view->form = $form;
}
}
I test che dovremo aggiungere sono:
1) eguaglianza delle due password nei due
campi;
2) presenza di uno username come quello scelto.
Se il primo si ottiene semplicemente con la strut-
tura:
if ( $form->getValue('password_1') == $form-
>getValue('password_2') )
{
.// Codice successivo
}
else
{
// Form non valido, viene ripresentato all'utente
echo "ERRORE le due password inserite non sono
eguali - controllare";
$this->view->form = $form;
}
Per il secondo dovremo invece accedere alla
tabella utenti sfruttando, come visto la volta scor-
sa, il database adapter messo a disposizione dal
framework, assieme ai dati di accesso al DB con-
servati in un file XML:
// Caricamento dati di configurazione
$config = new Zend_Config_Xml('./config.xml',
'database');
$db_host = $config->db_host;
$username = $config->username;
$password = $config->password;
$db_name = $config->dbname;
// Inizializzazione del dbAdapter
$connection_string = array('dbname' => $db_name,
'username' => $username , 'password' =>
$password , 'host' => $db_host);
$dbAdapter = new
Zend_Db_Adapter_Mysqli($connection_string);
// Verifica che lo username scelto non sia gi
esistente
$sql = "select count(*) as rst from users where
username = '" . $form->getValue('username') . "'";
$res = $dbAdapter->fetchAssoc($sql);
$num = $res[1]['rst'];
if ( $num == 0 )
{
. // Codice successivo
}
else
{
M
ioProgrammo Web
Novembre 2008/
35
G
La gestione degli account di un sito web
NOTA
CAPTCHA
Lacronimo CAPTCHA sta
per: Completely
Automated Turing test to
tell Computers and
Humans Apart (Test
automatizzato di Turing
per poter discernere
computer da umani),
ovvero un test che
concepito per poter essere
superato solo e soltanto da
un utente reale e non da
un computer.
ovviamente usato
proprio per evitare, come
nel nostro caso, la
creazione di account
massivi da parte di robot.
Lacronimo stato
concepito nel 2000 da Luis
von Ahn, Manuel Blum,
Nicholas J. Hopper
(Carnegie Mellon
University), e John
Langford (IBM).
Fig. 3: Lalberatura della nostra applicazione MVC
032-037:032-035 30-09-2008 16:45 Pagina 35
ht t p: / / www. i opr ogr ammo. i t
// Errore: username gi previsto nel sistema
echo "ERRORE: nel sistema esiste gia' uno
username come quello indicato - controllare";
$this->view->form = $form;
}
Si noti come il risultato della query viene inserito
in una variabile ($res) che strutturata come un
array associativo grazie al comando fetchAssoc.
A questo punto non ci rimane che effettuare i
passi di inserimento dellutente nella base dati e
di invio delle-mail allo stesso. Il primo molto
semplice, avendo la connessione gi aperta col
DB:
$sql = "insert into users
(USERNAME,PASSWORD,EMAIL,REALNAME,DATA)
values ('" . $form->getValue('username') . "','" .
$form->getValue('password_1') . "','" . $form-
>getValue('email') . "','" . $form-
>getValue('realname') . "',NOW())";
$dbAdapter->query($sql);
Si noti come si usata la funzione NOW() di
MySQL per inserire il campo della data.
Per inviare le-mail, invece, dovremo usare il
componente ZEND_MAIL. Questo permette sia di
inviare e-mail verso un server, sia di leggerle da
un server POP3. Nel caso di invio, tale operazio-
ne verr effettuata di default con lausilio di
SENDMAIL; tuttavia, qualora nella nostra rete
avessimo un mail server ben identificato che for-
nisse servizi SMTP, allora verrebbe pi comodo
usare questo per inviare la posta. Se questo ser-
ver risponde a out.miodominio.it, il codice che
ci permette di impostare questo come il metodo
di invio il seguente:
$tr = new
Zend_Mail_Transport_Smtp('out.miodominio.it');
Zend_Mail::setDefaultTransport($tr);
Fatto ci, potremo finalmente esaminare il codi-
ce che permette linvio effettivo:
$testo_email = Testo e-mail
$mail = new Zend_Mail();
$mail->setBodyText($testo_email);
$mail->setFrom('admin@miodominio.it ', 'Servizio
XXXX');
$mail->addTo($form->getValue('email'));
$mail->setSubject('Creazione account al servizio
XXXX');
$mail->send();
In questo modo viene inviata una e-mail testua-
le allindirizzo che lutente ha prima inserito. Per
cui, in base ad i nostri requisiti, il testo che
vogliamo inviare allutente deve contenere una
URL completa, dove andare ad attivare definiti-
vamente laccount. Dato che sicuramente pi
comodo fare in modo che lutente possa accede-
re a tale URL seguendo un hyperlink, allora pos-
siamo usare una e-mail in formato HTML, che
oramai viene gestito da praticamente tutti i mail
client. In tal caso il metodo da usare
setBodyHtml invece di setBodyText. Chiaramente
la variabile $testo_email potr contenere un
testo HTML.
Si noti come anche possibile, in modo sempli-
ce ed efficiente, inviare degli attachment alla e-
mail. Ad esempio: supponiamo di aver scelto un
CAPTCHA di tipo immagine e di volerla inviare
come attachment (perch si vuole che lutente la
reinserisca nella fase di attivazione, come ulte-
riore sicurezza). Se limmagine generata pub-
lic\images\captcha\immagine.gif, allora il codice
che ci permette linvio della stessa (da inserire
prima del punto di send visto sopra) sar:
$at = $mail->createAttachment($myImage);
$at->type = 'image/gif';
$at->disposition =
Zend_Mime::DISPOSITION_INLINE;
$at->encoding = Zend_Mime::ENCODING_8BIT;
$at->filename =
'public\images\captcha\immagine.gif';
Inviata le-mail, potremo ridirezionare la pagina
ad una schermata di conferma dellavvenuta
registrazione (pagina non riportata per brevit).
$this->_redirect('http://127.0.0.1/
quickstart/public/conferma.php');
INSERIMENTO
DEGLI ELEMENTI
DI LOG: ZEND_LOG
Linserimento di elementi di tracciamento (log)
allinterno delle nostre applicazioni una pratica
comune e ben nota. In genere si vuole che tali
tracciamenti, oltre a riportare la data ed ora,
assieme ad un messaggio, siano corredati di un
elemento che ne permette la classificazione.
In questo modo potremo distinguere eventi di
diverso tipo, come ad esempio tracciamenti di
errori applicativi gravi da quelli meno gravi.
Chiaramente tali informazioni verranno in gene-
re salvate in file (spesso in formato XML) o in un
database. Compito del componente ZEND_LOG
rendere quanto visto una operazione banale.
In particolare, questo componente di base preve-
de un log contenente 4 informazioni: un time-
stamp, un livello di priorit (o di classifica)
ioProgrammo Web
M
G
36
/Novembre 2008
La gestione degli account di un sito web
NOTA
FRANK, IAN AND
GLENNS LETTERS
FIGlet un acronimo che
sta per Frank, Ian and
Glenn's LETters, che sono
i tre autori dellomonimo
programma che permette
di creare delle lettere di
una certa dimensione
composte da caratteri
ordinari. Un esempio della
versione DOS riportato in
Fig. 2. In genere tale
tecnica si usa nelle e-mail
a caratteri come una sorta
di signature. I sorgenti del
programma sono
liberamente scaricabili da
http://www.figlet.org
032-037:032-035 30-09-2008 16:45 Pagina 36
ht t p: / / www. i opr ogr ammo. i t
numerico che va da 0 a 7 (0 priorit maggiore),
un livello di priorit testuale ed un messaggio.
Chiaramente il timestamp autogenerato e i
livelli numerico e testuale devono (per loro natu-
ra) essere allineati (per inciso, Zend usa la classi-
ficazione standard BSD del protocollo syslog).
Per cui lutente andando a scrivere un log dovr
essenzialmente scegliere il livello di priorit e
indicare il messaggio. Queste due informazioni
verranno passate a un writer, che si occuper di
scriverle dove si deciso.
Supponiamo, ad esempio, di voler tenere un log
del fatto che un utente ha correttamente richie-
sto un nuovo account e di voler conservare tale
informazione in una tabella del database (che
chiameremo logger).
La struttura di tale tabella, in base a quanto visto,
sar:
CREATE TABLE `logger`
(
`timestamp` varchar(100) NOT NULL,
`priorityName` varchar(20) NOT NULL,
`priority` int(11) NOT NULL,
`message` varchar(200) NOT NULL
)
ENGINE=InnoDB
Il codice che ci permette di definire il writer su
questa tabella sar il seguente:
$db = Zend_Db::factory('PDO_MYSQL',
$connection_string);
$columnMapping = array('priority' => 'priority',
'message' => 'message', 'priorityName' =>
'priorityName', 'timestamp' => 'timestamp');
$writer = new Zend_Log_Writer_Db($db, 'logger',
$columnMapping);
$logger = new Zend_Log($writer);
In cui $connection_string linsieme dei parame-
tri di connessione visti nel codice precedente.
Si noti come sia stato necessario indicare un
mapping tra gli elementi che compongono il log
creato da Zend e le colonne della base dati (che
per semplicit sono state chiamate in modo
eguale).
A questo punto, per inserire un messaggio di log
(a prescindere dal writer effettivo) useremo il
metodo log, che prende in ingresso il messaggio
e la priorit:
$logger->log('Messaggio di tipo
INFO',Zend_Log::INFO);
Infine, per chiudere il log sar sufficiente usare il
comando:
$logger = null;
In caso invece si voglia salvare i dati su un file
XML dovremo usare un writer di tipo stream di
dati verso un file ed indicargli che vogliamo un
formato XML (altrimenti sar plain text).
Anche in questo caso possiamo indicare, con un
array associativo, un mapping tra gli elementi del
log ed i tag XML tra cui vanno racchiusi in modo
analogo a come si aveva il mapping con le colon-
ne della tabella visto prima.
$writer = new
Zend_Log_Writer_Stream('/path/to/logfile');
$formatter = new Zend_Log_Formatter_Xml('log',
$columnMapping);
$writer->setFormatter($formatter);
CONCLUSIONI
In questo terzo articolo abbiamo visto come una
serie di funzioni standard di una Web
Application, come la gestione dei CAPTCHA,
linvio di messaggi di posta elettronica ad un
utente e la gestione di LOG vengano enorme-
mente semplificate usando il Zend Framwork,
che permette di usarle inserendo poche righe di
codice. Col prossimo articolo espanderemo
ancora di pi la nostra applicazione con ulteriori
elementi del framework.
Guido Pennella
M
ioProgrammo Web
Novembre 2008/
37
G
La gestione degli account di un sito web
LAUTORE
Guido Pennella un
ingegnere esperto di
informatica, laureato
allUniversit di Roma La
Sapienza. Si occupa
principalmente dello
sviluppo di sistemi sia via
Web, sia di applicazioni
embedded real time
distribuite. possibile
contattarlo allindirizzo di
posta elettronica
guidopennella@virgilio.it
Fig. 4: Le priorit definite dal protocollo BSD
syslog
Per quanto non necessario, comunque
consigliabile usare un ambiente di
sviluppo integrato per poter gestire
agevolmente lo sviluppo di Web
Applications in PHP cosa in generale
sempre vera per la programmazione di
sistemi. La stessa Zend, in
collaborazione con la IBM, ha rilasciato
Open Source anche un ambiente di
sviluppo: il PDT PHP Development
Tools. Questo un insieme completo di
componenti per Eclipse per sviluppare
applicazioni in PHP. Il progetto ben
vivo (il 10 giugno 2008 stata rilasciata
la versione 1.0.3) e la sua home page :
http://www.eclipse.org/pdt/.
Per i neofiti di Eclipse, si consiglia luso
del pacchetto all-in-one, installabile
semplicemente decomprimendolo in una
directory.
PHP DEVELOPMENT TOOLS
032-037:032-035 30-09-2008 16:45 Pagina 37
LA FASE DI BUILDING
DI UN PROGETTO
QUESTA RICETTA SPIEGA COME ESPORTARE IL LAVORO SVOLTO IN UN PROGETTO JAVA
DI ECLIPSE, IN MODO DA POTER CONSEGNARE A TERZI GLI ESEGUIBILI PRODOTTI,
LA DOCUMENTAZIONE ED, EVENTUALMENTE, I SORGENTI DEL PROPRIO SOFTWARE
A
nulla servirebbe un ambiente tanto ricco e
completo come Eclipse se, ad un certo
punto, non fosse possibile esportare i risul-
tati raggiunti. Bench nessun software possa mai
definirsi perfetto, ma sempre e solo perfezionabile,
arriva sempre un momento in cui si decide di realiz-
zare una build e di renderla disponibile agli altri.
necessario allora impacchettare gli eseguibili e
generare la documentazione. Nel caso di un softwa-
re Open Source, bisogna anche estrarre i sorgenti.
La presente ricetta spiega come compiere queste tre
semplici operazioni.
UN PROGETTO
ESEMPLIFICATIVO
Creiamo un progetto Java chiamato MiaLibreria,
come insegnato nella ricetta #2 (ioProgrammo 130).
Immagineremo di realizzare una libreria che, a lavo-
ro concluso, sar resa disponibile ad altri sviluppa-
tori, affinch la utilizzino per i loro software. Nella
cartella dei sorgenti creiamo i pacchetti package1 e
package2. Dentro ciascuno dei pacchetti inseriamo
un documento HTML chiamato package.html con
della documentazione circa i contenuti del pacchet-
to stesso. Realizziamo ora quattro classi, disposte
due per pacchetto, e chiamiamole Classe1, Classe2,
Classe3 e Classe4. Allinterno di ciascuna classe inse-
riamo alcuni metodi a piacere, e decoriamo il codi-
ce con dei commenti javadoc. Nel pacchetto con il
codice allegato alla ricetta potete trovare una strut-
tura di questo tipo gi pronta.
ESPORTARE UN JAR
Gli eseguibili Java, come dovrebbe essere gi noto e
come ribadito nella ricetta #3 (ioProgrammo 131),
vengono raccolti allinterno di archivi JAR, speciali
file con estensione .jar, che altro non sono che dei
pacchetti ZIP organizzati secondo una certa struttu-
ra. Questi archivi contengono i file .class che costi-
tuiscono il compilato di unapplicazione o di una
libreria, pi eventuali risorse ad essi collegate.
Selezioniamo la radice del progetto e scegliamo, dal
men, la voce File Export. Questa operazione
comporta lapertura di una finestra che permetter
la scelta fra le differenti procedure di Export dispo-
nibili. Per esportare un archivio JAR, come nostra
intenzione fare, bisogna selezionare la voce JAR
File, nel gruppo Java, e poi procedere con il tasto
Next.
A questo punto il wizard per lesportazione del JAR
avviato. La prima tra le sue schermate ci permette di
scegliere, nella parte alta, quali risorse includere nel
JAR che sar creato.
Scegliamo cosa includere nel nostro JAR, utilizzando
gli strumenti presenti nella met alta della scherma-
ta. Non necessario includere i file package.html,
che fanno parte della documentazione, pertanto
andiamo a deselezionarli. Facciamo in modo che
restino selezionate soltanto le quattro classi presen-
ti nei due pacchetti del progetto. Le quattro caselle
di spunta immediatamente sotto gli elenchi di sele-
zione permettono di affinare ulteriormente la scelta.
Normalmente si seleziona soltanto la voce Export
generated class files and resources. Questa fa in
modo che, di ciascuna classe selezionata, venga
inclusa nel pacchetto la versione compilata (i file
.class generati dal compilatore, in pratica).
SOFTWARE M Esportare eseguibili, documentazione e sorgenti
ht t p: / / www. i opr ogr ammo. i t
G
38
/Novembre 2008
J CD J WEB
eclipse-export.zip
cdrom.ioprogrammo.it
Conoscenze richieste
Rudimenti di Java
Software
Java SDK (JDK), Eclipse
Impegno
Tempo di realizzazione
REQUISITI
Fig. 1: La struttura iniziale del progetto MiaLibreria
038-041:088-093-corsi-xsl 30-09-2008 16:31 Pagina 38
ht t p: / / www. i opr ogr ammo. i t
Novembre 2008/
39
G
Esportare eseguibili, documentazione e sorgenti M
SOFTWARE
Opzionalmente possibile anche includere i sor-
genti .java nel JAR stesso, attivando la voce Export
Java source files and resources, ma generalmente
questa pratica non molto utile. Gli archivi JAR sono
infatti concepiti per contenere gli eseguibili, non il
codice. Impareremo oggi stesso una tecnica pi pro-
ficua per lexport dei sorgenti.
Nella met inferiore della schermata si seleziona la
destinazione su disco del file JAR che sar generato
ed alcune opzioni generali, come ad esempio se si
intende comprimere o meno il contenuto dellarchi-
vio (personalmente vi suggerisco di s).
Una volta compiute le scelte desiderate possibile
procedere alla schermata successiva, con il solito
tasto Next.
La seconda schermata propone alcune altre scelte:
includere o meno quelle classi che possono com-
portare alcuni problemi, o se sia necessario compi-
lare il progetto qualora questo non fosse compilato
automaticamente. Sono tutte caratteristiche che
imparerete ad utilizzare man mano che crescer la
vostra confidenza con la piattaforma. Interessante
lultima del gruppo, cio quella etichettata Save the
description of this JAR in the workspace. Attivando
questa opzione, ad export ultimato, sar inserito un
file allinterno del workspace di Eclipse con esten-
sione .jardesc. Questo file utile per ripetere in qual-
siasi momento lexport del JAR senza dover riattra-
versare tutto il wizard. Pertanto vi consiglio di attiva-
re lopzione e di scegliere di conservare il file genera-
to allinterno del progetto stesso, magari con nome
export-jar.jardesc.
Passiamo ora alla terza ed ultima schermata del
wizard.
Qui possibile compiere le ultime scelte disponi-
bili prima della generazione. Anzitutto possibi-
le manipolare il file MANIFEST che sar introdot-
to nel JAR. Eclipse ne genera uno automatica-
mente che riflette le scelte fatte nel wizard ma, se
volete, potete anche usarne uno realizzato da
voi. Se lasciate fare ad Eclipse, come avviene
nella maggior parte dei casi, potete inoltre farvi
salvare una copia del file generato nel workspa-
ce.
Le ultime due opzioni disponibili permettono di
sigillare i pacchetti (cfr. documentazione Java) e
di specificare una classe di avvio del pacchetto.
Questultima opzione risulta molto utile se si sta
esportando un software per lutente finale, piut-
tosto che una libreria come nel nostro caso. Un
software per lutente, infatti, dispone sicuramen-
te di un punto di avvio, cio di una classe con un
metodo main(). In questo caso possibile sele-
zionare la classe di questo tipi inclusa nel JAR da
generare. In questo modo lapplicazione potr
essere facilmente avviata (nei sistemi Windows,
ad esempio, un doppio clic sui JAR di questo tipo
sufficiente per attivare la macchina virtuale e
lanciare il programma).
Eseguita ogni scelta non resta che utilizzare il tasto
Finish e attendere la creazione del file .jar, che
potrete poi ritirare direttamente dal disco rigido,
alla posizione specificata nella prima schermata del
wizard. Un effetto collaterale della creazione del JAR,
se avete seguito il mio consiglio di attivare la voce
Save the description of this JAR in the workspace,
che il vostro progetto conterr ora un file export-
jar.jardesc.
Cliccando con il tasto destro sul file .jardesc
generato possibile scegliere la voce Create
Fig. 2: La prima schermata del wizard di esportazione
di un archivio JAR
NOTA
RUNNABLE JAR
FILE
A partire da Eclipse 3.4
stato messo a disposizione
il nuovo wizard di
esportazione chiamato
Runnable JAR file. Come
il nome lascia ad intendere,
questa procedura pu
essere utilizzata per
esportare pi velocemente
quegli archivi JAR al cui
interno contenuta e
registrata una classe
dotata di metodo main(),
cio il punto di lancio
dellapplicazione..
Fig. 3: Il file generato dal wizard, che permette di
ricreare il JAR pi volte senza dover ripercorrere
lintero wizard
038-041:088-093-corsi-xsl 30-09-2008 16:31 Pagina 39
SOFTWARE M Esportare eseguibili, documentazione e sorgenti
ht t p: / / www. i opr ogr ammo. i t
G
40
/Novembre 2008
JAR, per ricreare nuovamente lo stesso JAR,
oppure Open JAR packager, per modificare
alcune delle scelte fatte prima di procedere alla
nuova creazione.
ESPORTARE I JAVADOC
Se con Eclipse creare un archivio JAR semplice,
esportare la documentazione javadoc di un proget-
to, lo ancora di pi. Un requisito da soddisfare
affinch la documentazione sia veramente utile ,
naturalmente, quello di aver scritto del codice con
commenti javadoc completi e ben fatti. Una volta
ultimato questo lavoro, possibile generare le pagi-
ne HTML con la documentazione di pacchetti, clas-
si e metodi. La cosa, di per s, molto utile soprat-
tutto nel caso in cui si stia producendo una libreria
che altri sviluppatori utilizzeranno per programma-
re. Selezionate i pacchetti di cui desiderate generare
la documentazione javadoc, o lintera cartella src se
volete documentazione per tutte le classi del proget-
to, quindi dal men scegliete di nuovo la voce File
Export.
Questa volta, nella schermata di selezione del
wizard di export, scegliete la voce Javadoc, sempre
nel gruppo Java.
Il primo passo del wizard ricorda molto
lesportazione del JAR. Anche in questo caso si sele-
ziona tra i pacchetti e le classi del progetto, in modo
da scegliere in maniera granulare cosa far parte
della documentazione e cosa ne rester fuori. pos-
sibile (in alto) scegliere quale eseguibile sar richia-
mato per la generazione dei javadoc (per default
viene utilizzato quello compreso nel JDK predefinito
del workspace, ma la scelta pu essere arbitraria-
mente modificata). Quindi possibile scegliere il
livello di visibilit. La voce private produce la docu-
mentazione pi dettagliata, includendo anche la
descrizione dei metodi privati. Le voci successive
sono a scendere, fino a public che produce docu-
mentazione per metodi e propriet pubblici.
Normalmente, quando si produce documentazione
per una libreria, si sceglie protected, che documenta
i membri pubblici e quelli protetti. Lultima parte
della schermata permette di scegliere se utilizzare la
doclet predefinita di javadoc o una customizzata (la
doclet la classe che genera la documentazione, che
possibile alterare per avere documentazioni pi
personalizzate). Nel caso di utilizzo della doclet
standard, che poi quello pi ricorrente, bisogna
specificare quale la cartella che ospiter la docu-
mentazione generata. Tale directory pu essere
allinterno del progetto e del workspace, come sug-
gerisce per scelta predefinita Eclipse, oppure dove
volete sul vostro file system. Normalmente bene
inserire una cartella doc allinterno del progetto stes-
Fig. 5: La prima schermata del wizard di esportazione
della documentazione javadoc
Fig. 6: La seconda schermata del wizard di esportazio-
ne della documentazione javadoc
Fig. 4: Per avviare il wizard di esportazione della docu-
mentazione javadoc bisogna selezionare Java
Javadoc
NOTA
SCORCIATOIE UTILI
Le seguenti scorciatoie
sono valide durante
lutilizzo delleditor Java:
G CTRL + /
Disattiva la riga corrente o
il blocco di codice
selezionato, trasformandolo
in un commento. Ripetendo
nuovamente loperazione
sulla medesima riga o sullo
stesso blocco, il commento
viene rimosso e tutto torna
come era prima.
G F2
Mostra, in una finestra
pop-up, leventuale
documentazione javadoc
associata allelemento sul
quale posizionato il
cursore del testo.
G SHIFT + F2
Apre nel browser
predefinito del sistema
leventuale
documentazione javadoc
associata allelemento sul
quale posizionato il
cursore del testo.
G F3
Apre il sorgente
dellelemento sul quale
posizionate il cursore del
testo.
038-041:088-093-corsi-xsl 30-09-2008 16:31 Pagina 40
ht t p: / / www. i opr ogr ammo. i t
so, proprio come suggerisce Eclipse per default.
Compiute le scelte necessarie possibile muoversi
alla seconda schermata del wizard, con il tasto
Next.
In questa schermata, per cominciare, possibile
dare un titolo personalizzato alla documentazione.
Generalmente si mette il nome del software o della
libreria, seguito dal numero di versione (ad esempio
MiaLibreria 1.1). Tutte le opzioni sottostanti per-
mettono un controllo molto granulare su cosa
entrer a far parte dei javadoc generati: possibile
decidere se includere o meno un indice e delle pagi-
ne di navigazione correlate, se riportare o meno le
eventuali informazioni presenti nel codice circa
lautore e la versione di ciascuna classe, ed altro
ancora. La parte nella met centrale permette di
creare link verso documentazioni javadoc pre-esi-
stenti, solitamente disponibili online. Lultima delle
opzioni della schermata permette di modificare, se
ne sentite proprio lesigenza, laspetto delle pagine
HTML che saranno generate, attraverso luso di un
foglio di stile personalizzato. Ancora Next per pas-
sare alla terza e ultima schermata del wizard.
Prima di procedere alla generazione sono possibili
alcune altre scelte: linserimento di un file HTML di
overview (sar utilizzato nella pagina principale
della documentazione, per introdurre lintera libre-
ria o applicazione), opzioni per la macchina virtuale
che eseguir il comando javadoc e la doclet, impo-
stazioni per la compatibilit dei sorgenti ed altro
ancora. Da segnalare la possibilit di generare uno
script di Ant per ripetere successivamente la genera-
zione della documentazione javadoc. Si tratta delle-
quivalente del file .jardesc nel caso di esportazione
dei JAR, anche se la funzionalit meno immediata
perch richiede lintegrazione con Ant (di Ant si par-
ler sicuramente in una ricetta futura). Lultima delle
opzioni permette di aprire la documentazione con il
browser, non appena sar stata generata. Cliccando
su Finish, al primo atto di generazione, vi verr
probabilmente chiesto se volete adottare la cartella
scelta nella prima schermata come la predefinita per
la documentazione del progetto aperto. Fate pure la
vostra scelta.
Dopo aver generato la documentazione, dovreste
ritrovare una cartella doc allinterno del progetto (se
non avete scelto una locazione differente). Il file
index.html rappresenta il punto di partenza per
lesplorazione dei documenti. Potete trasferire la
documentazione fuori dal workspace di Eclipse con
una semplice operazione di copia-incolla della car-
tella doc, dal progetto allesplora risorse o analogo
del vostro sistema operativo.
ESPORTARE I SORGENTI
Esportare i sorgenti la pi semplice delle tre opera-
zioni del giorno: selezionate la cartella src, o un suo
sottoinsieme se non desiderate esportarla comple-
tamente, quindi adoperate unoperazione di copia-
incolla tra il Package Explorer di Eclipse e il file
manager del sistema operativo.
In alternativa potete provare qualcuno degli altri
wizard di export inclusi in Eclipse, ad esempio
Archive File o File System sotto la voce General.
NELLA
PROSSIMA RICETTA
La ricetta del prossimo mese vi insegner come si
utilizza il debugger, strumento indispensabile per il
bugfix di qualsivoglia software. Se non avete mai uti-
lizzato un debugger prima, e in particolare quello di
Eclipse, non potete assolutamente perdere la prossi-
ma ricetta: apprenderete lutilizzo di uno strumento
in grado di dimezzare il tempo necessario per la
risoluzione di qualsiasi problema presente nel
vostro codice.
Carlo Pelliccia
Esportare eseguibili, documentazione e sorgenti M
SOFTWARE
Novembre 2008/
41
G
Fig. 7: La terza (ed ultima) schermata del wizard di
esportazione della documentazione javadoc.
Fig. 8: Screenshot della documentazione generata
038-041:088-093-corsi-xsl 30-09-2008 16:31 Pagina 41
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
42
/Novembre 2008
Tecniche Ruby per la risoluzione di problemi complessi
L
a deformazione professionale pu influire an-
che sul modo in cui affrontiamo un passa-
tempo come il Sudoku. Da programmatori la
propensione naturale quella di riflettere sulle logi-
che che adottiamo per risolverli e modelizzarle.
In questo modo affrontiamo una sfida diversa: non ri-
solvere semplicemente uno schema ma capire qua-
li siano le tecniche adatte a risolverli tutti e poter con-
frontare fra loro queste tecniche.
La risoluzione di un Sudoku molto spesso richiede
l'alternarsi fra operazioni meccaniche, come il con-
trollare per ogni riga in quale casella si possa piaz-
zare un certo valore, e situazioni in cui i metodi pi
semplici e ripetitivi non bastano e dobbiamo trova-
re soluzioni pi creative e complesse. Il problema
che molto spesso le fasi cha abbiamo definito mec-
caniche richiedono molto tempo e non sono molto
stimolanti. L'idea , innanzitutto, quella di delegare
l'applicazione delle regole pi banali (ma lunghe e
tediose) a qualcosa di molto pi preciso e paziente
di noi: un programma scritto in Ruby, un vero e pro-
prio risolutore logicodi Sudoku. La sfida sar far cre-
scere il nostro risolutore ogni volta che incontrere-
mo uno schema che non sia in grado di risolvere, in-
segnandogli una nuova euristica, un nuovo modo di
procedere. Potremo anche utilizzare il nostro risolu-
tore per compiere solo i passi pi banali ed eseguire
personalmente i passaggi pi complessi di modo da
concentrarci solo sulla parte davvero interessante
della risoluzione.
Inizieremo vedendo come rappresentare i diversi
concetti presenti nel gioco; poste le basi implemen-
teremo le prime regole di risoluzione per poi esami-
narne una pi complessa e concluderemo lascian-
do aperto qualche spunto per continuare a migliorare
il nostro risolutore.
RAPPRESENTAZIONE
DELLO SCHEMA
Per prima cosa realizzeremo una classe le cui istan-
ze rappresenteranno uno schema di gioco. Ovvia-
mente, ogni istanza dovr memorizzare lo stato del-
le caselle: se contengono un valore oppure no e se si
di quale valore si tratta. Per il nostro scopo andr be-
nissimo un array di array (variabile @values) che con-
tenga, in corrispondenza di ogni casella, il valore del-
la medesima, oppure 0 per indicare che la casella vuo-
ta.
Avremo poi bisogno di un'altra informazione da as-
sociare a ogni casella: una lista di valori che abbia-
mo scoperto compatibile con la casella. In questo
caso optiamo per una tabella di hash (variabile @ex-
clusions) che utilizzer come chiave un array con le
coordinate della casella. In questo modo creeremo
le liste di valori esclusi solo quando ci serviranno.
Qui di seguito i metodi per leggere i dati che abbiamo
appena definito:
def value(row,col)
@values[row][col]
end
def exclusions(row,col)
r = @exclusions[[row,col]]
if !r
r = @exclusions[[row,col]] = []
end
r
end
come vedete, nel caso in cui sia richiesta una lista dei
valori esclusi per una casella, questa sar creata al
momento e salvata prima di restituirla.
Esaminiamo i metodi per la modifica di questi valo-
ri:
# Se il valore era gi assegnato viene restituito false,
# se si tratta in effetti di un nuovo assegnamento viene
# restituito true. Se si tratta di un tentativo di cambiare
# un valore assegnato viene sollevata un'eccezione
def set_value(row,col,v)
old_v = @values[row][col]
if old_v != 0
if old_v == v
return false
else
raise "tentativo di assegnare nuovo valore
UN RISOLUTORE
LOGICO DI SUDOKU
SCOPO DI QUESTO ARTICOLO IMPLEMENTARE UNAPPLICAZIONE CHE RISOLVE IL GIOCO
DEL SUDOKU. PER FARE QUESTO ADOPEREREMO RUBY, IMPLEMENTANDO METODI
EURISTICI E AVVANTAGGIANDOCI DELLE STRUTTURE PROPRIE DEL LINGUAGGIO
J CD J WEB
sudoku_solver.rb
cdrom.ioprogrammo.it
Conoscenze richieste
Conoscenza del
linguaggio Ruby di base
e uso delle closure
Software
Interprete Ruby
Impegno
Tempo di realizzazione
REQUISITI
042-046:072-080 30-09-2008 16:05 Pagina 42
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
43
G
Tecniche Ruby per la risoluzione di problemi complessi
differente alla casella [#{row},#{col}]"
end
else
@values[row][col] = v
true
end end
def exclude(row,col,v)
ex = exclusions(row,col)
if ex.include?(v)
false
else
ex.push(v)
true
end end
Questi metodi sono leggermente pi complessi per-
ch dobbiamo, non soltanto registrare il valore, ma an-
che verificare che si tratti di un valore novit, quin-
di non ancora assegnato n escluso in precedenza.
Restituiremo il valore true solo per gli effettivi cam-
biamenti; questo ci torner utile per capire, a ogni
iterazione, se i nostri algoritmi hanno realmente in-
dividuato dei valori utili alla risoluzione.
CARICAMENTO
DEGLI SCHEMI
Risulter molto utile poter caricare gli schemi da fi-
le. Il formato di questi file sar il pi semplice possi-
bile: nove righe di nove caratteri che rappresenta-
ranno ognuno una casella del nostro Sudoku.
I caratteri da 1 a 9 indicheranno una casella cui sta-
to assegnato un valore, il carattere '_' indicher, in-
vece, una casella vuota. L'array di array cos creato
verr utilizzato per inizializzare un'istanza della clas-
se Table:
# Carica da file lo stato del Sudoku
def Table.load(filename)
ri = 0
values = []
File.open(filename, "r") do |infile|
while (line = infile.gets)
line.chomp!
row = []
line.each_char do |c|
case c
when '_' :
row.push 0
when '0'..'9':
row.push c
else
raise "#{filename}: carattere inatteso, '#{c}'"
end
end
if row.length != 9
raise "#{filename}: ogni linea deve essere lunga
9 caratteri"
end
values.push(row)
end
if values.length != 9
raise "#{filename}: ci devono essere nove linee"
end end
Table.new values
end
AUMENTIAMO
L'ESPRESSIVIT
Introduciamo altri concetti basilari del Sudoku: ri-
ga, colonna, quadrato, casella, sfruttando l'espressivit
di Ruby. Abbiamo visto che la classe Table conterr i
valori presenti nel nostro Sudoku e anche i valori
scartati per una certa casella; questi dati non saran-
no replicati nelle classi che introdurremo, bens sa-
ranno recuperati da un'istanza della classe Table; da
queste stesse istanze faremo in modo che sia possi-
bile accedere ad ogni riga, colonna, quadrato e ca-
sella sia singolarmente sia eseguendo operazioni per
ognuno di essi tramite i metodi each_row, each_column,
each_square e each_box.
I metodi che permettono l'accesso ai singoli elementi
(la singola riga, la singola colonna e cos via) sono
estremamante semplici e non ci soffermeremo per-
tanto su di questi: si limitano a restituire oggetti crea-
ti all'atto dell'istanziazione di ogni Table. Pi inte-
ressanti sono, invece, i metodi che permettono di
eseguire operazioni sull'insieme dei vari elementi.
Questi metodi saranno quelli che ci permetteranno
di descrivere, con una certa semplicit, i nostri algo-
ritmi per la risoluzione del gioco:
def each_row
(0..8).each do |r|
yield row(r)
end
end
def each_column
(0..8).each do |c|
yield column(c)
end
end
def each_square
(0..2).each do |r|
(0..2).each do |c|
yield square(r,c)
end
end
end
def each_box
(0..8).each do |r|
(0..8).each do |c|
yield(box(r,c))
NOTA
IL GIOCO
DEL SUDOKU
Il Sudoku un gioco molto
semplice: lo scopo
posizionare in una griglia
dei numeri da 1 a 9 in modo
che in ogni quadrato 3x3, in
ogni riga e in ogni colonna
ogni numero sia presente
una e una sola volta.
042-046:072-080 30-09-2008 16:05 Pagina 43
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
44
/Novembre 2008
Tecniche Ruby per la risoluzione di problemi complessi
end
end
end
In quest'ottica scriviamo anche il metodo each_container
che esegue un'operazione su ogni container, cio
sull'insieme di tutte le righe, le colonne e i quadrati.
Questo metodo si comporta come i precedenti sfrut-
tando la lista @containers che contiene, con poca
sorpresa, tutti i container (righe, colonne e quadra-
ti); poich molto simile ai metodi precedenti non
viene riportata, il codice comunque disponibile nel
CD allegato.
LA CLASSE BOX
La classe Box, cos come vedremo per le classi Row,
Column e Square non contiene dati in senso stretto
(i valori delle caselle sono conservati nella classe
Table) ma rappresenta dei legami: in particolare ricorda
i legami di appartenenza di una casella ad ogni con-
tenitore nonch le coordinate della casella. Un'i-
stanza di Boxpu essere vista come una sorta di Proxy
ad una porzione di una Table. Esaminiamo alcuni
metodi, per gli altri, come sempre, vi rimandiamo al
codice allegato:
def can_contains?(value)
(self.value==0 || self.value==value) &&
!row.contains_value?(value) &&
!column.contains_value?(value) &&
!square.contains_value?(value) &&
!@table.exclusions(ri,ci).include?(value)
end
def filled?
self.value!=0
end
# Lista dei possibili valori che pu assumere la casella
# (nel caso sia gi stata assegnata la lista comprende
# il solo valore asegnato)
def possible_values
l=[]
(1..9).each do |n|
if self.can_contains?(n)
l.push(n)
end
end
l
end
I CONTAINER
Le classi Row, Columne Square sono pensate per es-
sere istanziate all'interno della classe Table e non di-
rettamente dall'utilizzatore. Le istanze di ognuna di
queste classi sono infatti legate a doppio filo ad un'i-
stanza di una classa Table; questo perch i nostri con-
tainer sono, proprio come le istanze di Box, delle
semplici viste di una porzione di una Table e han-
no bisogno di un riferimento a questa per accedere
ai valori che in ogni momento sono presenti nello
schema, nonch alla lista dei valori gi scartati per
ogni casella.
Ognuna delle tre classi che stiamo esaminando definisce
un metodo each che permette di eseguire un ciclo
sull'insieme di casella che il nostro container defini-
sce:
class Row
...
def each
(0..8).each do |col|
yield(@table.box(@i,col))
end
end
end
class Column
...
def each
(0..8).each do |row|
yield(@table.box(row,@i))
end
end
end
class Square
...
def each
(0..2).each do |row|
r = @ri*3+row
(0..2).each do |col|
c = @ci*3+col
yield(@table.box(r,c))
end
end
end
end
Tutti i nostri container includono un modulo chia-
mato BoxCollectionche definisce due metodi che si
riveleranno decisamente utili. Il primo
where_can_placeche fornisce una lista di caselle in cui
un determinato valore pu essere inserito:
# lista di elementi dove il numero pu
# essere piazzato (ma non ancora stato messo)
def where_can_place(value)
l = []
NOTA
GENERATORI
DI SUDOKU
Esistono diversi generatori
automatici di Sudoku, ma
secondo il parere degli
appassionati, rimangono
pi interessanti quelli
sviluppati da esseri umani,
il mio consiglio quindi, per
testare i vostri algoritmi di
risoluzione, di usare
schemi che trovate su
riviste o sulla rete.
042-046:072-080 30-09-2008 16:05 Pagina 44
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
45
G
Tecniche Ruby per la risoluzione di problemi complessi
self.each do |box|
if box.can_contains?(value) and box.value==0
l.push(box)
end
end
l
end
Il secondo il metodo contains_value? che indica se
il container contiene gi in qualche sua casella il va-
lore indicato:
def contains_value?(value)
self.each do |box|
if box.value==value
return true
end
end
false
end
PRIMI METODI
DI RISOLUZIONE
Dopo aver posto le basi e aver sviluppato quel lin-
guaggio che andremo a utilizzare possiamo comin-
ciare col definire i primi algoritmi che saranno i pi
semplici, quelli utilizzati da chiunque abbia mai gio-
cato a Sudoku. Creiamo una classe che chiamiamo My-
Solver che conterr un diverso metodo per ogni diversa
tecnica di risoluzione. Le diverse tecniche saranno
poi combinate all'interno del metodo solve.
Il primo metodo che implementeremo prevede di
piazzare un certo valore in una casella se non pu
contenere che quello (ed ovviamente se non stata
gi riempita). Sfruttando il lavoro fatto in preceden-
za esprimere questo concetto diventa decisamente fa-
cile:
# Se un solo valore pu essere piazzato in quella
# casella (non ancora assegnata!) allora lo piazzo
def solve_method_1(table)
puts "{method 1}"
progresses = false
table.each_box do |b|
if !b.filled?
l = b.possible_values
if l.size==1
puts "* Inserisco il valore #{l[0]} nella casella
[#{b.ri},#{b.ci}] perch l'unico valore che pu
contenere"
progresses = (progresses or b.set_value(l[0]))
end
end
end
puts "{progresses? #{progresses}}"
progresses
end
Stampiamo dei messaggi a video per indicare quali so-
no i passi che compiamo nella risoluzione ma so-
prattutto verifichiamo (e restituiamo come risulta-
to) se abbiamo o meno fatto dei progressi. Questo
importante per determinare la condizione d'arresto,
cos come avevamo anticipato quando abbiamo in-
trodotto i metodi set_value ed exclude della classe
Table.
Il secondo metodo consiste nel piazzare un certo va-
lore in una casella se per uno o pi dei suoi container
quella casella l'unica che possa contenere quel va-
lore:
# Se un valore all'interno di un contenitore pu
# andare in una sola casella ce lo metto
def solve_method_2(table)
puts "{method 2}"
progresses = false
table.each_container do |c|
(1..9).each do |n|
l = c.where_can_place(n)
if l.size==1
puts "* Inserisco il valore #{n} nella casella
[#{l[0].ri},#{l[0].ci}] perch l'unica pu essere
contenerlo in #{c}"
progresses = (progresses or l[0].set_value(n))
end
end
end
puts "{progresses? #{progresses}}"
progresses
end
UN METODO
PI COMPLESSO
Implementiamo ora un metodo pi complesso dei
precedenti. Immaginiamo che due caselle all'inter-
no dello stesso contenitore possano assumere solo
due valori, uguali per le due caselle. Nello schema
proposto di seguito, vediamo che le caselle della pri-
ma colonna, prima e seconda riga, possono assu-
mere solo i valori 8 e 9. Quindi, i valori 8 e 9, finiran-
no in queste due caselle anche se non sappiamo an-
cora quale valore occuper una casella e quale oc-
cuper l'altra, quello che per possiamo desumere
che i valori 8 e 9 non potranno essere presenti nel-
le altre caselle del quadrato. Questa tecnica, fra l'altro,
pu essere generalizzata per un numero pi grande
di due caselle (e ovviamente valori).
Ci sono diversi modi per implementare quest'algoritmo.
Nel nostro caso siamo partiti dal considerare che
applicabile a ogni container, quindi abbiamo utiliz-
zo il metodo each_container di table per eseguire le
stesse operazioni per ognuno di questi:
042-046:072-080 30-09-2008 16:05 Pagina 45
ht t p: / / www. i opr ogr ammo. i t
table.each_container do |c|
...
end
All'interno di ogni container esaminiamo ogni ca-
sella e ci interessiamo solo a quelle che possono as-
sumere solo due possibili valori. Inseriamo queste
caselle in una lista conservata nella hash values_sets
sotto la chiave rappresentante l'insieme di possibili
valori che la casella pu assumere. Alla fine di questa
prima fase, values_sets conterr quindi per ogni insieme
di valori possibili la lista di caselle che li possono as-
sumere:
values_sets = Hash.new
c.each do |b|
possible_values = b.possible_values
if possible_values.size == 2
if !values_sets[possible_values]
values_sets[possible_values] = []
end
values_sets[possible_values].push(b)
end
end
A questo punto esaminiamo solamente le liste di due
elementi (l'algoritmo ricerca due caselle che posso-
no assumere gli stessi due valori), per ognuna di que-
ste possiamo concludere che all'interno del contai-
ner solo le due caselle nella lista potranno assumere
i valori usati come chiave, per cui eseguiamo un nuo-
vo ciclo sulle caselle del container ed escludiamo i
valori in esame per tutte le altre:
values_sets.each do |values_set, boxes|
if boxes.size == 2
c.each do |b|
if !boxes.include?(b)
values_set.each do |value|
prog = b.exclude(value)
if prog
puts "* Escludo valore #{value} nella
casella [#{b.ri},#{b.ci}] perch apparir in #{boxes}"
progresses = true
end end
end end
end end
Il metodo completo il seguente:
def solve_method_3(table)
puts "{method 3}"
progresses = false
table.each_container do |c|
values_sets = Hash.new
c.each do |b|
possible_values = b.possible_values
if possible_values.size == 2
if !values_sets[possible_values]
values_sets[possible_values] = []
end
values_sets[possible_values].push(b)
end
end
values_sets.each do |values_set, boxes|
if boxes.size == 2
c.each do |b|
if !boxes.include?(b)
values_set.each do |value|
prog = b.exclude(value)
if prog
puts "* Escludo valore #{value} nella
casella [#{b.ri},#{b.ci}] perch apparir in #{boxes}"
progresses = true
end end
end end
end
end
end
puts "{progresses? #{progresses}}"
progresses
end
ULTERIORI MODIFICHE
A questo punto il nostro risolutore in grado di ri-
solvere completamente da solo i Sudoku pi sem-
plici ma per tutti gli altri si fermer dopo alcuni pas-
saggi. A quel punto potremo compiere qualche pas-
saggio a mano e ridare il Sudoku aggiornato in pasto
al nostro programma oppure studiare nuove tecniche
che gli permettano di superare l'ostacolo.
Federico Tomassetti
SISTEMA M
G
46
/Novembre 2008
Tecniche Ruby per la risoluzione di problemi complessi
Fig. 1: All'interno del quadrato in alto a sinistra i valori 8
e 9 possono essere assegnati solo alle caselle libere
della prima colonna
042-046:072-080 30-09-2008 16:05 Pagina 46
ht t p: / / www. i opr ogr ammo. i t
JAVA
ESEGUIRE UN COMANDO
DI SHELL
String cmd = "ls -al";
Runtime run = Runtime.getRuntime();
Process pr = run.exec(cmd);
pr.waitFor();
BufferedReader buf = new BufferedReader(new
InputStreamReader(pr.getInputStream()));
String line = "";
while ((line=buf.readLine())!=null) {
System.out.println(line);
}
CONFRONTARE DUE OGGETTI
public String[] sortNodes(ArrayList<Node> nodes) {
Node[] sortedNodes = new Node[nodes.size()];
Collections.sort(nodes, new Comparator<Node>() {
public int compare(Node o1, Node o2) {
return o2.priority - o1.priority;
}
});
for (int i=0; i < nodes.size(); i++) {
sortedNodes [i] = nodes.get(i);
}
return sortedNodes ;
}
class Node{
public String name = null;
public int priority;
}
CENTRARE UN TESTO IN SWING
String s;
int width, height;
Graphics g;
FontMetrics fm = getFontMetrics(ftDefault);
Rectangle2D textsize = fm.getStringBounds(s, g);
int xPos = (width - textsize.getWidth()) / 2;
int yPos = (height - textsize.getHeight()) / 2 + fm.getAscent();
g.drawString(s, xPos, yPos);
TROVARE UNA SOTTOSTRINGA
class ContinueWithLabelDemo {
public static void main(String[] args) {
String searchMe = "Look for a substring in me";
String substring = "sub";
boolean foundIt = false;
int max = searchMe.length() - substring.length();
test:
for (int i = 0; i <= max; i++) {
int n = substring.length();
int j = i;
int k = 0;
while (n-- != 0) {
if (searchMe.charAt(j++)
!= substring.charAt(k++)) {
continue test;
}
}
foundIt = true;
break test;
}
System.out.println(foundIt ? "Found it" :
"Didn't find it");
}
}
C#
SELEZIONARE TUTTO IL TESTO
CON CTRL-A
private void anyTextBox_KeyPress(object sender,
M TIPS&TRICKS
Novembre 2008/
49
G
Una raccolta di trucchi da tenere a portata di mouse
I trucchi del mestiere
Tips & Tricks
Questa rubrica raccoglie trucchi e piccoli pezzi di codice, frutto dellesperienza di chi programma, che solitamente non
trovano posto nei manuali. Alcuni di essi sono proposti dalla redazione, altri provengono da una ricerca su Internet, altri
ancora ci giungono dai lettori. Chi volesse contribuire, potr inviare i suoi Tips&Tricks preferiti. Una volta selezionati,
saranno pubblicati nella rubrica. Tutti i Tips elencati sono anche presenti nel supporto CD-Rom che accompagna la rivista
049-051:082-083 30-09-2008 16:44 Pagina 49
ht t p: / / www. i opr ogr ammo. i t
System.Windows.Forms.KeyPressEventArgs e)
{
if (e.KeyChar == '\x1')
{
((TextBox)sender).SelectAll();
e.Handled = true;
}
}
LE API DI GOOGLE PER ACCE-
DERE AGLI ALBUM DI PICASA
Prima di utilizzare il codice che di seguito proponiamo,
necessario scaricare le API di Google per .NET e copia-
re i file nel progetto.
I namespace da usare sono:
using Google.GData.Client;
using Google.GData.Photos;
namespace GetPicasaFeed
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.getAtomFeed();
return;
}
private void getAtomFeed()
{
AlbumQuery aQuery = new
AlbumQuery(PicasaQuery.CreatePicasaUri("saschatayefeh"));
PicasaService pService = new
PicasaService("PicasaDemo");
AtomFeed kResultFeed = pService.Query(aQuery);
foreach (AtomEntry entry in kResultFeed.Entries)
{
this.textBox1.AppendText(entry.Title.Text + "\r\n");
foreach (AtomLink eLink in entry.Links)
{
this.textBox1.AppendText(" " +
eLink.HRef.ToString() + "\r\n"); ;
}
}
return;
}
}
}
RIDIMENSIONARE
UNIMMAGINE MANTENENDONE
LE PROPORZIONI
public void ResizeImage(string OriginalFile, string NewFile, int
NewWidth, int MaxHeight, bool OnlyResizeIfWider)
{
System.Drawing.Image FullsizeImage =
System.Drawing.Image.FromFile(OriginalFile);
FullsizeImage.RotateFlip(System.Drawing.
RotateFlipType.Rotate180F lipNone);
FullsizeImage.RotateFlip(System.Drawing.
RotateFlipType.Rotate180F lipNone);
if (OnlyResizeIfWider)
{
if (FullsizeImage.Width <= NewWidth)
{
NewWidth = FullsizeImage.Width;
}
}
int NewHeight = FullsizeImage.Height * NewWidth /
FullsizeImage.Width;
if (NewHeight > MaxHeight)
{
NewWidth = FullsizeImage.Width * MaxHeight /
FullsizeImage.Height;
NewHeight = MaxHeight;
}
System.Drawing.Image NewImage = FullsizeImage.Get
ThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);
FullsizeImage.Dispose();
// Salva limmagine ridimensionata
NewImage.Save(NewFile);
}
COMPRIMERE E DECOMPRIMERE
UN ARRAY DI BYTE
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.IO;
using System.Collections;
namespace Utilities
{
class Compression
{
public static byte[] Compress(byte[] data)
{
MemoryStream ms = new MemoryStream();
DeflateStream ds = new DeflateStream(ms,
CompressionMode.Compress);
ds.Write(data, 0, data.Length);
ds.Flush();
ds.Close();
TIPS&TRICKS M
G
50
/Novembre 2008
Una raccolta di trucchi da tenere a portata di mouse
049-051:082-083 30-09-2008 16:44 Pagina 50
ht t p: / / www. i opr ogr ammo. i t
return ms.ToArray();
}
public static byte[] Decompress(byte[] data)
{
const int BUFFER_SIZE = 256;
byte[] tempArray = new byte[BUFFER_SIZE];
List<byte[]> tempList = new List<byte[]>();
int count = 0, length = 0;
MemoryStream ms = new MemoryStream(data);
DeflateStream ds = new DeflateStream(ms,
CompressionMode.Decompress);
while ((count = ds.Read(tempArray, 0, BUFFER_SIZE))>0)
{
if (count == BUFFER_SIZE)
{
tempList.Add(tempArray);
tempArray = new byte[BUFFER_SIZE];
}
else
{
byte[] temp = new byte[count];
Array.Copy(tempArray, 0, temp, 0, count);
tempList.Add(temp);
}
length += count;
}
byte[] retVal = new byte[length];
count = 0;
foreach (byte[] temp in tempList)
{
Array.Copy(temp, 0, retVal, count, temp.Length);
count += temp.Length;
}
return retVal;
}
}
}
UNA CLASSE PER ORDINARE
UNA LISTA GENERICA
public class Item
{
public Item(string term, int freq)
{
_term = term;
_freq = freq;
}
private string _term;
public string Term
{
get
{ return _term; }
set
{ _term = value; }
}
private int _freq;
public int Freq
{
get
{ return _freq;
}
set { _freq = value; }
}
}
public class ItemComparer:IComparer<Item>
{
#region IComparer<Item> Members
public int Compare(Item x, Item y)
{
return y.Freq - x.Freq;
//ordine decrescente
//return x.Freq - y.Freq;
//ordine crescente
}
#endregion
}
static void Main()
{
List<Item> items = new List<Item>();
items.Sort(new ItemComparer());
}
OTTENERE LA LISTA
DEI PROCESSI ATTIVI
Console.WriteLine("ID:\tNome del processo:");
Console.WriteLine("--\t------------");
foreach (System.Diagnostics.Process process in
System.Diagnostics.Process.GetProcesses())
Console.WriteLine("{0}\t{1}", process.Id, process.ProcessName);
UNA PROCEDURA PER
RIMUOVERE NODI XML VUOTI
public static void RimuoviNodiXMLVuoti(XmlDocument doc)
{
XmlNodeList nodes = doc.SelectNodes("//node()");
foreach (XmlNode node in nodes)
if ((node.Attributes.Count == 0) && (node.ChildNodes.Count
== 0))
node.ParentNode.RemoveChild(node);
}
M TIPS&TRICKS
Novembre 2008/
51
G
Una raccolta di trucchi da tenere a portata di mouse
049-051:082-083 30-09-2008 16:44 Pagina 51
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
52
/Novembre 2008
Sviluppare un moderno editor di testi
VB.NET 2008
E I WORD PROCESSOR
IN QUESTARTICOLO VENGONO SVELATI ALCUNI SEGRETI CHE SONO ALLA BASE DEI WORD
PROCESSOR DI MICROSOFT. IN PARTICOLARE MOSTREREMO COME IMPLEMENTARE LE
CLASSICHE FUNZIONI DI UN EDITOR, SFRUTTANDO IL CORE DEL .NET FRAMEWORK
C
on Visual Basic possibile creare
qualsiasi applicazione per scopi pro-
fessionali o ludici. Le difficolt di
questo strumento di sviluppo sono piena-
mente compensate da intelligenti agevola-
zioni, quali una documentazione completa
e ricca desempi, gli innumerevoli wizard
disponibili nellambiente integrato di lavo-
ro (IDE), le particolari tecnologie che aiu-
tano a programmare in maniera pi rapida
e le nuove funzionalit e caratteristiche del
linguaggio che, di solito, evitano di accede-
re alle funzioni a basso livello del sistema
operativo (API).
Lobiettivo principale di questo articolo quel-
lo di illustrare la programmazione di un text
editor completo tramite Visual Basic.NET 2008.
Le funzionalit rese disponibili sono, per alcu-
ni versi, pi sofisticate del classico Blocco note
di Windows, ma sicuramente meno avanzate di
Microsoft Word. Gli spunti che si possono trar-
re da questo articolo consentiranno, tuttavia,
di comprendere i meccanismi di base che rego-
lano la programmazione degli elaboratori testi
ed inoltre di sviluppare ulteriori e pi comples-
se funzionalit. Quello che si vuole fare, dun-
que, creare un text editor che abbia alcune
caratteristiche grafiche tipiche dei word pro-
cessor moderni, ossia menu a tendina con
icone identificative della voce di menu e barra
degli strumenti per laccesso veloce alle varie
funzionalit.
KICK OFF
DEL PROGETTO
Una volta avviato VB.NET, dovete fare clic sul
menu File-New-Project, selezionare Windows
Application nella sezione Templates, digitare Ap-
punti in Name e, poi, fare clic sul pulsante OK.
Da questo momento in poi comincia la fase di
programmazione vera e propria. Selezionate,
dunque, il controllo MainMenu sulla toolbox e
trascinatelo sulla form per cominciare ad edita-
re i vari menu da creare. Poich il menu ha una
veste grafica scarna, si cercher subito di ren-
derlo pi accattivante inserendo alcune icone
prima delle singole voci. Si tratta di unopera-
zione non troppo complessa, ma sicuramente
non automatica. Prima di tutto bisogna espandere
la regione che contiene il codice di progettazio-
ne generato da Windows Form e inserire la pro-
priet OwnerDraw ponendola uguale a True in
ogni sezione dedicata ai vari sottomenu (cio i
menu di secondo livello) tra lindice del menu e
il suo nome. Per la prima voce Nuovo le impo-
stazioni saranno, ad esempio, le seguenti:
Me.mnuNuovo.Index = 0
Me.mnuNuovo.OwnerDraw = True
Me.mnuNuovo.Text = "Nuovo"
Infatti, solo se la propriet OwnerDraw impo-
stata su True, potete personalizzare la grafica del
menu per creare visualizzazioni particolari via
codice.
Il secondo controllo da utilizzare e da trascina-
re sulla form ImageList per raggruppare nel-
linsieme Images tutte le icone che verranno uti-
lizzate sia nel menu personalizzato che nella bar-
ra degli strumenti.
Una volta ultimata questa operazione un po te-
diosa possiamo finalmente mettere mani al co-
dice in maniera massiccia inserendolo negli even-
ti MeasureIteme DrawItem. Di seguito viene ri-
portata parte della procedura disegnaMenu() che
consente la gestione unificata di tutti gli eventi
DrawItemdei menu.
Public Sub disegnaMenu(...) _
Handles mnuNuovo.DrawItem, ...,
mnuSuccessivo.DrawItem
Dim SottoMenu As MenuItem =
CType(sender, MenuItem)
Dim Immagine As Integer
Dim coloreTesto As Color =
SystemColors.MenuText
J CD J WEB
VBNET editor.rar
cdrom.ioprogrammo.it
Conoscenze richieste
Visual Studio e VB.NET
Software
Windows XP Service
Pack 2, Windows
Server 2003 o
Windows Vista e
Visual Studio 2008
versione Standard o
Professional
Impegno
Tempo di realizzazione
REQUISITI
053-058:072-080 30-09-2008 16:15 Pagina 52
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
53
G
Sviluppare un moderno editor di testi
Dim posizioneTesto As New
RectangleF(e.Bounds.Left + 20, _
e.Bounds.Top + 2, e.Bounds.Right,
e.Bounds.Bottom - 2)
Dim formattaTesto As New StringFormat()
Dim tabStops() As Single = {0}
formattaTesto.SetTabStops
(larghezzaMassimaMenu, tabStops)
If (ShowKeyboardCues) Then
formattaTesto.HotkeyPrefix =
Drawing.Text.HotkeyPrefix.Show
Else
formattaTesto.HotkeyPrefix =
Drawing.Text.HotkeyPrefix.Hide
End If
Dim selected As Boolean = False
selected = ((e.State And
DrawItemState.Selected) = _
DrawItemState.Selected)
If selected Then
e.DrawBackground()
coloreTesto = SystemColors.HighlightText
Else
e.Graphics.FillRectangle
(SystemBrushes.Menu, e.Bounds)
End If
...
End Sub
Sia nel caso di MeasureItemche di DrawItem
stata, dunque, definita una procedura per abbi-
nare, con la parola chiave Handle, levento cor-
rente a tutte le voci di menu, consentendo di ri-
sparmiare molto tempo in fase di scrittura del
programma.
Essendo stata messa a fattor comune la stessa
procedura, non dovrete ripeterla nei vari eventi
dei menu.
In effetti lalternativa agli array di controlli di
Visual Basic 6.0, che in Visual Basic.NET non esi-
stono pi.
Se osservate la procedura ToolBar1_ButtonClick,
che gestisce appunto la barra degli strumenti,
interessante notare il metodo PerfomClick che
consente di simulare un clic utente attivando la
voce di menu relativa. Ad esempio mnuNuo-
vo.PerformClick() lancia il menu Nuovo, come
se si facesse direttamente clic su di esso.
Private Sub ToolBar1_ButtonClick(...) Handles
ToolBar1.ButtonClick
If e.Button Is Nuovo Then
mnuNuovo.PerformClick()
ElseIf e.Button Is Apri Then
mnuApri.PerformClick()
...
mnuSuccessivo.PerformClick()
ElseIf e.Button Is PrimoPiano Then
mnuPrimoPiano.PerformClick()
End If
End Sub
LE DIMENSIONI
DEL MENU
Levento MeasureItemviene attivato tutte le vol-
te che un menu sta per essere disegnato. Il suo
obiettivo quello di accertare la dimensione del
menu stesso ed qui, perci, che dovete defi-
nirne altezza e larghezza per poi passarle a Win-
dows.
Private Sub dimensionaMenu(ByVal sender As
System.Object, ByVal e As
System.Windows.Forms.MeasureItemEventArgs) _
Handles mnuNuovo.MeasureItem, ...,
mnuSuccessivo.MeasureItem
Dim SottoMenu As MenuItem =
CType(sender, MenuItem)
Dim dimTesto As Size
Dim formattaTesto As New StringFormat()
Dim altezzaMassimaMenu As Integer
If (ShowKeyboardCues) Then
formattaTesto.HotkeyPrefix =
Drawing.Text.HotkeyPrefix.Show
Else
formattaTesto.HotkeyPrefix =
Drawing.Text.HotkeyPrefix.Hide
End If
dimTesto = e.Graphics.MeasureString
(SottoMenu.Text, aFont).ToSize()
larghezzaMassimaMenu = Math.Max
(larghezzaMassimaMenu, dimTesto.Width + _
SystemInformation.SmallIconSize.Width + 4)
e.ItemWidth = larghezzaMassimaMenu
altezzaMassimaMenu =
SystemInformation.SmallIconSize.Height + 2
Fig. 1: Le icone inserite nel controllo ImageList
NOTA
UTILIZZARE
MATH.MAX()
Il metodo Max() di Visual
Basic.NET serve a
determinare qual il valore
pi elevato tra due numeri e
a restituire il risultato alla
variabile a cui uguagliato.
La libreria di riferimento a
cui appartiene Max()
quella matematica che
appartiene alla classe
Math().
053-058:072-080 30-09-2008 16:15 Pagina 53
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
54
/Novembre 2008
Sviluppare un moderno editor di testi
e.ItemHeight = altezzaMassimaMenu
End Sub
La propriet SmallIconSize della classe System-
Informationrestituisce in Height laltezza delle ico-
ne piccole del sistema operativo. Tenendo con-
to di due pixel di spaziatura possibile definire
laltezza di ogni singolo elemento del menu.
Per la larghezza il procedimento adottato pi com-
plicato, infatti simmagazzina in una variabile
globale la larghezza del menu ottenuta misu-
rando tutte le voci dello stesso gruppo, ad esem-
pio del menu File, e tutte le volte si valuta se il
valore pi grande quello corrente o quello sal-
vato. In questo caso si utilizzata la funzione
Max() e inizialmente la classe Graphics per de-
terminare la dimensione della voce corrente. La
variabile larghezzaMassimaMenu viene reim-
postata a 0 tutte le volte che viene attivato levento
Select sui menu di primo livello. La procedura
la azzeraLarghezzaMenu().
Private Sub azzeraLarghezzaMenu(...) _
Handles mnuFile.Select, mnuModifica.Select,
mnuHelp.Select
larghezzaMassimaMenu = 0
End Sub
DISEGNARE IL MENU
Per disegnare il menu dellapplicazione dovete scri-
vere la procedura disegna Menu() che costruisce
le componenti grafiche dopo che ne sono gi sta-
te individuate le dimensioni. In effetti, se com-
mentate lintera procedura ed eseguite
lapplicazione si pu notare che il menu viene
creato ugualmente, ma mancano totalmente ico-
ne e descrizioni essendo visibili solo dei riquadri
bianchi. Il fulcro della procedura la Select Case
finale, i metodi DrawImageUnscaled e Draw-
String necessari rispettivamente per individua-
re limmagine giusta nel controllo ImageList, per
disegnare limmagine stessa e per disegnare la
corrispondente scritta di menu.
Select Case SottoMenu.Text
Case "Nuovo"
Immagine = 0
Case "Apri..."
Immagine = 1
...
...
Case "Salva con nome..."
Immagine = 11
Case Else
Immagine = -1
End Select
If Immagine > -1 Then
e.Graphics.DrawImageUnscaled(imgList.Images
(Immagine), e.Bounds.Left + 1, e.Bounds.Top + 1)
End If
e.Graphics.DrawString(SottoMenu.Text, aFont, _
New SolidBrush(coloreTesto), posizioneTesto,
formattaTesto)
Se un menu non ha alcuna icona abbinata, come
Informazioni su, viene impostato il valore di
default 1 e, quindi, non viene richiamato il co-
struttore dellimmagine. Fate attenzione che ogni
voce tra virgolette dopo i vari Case deve corri-
spondere esattamente alla propriet text delli-
tem di menu corrispondente. Ad esempio Case
Nuovo corrisponde al contenuto della pro-
priet mnuNuovo.text.
IL TEXT EDITOR
VERO E PROPRIO
Una volta definita la parte grafica, impostando an-
che una TextBox multiline per consentire la scrit-
tura del testo, necessario che scriviate il codi-
ce dedicato alla gestione dei documenti che ven-
gono creati.
In questapplicazione si fa largo uso degli oggetti
di tipo Common Dialog, che forniscono una se-
rie di finestre di dialogo standard nelle quali
possibile aprire, chiudere, salvare e stampare fi-
le o anche selezionare colori e tipi di caratteri.
In particolare lapertura di un documento esi-
stente definita nellevento mnuApri_Click() do-
ve viene attivato il metodo ApreDialo-
go.ShowDialog() per visualizzare la schermata
standard Apri di Windows.
If Risposta <> vbCancel Then
If ApreDialogo.ShowDialog() = vbOK Then
Call ApreilFile(ApreDialogo.FileName)
Me.Text = ApreDialogo.FileName
Salvato = True
End If
End If
Nel caso in cui lutente scelga il pulsante Apri il
risultato uguale alla costante vbOK e, quindi,
si prosegue richiamando la procedura ApreilFile
alla quale viene passato il parametro ApreDial-
ogo.FileName, che corrisponde al nome del file scel-
to.
Protected Sub ApreilFile(ByVal NomeDelFile As
String)
Dim legge As StreamReader
Dim riga As String
Dim finito As Boolean = False
NOTA
TEXTBOX
MULTILINE
Il controllo TextBox in
modalit multiline in
grado di gestire la scrittura
di testo su pi righe. Inoltre,
se viene impostata la
propriet ScrollBars
possibile aggiungere barre
di scorrimento sia
orizzontali che verticali a
controlli TextBox di grandi
dimensioni.
053-058:072-080 30-09-2008 16:15 Pagina 54
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
55
G
Sviluppare un moderno editor di testi
txtAppunti.Text = ""
legge = New StreamReader(NomeDelFile)
Try
While Not finito
riga = legge.ReadLine()
If riga Is Nothing Then
finito = True
Else
txtAppunti.Text = txtAppunti.Text &
riga & vbCrLf
End If
End While
Finally
legge.Close()
End Try
End Sub
Come si vede dal listato dovete utilizzare la clas-
se StreamReader() del Framework .NET per leg-
gere i caratteri dal file selezionato e, via via tra-
mite il ciclo While Not finito, riportati nella textbox
txtAppunti.Text. Si ricorda che StreamReader
presente nello spazio dei nomi System.IO. In al-
ternativa potreste ricorrere alla funzione File
Open() che consente di aprire un file per linput
o loutput.
La scrittura del file avviene tramite la procedura
SalvailFile() che pu essere richiamata in pi
punti, ossia premendo il pulsante Salva oppure
ogniqualvolta lutente decida di uscire dal file
corrente, anche cercando di chiudere lintera ap-
plicazione. Questultima possibilit si verifica
nellevento Form1_Closing() che interviene in
tutti i casi in cui si cerca di chiudere lapplicazione:
con Alt+F4, cliccando sul pulsante di default di Win-
dows Chiudi da menu o sulla X in alto a destra.
Private Sub Form1_Closing(...) Handles
MyBase.Closing
Dim Cancel As Short = eventArgs.Cancel
Dim Risposta As Byte = vbYes
If Len(txtAppunti.Text) > 0 And Not Salvato
Then
Risposta = MsgBox("Il testo stato
cambiato?" & _
vbCrLf & "Salvare i cambiamenti?", _
MsgBoxStyle.YesNoCancel +
MsgBoxStyle.Question)
If Risposta = vbYes Then
If Me.Text = "Appunti" Then
If ChiudeDialogo.ShowDialog() =
vbOK Then
Call
SalvailFile(ChiudeDialogo.FileName)
End If
Else
Call SalvailFile(Me.Text)
End If
End If
End If
If Risposta = vbCancel Then
Cancel = True
End If
eventArgs.Cancel = Cancel
End Sub
La procedura SalvailFile() interviene se il meto-
do ChiudeDialogo.ShowDialog(), destinato a vi-
sualizzare la schermata standard Salva con
nome di Windows, uguale alla costante vbOK,
e cio se lutente ha premuto il pulsante Salva.
Protected Sub SalvailFile(ByVal NomeDelFile As
String)
Dim scrive As StreamWriter
Dim i As Integer
scrive = New StreamWriter(NomeDelFile)
Try
For i = 0 To txtAppunti.Lines.Length - 1
Call scrive.WriteLine(txtAppunti.Lines(i))
Next
Finally
scrive.Close()
End Try
End Sub
In SalvailFile ancora una volta viene utilizzata
una potente classe del Framework .NET, la
StreamWriter, che pemette di scrivere tutto ci
che presente nella textbox in un file chiamato
NomeDelFile. poi il ciclo For...Next, basato sul
metodo WriteLine, che si prende cura di fare ci.
LA STAMPA DEL TESTO
Ogni text editor che si rispetti non pu proprio fa-
re a meno delle funzioni di stampa. Una possibilit
per implementarle quella di utilizzare il con-
trollo CrystalReportViewer, anche se per questo
programma stata scelta una strada alternati-
va. Innanzitutto si consideri il codice che per-
mette dimpostare le caratteristiche della pagina
da inviare alla stampante. Nella procedura im-
postaPagina() viene definita una nuova istanza
della classe PageSetupDialog() che fornisce tut-
te le opzioni standard per gestire margini, orien-
tamento, alimentazione e formato della pagina.
Private Sub impostaPagina()
Dim miaPagina As New PageSetupDialog()
With miaPagina
.Document = stampaDoc
.PageSettings =
stampaDoc.DefaultPageSettings
NOTA
IL CONTROLLO
RICHTEXTBOX
Il controllo RichTextBox pu
essere utilizzato in
alternativa alla TextBox per
visualizzare, immettere e
modificare testo formattato.
Oltre alle funzionalit offerte
dal controllo TextBox,
RichTextBox consente di
visualizzare tipi di carattere,
colori e collegamenti, di
caricare da un file un testo
e immagini incorporate e di
trovare caratteri specificati.
Viene generalmente
utilizzato per fornire
funzionalit di
visualizzazione e modifica
del testo simili a quelle
offerte da programmi di
elaborazione testi quale
Microsoft Word.
053-058:072-080 30-09-2008 16:15 Pagina 55
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
56
/Novembre 2008
Sviluppare un moderno editor di testi
End With
If miaPagina.ShowDialog = vbOK Then
stampaDoc.DefaultPageSettings =
miaPagina.PageSettings
End If
End Sub
Il documento rappresentato dalla variabile og-
getto stampaDoc che fa riferimento a unistan-
za di una classe che pu generare eventi. Ecco
perch viene dichiarata come segue: Private With-
Events stampaDoc As New PrintDocument()
Ricordate anche che la propriet DefaultPage-
Settings serve ad attribuire le impostazioni pre-
definite a tutte le pagine da stampare. Successi-
vamente, quando vengono accettate le perso-
nalizzazioni dellutente, viene modificato il de-
fault uguagliandolo ai nuovi settings: stam-
paDoc.DefaultPageSettings = impostaPagi-
na.PageSettings
Unaltra importante procedura quella che ge-
stisce limpostazione delle stampanti.
Private Sub mostraStampanti()
Dim mieStampanti As New PrintDialog()
mieStampanti.Document = stampaDoc
If mieStampanti.ShowDialog = vbOK Then
stampaDoc.Print()
End If
End Sub
In questo caso mostraStampanti() attiva una
nuova istanza della classe PrintDialog che permette
di visualizzare la finestra di dialogo delle stam-
panti, dove sono presenti le opzioni per indiriz-
zare la stampa su file, per fascicolare le copie e per
definire quante copie inviare in stampa. Se si fa
clic su OK viene inviata la pagina in stampa con
stampaDoc.Print()
ANTEPRIMA DI
STAMPA
Non sempre i text editor hanno a disposizione
la funzionalit di anteprima di stampa, ad esem-
pio Microsoft Notepad non ce lha, ma in questo
caso si pensato di contemplarla. La classe chia-
ve questa volta PrintPreviewDialog(). Anche in
questo caso possibile inviare direttamente la
pagina alla stampante.
Private Sub anteprimaStampa()
Dim anteprima As New PrintPreviewDialog()
Try
anteprima.Document = stampaDoc
anteprima.ShowDialog()
Catch exp As Exception
MsgBox("Si verificato un errore durante " & _
"lanteprima di stampa. Probabilmente " & _
"la stampante non connessa o non
accessibile.", MsgBoxStyle.OKOnly +
MsgBoxStyle.Critical)
End Try
End Sub
Infine potete creare la procedura per la stampa
diretta della pagina, che simile alla mostra-
Stampanti() anche se procede subito allinvio
della stampa.
Private Sub Stampa()
Dim stampa As New PrintDialog()
Try
stampa.Document = stampaDoc
stampaDoc.Print()
Catch exp As Exception
MsgBox("Si verificato un errore mentre si
tentava " & "di stampare il documento.
Verificare" & _
"che una stampante sia disponibile " & _
"ed accessibile.", MsgBoxStyle.OkOnly +
MsgBoxStyle.Critical)
End Try
End Sub
IL CUORE DELLE
FUNZIONALIT
DI STAMPA
Per poter stampare non sufficiente il codice
mostrato fino ad ora, ma anche necessario pro-
grammare levento stampaDoc_PrintPage() in
maniera dettagliata. Vediamone alcuni elemen-
ti chiave. La variabile statica ultimoCarattere-
Stampato tiene traccia dellultimo carattere stam-
pato, ecco perch dovete dichiararla Static; so-
lo in questo modo il successivo evento PrintPage
pu fare riferimento ad essa. In seguito viene di-
chiarato il font che si vuole visualizzare in stam-
pa con Dim font As New Font("Microsoft Sans
Serif", 10). Vengono inoltre inizializzate le varia-
bili che tengono traccia dei confini dellarea ret-
tangolare di stampa:
Dim altezzaArea, larghezzaArea, margineSX,
margineAlto As Int32
With stampaDoc.DefaultPageSettings
altezzaArea = .PaperSize.Height -
.Margins.Top - .Margins.Bottom
larghezzaArea = .PaperSize.Width -
.Margins.Left - .Margins.Right
margineSX = .Margins.Left X coordinate
NOTA
CHIUSURA
DELLAPPLICAZIONE
In Visual Basic.NET le
applicazioni possono essere
chiuse utilizzando il metodo
Exit() applicato alla classe
Application oppure tramite il
metodo Me.Close().
Questultima soluzione si fa
preferire se si vuole
scatenare levento Closing
della form, nel quale
possibile inserire gli ultimi
controlli di convalida prima
di fare il kill di tutte le
risorse. Qualora questi
controlli imponessero di
bloccare luscita
dallapplicazione
necessario impostare la
propriet Cancel delloggetto
FormClosingEventArgs a
True.
053-058:072-080 30-09-2008 16:15 Pagina 56
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
57
G
Sviluppare un moderno editor di testi
margineAlto = .Margins.Top Y coordinate
End With
Poich lorientamento predefinito della pagina
verticale, necessaria una procedura per ruo-
tarla quando viene scelta limpostazione oriz-
zontale. Questo viene fatto tramite la variabile
di appoggio varTemp.
If stampaDoc.DefaultPageSettings.Landscape
Then
Dim varTemp As Int32
varTemp = altezzaArea
altezzaArea = larghezzaArea
larghezzaArea = varTemp
End If
Dovete, poi, dichiarare ed inizializzare la varia-
bile areaStampaRet che definisce larea rettan-
golare da mandare in stampa: Dim areaStam-
paRet As New RectangleF(margineSX, margin-
eAlto, larghezzaArea, altezzaArea)
Successivamente dovete istanziare la classe String-
Format che incapsula le informazioni per il layout
del testo, come lallineamento e la spaziatura tra
le linee. Inoltre il suo utilizzo impone a Mea-
sureString e a DrawString di utilizzare solo un
numero intero di righe quando viene stampata ogni
pagina, non considerando eventuali valori fra-
zionari: Dim formato As New StringFor-
mat(StringFormatFlags.LineLimit)
Il metodo Graphics.MeasureString deve essere
richiamato per determinare il numero dei ca-
ratteri che devono entrare nellarea rettangola-
re di stampa, dove la variabile caratteriPerPagi-
na indica il numero di caratteri che viene inserito
nella pagina corrente, mentre righePerPagina
indica il numero di righe che viene inserito nel-
la pagina corrente. Questultima variabile, in ef-
fetti, non qui utilizzata, ma comunque ri-
chiesta dal metodo Graphics.MeasureString().
Infine il primo parametro che contiene la fun-
zione Mid utilizzato per mandare in stampa la
parte del documento che non era stata stampa-
ta nella pagina precedente grazie alla variabile
ultimoCarattereStampato che mantiene i valo-
ri, essendo di tipo Static.
Dim righePerPagina, caratteriPerPagina As Int32
e.Graphics.MeasureString(Mid(txtAppunti.Text, _
ultimoCarattereStampato + 1), font, _
New SizeF(larghezzaArea, altezzaArea),
formato, caratteriPerPagina, righePerPagina)
Il metodo Graphics.DrawString, invece, vi per-
metter di scrivere il testo nellarea di stampa
specificata, essendo anche qui necessario indi-
care la funzione Mid, il font, il colore del testo,
appunto larea di stampa areaStampaRet e infi-
ne il formato definito in precedenza con la fun-
zione StringFormat().
e.Graphics.DrawString(Mid(txtAppunti.Text, _
ultimoCarattereStampato + 1), font, _
Brushes.Black, areaStampaRet, formato)
Lultimo elemento importante da segnalare la
propriet HasMorePages che indica alla proce-
dura di stampa se deve essere stampata una pa-
gina aggiuntiva oppure no.
If ultimoCarattereStampato <
txtAppunti.Text.Length Then
e.HasMorePages = True
Else
e.HasMorePages = False
ultimoCarattereStampato = 0
End If
Per verificare la lunghezza della textbox txtAp-
punti stata utilizzata la propriet Length ap-
plicata a txtAppunti.Text. Si tratta di una carat-
teristica del Framework .NET e che non era sup-
portata con le versioni precedenti di Visual Basic
dove sarebbe stato necessario utilizzare la funzione
Len per ottenere lo stesso risultato:
Len(txtAppunti.Text).
TAGLIA,
COPIA E INCOLLA
Le operazioni di copia, taglia e incolla sono uno
standard di Windows fin dalle prime versioni del
sistema operativo di Microsoft ed , opportuno
che vengano abilitate anche nel text editor. Le
possibilit disponibili sono almeno due e ver-
ranno esaminate entrambe.
Per tagliare il testo possibile utilizzare il meto-
do Clipboard.SetDataObject() al quale devono
essere passati due parametri: il testo seleziona-
to da mettere negli appunti; un flag booleano,
che se impostato a True permette di mantene-
re negli appunti di sistema il testo salvato anche
dopo la chiusura dellapplicazione. Se imposta-
to a False i dati verranno eliminati dalla clipboard
alla chiusura dellapplicazione.
Private Sub mnuTaglia_Click(...) Handles
mnuTaglia.Click
Clipboard.SetDataObject(txtAppunti.SelectedText,
True)
txtAppunti.SelectedText = ""
End Sub
053-058:072-080 30-09-2008 16:15 Pagina 57
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
58
/Novembre 2008
Sviluppare un moderno editor di testi
Per impostare la funzione di copia il codice lo
stesso di taglia, escludendo per la cancellazio-
ne del testo selezionato.
Private Sub mnuCopia_Click(...) Handles
mnuCopia.Click
Clipboard.SetDataObject(txtAppunti.SelectedText,
True)
End Sub
Infine, per incollare il testo si deve utilizzare il
metodo Clipboard.GetDataObject() che restitui-
sce il valore in un oggetto IdataObject che pote-
te testare per capire se ci che si trova negli ap-
punti del testo o unimmagine.
Private Sub mnuIncolla_Click(...) Handles
mnuIncolla.Click
Dim Appunti As IDataObject =
Clipboard.GetDataObject
Dim oggettoAppunti As Object
oggettoAppunti =
Appunti.GetData(DataFormats.Text, False)
If Not (oggettoAppunti Is Nothing) Then
txtAppunti.SelectedText =
CStr(oggettoAppunti)
End If
End Sub
Nelloggetto oggettoAppunti viene immagazzi-
nato il valore presente in quel momento negli
appunti tramite Appunti.GetData(DataFormats.
Text, False), lasciando il secondo parametro a
False per impedire una conversione automatica
del formato dei dati presenti nella clipboard di
Windows. Dopo il test della presenza di dati av-
viene lincollaggio vero e proprio del testo con
txtAppunti.SelectedText = CStr(oggetto Appunti)
Esistono anche altri metodi che possono essere
utilizzati per le operazioni di copia, taglia e in-
colla. Questa seconda modalit quella che de-
ve essere utilizzata se si utilizza una funzione di
annulla.
Nel caso di Taglia e Copia basta ricorrere rispet-
tivamente ai metodi Cut() e Copy() che interagi-
scono direttamente con la clipboard.
Private Sub mnuTaglia_Click(...) Handles
mnuTaglia.Click
txtAppunti.Cut()
End Sub
Private Sub mnuCopia_Click(...) Handles
mnuCopia.Click
txtAppunti.Copy()
End Sub
Per incollare i dati, invece, dovete utilizzare un al-
goritmo differente che prima verifica se c qual-
cosa nella clipboard da incollare esaminando se
Clipboard.Get-DataObject().GetDataPresent
(DataFormats.Text) uguale a True. Successiva-
mente viene anche verificato se lutente ha se-
lezionato del testo e si richiede conferma per
laggiornamento. Se okPaste False significa che
non si vuole procedere alloverwrite e, quindi, i
dati non vengono incollati con il metodo Paste().
Private Sub mnuIncolla_Click(...) Handles
mnuIncolla.Click
Dim okPaste As Boolean = True
If Clipboard.GetDataObject().
GetDataPresent(DataFormats.Text) Then
If txtAppunti.SelectionLength > 0 Then
If MsgBox("Vuoi incollare sopra la
selezione corrente?",
MsgBoxStyle.YesNo +
MsgBoxStyle.Question, "Appunti") = vbNo Then
okPaste = False
End If
End If
If okPaste Then txtAppunti.Paste()
End If
End Sub
RICERCHE ALLINTERNO
DEL TESTO
Lonnipresente Find ha unindubbia utilit e,
se possibile, deve sempre essere fornita quando
si progetta un text editor. Lalgoritmo proposto
piuttosto semplice e ha due punti importanti da
evidenziare. Il primo quello dove viene fatta la
ricerca vera e propria del testo tramite la fun-
zione InStr() che riceve tre parametri: la posi-
zione di partenza del cursore, il testo su cui effettare
la ricerca e il testo da ricercare.
Il testo ricercato viene evidenziato a partire dal
carattere indicato dalla propriet SelectionStart
e per una lunghezza pari alla SelectionLength. Il
metodo ScrollToCaret consente, poi, di scrollare
il contenuto della textbox fino al punto in cui il
cursore ha trovato la stringa ricercata: fonda-
mentale per rendere immediatamente visibile
allutente il risultato della Find.
posizioneParola = InStr(Partenza,
txtAppunti.Text, parolaCercata)
If parolaCercata.Length <> 0 Then
If posizioneParola Then
txtAppunti.SelectionStart =
posizioneParola - 1
txtAppunti.SelectionLength =
parolaCercata.Length
txtAppunti.ScrollToCaret()
053-058:072-080 30-09-2008 16:15 Pagina 58
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
59
G
Sviluppare un moderno editor di testi
Else
posizioneParola = 0
Partenza = 1 : parolaPrecedente = ""
MsgBox("Stringa non trovata",
MsgBoxStyle.Information)
End If
End If
Il secondo punto quello che attribuisce il giu-
sto valore ai parametri eventualmente per pro-
seguire nella ricerca. , infatti, la variabile Parten-
za che viene inzializzata diversamente a secon-
da che si tratti di una ricerca inziale o successi-
va.
parolaPrecedente = parolaCercata
If Not TrovaSuccessivo Then
parolaCercata = InputBox("Digitare la
parola da ricercare", _ "Trova", parolaCercata)
End If
If Not (parolaPrecedente Is Nothing) Then
If parolaPrecedente = parolaCercata Then
per ricerca successiva
Partenza = IIf(posizioneParola +
parolaCercata.Length = 0, Partenza, _
posizioneParola +
parolaCercata.Length)
Else
posizioneParola = 0
Partenza = 1
End If
End If
Viene, infine, fatto largo utilizzo delle variabili
statiche per mantenere i valori che altrimenti
verrebbero persi.
Si ricorda che una variabile Static ha visibilit
solo allinterno della routine che la creata, ma la
persistenza del suo valore in memoria pari al-
la durata dellapplicazione. Fintanto che il pro-
gramma non viene chiuso queste variabili sono
utilizzabili e mantengono il loro valore.
Static posizioneParola As Integer,
parolaCercata As String = ""
Static Partenza As Integer = 1,
parolaPrecedente As String
Infine anche possibile cercare con il tasto F3, con
licona sulla barra degli strumenti e con Trova
Successivo da menu loccorrenza successiva del
testo ricercato. Per fare questo stato sufficien-
te gestire il flag globale TrovaSuccessivo nelle pro-
cedure mnuSuccessivo_Click e mnuTrova_Click.
If txtAppunti.Text.Length > 0 Then
TrovaSuccessivo = True
mnuTrova.PerformClick()
TrovaSuccessivo = False
End If
ANNULLARE
UNOPERAZIONE
Anche in questo caso ci si trova di fronte ad una
funzionalit importante per lutente finale.
Per fortuna Visual Basic.NET aiuta notevolmen-
te i programmatori e impone la scrittura di poche
righe di codice per implementarla, cos come di
seguito riportato:
If txtAppunti.CanUndo Then
txtAppunti.Undo()
End If
Si tratta, infatti, di testare prima con la propriet
CanUndo se possibile annullare qualcosa ed,
in seguito, grazie al metodo Undo(), si procede
allannullamento dellultima operazione effet-
tuata. Se si volesse ripulire il buffer per non con-
sentire di ripristinare quanto appena annullato,
bisogna utilizzare il metodo txtAppun-
ti.ClearUndo().
Enrico Bottari
LAUTORE
Enrico Bottari lavora da 18
anni nel mondo ICT. Ha
avuto esperienze
significative nel campo
dello sviluppo software su
piattaforme Microsoft,
dellamministrazione di
database Oracle e della
gestione di sistemi UNIX
Solaris System V.
Attualmente responsabile
delle attivit operazionali
(LAN, Office Automation e
servizi Desktop) presso la
sede italiana di unimpor -
tante organizzazione
internazionale.
Fig. 2: La gestione dei cambiamenti avviene tramite
unesplicita richiesta di conferma
Fig. 3: La finestra di dialogo per ricercare il testo realiz-
zata tramite la funzione di Visual Basic.NET InputBox()
053-058:072-080 30-09-2008 16:15 Pagina 59
ht t p: / / www. i opr ogr ammo. i t
MOBILE M
G
60
/Novembre 2008
Programmare le interfacce grafiche di basso livello
I
componenti di interfaccia esaminati nel
corso delle precedenti puntate ci torneranno
sicuramente utili. Immaginiamo che, al ter-
mine di una partita, lutente abbia realizzato un
punteggio da record. Il nostro gioco offre la pos-
sibilit di inviare questo punteggio attraverso
Internet, per memorizzarlo in una top ten globa-
le. Lutente dovr necessariamente inserire il
proprio nome. Potremo raccogliere linforma -
zione servendoci di un TextField in un Form, o di
un TextBox direttamente sul display. Non dovre-
mo programmare da zero un meccanismo per
raccogliere linput testuale. I componenti grafici
di alto livello ci torneranno utili proprio per com-
piti di questo tipo. Il gioco vero e proprio, per,
pu servirsi ben poco dei componenti di alto
livello. Per realizzare un gioco necessario con-
trollare pixel per pixel quel che appare sul
display del dispositivo. I componenti di alto livel-
lo non offrono alcuna funzionalit per inserire
nel display astronavi, paesaggi e personaggi da
animare. Per far questo necessario fare grafica
in libert, andando a controllare a un pi basso
livello quel che compare sullo schermo. Questa
lezione, con quelle immediatamente a seguire, vi
introdurranno alle interfacce grafiche di basso
livello, spiegando come fare per ottenere il con-
trollo totale di quello che mostrato sul display.
UTILIZZO
DELLA CLASSE CANVAS
Nel corso della terza parte di questa serie
(ioProgrammo 129) abbiamo spiegato come
linterfaccia Displayable venga implementata, in
primo luogo, dalle due classi Screen e Canvas.
Da Screen derivano tutti i componenti utili per
assemblare interfacce grafiche di alto livello,
come Alert e Form, gi esaminati nel corso degli
appuntamenti pi recenti. La classe Canvas
(javax.microedition.lcdui.Canvas), che ora diviene
oggetto del nostro studio, deve invece essere uti-
lizzata ogni volta che si desidera accedere al con-
trollo delle funzionalit grafiche di basso livello.
Canvas una classe astratta. Le classi astratte
non possono essere istanziate, in genere perch
contengono uno o pi metodi di cui stata forni-
ta la firma ma non limplementazione (un po
come succede con le interfacce). Una classe
astratta pu soltanto essere estesa, e chi la esten-
de deve implementare tutti i metodi che nella
genitrice sono privi di corpo (tranne nel caso in
cui sia astratta anche la classe derivata). Per que-
sto motivo luso che dovremo fare di Canvas
particolare rispetto ai casi precedentemente
analizzati. Da nessuna parte potremo scrivere:
Canvas canvas = new Canvas();
// Errore di compilazione! Canvas astratta!
display.setCurrent(canvas);
Dovremo piuttosto fare:
public class MiaCanvas extends Canvas
{
// Implementazione dei metodi necessari
}
Poi, nella MIDlet, saremo finalmente liberi di
richiamare:
MiaCanvas canvas = new MiaCanvas();
display.setCurrent(canvas);
Scendiamo un po pi nel dettaglio. Canvas ha
un solo metodo astratto, cio privo di implemen-
tazione:
public void paint(Graphics g)
A paint(), di fatto, tocca il lavoro duro: proprio
al suo interno che deve essere stabilito, pixel per
pixel, quel che dovr apparire sul display.
Largomento passato al metodo viene detto con-
testo grafico, e fornisce tutte le funzionalit di cui
abbiamo bisogno per il disegno. Il contesto grafi-
Conoscenze richieste
Rudimenti di Java
Software
JDK, WTK
Impegno
Tempo di realizzazione
REQUISITI
VIDEOGIOCHI IN JAVA
PER CELLULARI
SESTO APPUNTAMENTO. LO SVILUPPO DI UN GIOCO, OLTRE A PREVEDERE LUTILIZZO DI COMPO-
NENTI GRAFICI AD ALTO LIVELLO, SPESSO E VOLENTIERI NECESSITA DI STRUTTURE GRAFICHE
DI BASSO LIVELLO. IN QUESTO APPUNTAMENTO CI OCCUPEREMO PROPRIO DI QUESTE ULTIME
J CD J WEB
videogiochi_per_cellulari_06.rar
cdrom.ioprogrammo.it
060-065:032-035 2-10-2008 10:39 Pagina 60
co fornito al metodo paint() in diretto collega-
mento con quanto mostrato sul display: quel che
disegniamo nel contesto grafico viene trasferito
direttamente nello schermo del dispositivo.
LA CLASSE GRAPHICS
La classe Graphics (javax.microedition.lcdui.Gra -
phics) realizza il contesto grafico necessario per
eseguire le operazioni di disegno. Un contesto gra-
fico indissolubilmente legato ad una matrice di
pixel, cio ad unimmagine. Quando si manipola il
contesto grafico si modifica il contenuto dellim-
magine cui collegato. Graphics offre una serie di
metodi utili per il disegno: linee, rettangoli, curve e
cos via. Inoltre, sempre attraverso il contesto grafi-
co, possibile introdurre nella matrice collegata
delle scritte e delle immagini, sia caricate dal file
system locale sia prelevate attraverso una rete. Non
ci troveremo mai ad istanziare Graphics in maniera
diretta: otterremo sempre oggetti di questo tipo gi
pronti alluso. Due sono i casi in cui li useremo:
quando un contesto grafico ci viene passato auto-
maticamente in un metodo paint(), oppure quan-
do chiederemo esplicitamente di avere il contesto
grafico associato ad unimmagine modificabile.
GESTIONE
DELLE COORDINATE
Per identificare ciascun pixel appartenente a un
contesto grafico necessario ricorrere ad un siste-
ma di riferimento. Graphics ci offre un sistema di
riferimento molto simile al piano cartesiano, che
identifica ciascun pixel attraverso unascissa (coor-
dinata x) ed unordinata (coordinata y). Lorigine
del sistema posta in alto a sinistra. Lasse delle
ascisse cresce da sinistra verso destra, mentre quel-
lo delle ordinate si muove dallalto verso il basso.
Mentre in geometria si assume che il punto non
abbia dimensione, lunit atomica del nostro
sistema di riferimento, il pixel, ha una misura
propria. Tracciamo idealmente delle linee fra i
pixel del contesto grafico. Si ottiene una griglia.
Le coordinate del sistema, piuttosto che identifi-
care i singoli pixel, individuano gli incroci fra le
linee della griglia.
I pixel che hanno coordinata x o y pari a 0 non
giacciono sugli assi, ma sono immediatamente al
di sotto (nel caso della coordinata y) o alla destra
(coordinata x) di essi. In un contesto largo n e
alto m, il pixel pi in alto a sinistra ha coordinate
(0, 0), mentre il pi basso a destra sar alla posi-
zione (n - 1, m - 1).
Questo meccanismo ci permette di identificare
esattamente ogni posizione del contesto grafico.
Potremo quindi comandare disegna una retta che
va dal punto (5, 3) al punto (10, 8), oppure disegna
un rettangolo alle coordinate (6, 2) di larghezza 15 e
ht t p: / / www. i opr ogr ammo. i t
M
MOBILE
Novembre 2008/
61
G
Programmare le interfacce grafiche di basso livello
NOTA
MIDP JAVADOC
Ora che si sta iniziando a
lavorare con le API di Java
ME, in particolare con
quelle di MIDP, un po di
documentazione javadoc
fa sempre comodo. Il
vostro WTK contiene i
javadoc di numerose
librerie collegate alla
piattaforma. Quelli per
MIDP li trovate ad un
percorso del tipo:
<DIRECTORY DEL
WTK>/docs/api/midp
Fig. 1: Nel sistema di riferimento usato da
Graphics, lorigine degli assi in alto a sinistra. Il
valore della coordinata x aumenta andando da sini-
stra verso destra, mentre quello della coordinata y
cresce spostandosi dallalto verso il basso
Fig. 2: La griglia che si ottiene tracciando delle
linee fra i pixel
Fig. 3: Il pixel alle coordinate (0, 0) non coincide
in realt con lorigine, ma immediatamente sotto
e a destra di essa
060-065:032-035 2-10-2008 10:39 Pagina 61
ht t p: / / www. i opr ogr ammo. i t
MOBILE M
G
62
/Novembre 2008
Programmare le interfacce grafiche di basso livello
altezza 10, ma anche stampa una certa scritta a
partire dalle coordinate (3, 5) e incolla questa
immagine a partire dalle coordinate (8, 3).
Se il punto in cui posta lorigine degli assi, per
qualche motivo, vi risulta scomodo, potete sem-
pre traslare lorigine verso unaltra coordinata.
Sono disponibili i seguenti metodi:
G public void translate(int x, int y)
Sposta lorigine degli assi al punto (x, y). Dopo
la chiamata al metodo, quel che nel riferimen-
to precedente era il punto (x, y) sar (0, 0) nel
nuovo sistema.
G public int getTranslateX()
Restituisce la coordinata x dellorigine corren-
te rispetto a quella predefinita in alto a sini-
stra.
G public int getTranslateY()
Restituisce la coordinata y dellorigine corren-
te rispetto a quella predefinita in alto a sini-
stra.
Fate attenzione al fatto che le chiamate a trans-
late() risultato cumulative. Se prima invocate
translate(3, 4) e poi translate(2, 6), lorigine degli
assi, rispetto al sistema di riferimento di parten-
za, si trover al punto (5, 10).
GESTIONE DEL COLORE
Prima di disegnare un punto, una linea, un ret-
tangolo o quel che volete, necessario imposta-
re il colore da utilizzare. Graphics mette a dispo-
sizione due metodi utili allo scopo:
G public void setColor(int red, int green, int blue)
G public void setColor(int rgb)
I colori, in ambito informatico, vengono spesso
definiti come una mescolanza di tre canali: rosso
(red), verde (green) e blu (blue), da cui la sigla
RGB. Quando tutti e tre i canali sono a zero si
ottiene il nero. Quando tutti e tre i canali sono al
loro massimo valore si ottiene il bianco.
Se il canale rosso non nullo, ma lo sono gli altri
due, si ottiene una sfumatura di rosso. Quando
il canale verde lunico a non essere nullo si ottie-
ne una sfumatura di verde. Lo stesso vale per il
canale blu. Tutte le rimanenti combinazioni
intermedie identificano ogni altro colore sup-
portato. Nello standard di MIDP ciascun canale
pu avere valore compreso tra 0 e 255 (estremi
inclusi). Si ottengono pi di sedici milioni di
combinazioni possibili. Il primo metodo fra
quelli presentati permette di impostare singolar-
mente il valore di ciascuno dei tre canali, in
maniera semplice ed intuitiva. Il secondo per-
mette di specificare il colore servendosi di un
solo argomento. Troverete comoda questa forma
se siete gi abituati, da esperienze precedenti, a
lavorare con i colori in notazione esadecimale.
Infatti possibile scrivere:
graphics.setColor(0xFF0000)
per ottenere lo stesso risultato di:
graphics.setColor(255, 0, 0)
Non mi dilungher nellillustrare la definizione
dei colori attraverso la notazione esadecimale.
Se gi la conoscete, buon per voi. In caso contra-
rio non c problema, data la disponibilit di una
forma alternativa che controlla singolarmente i
tre canali RGB.
Se non avete mai lavorato con la definizione
RGB dei colori, la seguente applicazione vi tor-
ner molto utile. Si tratta di unapplicazione Java
Swing per la Standard Edition di Java, che potre-
te compilare ed eseguire sul vostro desktop.
Vi aiuter a capire le corrispondenze fra i valori
dei tre canali ed il colore effettivamente elabora-
to.
package it.ioprogrammo.rgbviewer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class RGBViewer extends JFrame implements
ChangeListener {
private static final long serialVersionUID = 1L;
private JSlider redSlider = new JSlider(0, 255, 0);
private JSlider greenSlider = new JSlider(0, 255, 0);
private JSlider blueSlider = new JSlider(0, 255, 0);
private JPanel colorPanel = new JPanel();
private JLabel colorLabel = new JLabel("0 0
NOTA
QUALE VERSIONE
DI MIDP?
Allindirizzo
http://www.club-
java.com/TastePhone/J2M
E/MIDP_mobile.jsp
troverete uninteressante
lista, in continuo
aggiornamento, che
abbina a ogni modello di
cellulare la versione di
MIDP supportata.
060-065:032-035 2-10-2008 10:39 Pagina 62
ht t p: / / www. i opr ogr ammo. i t
0", JLabel.CENTER);
public RGBViewer()
{
super("RGBViewer");
redSlider.addChangeListener(this);
greenSlider.addChangeListener(this);
blueSlider.addChangeListener(this);
JPanel north = new JPanel(new GridLayout(6, 1));
north.add(new JLabel("Rosso", JLabel.CENTER));
north.add(redSlider);
north.add(new JLabel("Verde", JLabel.CENTER));
north.add(greenSlider);
north.add(new JLabel("Blue", JLabel.CENTER));
north.add(blueSlider);
colorPanel.setPreferredSize(new
Dimension(200, 100));
colorPanel.setBorder(new EtchedBorder());
colorPanel.setBackground(Color.BLACK);
JPanel all = new JPanel(new BorderLayout(3, 3));
all.setBorder(new EmptyBorder(5, 5, 5, 5));
all.add(north, BorderLayout.NORTH);
all.add(colorPanel, BorderLayout.CENTER);
all.add(colorLabel, BorderLayout.SOUTH);
getContentPane().add(all);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void stateChanged(ChangeEvent e) {
int r = redSlider.getValue();
int g = greenSlider.getValue();
int b = blueSlider.getValue();
colorPanel.setBackground(new Color(r, g, b));
colorLabel.setText(r + " " + g + " " + b);
}
public static void main(String[] args)
{
RGBViewer mainFrame = new RGBViewer();
mainFrame.setVisible(true);
}
}
Il colore impostato pu successivamente essere
recuperato servendosi dei metodi:
G public int getRedComponent()
Restituisce il valore del canale rosso.
G public int getGreenComponent()
Restituisce il valore del canale verde.
G public int getBlueComponent()
Restituisce il valore del canale blu.
G public int getColor()
Restituisce la mescolanza corrente espressa in
notazione esadecimale.
Non detto che il dispositivo in uso supporti
tutte le sedici milioni e passa di combinazioni
possibili. In tal caso la preoccupazione resta
comunque marginale: se la mescolanza impo-
stata non fra quelle disponibili, il dispositivo
provveder automaticamente a mettere in uso la
pi simile fra quelle supportate. Ad ogni modo
sempre possibile interrogare il dispositivo per
sapere quale combinazione fatta effettivamen-
te corrispondere ad ogni possibile mescolanza
comandata, attraverso il metodo:
G public int getDisplayColor(int color)
Purtroppo questo metodo, disponibile a partire
da MIDP 2.0, lavora esclusivamente con la nota-
zione esadecimale. Largomento fornito il colo-
re desiderato dal programmatore, mentre il
valore restituito la combinazione che, in caso,
il dispositivo gli farebbe effettivamente corri-
spondere. Se non conoscete la notazione esade-
cimale dei colori, ma avete ugualmente inten-
zione di usufruire del metodo, potrete sfruttare
la seguente classe di utilit nelle vostre MIDlet:
package it.ioprogrammo.util;
public class ColorUtils
{
public static int getHexColor(int red, int
green, int blue)
{
int retValue = 0;
M
MOBILE
Novembre 2008/
63
G
Programmare le interfacce grafiche di basso livello
Fig. 4: Lapplicazione RGBViewer (per il desktop,
non per il cellulare) aiuta a comprendere meglio la
composizione dei colori attraverso la mescolanza
dei tre canali RGB
NOTA
SIGLARIO
Java ME, JME o J2ME
Java Micro Edition
WTK
Java Wireless Toolkit
MIDP
Mobile Information Device
Profile
CLDC
Connected Limited Device
Configuration
060-065:032-035 2-10-2008 10:39 Pagina 63
ht t p: / / www. i opr ogr ammo. i t
retValue = retValue | blue;
retValue = retValue | (green << 8);
retValue = retValue | (red << 16);
return retValue;
}
public static int getRedComponent(int rgb)
{
return (rgb & 0xff0000) >>> 16;
}
public static int getGreenComponent(int rgb)
{
return (rgb & 0xff00) >>> 8;
}
public static int getBlueComponent(int rgb)
{
return rgb & 0xff;
}
}
La classe contiene quattro metodi statici. Date le
tre componenti di un colore possibile ottenere
il corrispondente valore esadecimale invocando:
int color = ColorUtils.getHexColor(rosso, verde, blu);
Al contrario, dato un colore in notazione esadeci-
male, possibile ricavare i valori dei tre distinti
canali usando:
int rosso = ColorUtils.getRedComponent(color);
int verde = ColorUtils.getGreenComponent(color);
int blu = ColorUtils.getBlueComponent(color);
Se il display del dispositivo a toni di grigio, il
colore impostato con uno dei metodi setColor()
viene automaticamente trasformato nella sfu-
matura di grigio che meglio ne riflette la brillan-
tezza. In alternativa, se il display stato a priori
riconosciuto come a toni di grigio, conviene
usare i metodi:
G public void setGrayScale(int value)
Imposta un tono di grigio usando largomento
fornito, che deve essere compreso tra 0 e 255
(estremi inclusi). Il valore 0 corrisponde al
nero, mentre 255 il bianco (dico bianco a
livello logico, visto che di solito negli schermi a
toni di grigio questo valore corrisponde allo
sfondo del display, vale a dire al pixel spento).
G public int getGrayScale()
Restituisce il tono di grigio impostato prece-
dentemente con setGrayScale() oppure elabora-
to automaticamente a partire da un colore
impostato mediante setColor().
Anche nel caso dei dispositivi con display a toni
di grigio non detto che tutte e 256 le combina-
zioni possibili siano supportate.
Automaticamente, ogni sfumatura non visualiz-
zabile sar sostituita dalla pi prossima fra quel-
le supportate. Il metodo getDisplayColor(), anche
in questo caso, fornisce informazioni sulle effet-
tive corrispondenze.
In generale, dunque, possiamo osservare come i
dispositivi con display a colori o a toni di grigio
siano perfettamente in grado di automatizzare
ed ottimizzare la selezione dei colori quando
viene comandato luso di una tinta non riprodu-
cibile. Spesso e volentieri non c neanche biso-
gno di distinguere fra gli schermi a colori e quel-
li a toni di grigio: si pu lavorare come se si stes-
se sempre manipolando un display con sedici
milioni di combinazioni possibili, fidandosi cie-
camente degli automatismi eventualmente
messi in atto dal dispositivo. Lunica accortezza
che vi raccomando di usare lapplicare sempre
un netto contrasto ogni volta che due elementi
grafici vicini devono risultare nettamente distin-
guibili. Ad esempio evitate di mettere una scritta
verde su uno sfondo di un verde poco pi chiaro
o poco pi scuro: se il dispositivo in uso non sup-
porta lintera gamma delle sfumature, potrebbe
accadere che le due tinte vengano entrambe
risolte nella medesima mescolanza, rendendo la
scritta indistinguibile dallo sfondo.
Problemi pi seri emergono manipolando i
dispositivi monocromatici, o in bianco e nero
che dir si voglia (intendendo sempre un bianco
logico). Anche in questo caso avvengono delle
conversioni automatiche, ma i valori di destina-
zione sono soltanto due. Un gioco che sfrutta
svariati milioni di colori pu lo stesso funzionare
con solo qualche migliaio di tinte o anche meno.
Qualche dettaglio visivo andr sicuramente
perso, ma usando le accortezze citate prima, i
livelli di usabilit e di giocabilit ne usciranno
intatti. Lo stesso non si pu dire trasferendo il
gioco su un display monocromatico. Se avete
intenzione di supportare questa categoria di
schermi dovrete di fatto allestire una linea grafi-
ca alternativa, studiata esclusivamente per la
situazione. Il display in uso potr poi essere rico-
nosciuto a tempo di esecuzione, in modo da cari-
care ed eseguire la linea grafica pi consona alla
circostanza.
GESTIONE DEL TRATTO
Oltre al colore, possibile gestire il tratto, cio la
maniera in cui vengono tracciate le linee ed i
MOBILE M
G
64
/Novembre 2008
Programmare le interfacce grafiche di basso livello
060-065:032-035 2-10-2008 10:39 Pagina 64
ht t p: / / www. i opr ogr ammo. i t
bordi. Le possibilit offerte sono due:
G Graphics.SOLID
Identifica una linea di disegno continua.
G Graphics.DOTTED
Identifica una linea di disegno tratteggiata.
Il tratto si controlla attraverso i metodi:
G public void setStrokeStyle(int style)
Imposta il tratto.
G public int getStrokeStyle()
Restituisce il tratto in uso.
DISEGNO DI FORME
GEOMETRICHE
A questo punto non resta che imbrattare la
nostra tela. Cominciamo con le linee. Per dise-
gnare una linea servono due punti, uno di par-
tenza ed uno darrivo. Il metodo utile :
G public void drawLine(int x1, int y1, int x2, int y2)
Disegna una linea che va dal punto (x1, y1) al
punto (x2, y2), usando il colore e il tratto cor-
rente.
Passiamo ai rettangoli. Per disegnare un rettan-
golo sono necessari quattro dati: una coordinata
x, una coordinata y, una misura di larghezza ed
una di altezza. Il sistema comincia a disegnare
dal punto (x, y) specificato, quindi sviluppa il ret-
tangolo verso destra e verso il basso, ponendo
lestremo inferiore al punto di coordinate (x +
larghezza, y + altezza). Ragionando (con lausilio
della Fig. 5) vi renderete conto che il rettangolo, a
lavoro ultimato, si sviluppa per larghezza + 1 pixel
in orizzontale e per altezza + 1 pixel in verticale.
Il sistema pone i due estremi in (2, 2) e in (2 + 6,
2 + 4), cio (8, 6). Contate ora quanti pixel ci sono
lungo i lati del rettangolo: ne misurerete 9 lungo
le x e 5 lungo le y.
I metodi per il disegno dei rettangoli sono:
G public void drawRect(int x, int y, int width, int
height)
Disegna il bordo di un rettangolo con estremo
superiore in (x, y) ed estremo inferiore in (x +
width, y + height), usando il colore ed il tratto
correnti.
G public void drawRoundRect(int x, int y, int
width, int height, int arcWidth, int arcHeight)
Disegna il bordo di un rettangolo con estremo
superiore in (x, y) ed estremo inferiore in (x +
width, y + height), usando il colore ed il tratto
correnti. I quattro angoli del rettangolo vengo-
no arrotondati con un arco di larghezza
arcWidth e altezza arcHeight.
G public void fillRect(int x, int y, int width, int
height)
Come drawRect(), ma il rettangolo in questo
caso viene riempito con il colore corrente.
G public void fillRoundRect(int x, int y, int width,
int height, int arcWidth, int arcHeight)
Come drawRoundRect(), ma il rettangolo in
questo caso viene riempito con il colore cor-
rente.
Altre funzionalit disponibili permettono il
disegno di triangoli e curve.
Giacch poco utili per quelli che sono i nostri
scopi (lavoreremo soprattutto con immagini
preconfezionate), i metodi corrispondenti non
vengono qui descritti. Fate riferimento alla
documentazione ufficiale del linguaggio se
siete interessati ad approfondire queste carat-
teristiche.
Sul CD allegato alla rivista, troverete un esem-
pio riassuntivo che mette in pratica le nozioni
appena apprese.
CONCLUSIONI
La trattazione prosegue sul prossimo numero.
Imbratteremo la nostra tela con testi ed imma-
gini, ed impareremo anche alcune tecniche
indispensabili per la corretta e completa
gestione di una Canvas. Come sempre non
mancheranno le dimostrazioni pratiche. Non
potete perderlo!
Carlo Pelliccia
M
MOBILE
Novembre 2008/
65
G
Programmare le interfacce grafiche di basso livello
Fig. 5: Si comanda il disegno di un rettangolo alle
coordinate (2, 2), con larghezza 6 e altezza 4
LAUTORE
Carlo Pelliccia lavora
presso la Software Factory
di Altran Italia, dove si
occupa di analisi e
sviluppo software per
piattaforme Java. Nella
sua carriera di technical
writer ha pubblicato
cinque manuali ed oltre
centocinquanta articoli,
molti dei quali proprio tra
le pagine di ioProgrammo.
Il suo sito, che ospita
anche diversi progetti Java
Open Source, disponibile
allindirizzo
www.sauronsoftware.it
060-065:032-035 2-10-2008 10:39 Pagina 65
ht t p: / / www. i opr ogr ammo. i t
LIBRARY M
G
70
/Novembre 2008
Familiarizziamo con la libreria Scientific Library (GSL)
C
i sono molti modi di generare sequenze di
numeri casuali, e ogni linguaggio di pro-
grammazione ne offre una o pi implemen-
tazioni, direttamente o tramite librerie. Una delle li-
brerie create per fornire metodi matematici la GNU
Scientific Library (GSL). GSL in grado di generare mol-
to facilmente degli istogrammi. Linstallazione sot-
to Linux non comporta alcun problema: la sequen-
za solita ./configure; make; make install compila ed
installa la libreria. La compilazione sotto Windows
prevede luso di Gcc con Cygwin, lasciando invaria-
ta la sequenza di operazioni. GSL fornisce unampia
selezione di strumenti per chi scrive programmi:
Nel nostro progetto vedremo come usare alcune fun-
zioni relative alla generazione di numeri casuali e al-
la creazione di istogrammi.
LA DISTRIBUZIONE
DI PROBABILIT
Listogramma delle probabilit un grafico bidimen-
sionale. Riporta sullasse delle ascisse (orizzontale) gli
eventi possibili, sullasse delle ordinate le occorrenze
di ogni evento. Lo stile pi diffuso dellistogramma
un diagramma a barre. La distribuzione di probabi-
lit associata a un istogramma consiste in un insieme
di caselle misuranti la probabilit per un evento di ri-
cadere entro un certo range di una variabile continua
x. In pratica, la probabilit che esca 1 y > 0, la pro-
babilit che esca 1 o 2 y > y, e cos via. Alla fine, la pro-
babilit che esca un numero tra 1 e 6 1. Una funzio-
ne di distribuzione di probabilit definita, in GSL,
da una struttura contenente la funzione di distribu-
zione della probabilit cumulata.
PROVIAMO
CON UN DADO
Il problema del lancio di uno o pi dadi archetipi-
co del calcolo delle probabilit, quasi quanto quello
del lancio della moneta. La probabilit di uscita di
un numero, in caso di dado equo, deve essere ugua-
le a quella di uscita di ogni altro numero (gli eventi so-
no equiprobabili): nel caso osservato, 1/6. Per os-
servarne sperimentalmente la veridicit, occorre fa-
re un gran numero di lanci (in teoria, infiniti) e os-
servare la validit generale della legge dei grandi nu-
meri: la frequenza tende alla probabilit. Disegne-
remo quindi lesperimento in modo da calibrare il
numero dei lanci, e ne tracceremo listogramma.
Vediamo alcuni brani di codice:
#define MAX 6
gsl_rng * rand_gen = gsl_rng_alloc (gsl_rng_taus);
gsl_histogram * distr_hist = gsl_histogram_alloc (MAX);
double range[MAX + 1] = { 0.5, 1.5, 2.5, 3.5, 4.5, 5.5,
6.5 };
Abbiamo definito le variabili di tipo numero casua-
le (creando unistanza di generatore di Tausworthe)
e istogramma, allocando loro memoria.
Per listogramma abbiamo anche definito la di-
mensione orizzontale delle barre, dando i limiti inferiore
e superiore di ogni range. Con le seguenti istruzioni
creiamo la struttura contenitore dei dati dellisto-
gramma e inizializziamo il generatore di numeri ca-
suali.
gsl_histogram_set_ranges (distr_hist, range, MAX + 1);
gsl_rng_set (rand_gen, time(NULL));
Infine, allinterno di un ciclo, potremo generare il nu-
mero casuale (effettuare un lancio) e incrementare la
barra relativa dellistogramma:
gsl_histogram_increment (distr_hist,
(double) gsl_rng_uniform_int (rand_gen, MAX) + 1);
Qui la tecnica usata banale: genera un numero, con
distribuzione uniforme, tra 0 e MAX 1, e sommagli
1. Trasferire i dati dellistogramma a stdout facilis-
simo, grazie allistruzione fornita da GSL:
gsl_histogram_fprintf (stdout, distr_hist, "%g", "%g");
Infine, liberare la memoria allocata alle strutture pri-
STUDIAMO IL LANCIO
DEI DADI CON LE GNU
ABBIAMO DIVERSI MODI PER GENERARE NUMERI CASUALI. LA GNU SCIENTIFIC LIBRARY, A
TAL FINE, PROMETTE DI FORNIRE OTTIMI METODI. SFRUTTIAMOLI PER SIMULARE IL LANCIO
DI DADI E VEDERNE GRAFICAMENTE LA DISTRIBUZIONE
J CD J WEB
lancio_di_dadi.zip
cdrom.ioprogrammo.it
Conoscenze richieste
Compilatori C
Software
Utenti Linux: Gcc;
utenti Windows:
Cygwin e Gcc
(dovrebbe essere
presente una versione
precompilata di GSL)
Impegno
Tempo di realizzazione
REQUISITI
070-071:072-080 30-09-2008 16:29 Pagina 70
ht t p: / / www. i opr ogr ammo. i t
M LIBRARY
Novembre 2008/
71
G
Familiarizziamo con la libreria Scientific Library (GSL)
ma definite semplice come fare un free():
gsl_rng_free (rand_gen);
gsl_histogram_free (distr_hist);
Appena abbiamo compilato il programma, ne possiamo
testare i risultati. Simuliamo il lancio di un dado per
10 milioni di volte: ci aspettiamo di vedere la fre-
quenza di uscita di 1 circa pari a quella di uscita di 2,
o degli altri valori. In Fig. 1 vediamo listogramma
determinato dai dati ottenuti: il suo profilo piatto in-
dica la correttezza dellipotesi.
COSA CAMBIA
CON DUE DADI
Per rafforzare la comprensione delle nuove istruzio-
ni introdotte, e vederne altre, realizziamo un pro-
getto simile: lancio di due dadi e studio della distri-
buzione del risultato complessivo. Ci attendiamo un
picco nellistogramma in corrispondenza dei risul-
tati 6, 7 e 8 (ottenibili come 5+1, 4+2, 3+3, e 6+1, 5+2,
4+3, e 6+2, 5+3, 4+4). Gli altri risultati dovrebbero ve-
rificarsi con minor frequenza, con un minimo in cor-
rispondenza di 2 (1+1) e 12 (6+6). Di nuovo, eseguia-
mo lesperimento con un alto numero di lanci, e ve-
diamo listogramma risultante (Fig. 2):
A questo progetto abbiamo aggiunto la capacit di
restituire alcuni dei valori caratteristici dellisto-
gramma. Inoltre, vedremo quanto sia immediato ot-
tenere la funzione di distribuzione di probabilit.
fprintf (stderr, "Valore minimo nei range: %f\n",
gsl_histogram_min_val (distr_hist));
fprintf (stderr, "Valore massimo nei range: %f\n",
gsl_histogram_max_val (distr_hist));
fprintf (stderr, "Valore medio della distribuzione: %f\n",
gsl_histogram_mean (distr_hist));
fprintf (stderr, "Deviazione standard della distribuzione:
%f\n",
gsl_histogram_sigma (distr_hist));
Le precedenti fprintf() stampano, inviando su stan-
dard error, il minimo valore dellistogramma (il pun-
teggio ottenuto meno frequentemente), quello mas-
simo, e media e deviazione standard della distribuzione
di probabilit associata allistogramma.
gsl_histogram * dhcopy;
dhcopy = gsl_histogram_clone (distr_hist);
gsl_histogram_fprintf (stderr, dhcopy, "%g", "%g");
Vediamo inoltre una delle due operazioni di copia a
disposizione: si clona listogramma assegnando il
puntatore ad una variabile istogramma non ancora
allocata. Se avessimo voluto fare una copia fisica
(istruzione con suffisso memcpy), avremmo dovuto
allocare esplicitamente la memoria alla variabile
dhcopy. Manca solo la generazione del grafico della
distribuzione di probabilit:
gsl_histogram_pdf_init (prob_distr, distr_hist);
gsl_histogram_fprintf (stderr, prob_distr, "%g", "%g");
La prima funzione inizializza una funzione di distri-
buzione in base allistogramma fornito. La seconda
viene usata in maniera forzata, perch non sembra es-
serci una funzione che stampi le funzioni di distri-
buzione. In compilazione generer un warning (di-
verso puntatore a tipo in input), ma alla fine la di-
stribuzione viene stampata ugualmente.
Gianluca Pignalberi
Fig. 1: Istogramma della distribuzione del lancio
di 1 dado (10.000.000 lanci)
Fig. 2: Istogramma della distribuzione del lancio
di 2 dadi (10.000.000 lanci)
Fig. 3: Funzione di distribuzione di probabilit dellesperi-
mento lancio di 2 dadi)
NOTA
LIBRI
IRRINUNCIABILI
Un libro notissimo (lo
definirei un must have) tra i
programmatori C impegnati
a realizzare programmi
fortemente orientati al
calcolo numerico
Numerical Recipes in C.
Gli autori spiegano
dettagliatamente molte
tecniche di realizzazione
delle routine proposte,
indicandone anche le
motivazioni tecniche.
William H. Press, Brian P.
Flannery, Saul A. Teukolsky,
William T. Vetterling,
Numerical Recipes in C: The
Art of Scientific Computing,
New York: Cambridge
University Press, 1992
Un altro libro utile, specie
nel nostro caso, The Art Of
Computer Programming di
Donald Knuth. Il terzo
capitolo del secondo
volume, dedicato proprio
ai numeri casuali.
Donald E. Knuth, The Art of
Computer Programming,
volume 2 (Seminumerical
Algorithms), seconda
edizione; Reading,
Massachusetts: Addison-
Wesley Publishing
Company, 1981.
070-071:072-080 30-09-2008 16:29 Pagina 71
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
72
/Novembre 2008
Sensore di movimento a costo zero grazie al WiiMote
C
ome amante della fotografia naturalistica mi
sono imbattuto, ammirando alcuni scatti,
in eventi ripresi a distanza estremamente
ravvicinata con un animale selvatico; la voglia di
emulare questi grandi fotografi del National Geo-
graphics mi ha spinto a creare una mia soluzione
utilizzando il nostro controller della Wii. Questo
piccolo software vi permetter di risolvere in ma-
niera economica questo semplice problema, ma
potrebbe risultare utile per "catturare", fotografi-
camente parlando, una persona che si avvicinasse
ad una certa zona che volete monitorare. Sfrutte-
remo l'SDK di Flex rilasciato da Adobe sotto licen-
za MPL (Mozilla Public License), comunicheremo
con il controller della Wii tramite un semplice ser-
ver che realizzeremo in C#, completeremo il tutto
adoperando Adobe AIR per poter salvare le istan-
tanee sul nostro hard disk.
ACTIONSCRIPT
FLEX ED AIR
L'evoluzione ed il conseguente successo del lin-
guaggio Actionscript sono stati merito della ricet-
tivit dei programmatori Macromedia, prima, e
Adobe, poi, che hanno implementato sempre pi
soluzioni seguendo le richieste degli sviluppatori
presenti nel mondo; si passati da un "lento" lin-
guaggio interpretato nato per mostrare su schermo
animazioni vettoriarli ad uno compilato e object
oriented eseguito in una veloce virtual machine
(ActionScript Virtual Machine, chiamata AVM2) di
dimensioni veramente ridotte. Il passaggio suc-
cessivo stato quello di favorire i programmatori
"puri", tradizionalmente meno indirizzati alle ani-
mazioni, tramite il rilascio del framework FLEX,
incentrato sullo sviluppo di soluzioni che si basa-
no su componenti grafici ed interfacce predefini-
ti (anche se liberamente personalizzabili). Alla fi-
ne giunto AIR (Adobe Integrated Runtime), cross-
platform runtime environment realizzato allo sco-
po di consentire a ogni programmatore di sfrutta-
re le funzionalit principali presenti su un sistema
operativo senza dover risentire delle restrizioni
presenti nel Flash player: tramite AIR ora possi-
bile consentire ad una RIA (Rich Internet Applica-
tion), realizzata in Flash, Flex o solo Actionscript,
di essere eseguita in locale, con pieno accesso ai
contenuti del vostro hard disk, con un minimo sfor-
zo apportato al codice. Altre societ stanno mo-
strando interesse nella direzione intrapresa da Ado-
be, tra cui Mozilla con Prism e Google con il suo
Docs in primis).
COME INTEGRARE
LE VARIE TECNOLOGIE
Entriamo nel dettaglio su come utilizzeremo que-
ste tecnologie: scriveremo il codice in Actionscript
3, con approccio object oriented, utilizzeremo Flex
per velocizzare lo sviluppo di un'interfaccia grafi-
ca minimale, adoperando i componenti forniti dal-
l'SDK; con relativamente poche righe di codice po-
tremo realizzare la logica del nostro applicativo.
AIR ci consentir di salvare in locale le immagini cat-
turate dalla nostra trappola fotografica senza ob-
bligarci ad inviare tali informazioni ad un server, lo-
cale o remoto che sia; grazie a questa nuova solu-
zione Adobe, potremo anche realizzare un file au-
toinstallante che, opportuanemente firmato digi-
talmente, potrebbe essere distribuito ad altre per-
sone.
MOVIMENTI SOTTO
CONTROLLO COL WII
UN NUOVO ARTICOLO PER ESAMINARE LE POTENZIALIT DEL CONTROLLER WIIMOTE.
VEDREMO COME ADOPERARE LO STESSO PER REALIZZARE, IN FLEX E CON LAUSILIO DI C#,
UN SISTEMA PER SCATTARE INSTANTANEE NEL CASO IN CUI SI RILEVI UN MOVIMENTO
J CD J WEB
wiiphototrap.zip
cdrom.ioprogrammo.it
Conoscenze richieste
OOP, C#, Actionscript 3
Software
C# Express, Flex SDK,
Adobe Flex,
FlashDevelop o altri
Impegno
Tempo di realizzazione
REQUISITI
Fig. 1: L'applicativo che andremo a realizzare
072-075:072-080 30-09-2008 16:49 Pagina 72
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
73
G
Sensore di movimento a costo zero grazie al WiiMote
Poich faremo utilizzo, anche questa volta delle li-
brerie di Brian Peek, scriveremo un server di po-
che righe in C# con un'interfaccia grafica mini-
male in Windows Forms e invieremo un segnale
tramite socket TCP (in Actionscript UDP non ri-
sulta essere disponibile) quando un oggetto inter-
romper la visione del segnale ad infrarosso; tale mes-
saggio sar ricevuto dal nostro applicativo Flex/Air
e dar inizio alla raffica di scatti.
SVILUPPIAMO IL SERVER
IN LINGUAGGIO C#
Il funzionamento di tale applicativo molto sem-
plice: avviando l'eseguibile, invieremo un ping tra-
mite socket TCP (un semplice byte di valore uni-
tario) ad ogni interruzione di segnale infrarosso;
al termine dell'operazione porremo in pausa il ser-
ver per non inviare ulteriori messaggi per un cer-
to intervallo di tempo: lo stato dei segnali ad in-
frarosso che arrivano dal wiimote non risulta ave-
re continuit temporale, ma una rapida sequen-
za che genererebbe una raffica di messaggi per lo
stesso evento.
Poich la fase di attesa del client stata realizzata
in questo caso in maniera sincrona, tramite il me-
todo AcceptTcpClient, per rendere pi compren-
sibile il codice, si avrebbe un freeze dell'intera in-
terfaccia grafica: avviando un semplice thread con-
tenente tale codice si risolve l'inconveniente; tale
metodo termina quando viene instaurata la con-
nessione TCP ed esegue il codice responsabile del-
l'inizializzazione dell'istanza della classe Wiimo-
te permettendoci quindi di monitorare lo stato de-
gli infrarossi:
private void ServerHandler()
{
//Loopback = 127.0.0.1
listener = new TcpListener
(System.Net.IPAddress.Loopback, LISTENPORT);
listener.Start();
logMessage("Listening...");
client = listener.AcceptTcpClient();
logMessage("Client Connected: " +
client.Client.RemoteEndPoint);
logMessage("Getting Stream...");
clientStream = client.GetStream();
logMessage("Starting Wiimote...");
//Inizializziamo il controller e avviamo la
cattura degli eventi del wiimote
SetupWiimote();
}
Il metodo SetupWiimote() lo stesso che stato
utilizzato negli articoli precedenti (ovviamente uti-
Fig. 2: Semplificazione delle tecnologie utilizzate
Fig. 3: Schema che mostra come comunicheranno
gli applicativi
Fig. 4: L'interfaccia grafica del server
NOTA
DRIVER WIIMOTE
E BSOD
Purtroppo anche i driver di
Brian Peek si sono rivelati
per alcuni utenti fonte di
problemi, il famigerato Blue
Screen of Death non si
esentato a presentarsi: la
causa, da quanto ho
appurato, dovuta alla
presenza del software
Bluesoleil; una sua
disinstallazione ha interrotto
immediatamente la
comparsa di tale
schermata.
072-075:072-080 30-09-2008 16:49 Pagina 73
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
74
/Novembre 2008
Sensore di movimento a costo zero grazie al WiiMote
lizzeremo SetReportType(InputReport.IRAccel,true)
per abilitare la ricezione delle sorgenti ad infra-
rosso) quindi viene omesso; la parte di codice pi
interessante quella che gestisce gli eventi rice-
vuti dal wiimote:
private void wm_WiimoteChanged(object sender,
WiimoteChangedEventArgs e)
{
if (
(!e.WiimoteState.IRState.IRSensors[0].Found) &&
(!e.WiimoteState.IRState.IRSensors[1].Found) &&
(!e.WiimoteState.IRState.IRSensors[2].Found) &&
(!e.WiimoteState.IRState.IRSensors[3].Found)
)
{
//Inviamo un messaggio per indicare che qualcosa si
interposto tra controller e sensor bar
if (started)
{
if (clientStream != null)
{
logMessage("Sending Alert to client.");
//Inviamo il nostro ping
clientStream.WriteByte(1);
logMessage("Sleeping...");
//Evitiamo che il server invii un burst di messaggi per
lo stesso evento
Thread.Sleep(5000);
logMessage("Awaken!");
}}}}
Invece di interrompere momentaneamente il thread,
si potrebbe utilizzare un contatore che, ad ogni
certo numero di campioni, invierebbe il messaggio.
Un'ultima nota, ma sul metodo logMessage: prov-
vede a gestire l'inserimento dei messaggi di log ad
un'area di testo creando un delegate se necessa-
rio; questa scelta obbligatoria perch altrimenti
si riceverebbe un messaggio di errore che notifica
l'impossibilt da parte di un thread, che non ri-
sulta essere quello che ha creato il componente (
infatti quello generato della classe Wiimote), di
modificare il suo stato.
L'APPLICATIVO
IN FLEX/AIR
Il software in question contiene un'area centrale
in cui viene mostrata la webcam, sulla destra c'
un TileListDisplay che conterr le anteprime de-
gli scatti effettuati, ed in basso una serie di tab che
mostrano lo stato della connessione con il server,
una finestra di log e una serie di opzioni modifi-
cabili a piacimento (risoluzione della webcam,
scatti al secondo, framerate etc). Si e sfruttata la
possibilit di impostare la sensibilit della web-
cam per avviare la cattura utilizzando il metodo
setMotionLevel, in tal modo si incrementa la pos-
sibilt di intercettare un oggetto in movimento, nel
caso avesse evitato di far scattare l'allarme inter-
rompendo la visibilt delle sorgenti ad infrarosso.
Il metodo captureCameraImage quello che con-
tiene il codice responsabile della creazione dell'i-
stantanea, della sua anteprima, del salvataggio del-
l'immagine e dell'aggiunta di questi dati (rag-
gruppati in un oggetto) alla struttura dati utilizza-
ta dal TileList per mostrare l'anteprima e la data
dell'evento, (un array contenuto in un wrapper
ArrayCollection). La presenza di numeri in esade-
ciamale con otto (ARGB) cifre invece delle classiche
sei (RGB) dovuta all'utilizzo della notazione che
include un valore di trasparenza (la A appunto
Alpha) nelle prime due posizioni.
private function captureCameraImage():void {
//Creiamo l'istanza solo se necessario
if (bitmapData==null) {
bitmapData = new
BitmapData(vid.width,vid.height,false,0x00000000);
}
else if ((bitmapData.width!=vid.width)
||(bitmapData.height!=vid.height)) {
bitmapData = new
BitmapData(vid.width,vid.height,false,0x00000000);
}
//Memorizziamo la fotografia utilizzando il contenuto
del videoDisplay
bitmapData.draw(vid);
//Creiamo l'anteprima da posizionare nella colonna di
destra
//Aggiorniamo l'orario in cui stato scattata la foto
currentDate = new Date().toLocaleTimeString();
//Creiamo una sovraimpressione contenente tale
orario e la disegnamo sul bitmapData
txtField.text=currentDate;
bitmapData.draw(txtField);
//Creiamo l'immagine con un Bitmap utilizzando tale
bitmapData
imgBig = new Image();
imgBig.width = bitmapData.width;
imgBig.height = bitmapData.height;
imgBig.source = new Bitmap(bitmapData.clone());
//Impostiamo i valori di scala da utilizzare, le
proporzioni sono ottenute analizzando il rapporto tra
larghezza in pixel dell'anteprima e la
NOTA
RIFERIMENTI WEB
G http://www.wiili.org
(specifiche, driver e risorse
sul wiimote)
G http://wiibrew.org
(specifiche, driver e risorse
sul wiimote)
G http://www.microsoft.
com/express/vcsharp/
(Visual C# Express Edition
2008)
G http://www.codeplex.
com/WiimoteLib
(la libreria realizzata da
Brian Peek)
072-075:072-080 30-09-2008 16:49 Pagina 74
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
75
G
Sensore di movimento a costo zero grazie al WiiMote
dimensione originale dell'immagine ottenuta dalla
camera; il rapporto di scala dell'altezza relazionato
ovviamente a quello relativo alla larghezza.
mtx = new Matrix(DEFAULT_WIDTH_PREVIEW /
bitmapData.width,0,0,(DEFAULT_WIDTH_PREVIEW/(vi
d.width/vid.height))/bitmapData.height,0,0);
//Creiamo l'anteprima
previewBitmapData = new
BitmapData(DEFAULT_WIDTH_PREVIEW,DEFAULT_WI
DTH_PREVIEW/(vid.width/vid.height));
//Applichiamo la matrice di trasformazione per ridurre
le dimensioni dell'immagine
previewBitmapData.draw(bitmapData.clone(),mtx);
//Creiamo l'immagine di anteprima con un Bitmap
creato utilizzando tale bitmapData
imgPreview = new Image();
imgPreview.width = previewBitmapData.width;
imgPreview.height = previewBitmapData.height;
imgPreview.source = new
Bitmap(previewBitmapData);
Ora utilizziamo le funzionalit di AIR: andremo a
creare un file e salveremo l'immagine su hard disk;
abbiamo prefrito utilizzare il salvataggio sincrono
per non complicare ulteriomente il codice:
//Inizializziamo il file assegnandogli un nome
"univoco",
//La cartella C:\Users\NomeUtente\AppData
\Roaming\WiiPhotoTrap\Local Store.
fileLocation = File.applicationStorageDirectory.
resolvePath((new Date).getTime()+".jpg");
//Apriamo in scrittura il file appena creato
fileStream.open( fileLocation, FileMode.WRITE );
//Codifichiamo utilizzando il formato jpeg l'immagine
e salviamo il file su HD
fileStream.writeBytes(encoder.encode( bitmapData));
//Terminiamo le operazioni sull file collegato allo
stream
fileStream.close();
Provvediamo, infine, a memorizzare un oggetto
creato appositamente per contenere le varie infor-
mazioni, e lo inseriamo nell'array:
//Aggiungiamo data, anteprima, immagine e numero
di scattto all'elenco
TileListdp.addItem({time:currentDate,
image:imgPreview, bigImage:imgBig,
number:imageCounter});
//Incrementiamo il numero di sequenza di scatto
imageCounter++;
}
Per gestire la connessione con il server ci avvan-
taggiamo di un componente scritto ad hoc, chiamato
CustomSocket; questa classe in realt un UICom-
ponent, un componente grafico quindi, nel quale
si inserito sia un elemento grafico (la finestra di
log chiamata TCP-Monitor) che il socket respon-
sabile della gestione della connessione:
public function connect():void {
//Creiamo il socket e lo poniamo in attesa di ricevere
una connessione (evento segnalato dall' Event
SOCKET_DATA)
this.socket = new Socket(host, port);
//Intercettiamo tutti gli eventi che ci possono
interessare che provengono dal socket
this.socket.addEventListener(Event.CLOSE,
closeHandler);
this.socket.addEventListener(Event.CONNECT,
connectHandler);
this.socket.addEventListener(IOErrorEvent.IO_ERROR
, ioErrorHandler);
this.socket.addEventListener(SecurityErrorEvent.SEC
URITY_ERROR, securityErrorHandler);
this.socket.addEventListener(ProgressEvent.SOCKET_
DATA, onData);
}
//Questo metodo provvede a monitorare lo stato del
socket in attesa della ricezione di un messaggio
private function onData( event:ProgressEvent ): void
{
if ( socket.bytesAvailable > 0 )
{
writeLog("Received:"+socket.readByte());
//Notifichiamo l'applicativo che arrivato un
messaggio
dispatchEvent(new Event("ping"));
}
}
CONCLUSIONI
Quando compilerete il codice in questione sar
necessario esportarlo come eseguibile AIR e fir-
marlo digitalmente utilizzando gli applicativi ri-
chiamabili via console, oppure in Flex utilizzando
il comando Export Release Build, in caso contra-
rio potrete utilizzarlo solo all'interno di
Flex/FlashDevelop. In questo applicativo abbia-
mo integrato svariate tecnologie attualmente pre-
senti sul mercato, mostrando con quale facilit pos-
sibile realizzare una soluzione multimediale.
Al prossimo articolo.
Andrea Leganza
LAUTORE
Laureato in Ingegneria
Informatica presso
"La Sapienza - Universit
di Roma". Certificato
EUCIP Core, ha svolto
incarico di tester di
dispositivi mobili per un
grande operatore mobile;
appassionato di fotografia,
lingua giapponese e
istruttore di nuoto FIN,
contattabile presso
neogene@tin.it
o direttamente sul sito
www.leganza.it
072-075:072-080 30-09-2008 16:49 Pagina 75
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
76
/Novembre 2008
Python 3000, tante novit e incompatibilit con il vecchio
Q
uando leggerete queste righe, dovrebbe gi
essere disponibile Python 3000 (Python 3.0
o Py3k). Si tratta di un evento di impor-
tanza epocale per il mondo Python perch, per la
prima volta nella storia di questo linguaggio, si per-
der la compatibilit con le versioni precedenti e i
programmatori saranno costretti a modificare gran
parte del codice esistente. Per quanto possa sem-
brare strano, questa release non introduce nessu-
na funzionalit realmente nuova. Si limita a cor-
reggere molti piccoli errori di progettazione che
affliggevano Python sin dalle prime release. Il team
di sviluppo, infatti, ha deciso di concentrare in que-
sta release tutte le modifiche che imponevano la
rottura della compatibilit con le versioni prece-
denti in modo da porre delle solide basi per le fu-
ture versioni. Questa dovrebbe essere l'ultima vol-
ta che si verifica un cambiamento cos drastico.
In questo articolo esaminiamo le principali novit
di Py3k e spieghiamo come eseguire la migrazione
del codice esistente. A fianco di ogni nuova featu-
re, indichiamo la PEP(Python Enhancement Proposal)
da cui trae origine in modo che possiate approfondire
l'argomento sul web. Le PEP sono i documenti
HTML che descrivono in dettaglio ogni nuova fea-
ture e le motivazioni che hanno portato al suo svi-
luppo. Le trovate allindirizzo http://www.python.
org/dev/peps
Nel corso dell'articolo mostreremo alcuni snippet
di codice. Potete vedere com' fatto un vero pro-
gramma Python 3.0, dando un'occhiata alla im-
plementazione di prova della PEP 362 (Function
Signature Object) creata da Brett Cannon
(http://sayspy.blogspot.com). Tra l'altro, questo uno
dei rari programmi che funzionano sia con Python
2.6 che 3.0. Potete trovare il codice qui:
http://svn.python.org/view/sandbox/trunk/ pep362 .
NIENTE PAURA
Il ramo di sviluppo 2.X verr portato avanti ancora
per diversi anni e continuer a essere pienamente
supportato. La versione 2.7 gi in fase di proget-
tazione ed probabile che verr realizzata anche
una versione 2.8. Il team di sviluppo ha gi prepa-
rato un apposito strumento di traduzione auto-
matica (chiamato 2to3) che in grado di conver-
tire gran parte del codice esistente. Infine, la ver-
sione 2.6 di Python dispone di una opzione di ese-
cuzione (python -3) che costringe l'interprete a mo-
strare dei warning relativi alle funzionalit che non
saranno pi supportate da py3k. Parleremo in det-
taglio della migrazione al termine dell'articolo.
LA STANDARD LIBRARY
L'elemento che ha subito l'intervento pi esteso e
pi visibile stata la libreria standard del linguag-
gio. Oltre 100 moduli sono stati rimossi e 36 sono
stati aggregati tra loro in 6 package di maggiori di-
mensioni. La PEP 3108 documenta nei dettagli le mo-
difiche apportate e ne spiega le ragioni. Nella Tabella
1 riportiamo i casi pi rilevanti. La rimozione di
molti vecchi moduli costringer i programmatori
a intervenire manualmente in molti punti dei loro
programmi. Gli strumenti di conversione auto-
matica del codice non sono in grado di interveni-
re su questo punto.
PYTHON 3000
TUTTE LE NOVIT
LA NUOVA VERSIONE ROMPE LA COMPATIBILIT CON LE VECCHIE E LO FA PER UNA BUONA
CAUSA. TRA LE NUOVE CARATTERISTICHE, UNA MAGGIORE COERENZA INTERNA
E MAGGIORE ELEGANZA DEL CODICE
J CD J WEB
Python3000.rar
cdrom.ioprogrammo.it
Conoscenze richieste
Limitata conoscenza
di Python 2.X
Software
Python3000.
L'interprete gira su
Windows, Linux, BSD,
Mac OS X e molti altri
sistemi operativi.
Impegno
Tempo di realizzazione
REQUISITI
Fig. 1: David Ascher il managing Director di ActiveState
e co-autore, insieme a Mark Lutz, di Learning Python
076-080:072-080 30-09-2008 16:19 Pagina 76
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
77
G
Python 3000, tante novit e incompatibilit con il vecchio
UNICODE,
STRINGS E BYTES
Come previsto dalle PEP 3120 e 3131, Py3k usa Uni-
code (UTF-8, di default) per le stringhe e per gli
identificatori (nomi di variabili, di funzioni e di
classi). Questo vuol dire che si possono avere no-
mi di variabili e di funzioni in ideogrammi cinesi o
commenti in lingua thailandese. Questa funzio-
nalit stata voluta per facilitare l'uso di Py3k co-
me strumento didattico nelle scuole non europee.
La comunit che sviluppa Python, tuttavia, conti-
nuer a usare solo l'inglese ed un set di caratteri
equivalente all'ASCII 7bit. Questo vale soprattutto
per il codice della Standard Library.
In Py3k, stringhe e byte sono oggetti diversi e non
intercambiabili (PEP 3137 e 3112). Le stringhe (str)
sono sempre sequenze di caratteri Unicode e i by-
te (bytes) sono sempre e solo sequenze di byte. Per
convertire questi tipi di dato dall'uno all'altro so-
no disponibili un metodo bytes.decode() che resti-
tuisce la stringa equivalente a una sequenza di by-
te ed un metodo str.encode() che restituisce la se-
quenza di byte equivalente ad una stringa.
PRINT COME FUNZIONE
Lo statement print diventato una funzione (PEP
3105). Questa modifica stata introdotta per por-
re fine agli equivoci e ai bug dovuti alla gestione
degli spazi (soft space) del vecchio comando.
Ora la sintassi la seguente.
print([object, ...][, sep=' '][, end='n'][,
file=sys.stdout])
In cui object l'oggetto (o gli oggetti) da stampare,
sep il separatore da inserire tra i vari elementi da
stampare, end il carattere finale della stringa e
file il nome del file su cui scrivere. Per esempio:
print('hello', 'world', sep=' ', end='\n',
file=output.txt)
Stampa la frase hello world, seguita da un carat-
tere di newline (\n) sul file output.txt.
In modo analogo, anche lo statement exec di-
ventato una funzione. In entrambi i casi, il tool 2to3
in grado di tradurre il codice in modo automati-
co nella stragrande maggioranza dei casi.
CLASSI E METACLASSI
Il vecchio meccanismo di creazione delle classi sta-
to abbandonato e la gerarchia dei tipi stata rior-
ganizzata. Ora tutte le classi derivano esplicita-
mente da object:
class C: # vecchio stile di definizione
(fino Rel. 2.9)
class C(object): # nuovo stile di definizione (Rel.
2.2 -> 2.9)
Si tratta di una finezza di cui pochi dovranno preoc-
cuparsi. Come previsto dalla PEP 3115, cambia-
to anche il meccanismo di dichiarazione di una
metaclasse:
class MyClass(baseclass1, baseclass2,
metaclass=mymetaclass):
anche possibile passare alla classe altri parame-
tri che verranno passati alla metaclasse:
class MyClass(baseclass1, baseclass2,
metaclass=mymetaclass, private=True):
Le metaclassi permettono di controllare alcuni det-
tagli cruciali del meccanismo di creazione delle
classi. David Mertz ha pubblicato su IBM develo-
perworks una trattazione approfondita di questo ar-
gomento:
http://www.ibm.com/developerworks/linux/library/l-
pymeta.html
http://www.ibm.com/developerworks/linux/library/l-
pymeta2/index.html
http://www.ibm.com/developerworks/linux/library/l-
pymeta3.html
ABSTRACT
BASE CLASSES
La PEP 3119 introduce il concetto di Abstract Base
Class (ABC). Una ABC una classe in cui almeno
Fig. 2: Alex Martelli l'autore di Python Cookbook e
Python in a nutshell
NOTA
GUIDO
VON ROSSUM
Guido von Rossum il
padre di Python. Guido un
matematico olandese e
lavora su questo progetto
dal 1991. Dal 2005 lavora
per Google. Nel corso degli
anni, riuscito a raccogliere
attorno a s uno dei team di
sviluppo Open Source pi
attivi e pi qualificati del
mondo.
076-080:072-080 30-09-2008 16:19 Pagina 77
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
78
/Novembre 2008
Python 3000, tante novit e incompatibilit con il vecchio
uno dei metodi non ha implementazione e non
pu essere invocato. La classe astratta viene inse-
rita nella genealogia di un'altra classe per segnalare
al codice esterno la disponibilit dei metodi che
essa definisce (e che la classe derivata implemen-
ta al suo interno). Le ABC svolgono quindi la stes-
sa funzione delle interfacce o delle funzioni ge-
neriche di altri linguaggi e ne ricordano da vici-
no la struttura.
Si possono usare le ABC per fare in modo che una
classe si presenti al mondo esterno come una clas-
se derivata da un'altra classe senza per che esi-
sta una derivazione reale. Per esempio:
class Duck(metaclass=ABCMeta):
@classmethod
def __instancecheck__(cls, x):
# Redefines instancecheck so that it returns true...
return hasattr(x, "__quack__")
# ... if the derived class has a quack attribute.
class AwkwardBird:
def __quack__(self):
return "DoesNotMatter"
isinstance(AwkwardBird(), Duck) # Returns True
In questo caso, la classe AwkwardBird risulta ap-
partenere alla classe Duck, anche se in realt non
deriva da essa. Questo avviene perch il metodo
__instancecheck__ della classe Duck stato ridefi-
nito in modo tale che ritorni True se la classe con
cui viene confrontata dispone di un attributo
__quack__ La PEP 3129 estende l'uso dei decoratori
alle classi, una possibilit che viene poi usata in-
ternamente dal sistema delle ABC. La sintassi la
stessa usata per le funzioni:
@decorator1
@decorator2
class A:
pass # internal class code...
La PEP 3141utilizza le ABC per creare una gerarchia
di tipi di dato che rappresenta varie tipologie di
numeri.
GESTIONE
DELLE ECCEZIONI
Ora tutte le eccezioni devono derivare da BaseEx-
ception(PEP 352) e possono avere un attributo tra-
ceback.
raise RuntimeError("foo occurred").with_traceback
(tracebackobj) # defines an Exceptions with TB
La sintassi di attivazione e di intercettazione delle
eccezioni stata leggermente modificata (PEP 3109,
3110 3134).
try:
print(1 / 0)
except:
raise RuntimeError("Something bad happened")
VISTE SUI DIZIONARI
La PEP 3106 ha introdotto il concetto di dictio-
nary view. Ora i metodi .keys(), .values() e .items()
dell'oggetto dict (l'oggetto dizionario presente
nella Standard Library) restituiscono un set (un
insieme) non ordinato, non pi una lista (list).
Questa modifica permette di operare pi libera-
mente su questi oggetti e risulta di uso pi intuiti-
vo. Ad esempio, possibile verificare se due dizio-
nari contengono le stesse chiavi con un semplice
confronto diretto:
if a.keys() == b.keys():
NUMERI:COSA CAMBIA
La PEP 237 prevede che esista un solo tipo nume-
rico, equivalente al vecchio long ma chiamato int.
In Py3k il risultato di una divisione tra interi sem-
pre un float (numero in virgola mobile), come pre-
visto dalla PEP 238. Questo comportamento sta-
to ritenuto pi intuitivo. La divisione tra interi che
restituisce un intero approssimato per difetto (floor
division) si esegue con il simbolo // (doppio sla-
sh). Ovvero:
NOTA
PYTHON
Python un linguaggio di
programmazione
interpretato, simile a Java
e Ruby. Ha ormai sostituito
il pi anziano Perl in
moltissime applicazioni e
ora considerato uno dei
linguaggi pi diffusi al
mondo. Tra l'altro, viene
usato come linguaggio
interno di automazione e
di personalizzazione da
molti applicativi, come il
desktop manager KDE ed
il modellatore 3D Blender.
Fig. 3: Python Cookbook ActiveState mantiene in funzione da anni questo sito che rac-
coglie circa 2500 'ricette' di programmazione in Python
076-080:072-080 30-09-2008 16:19 Pagina 78
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
79
G
Python 3000, tante novit e incompatibilit con il vecchio
3 / 2 # returns 1,5
3 // 2 # returns 1
FORMATTAZIONE
DI STRINGHE
Seguendo la PEP 3101, stato sostituito il vecchio
meccanismo di formattazione delle stringhe di te-
sto con uno molto pi potente e versatile. Il nuovo
metodo non fa pi uso dell'operatore % ereditato
dal C ed usa invece un nuovo metodo della classe
string: str.format().
"See {0}, {1} and {foo}".format("A", "B", foo="C")
# Returns: "See A, B and C"
Questo nuovo metodo permette di fare riferimen-
to agli elementi variabili del template di testo
usando sia degli identificatori posizionali (numeri)
che dei nomi, anche mescolati tra loro. Inoltre,
possibile fare in modo che il testo venga sostituito
in modo condizionale, sulla base di una variabile
esterna.
OPERATORI
PER LE COMPARAZIONI
Gli operatori di confronto di default di Python han-
no sempre avuto la curiosa capacit di confronta-
re tra loro oggetti di tipo diverso, restituendo ri-
sultati assolutamente privi di senso. Ora questa
funzionalit stata rimossa e Py3k restituisce un er-
rore se si tenta di confrontare oggetti per i quali
non sono stati definiti a priori degli operatori di
confronto adeguati.
Object1 == Object2
# Confronto limitato all'identit. Funziona ancora.
Object1 != Object2
# Confronto limitato alla non
identit. Funziona ancora.
Object1 < Object2
# Solleva una eccezione:
operatore di confronto non definito.
Object1 > Object2
# Solleva una eccezione:
operatore di confronto non definito.
Con l'adozione della PEP 3116, stato completamente
riscritto il sistema di I/O e quindi cambiata la sin-
tassi della funzione open(). La nuova implemen-
tazione aggiunge diverse funzionalit importanti,
tra cui la possibilit di avere o meno un buffer, la pos-
sibilit di decidere un encoding (Unicode) ed il
mapping dei CRLF (Windows) con i LF (Unix). No-
nostante questo, la nuova versione compatibile
quasi al 100% con la vecchia.
open(filename)
# Restituisce un file di testo con buffer
read()
# In questo caso restituisce una stringa
readline()
# In questo caso restituisce una stringa
open(filename, "b")
# Restituisce un file binario con buffer
read()
# Restituisce dei byte quando usata su file binari
readline()
# Non pu essere usata su file binari
NONLOCAL NAME
La PEP 3104 ha introdotto il concetto di nonlo-
cal name. Fino ad ora, Python poteva accedere
solo a oggetti che erano definiti nello spazio dei
nomi locale (la funzione o la classe di apparte-
nenza) o nello spazio globale (il file che contie-
ne lo script od il modulo). Con la dichiarazione
nonlocal ora possibile accedere ad una fun-
zione o ad una variabile che definita nello spa-
zio dei nomi (scope) immediatamente prece-
dente (esterno) a quello in cui ci si trova. Si trat-
ta di una funzionalit simile (ma non equiva-
lente) alle closure di altri linguaggi.
def outer():
x = 42
def inner():
nonlocal x # <--- Accede alla variabile
dichiarata nello scope precedente
Fig. 3: L'indice delle PEP Le Python Enhancement Proposal (PEP) sono i documenti
che descrivono, una per una, le nuove funzionalit e le ragioni che hanno portato alla loro
introduzione
076-080:072-080 30-09-2008 16:19 Pagina 79
ht t p: / / www. i opr ogr ammo. i t
print(x)
x += 1
return inner
IMPORTARE OGGETTI
DA PACKAGE ESTERNI
Py3k segue una logica diversa da Python 2.X per
importare oggetti da package esterni. La nota-
zione standard effettua ora l'importazione solo
da package che sono reperibili in sys.path (solo
nei package standard di sistema). Non cerca pi
l'oggetto all'interno dei package che si trovano nel
path locale. Questo evita collisioni e confusioni
tra package di sistema e package installati dal-
l'utente.
import modulo
# cerca il modulo solo in sys.path
from . import modulo
# cerca anche nel percorso locale
nome-completo-package import modulo
# carica il modulo direttamente
LA MIGRAZIONE
Come abbiamo detto, esiste gi uno strumento au-
tomatico (incluso nella distribuzione ) per la con-
versione del codice da Python 2.X a Py3k: 2to3. Ov-
viamente, questo tool non in grado di sostitui-
re gli import di moduli non pi esistenti e non
in grado di intervenire l dove necessaria una
scelta da parte del programmatore. Di conse-
guenza, 2to3 in grado di convertire solo una
parte del codice. Il resto deve essere convertito a
mano. Il seguente comando converte nomefile.py
da python 2.X a 3.0 (e conserva una copia del-
l'originale).
2to3 -w nomefile.py
Purtroppo, in questo momento 2to3 ancora af-
flitto da alcuni brutti bug, tra cui almeno uno
considerato bloccante (http://bugs.python.org/ is-
sue3131), e quindi non pu essere sfruttato ap-
pieno. Va per detto che gi allo studio una
nuova versione di 2to3, sviluppata durante il Goo-
gle Summer of Code 2008: http://isnomore.net/2to3
/gsocapplicationrev4.
Sul web sono disponibili degli articoli molto uti-
li a comprendere meglio il meccanismo del por-
ting con 2to3. Ad esempio, Sam Ruby, l'autore di
Universal Feedparser (http://www.feedparser.org)
ha fatto un primo, positivo esperimento con 2to3
e ha descritto la sua esperienza sul suo blog
(http://www.intertwingly.net/blog/2007/09/01/2to3),
mettendo a disposizione sia i log che i file diff. Il
team di Django ha fatto una esperienza di mi-
grazione molto significativa ed ha pubblicato un
report molto dettagliato a questo indirizzo:
http://wiki.python.org/moin/PortingDjangoTo3k .
Python 2.6 supporter la migrazione a Py3k in
due modi. Il primo consiste in una nuova op-
zione di invocazione dell'interprete (python -3)
che costringe l'interprete Python a visualizzare
una serie di avvertimenti riguardo alle funzio-
nalit che non saranno pi supportate in Py3k. Il
secondo consiste nella importazione del modu-
lo __future__ . Questo modulo contiene (o, me-
glio, conterr) i backport di molte (ma non tut-
te) le funzionalit di Py3k in Python 2.6. Il se-
guente comando importa la funzionalit Fea-
tureName dal modulo __future__.
from __future__ import FeatureName
Giovanni Bajo, nella sua presentazione di Py3k a
PyCon1 (Giugno 2007) ha proposto un percorso di
migrazione che ci sentiamo di consigliare a tutti:
1) Aggiungere ed utilizzare tutti i __future__ di-
sponibili, in modo da adattare il codice a Py3k in
modo progressivo.
2) Eseguire la propria test suite con python -3
3) Convertire il codice con 2to3
4) Ripulire a mano il codice cos ottenuto
Potete trovare le slide di Bajo qui:
http://www.pycon.it/static/talks/py3k-pycon.pdf . Il video
del suo talk disponibile sul sito di Develer
(http://www.develer.com).
Alessandro Bottoni
SISTEMA M
G
80
/Novembre 2008
Python 3000, tante novit e incompatibilit con il vecchio
NOTA
MULTIPARADIGMA
Python permette di
scrivere codice secondo
diverse modalit, dalla
programmazione
imperativa (come C), alla
programmazione ad
oggetti (come C++) alla
programmazione
funzionale (come Lisp,
Scheme ed Erlang).
Dispone anche di alcuni
costrutti utili al design-by-
contract (a l Eiffel).
Fig. 2: Giovanni Bajo, di Develer SRL, uno dei pi noti
esperti italiani di Python. Sono suoi alcuni dei pi interes-
santi talk del PyCon, la conferenza italiana su Python
076-080:072-080 30-09-2008 16:19 Pagina 80
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
82
/Novembre 2008
JMS con Open Message Queue 4.2
L
avorando con sistemi enterprise di dimensioni
medio-grandi, spesso ci si trova di fronte a proble-
matiche riguardanti la comunicazione tra sistemi
eterogenei. Lesempio classico si ha quando necessario far
parlare una nuova applicazione con un vecchio sistema (o
sistema legacy). Lo strato software che si prende cura di que-
sti aspetti viene chiamato middleware, in quanto si
pone in mezzo alle due tecnologie per consentirne
lintero perabilit e lo scambio dati. Il ruolo principa-
le del middleware quello di fornire allo sviluppato-
re uninterfaccia di programmazione astratta. Cos
facendo, lo sviluppo applicativo diventa considere-
volmente pi semplice in quanto i dettagli di basso
livello e le problematiche di comunicazione vengo-
no mascherate dal middleware stesso.
Una risposta oramai matura ai problemi di intero-
perabilit sono i Message Oriented Middleware. Con
lacronimo MOM si soliti identificare una ben
determinata tipologia di infrastruttura software,
basata su architettura client/server, che permette la
comunicazione asincrona tra sistemi eterogenei.
Questo tipo di middleware solitamente implemen-
tato come una coda, ovvero un sistema che si occu-
pa di memorizzare i messaggi ricevuti e non ancora
consegnati e tiene traccia delle informazioni sui
messaggi gi consumati. Un messaggio pu conte-
nere sia dati che istruzioni software. Le caratteristi-
che pi importanti di un sistema MOM si possono
riassumere nei seguenti punti:
G alto livello di disaccoppiamento: il client interes-
sato a ricevere un messaggio non deve essere
necessariamente attivo durante linvio dello stes-
so. Cos come il client che spedisce non deve
aspettare alcuna risposta.
G indipendenza tra i client: i client comunicano tra
loro senza bisogno di conoscersi lun laltro.
Entrambi si connettono ad una coda e spedisco-
no/ricevono messaggi secondo un formato defi-
nito precedentemente.
G point-to-point o broadcast: possible definire,
durante la fase di configurazione, le modalit di
routing.
I primi componenti MOM rilasciati sul mercato,
nonostante garantissero l'implementazione di siste-
mi eterogenei, affidabili e flessibili, presentavano
una importante carenza: la mancanza di uno stan-
dard comune. Ognuno di questi sistemi dettava un
proprio modo di gestire i messaggi e, di conseguen-
za, una propria API. L'uso di API proprietarie crea
una dipendenza diretta tra i client ed i server che si
traduce nella modifica del codice di tutti i client nel
momento in cui si decide di sostituire MOM.
JAVA MESSAGE
SERVICE
Java Message Service lo standard, facente parte
delle specifiche di Java EE, realizzato da Sun
Microsystems, che fornisce un'astrazione indipen-
dente dal sistema per infrastrutture MOM.
Originariamente sviluppato per consentire laccesso
via Java su sistemi preesistenti, adesso largamente
adottato dai pi importanti produttori di sistemi
MOM. Cos come la tecnologia JDBC (Java Database
Connectivity) permette l'accesso ai pi diffusi data-
base attraverso una combinazione di API e linguag-
gio SQL, JMS mette a disposizione dello sviluppato-
re un'elegante interfaccia che consente la massima
portabilit verso altri sistemi basati sulla stessa spe-
cifica.
Gli oggetti con cui ci si trova a lavorare utilizzando
JMS sono:
G Connection: rappresenta la connessione attiva
tra client e server, consente il trasferimento dei
messaggi da e verso il server.
Per creare una Connection sono disponibili degli
oggetti factory raggiungibili attraverso JNDI (Java
Naming and Directory Interface).
G Session: lo stream sul quale transitano i messag-
gi. Essendo single-thread non utilizzabile in
maniera concorrente. possibile creare una
Session da una specifica Connection ed al
momento della creazione possibile specificare
alcune propriet tra le quali la transazionalit.
G Destination: rappresenta il contenitore dove si
JMS: I MESSAGGI
ASINCRONI
I SISTEMI BASATI SU CODE DI MESSAGGI OFFRONO INTERESSANTI ALTERNATIVE RISPETTO
AI TRADIZIONALI METODI DI INTEROPERABILIT IN AMBITO ENTERPRISE. A TAL PROPOSITO
SCOPRIAMO JMS CON OPEN MESSAGE QUEUE, IL PROGETTO OPEN SOURCE DI SUN
J CD J WEB
message_queue.rar
cdrom.ioprogrammo.it
Conoscenze richieste
Concetti base di
programmazione Java
Software
Java 2 Standard
Edition SDK 1.5.0 o
superiore
Impegno
Tempo di realizzazione
REQUISITI
082-086:072-080 30-09-2008 17:11 Pagina 82
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
83
G
JMS con Open Message Queue 4.2
inviano e ricevono i messaggi. caratterizzato da
un nome e una tipologia (queue o topic). Per
garantirne la portabilit anchesso raggiungibi-
le via JNDI.
G Message: lelemento che sta al centro di tutto il
sistema e rappresenta lentit che transita avan-
ti e indietro e permette la comunicazione tra il
produttore ed il consumatore del messaggio
stesso. creato dalla Session ed caratterizzato
da: header, properties e body.
G Producer: responsabile della creazione dei mes-
saggi da inviare ad una determinata Destination.
G Consumer: loggetto in ascolto su una specifica
Destination da cui ne legge i messaggi.
La specifica JMS distingue tra due diverse tipologie
di Destination: point-to-point (o queue) e publish-
subscribe (o topic). Nella prima il messaggio creato
da un Producer viene consumato da un solo
Consumer mentre nella seconda i messaggi vengono
letti da tutti i Consumer registrati.
OPEN MQ
OpenMQ limplementazione di riferimento della
specifica JMS 1.1. Il progetto, i cui dettagli sono con-
sultabili allindirizzo https://mq.dev.java.net, mem-
bro della community Glassfish. La versione utilizza-
ta in questo articolo la 4.2, ultima release stabile
rilasciata lo scorso Luglio. OpenMQ stato gi adot-
tato in molte soluzioni enterprise di larghe dimen-
sioni in quanto open-source, robusto e con una con-
sistente community alle spalle. Attualmente distri-
buito in due versioni: stand-alone o integrato allin-
terno dellapplication server Glassfish. La versione
stand-alone, che verr utilizzata nella nostra appli-
cazione dimostrativa, composta da un Messaging
Server (anche conosciuto con il nome di broker),
librerie per lintegrazione dei client (Java, C e JCA 1.5
per container JEE) ed una serie di tool per
lamministrazione. Per trasfromare i concetti finora
descritti in azioni pratiche, implementeremo una
semplice applicazione di messagistica tra utenti.
Ciascun utente potr accedere a tale servizio immet-
tendo un nome utente. Una volta connesso, avr la
possibilit di inviare messaggi di testo in modalit
privata (specificando il nome del destinatario) o
pubblica (il messaggio verr ricevuto da tutti gli
utenti connessi al sistema). Il sistema notificher
lutente alla ricezione di ogni singolo messaggio, dif-
ferenziando quelli pubblici da quelli privati. Se il
destinatario di un messaggio privato non in linea
al momento dellinvio, il sistema dovr conservare il
messaggio e recapitarlo alla successiva connessione
dellutente stesso. Lato server, configureremo il con-
tenitore di messaggi pubblici attraverso un topic e
quello per i messaggi privati con una queue.
Per quanto riguarda il client, creeremo una interfac-
cia grafica da cui inviare e ricevere i messaggi.
Nel prosieguo dellarticolo non ci soffermeremo sul
codice riguardante linterfaccia grafica poich va al
di fuori dellargomento trattato.
Prima di iniziare limplementazione necessario
avviare e configurare il server come descritto nella
sezione Configurazione del Broker.
CONNESSIONE
AL BROKER
Il primo passo quello di creare un file di configura-
zione contenente le propriet di connessione verso
il server OpenMq.
In questo modo potremmo agevolmente cambiare i
parametri di connessione senza il bisogno di modi-
ficare il codice:
mq.server.url=localhost:7676
mq.queue.name=private
mq.topic.name=public
La prima propriet indica lURL del server, mentre le
altre due contengono i nomi delle Destination dove
sono memorizzati i messaggi.
La classe MessageManager, che gestir la comunica-
zione con il server mq, avr la responsabilit di leg-
gere tali propriet durante la fase di inizializzazione.
Successivamente, queste propriet verrano utilizza-
te in fase di connessione dal metodo connect:
ConnectionFactory myFctry = new ConnectionFactory();
myFctry.setProperty(ConnectionConfiguration.imqA
COME INIZIARE
indispensabile avere installato
sul computer Java 2 Standard
Edition SDK 1.5.0 o superiore.
preferibile utilizzare l'ultima
versione disponibile sul sito
http://java.sun.com
Scaricare Open Message Queue
dal sito
https://mq.dev.java.net/downloads.
html
Attualmente, possibile
installare il prodotto su
piattaforme Linux, Solaris,
Windows e Unix. Per i primi tre
sistemi operativi sono disponibili
installer corredati di interfaccia
grafica che, attraverso un wizard
abbastanza intuitivo, guidano
l'utente step-by-step nella fase
di installazione.
Alternativamente, possibile
utilizzare il formato compresso
sotto forma di file JAR. In questo
caso sufficiente estrarre il
contenuto dell'archivio e
scaricarlo in una directory a
piacere sul file system.
Per garantire il corretto
funzionamento dei comandi di
amministrazione,
indispensabile impostare la
variabile di sistema
IMQ_JAVAHOME con il percorso
della home directory contenente
il Java Runtime Environment.
Una volta completata la fase di
installazione possibile avviare
il server attraverso lo script
imqbrokerd contenuto nella
cartella bin. Lo startup del server
pressoch immediato e riporta
a video una serie di informazioni
utili (porta di ascolto, storage
path etc.).
082-086:072-080 30-09-2008 17:11 Pagina 83
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
84
/Novembre 2008
JMS con Open Message Queue 4.2
ddressList,this.properties.getProperty("mq.server.url"));
myFctry.setProperty(ConnectionConfiguration.imqR
econnectEnabled, "true");
this.brokerConnection = myFctry.createConnection();
this.brokerConnection.start();
session = this.brokerConnection.createSession
(false, Session.AUTO_ACKNOWLEDGE);
Loggetto Connection viene creato dal
ConnectionFactory a cui viene passato lURL del
broker.
La connessione viene attivata dal metodo start() su
cui successivamente viene creata una Session non
transazionale.
INVIO E RICEZIONE
DEI MESSAGGI
Sempre all'interno del metodo connect() verrano
creati gli oggetti che gestiranno l'invio dei messaggi:
Queue privateRoom = new Queue(this.properties.
getProperty("mq.queue.name"));
this.privateSender =session.createProducer(privateRoom);
Topic publicRoom = new
Topic(this.properties.getProperty("mq.topic.name"));
this.publicSender = session.createProducer(publicRoom);
L'invio dei messaggi privati delegato al metodo
sendPrivateMessage:
AVVIO DELLA CONSOLE DI
AMMINISTRAZIONE
Avviamo la console di
amministrazione eseguendo il
comando imqadmin contenuto nella
cartella bin.
AGGIUNGIAMO UN NUOVO
BROKER
Tasto destro su Brokers _ Add Broker.
Inseriamo come label
ioProgrammoChat e settiamo le
informazioni relative alla connessione.
Prima di iniziare l'implementazione della nostra applicazione di esempio necessario configurare il server OpenMQ.
Le istruzioni sono riportate nel seguente riquadro.
CONFIGURAZIONE DEL BROKER IN SEI PASSI
NUOVE
DESTINATIONS
Tasto destro su Destination _ Add
Broker Destination. necessario
aggiungere due tipi di Destination: una
Queue (private) ed un Topic (public).
1
3 2
DEOBJECT
STORE
Tasto destro su Object Stores _ Add
Object Store. La propriet
java.naming.factory.initial
valorizzata con com.sun.jndi.fscon -
text.RefFSContextFactory.
CONNECTION
FACTORY
Tasto destro su Connection Factories
_ Add Connection Factory Object.
un passaggio fondamentale nella
costruzione del collegamento.
DESTINATION
OBJECTS
Tasto destro su Destinations _ Add
Destination Object. necessario
aggiungere un Destination Object per
ogni Destination configurata.
4 5 6
082-086:072-080 30-09-2008 17:11 Pagina 84
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
85
G
JMS con Open Message Queue 4.2
public void sendPrivateMessage(String text, String user) {
try {
TextMessage msg = session.createTextMessage();
msg.setStringProperty("recipient", user);
msg.setStringProperty("sender",this.username);
msg.setText(text);
privateSender.send(msg);
} catch (JMSException ex) {
Logger.getLogger(MessageManager.
class.getName()).log(Level.SEVERE, null, ex); }
}
Un oggetto di tipo TextMessage viene creato dalla
Session e le propriet recipient e sender vengono
valorizzate rispettivamente con i nomi utenti del
destinatario e del mittente. Analogamente il metodo
sendPublicMessage si occupa dell'invio dei messaggi
verso la Destination pubblica:
public void sendPublicMessage(String text) {
try { TextMessage msg = session.createTextMessage();
msg.setStringProperty("sender", this.username);
msg.setText(text);
publicSender.send(msg);
} catch (JMSException ex)
{
Logger.getLogger(MessageManager.
class.getName()).log(Level.SEVERE, null, ex); }
}
Una volta completato l'invio dei messaggi non ci
rimane che implementare la parte di ricezione.
Come descritto precedentemente, JMS permette lo
scambio di messaggi in modo asincrono pertanto il
nostro codice non dovr interrogare direttamente le
Destination (polling), ma verr automaticamente
notificato dal sistema della presenza di un nuovo
messaggio:
session.createConsumer(publicRoom).setMessage
Listener(this);
session.createConsumer(privateRoom, "recipient=
'"+username+"'").setMessageListener(this);
La prima istruzione crea un ascoltatore verso i mes-
saggi contenuti nel topic, mentre la seconda lo fa
sulla queue. In quest'ultima, nella parte relativa alla
creazione del Consumer, presente un parametro
aggiuntivo denominato selector, il cui compito fil-
trare i messaggi a cui siamo interessati. Il selector
definito da una stringa la cui sintassi basata su un
sottoinsieme del linguaggio SQL92. Nel nostro caso
il selector ci permette di filtrare i messaggi sulla
queue relativi all'utente attualmente connesso al
client.
Infine, la nostra classe dovr implementare
l'interfaccia javax.jms.MessageListener e definire il
metodo onMessage:
public void onMessage(Message msg)
{ ChatMessage cm = new ChatMessage();
try { cm.setPublicMsg(msg.getJMSDestination()
instanceof Topic);
cm.setUser(msg.getStringProperty("sender"));
cm.setText(((TextMessage)msg).getText()); }
catch (JMSException ex)
{
Logger.getLogger(MessageManager.
class.getName()).log(Level.SEVERE, null, ex);}
listener.messageReceived(cm);
//notifica la GUI della ricezione di un msg
}
Il Message ricevuto viene mappato all'interno di un
oggetto ChatMessage, che fa parte del model della
nostra applicazione, e viene notificato all'interfaccia
grafica che si occuper di informare l'utente dell'av-
venuta ricezione di un nuovo messaggio.
ALTRE FUNZIONALIT
Nel nostro esempio stata utilizzato il file system
per la persistenza dei messaggi. Oltre a questa
opzione, OpenMq supporta anche database relazio-
nali raggiungibili via JDBC. Inoltre, OpenMq contie-
ne una lista di funzionalit molto interessanti, che
non fanno parte della specifica JMS e che completa-
no il prodotto facilitando molte attivit. Tra queste,
una delle pi utili, sicuramente la Dead Message
Queue: una particolare coda dove vengono automa-
ticamente spostati i messaggi scaduti, ovvero non
consegnati dopo un determinato tempo (configura-
bile dalla console di amministrazione). possibile
esaminare il contenuto di questa speciale coda in
modo da monitorare ed individuare eventuali mal-
funzionamenti.
Fabrizio Fortino
NOTA
ALTRE
IMPLEMENTAZIONI
JMS OPEN SOURCE
GActiveMQ
//activemq.apache.org
G JBossMQ
//wiki.jboss.org/wiki/JBos
sMQ
G JORAM
//joram.objectweb.org
G Mantaray
//sourceforge.net/
projects/mantaray
G OpenJMS
//openjms.source
forge.net
G OSMQ
www.osmq.org
G xmlBlaster
www.xmlblaster.org
Fig. 1: La nostra applicazione di scambio messaggi in esecuzione
082-086:072-080 30-09-2008 17:11 Pagina 85
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
88
/Novembre 2008
Grafici statistici in Java grazie a JFreeChart
M
olti programmi offrono funzioni per ma-
nipolare il contenuto delle basi di dati:
dalla gestione degli ordini alle vendite,
dalla raccolta di dati scientifici alla gestione di so-
cial networks. A lungo andare, in tutte queste si-
tuazioni, si creano notevoli mole di dati e la visua-
lizzazione tramite tabelle risulta presto inadegua-
ta per chi deve avere una visione sintetica sulla
struttura. Per questo motivo la visualizzazione di da-
ti tramite grafici riassuntivi risulta essere una ca-
ratteristica fondamentale per chi deve fare analisi
e prendere decisioni. Inoltre, anche in applicazio-
ni non molto complesse, la presenza di grafici pu
apparire allutente come una funzionalit di pre-
stigio del nostro sistema.
Evidentemente, creare da zero un supporto per
rappresentare grafici, un compito che richiede
molte conoscenze di base, nonch molte risorse
in termini di sviluppo e test. Quindi, una valida al-
ternativa costituita dalluso di librerie free o com-
merciali. In questo articolo vedremo con quanta
semplicit possibile arricchire le nostra applica-
zioni con dei magnifici grafici grazie allintegra-
zione della libreria JFreeChart.
CARATTERISTICHE
DI JFREECHART
JFreeChart una libreria free realizzata interamente
in Java, che rende semplice la creazione e visua-
lizzazione di grafici allinterno di applicazioni.
David Gilbert ha rilasciato la prima versione di
JFreeChart nel 2000 e, a tuttoggi, il progetto con-
tinua a diffondersi ed evolversi con grande viva-
cit. Le principali caratteristiche di questa libreria
sono:
G Una grande variet di tipologie di grafici sup-
portati.
G Possibilit di integrazione in applicazioni client-
side e server-side.
G Integrazione in JasperReports e Struts tramite ap-
positi plug-ins.
G Possibilit di otput in vari formati tra cui swingcom-
ponents, file di immagini (PNG, JPG), file di tipo
vettoriale (PDF, EPS e SVG).
G La libreria distribuita con licenza LGPL.
Dopo esserci procurati la libreria dal sito ufficiale
vedi riquadro potremo farci unidea delle poten-
zialit di questo strumento lanciando il file jfreechart-
version-demo.jar. I grafici che possiamo rappre-
sentare sono i pi disparati: a torta, a barre, ad aree,
di tipo finanziario, di Gantt, a linee, statistici, di di-
spersione, di regressioni, serie temporali e an-
che pi divertenti come tachimetri, termometri e
orologi. Inoltre, la libreria offre grandi possibilit di
personalizzazione nei colori degli elementi e del-
lo sfondo, nei font e nel tipo di visualizzazione. Vo-
lendo potremo estendere la libreria implemen-
tando le varie interfacce, in funzione dei nostri sco-
pi.
La libreria ben strutturata, per cui, a prescinde-
re dal tipo di grafico che ci interessa realizzare,
avremo bisogno dei seguenti elementi:
G Creazione di un Dataset. Come vedremo tra bre-
ve questo elemento ci permetter di organizza-
re i dati da inserire nel grafico. La libreria forni-
sce numerose tipologie di Dataset, dovremo se-
lezionare quella adatta al nostro grafico.
G Creazione di un oggetto JFreeChart, elemento
che contiene effettivamente il grafico, tramite la
ChartFactory.
G Renderizzare il grafico e visualizzarlo o salvarlo.
A questo punto possiamo realizzare il nostro primo
grafico.
GRAFICI A TORTA
Un grafico a torta pu mostrare a colpo docchio
lincidenza dei singoli elementi sul totale, vedia-
mo come crearlo. Per prima cosa abbiamo biso-
gno di un Dataset appropriato. Tra quelli presenti
nella libreria possiamo usare il DefaultPieDataSet
UNA LIBRERIA FREE
PER CREARE GRAFICI
UN REPORT, SPESSO, DEVE ESSERE ACCOMPAGNATO DA GRAFICI, CHE MEGLIO AIUTANO A
INTERPRETARE QUELLO CHE POTREBBE SEMBRARE UN INCOMPRENSIBILE GROVIGLIO DI
NUMERI. ECCO ALLORA CHE JFREECHART PU ESSERE UNA VERA MANNA DAL CIELO
Conoscenze richieste
Java
Software
JDK, JFreeChart 1.0.9
Impegno
Tempo di realizzazione
REQUISITI
088-093:072-080 30-09-2008 16:50 Pagina 88
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
89
G
Grafici statistici in Java grazie a JFreeChart
che contiene elementi costituiti da una String uti-
le per rappresentare letichetta e un valore Dou-
ble. Vediamo come crearlo e popolarlo con alcuni
elementi:
DefaultPieDataset ds = new
DefaultPieDataset();
ds.setValue("Mele", 10.0d);
ds.setValue("Pere", 6.0d);
ds.setValue("Banane", 8.4d);
ds.setValue("Mandarini", 3.2d);
Dopo aver creato il Dataset, abbiamo inserito quat-
tro valori costituiti dal nome delletichetta e dal cor-
rispettivo valore assoluto. A questo punto creiamo il
grafico attraverso la classe statica ChartFactory. Que-
sta classe ci permette di generare tutti i grafici contenuti
nella libreria tramite metodi della forma ChartFac-
tory.createChartName. Vediamo il codice:
JFreeChart c = ChartFactory.createPieChart(
"Frutta", // titolo
ds, // dataSet
true, // legenda
true, // ToolTip
false // Locale
);
Esistono vari costruttori per ottenere uno stesso tipo
di grafico. Quello presentato uno dei pi semplici e
richiede il titolo, la sorgente di dati e una serie di flag
per impostare alcune propriet di visualizzazione.
Non ci rimane dunque che decidere come utilizzare
il nostro grafico, possiamo salvarlo come immagine
o visualizzarlo a video. Procediamo con la visualiz-
zazione a video: in questo caso JFreeChart ci viene
incontro fornendoci la classe ChartPanel che esten-
de un normale JPanel. Ecco il codice:
ChartPanel chartPanel = new ChartPanel(c,
false);
Indichiamo al costruttore il grafico che desideria-
mo visualizzare, il secondo parametro specifica se
il pannello deve essere bufferizzato. Il risultato
mostrato in figura:
Una delle caratteristiche di JFreeChart la possibilit
di visualizzare i grafici sia in modo bidimensiona-
le che tridimensionale. Infatti, sostituendo il co-
struttore della ChartFactory che abbiamo appena
usato con ChartFactory.createPieChart3De usan-
do gli stessi parametri otterremo il grafico 3D, co-
s come in Fig. 2.
Premendo il tasto destro del mouse sul pannello
che abbiamo creato, si visualizza un menua tendina
che ci permette di salvare o stampare il grafico. Ov-
viamente queste funzionalit possono essere di-
sabilitate, ma dato che non ci costano nulla, perch
non impiegarle?
Nel codice precedente abbiamo usato un Dataset
molto semplice, ma la libreria ne offre di pi evo-
luti. Unalternativa costituita da JDBCPieDataset,
in questo caso possiamo passare al costruttore del
Dataset la connessione JDBCad un database e una
stringa corrispondente allinterrogazione. La query
deve ritornare due campi, letichetta e il valore re-
lativo. Quindi, una possibile interrogazione la se-
guente:
SELECT o.cliente, SUM(o.qta) FROM ordini o GROUP
BY o.cliente
per ottenere il grafico che rappresenta la percentuale
di ordini effettuati da ogni cliente.
JFreeChart ci permette, inoltre, di aver accesso a
vari parametri di visualizzazione. Se, per esempio,
desideriamo cambiare la dimensione del font del-
le etichette e il colore di sfondo possiamo farlo at-
traverso il seguente codice:
PiePlot3D plot = (PiePlot3D) c.getPlot();
plot.setLabelFont(new Font("Sans Serif",
Font.PLAIN, 20));
plot.setLabelBackgroundPaint(Color.getHSBColor(0.6f,
0.1f, 1.0f));
plot.setBackgroundPaint(Color.CYAN);
Come si vede dal codice, recuperiamo un oggetto Fig. 1: Grafico a torta in applicazione swing
Fig. 2: Visualizzazione tridimensionale del grafico a torta
NOTA
JCOMMON
Per utilizzare JFreeChart
avremo bisogno anche della
versione adeguata di
JCommon. Solitamente la
troveremo nello stesso
pacchetto di JFreeChart.
Questa libreria contiene un
insieme di classi di utilit.
088-093:072-080 30-09-2008 16:51 Pagina 89
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
90
/Novembre 2008
Grafici statistici in Java grazie a JFreeChart
di tipo PiePlot3Ddalla nostra istanza di JFreeChart.
Dato che JFreeChart la classe che ci permette di
gestire tutti i tipi di grafico, sar necessario effettuare
un cast per ottenere il Plot adatto. A questo punto
abbiamo accesso a numerosi elementi grafici. Per
brevit nel codice ci limitiamo a modificare il back-
grounddelle label e del grafico, e impostiamo un font
pi grande per rendere pi leggibili le etichette. In
figura rappresentato il risultato:
DIAGRAMMI A BARRE
I diagrammi a barre sono impiegati in numerosi
casi. Per esempio ci permettono di visualizzare e
comparare landamento delle vendite in vari ne-
gozi in un determinato periodo. A differenza dei
diagrammi a torta, otteniamo una visualizzazio-
ne in termini assoluti, non una proporzione tra i
vari elementi. Per prima cosa creiamo il Dataset
ed inseriamo alcuni valori. Il Dataset dei diagram-
mi a barre il DefaultCategoryDataset. Questo
DataSet permette di organizzare i valori da visua-
lizzare in serie e categorie. Vediamo il codice:
DefaultCategoryDataset ds = new
DefaultCategoryDataset();
String s1 = "Mele";
String s2 = "Pere";
String s3 = "Banane";
String s4 = "Mandarini";
String c1 = "01";
String c2 = "02";
ds.addValue(10.0d, s1, c1);
ds.addValue(6.0d, s2, c1);
ds.addValue(8.4d, s3, c1);
ds.addValue(3.2d, s4, c1);
ds.addValue(9.0d, s1, c2);
ds.addValue(7.0d, s2, c2);
ds.addValue(5.3d, s3, c2);
ds.addValue(3.7d, s4, c2);
Le serie del nostro grafico composta dai nomi dei
frutti, le categorie sono i valori 01 e 02, che pos-
siamo immaginare come i mesi dellanno. Quin-
di, il grafico ci permette di vedere landamento di
quattro distinte serie in due momenti diversi. Creia-
mo ora il grafico con la ChartFactory:
JFreeChart c = ChartFactory.createBarChart3D(
"Frutti", // titolo
"Mesi", // dominio
"Qt", // range
ds, // dataSet
PlotOrientation.VERTICAL, // orientamento
true, // legenda
true, // tooltips
false // URLs
);
Come per il diagramma a torta, a questo punto
dobbiamo inserire loggetto JFreeChart nel Chart-
Panel. La figura mostra il risultato:
La correlazione esistente tra i grafici a barre e quel-
li a torta abbastanza chiara. Infatti, un grafico a
torta pu rappresentare le serie allinterno di una
determinata categoria, o una singola serie su pi ca-
tegorie. JFreeChart mette a disposizione la classe Cat-
egoryToPieDataset che ci permette di recuperare i
dati dal Dataset di un grafico a barre per ottenere
un PieDataset.
In alcuni casi la visualizzazione allinterno di unin-
terfaccia swing potrebbe non essere utile per la no-
stra applicazione. Tramite JFreeChart semplice
ottenere altri tipi di output attraverso la classe Char-
tUtilities. Ad esempio possiamo salvare unimma-
gine JPEG:
try{
ChartUtilities.saveChartAsJPEG(
new File("grafico.jpg"),
c,
700,
700);
}catch(IOException e){
Fig. 3: Accesso alle propriet di visualizzazione
Fig. 4: Diagramma a barre: serie e categorie
NOTA
DAVID GILBERT
David Gilbert, il fondatore
del progetto JFreeChart
mantiene un blog sul quale
possiamo trovare numerosi
esempi e le informazioni
relative ai nuovi rilasci:
http://www.jroller.com/dg
ilbert
088-093:072-080 30-09-2008 16:51 Pagina 90
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
91
G
Grafici statistici in Java grazie a JFreeChart
System.out.println("Errore creazione file\n"
+ e);
}
I parametri della funzione sono: il file che voglia-
mo creare, il grafico e le dimensioni dellimmagi-
ne. Un metodo equivalente saveChartAsPNG()
per salvare immagini di tipo png.
IL PIANO CARTESIANO
Sebbene non sia particolarmente complesso rea-
lizzare con java un programma in grado di rap-
presentare funzioni matematiche, potremmo vo-
ler rappresentare qualche tipo di informazione sul
piano cartesiano attraverso JFreeChart.
Per prima cosa necessario individuare il DataSet
che fa al caso nostro. Un buon punto di partenza
linterfaccia XYDataset. Tra le sue implementazio-
ni troviamo DefaultXYDataset. Si tratta di unim-
plementazione piuttosto semplice, che ci permet-
te di inserire varie serie composte da unetichetta
e da una matrice di valori. La matrice deve essere
costituita da due vettori di uguale dimensione, uno
per rappresentare il valore sulle ascisse (asse x),
laltro per il valore sulle ordinate (asse y).
Proviamo a realizzare il codice che ci permette di
rappresentare una semplice parabola di equazio-
ne y=x
2
:
DefaultXYDataset ds = newDefaultXYDataset();
int points = 101;
double[][] val = new double[2][points];
for (int i = 0; i < points; i++){
int x = i - (points/2);
val[0][i] = x;
val[1][i] = x * x;
}
ds.addSeries("Parabola", val);
Dopo aver istanziato il Dataset , decidiamo il numero
di punti che desideriamo rappresentare, e riem-
piamo la matrice con un ciclo che calcola i valori dei
vari punti da rappresentare. Ovviamente, pi fitti
saranno i punti, maggiormente definita sar la rap-
presentazione. Infine inseriamo la matrice otte-
nuta nel Dataset assegnandole unetichetta.
Non ci resta che creare il grafico:
JFreeChart c = ChartFactory.createXYLineChart(
"Piano Cartesiano", //Titolo
"X", //Label
"Y",
ds, //DataSet
PlotOrientation.VERTICAL,
//Orientamento
true, //Legenda
true, //ToolTip
false); //Url
Possiamo inserire nel grafico tutte le serie che de-
sideriamo. Ad esempio la Fig. 5 riporta un grafico
cartesiano con una parabola e una retta:
Il metodo utilizzato per rappresentare le funzioni
semplice e intuitivo, ma volendo fare un lavoro mi-
gliore sarebbe stato preferibile implementare un
XYDataset personalizzato atto a risolvere i valori
delle funzioni. Infatti, con il metodo usato, variando
il livello di zoompotremo notare che la parabola
composta da vari segmenti e che presenta un ini-
zio e una fine. Implementando il nostro Dataset
potremmo calcolare adeguatamente i punti in fun-
zione della visualizzazione.
DIAGRAMMI STATISTICI
I grafici statistici possono essere estremamente
utili per fornire una visualizzazione corretta dei
dati. Per esempio, possiamo rappresentare il valo-
re medio di una grandezza ma anche lo scarto tra
il valore minimo e massimo della grandezza al-
linterno di diverse categorie o la deviazione stan-
dard dalla media.
Proviamo a creare un diagramma a linee che rap-
presenti questi dati: come sempre, per prima cosa,
scegliamo il Dataset. Tra le classi fornite dalla li-
breria, quella che fa al caso nostro DefaultStatis-
ticalCategoryDataset. Tale classe unimplemen-
tazione di StatisticalDataset che permette, in ge-
nerale, di rappresentare molti dati statistici.
In particolare il seguente esempio crea la rappre-
sentare di una rilevazione fatta sul peso di alcuni og-
getti suddivisi in tre classi. Il Dataset ci permette
di inserire il valore medio rilevato e la deviazione
NOTA
SUL WEB
Il sito ufficiale da cui poter
scaricare la libreria e la
documentazione dellAPI :
http://www.jfree.org/jfre
echart/
NOTA
DEVIAZIONE
STANDARD
La deviazione standard o
scarto quadratico medio
un indice di dispersione,
cio una misura di
variabilit di una
popolazione intorno al
valore atteso, ha la stessa
unit di misura dei valori
osservati. Ha svariati usi,
per esempio, in ambito
finanziario, viene usata per
indicare la variabilit di
unattivit finanziaria
ovvero il suo rischio.
Fig. 5: Rappresentazione di retta e parabola sul piano
cartesiano
088-093:072-080 30-09-2008 16:51 Pagina 91
ht t p: / / www. i opr ogr ammo. i t
standard. Ecco il codice:
DefaultStatisticalCategoryDataset ds = new
DefaultStatisticalCategoryDataset();
String r1 = "Peso";
String c1 = "classe 1";
String c2 = "classe 2";
String c3 = "classe 3";
ds.add(10d, 2.2d, r1, c1);
ds.add(12d, 1.0d, r1, c2);
ds.add(9.0d, 2.0d, r1, c3);
Dopo aver istanziato il Dataset creiamo un ele-
mento di riga che rappresenta una rilevazione e
tre elementi di categoria, cio le tre classi di og-
getti su cui definita la nostra indagine. Per ogni
classe specifichiamo poi il valore della media e del-
la deviazione standard. A questo punto proviamo
a visualizzare il grafico come al solito:
JFreeChart c = ChartFactory.createLineChart(
"Visualizzazione statistica",
"Classi di oggetti",
"Peso",
ds,
PlotOrientation.VERTICAL,
true,
true,
true);
Il risultato mostrato nella Fig. 6:
Ma questo non quello che desideriamo. Infatti,
creando il grafico tramite createLineChart(), otte-
niamo un sistema che visualizza un Category-
Dataset, come quello che abbiamo usato per il dia-
gramma a barre. Noi vogliamo che il nostro grafi-
co rappresenti dei dati pi specializzati relativi al-
le statistiche. Per fare ci dobbiamo intervenire sul
sistema di visualizzazione introdotto in prece-
denza. In particolare dobbiamo recuperare lelemento
CategoryPlot e specificare un differente Renderer,
adeguato al nostro StatisticalDataset. Ecco il codi-
ce:
CategoryPlot plot = (CategoryPlot) c.getPlot();
plot.setRenderer( new
StatisticalLineAndShapeRenderer());
Lo StatisticalLineAndShapeRenderer modifica il si-
stema di visualizzazione del grafico a linee intro-
ducendo i dati statistici. Il risultato finale e corret-
to mostrato nella Fig. 7:
DIAGRAMMA DI GANTT
Il diagramma di Gantt permette di visualizzare la
strutturazione nel tempo di una determinata at-
tivit che attraversa varie fasi.
Per esempio possiamo utilizzare un diagramma
di Gantt per mostrare la richiesta di tempo per
la lavorazione di un lotto di pezzi attraverso va-
ri reparti.
Oppure possiamo utilizzarlo per descrivere la ri-
chiesta di tempo di un progetto attraverso varie
fasi: analisi, progettazione, sviluppo, testing etc.
JFreeChart ci permette di creare agevolmente
anche questa categoria di diagrammi. Per qual-
che motivo, per, i progettisti hanno pensato il
Dataset di questo tipo di grafici in un modo leg-
germente diverso. Dovremo infatti specificare i
dati direttamente nel costruttore del Dataset. Ve-
diamo un esempio:
String[] seriesNames = {"Tempo previsto"};
double[][] starts = {{1, 4, 5, 8, 13}};
double[][] ends = {{4, 5, 8, 13, 18}};
DefaultIntervalCategoryDataset ds = new
DefaultIntervalCategoryDataset(starts, ends);
ds.setSeriesKeys(seriesNames);
SISTEMA M
G
92
/Novembre 2008
Grafici statistici in Java grazie a JFreeChart
Fig. 6: Visualizzazione di dati statistici tramite LineChart
Fig. 7: Visualizzazione statistica con media e deviazione
standard
NOTA
DIAGRAMMI
DI GANTT
Il diagramma di Gantt uno
strumento di supporto alla
gestione dei progetti.
Prende il suo nome
dallingegnere statunitense
Henry Laurence Gantt che
lo ide nel 1917.
088-093:072-080 30-09-2008 16:51 Pagina 92
ht t p: / / www. i opr ogr ammo. i t
Il Dataset da utilizzare DefaultIntevalCatego-
ryDataset, che come gi il nome suggerisce ci
permette di organizzare dei dati basati su inter-
valli allinterno di categorie. Uno dei costrutto-
ri della classe prende come parametri due ma-
trici che identificano i punti di start e end delle at-
tivit nei vari reparti.
Nei vettori starts e ends abbiamo specificato una
sola serie di dati che attraversa 5 operazioni. In
particolare abbiamo la prima operazione che
inizia a 1 e finisce a 4, loperazione 2 che inizia a
4 e finisce a 5 e cos via. Si noti che il primo indice
della matrice indica la serie, mentre il secondo in-
dica lattivit.
Le matrici di inizio e fine devono avere la stessa
dimensione altrimenti si generer un Exception
a run-time.
A questo punto possiamo creare il grafico nel so-
lito modo:
JFreeChart c = ChartFactory.createGanttChart(
"Diagramma di Gantt",
"Tempo",
"Reparti",
ds,
true,
true,
false);
Il risultato mostrato in Fig. 8:
ALTRE TIPOLOGIE
DI GRAFICI
Come accennato nellintroduzione JFreeChart
permette di visualizzare anche diagrammi pi
particolari che potremmo impiegare ad esem-
pio per monitorare landamento di una variabi-
le. Un esempio di questi diagrammi costituito
dal termometro in grado di indicare la tempera-
tura. A differenza dei grafici presentati sinora,
questo diagramma viene fornito in un modo de-
cisamente differente. Non necessario creare
un Dataset e non presente nessun metodo del-
la ChartFactory in grado di creare questa tipolo-
gia di grafico.
Per creare il termometro usiamo la classe JTher-
mometer che un estensione di un JPanel, quin-
di possiamo inserire la rappresentazione diret-
tamente nellinterfaccia swing. La classe forni-
sce vari metodi per impostare il valore selezio-
nato, il range dei valori rappresentati e molto al-
tro. Ecco il codice:
JThermometer jt = new JThermometer();
jt.setBackground(Color.WHITE);
jt.setValue(22.0d);
jt.setRange(-30.0d, 50.0d);
Il risultato che si ottiene mostrato nellimmagine:
CONCLUSIONI
Inserire grafici nelle nostre applicazioni una
caratteristica che pu determinare il successo o
meno del nostro progetto. Infatti, spesso gli uten-
ti badano molto allaspetto estetico dellappli-
cazione. Inoltre, i grafici si rivelano un potente
strumento di visualizzazione in numerose si-
tuazioni.
La libreria JFreeChart si presenta ben struttura-
ta anche se alcune scelte implementative ci lasciano
perplessi, ad esempio la differente modalit di
accesso al Thermometer. Comunque eviden-
te il grande lavoro che stato svolto e le grandi po-
tenzialit di questo strumento.
Possiamo usare JFreeChart per arricchire le no-
stre applicazioni con una grande variet di gra-
fici senza bisogno di conoscenze particolari. Inol-
tre, se seguiamo il progetto scopriremo che ven-
gono rilasciati regolarmente aggiornamenti e
nuove funzionalit.
Marco Buccio
M SISTEMA
Novembre 2008/
93
G
Grafici statistici in Java grazie a JFreeChart
LAUTORE
Marco Buccio ha
conseguito la laurea
specialistica in Scienze
dellInformazione presso
lUniversit di Milano con
una tesi riguardante
sistemi di collaborazione
basati sullAugmented
Reality. Appassionato di
innovazione e tecnologia,
sviluppa applicazioni Java
e programmi per il
supporto alla didattica.
Potete contattarlo al
seguente indirizzo:
marco.buccio@gmail.com
Fig. 8: Esempio di diagramma di Gantt
Fig. 9: Il termometro visualizzabile con il pannello
JThermometer
088-093:072-080 30-09-2008 16:51 Pagina 93
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
94
/Novembre 2008
Sviluppare applicazioni per Facebook
F
acebook oramai un fenomeno di massa.
Dopo l'incredibile successo iniziale come
luogo di incontro e di ricerca di vecchie e
nuove amicizie, dal giugno 2007 l'apertura a tutti gli
utenti della piattaforma di sviluppo Facebook De-
veloper e il proliferare di piccole applicazioni, a
volte utili, altre volte dal carattere puramente ludico,
hanno fatto s che l'esperienza sul social network
diventasse varia e "magnetica".
Il fenomeno dei social network, Facebook e My-
Space fra i maggiori, interessa non a caso ai mag-
giori player del mondo dell'intrattenimento. Dal-
le ultime analisi emerso che il traffico generato da-
gli utenti dei social network supera quello genera-
to dai siti per soli adulti (fonte Click: What Millions
of People Are Doing Online and Why it Matters di
Bill Tancer)
PERCH UN'AMBIENTE
DI SVILUPPO FACEBOOK?
La possibilit di scrivere un'applicazione che vie-
ne utilizzata all'interno di Facebook, piuttosto che
realizzare un proprio sito web, offre innumerevo-
li vantaggi. Innanzitutto il login unico. L'utente
non deve compilare noiosi form di registrazione
che il pi delle volte prevedono anche una visita
alla propria e-mail, ma possono semplicemente
con un clic dare la propria adesione, utilizzare
l'applicazione e nel caso buttarla via. Questa rapi-
dit fa si' che gli utenti provino diverse applica-
zioni, dando ad ognuna la possibilit di essere co-
nosciuta e di diffondersi. L'integrazione delle ap-
plicazioni con le funzionalit del social network,
mediante strumenti che utilizzeremo in questa se-
rie di articoli, fa che l'applicazione, se ritenuta va-
lida, si diffonda in modo virale nel bacino di uten-
za Facebook che, ad oggi, conta oltre 100 milioni di
utenti nel mondo. Il tipo di diffusione possibile
viene detto "virale" perch rapportabile alla diffu-
sione dei virus come l'influenza. Una persona la
trasmette ad alcuni suoi conoscenti che a loro vol-
ta la trasmettono ad altri, ed ecco che in brevissi-
mo tempo si parla di contagio diffuso su tutto il
territorio. In questa serie di articoli vedremo qua-
li sono gli accorgimenti necessari per ridurre al
massimo la latenza della nostra applicazione.
Non mancano esempi di valide applicazioni che
si sono diffuse in modo talmente vasto da attrarre
investitori. Lo sviluppatore Craig Ulliott dopo tre set-
timane dal lancio di "Where I've been" scriveva
delle difficolt incontrate nello scalare la propria ap-
plicazione per servire i suoi 250.000 utenti regi-
strati. (fonte: http://www.insidefacebook.com/2007/06/21/i-
have-250000-users-now-what/)
Due mesi pi tardi, con una base d'utenza di 2.3
milioni di utenti, l'applicazione stata acquistata
per 3 milioni di dollari. (fonte: http://www.techcrunch.com/
2007/08/16/tripadvisor-acquires-facebook-app-where-
i%e2%80%99ve-been-for-3-million)
COME FUNZIONA
UN'APPLICAZIONE
FACEBOOK ?
Dal nostro punto di vista, un'applicazione Face-
book funziona come un normalissimo sito web.
La differenza consiste nel fatto che gli utenti non ac-
cederanno mai direttamente al nostro server, ma ci
sar sempre Facebook come intermediario tra il
browser dell'utente e la nostra applicazione.
L'utente, tramite il browser, invia una richiesta
HTTP a Facebook, il quale la processa, aggiun-
gendo dei parametri con informazioni relative al-
l'utente e la inoltra al nostro server, su una URL di
callback.
Il nostro server elabora la risposta e restituisce del-
l'HTML arricchito con dei tag speciali FBML(Face-
RUBY ON RAILS
SPOSA FACEBOOK
IN QUESTO PRIMO ARTICOLO DEDICATO ALLA PROGRAMMAZIONE DI FACEBOOK, VEDREMO
COME PREPARARE L'AMBIENTE DI SVILUPPO, CI INTERFACCEREMO CON IL SITO DI SOCIAL
NETWORKING E INIZIEREMO A INTERAGIRE CON I DATI DEGLI UTENTI
Conoscenze richieste
Ruby on Rails di base
Software
Ruby 1.8, Rails 2.1.1
Impegno
Tempo di realizzazione
REQUISITI
Fig. 1: Flusso delle richieste HTTP
094-097:072-080 30-09-2008 15:59 Pagina 94
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
95
G
Sviluppare applicazioni per Facebook
book Markup Language). Il server Facebook pro-
cessa la nostra risposta reinterpretando i tag FBML
e riportando all'utente del normalissimo HTML.
In questo processo ritorna fortemente il problema
della latenza. Dato che le richieste devono fare un
doppio passaggio rispetto a un normale sito web,
Facebook tollera dalle applicazioni un tempo di ri-
sposta di massimo 8 secondi altrimenti restituisce
un errore. In questo modo applicazioni "lente" che
darebbero un'esperienza frustrante per l'utente
vengono messe "fuori mercato" in modo naturale.
SETUP DELLAMBIENTE
DI SVILUPPO
evidente che dal punto di vista dello sviluppo
questo meccanismo ci pone dinnanzi a un pro-
blema: non possiamo sviluppare l'applicazione
Rails come siamo abituati, testando la nostra ap-
plicazione puntando il browser su http://localhost:3000.
Ma dobbiamo farlo da http://apps.facebook.com, dan-
do a Facebook la possibilit di accedere alla nostra
applicazione.
Infatti la URL di callback deve essere necessaria-
mente pubblica, cio accessibile dall'esterno.
Abbiamo a questo punto due possibilit: la prima
pi evidente utilizzare un server pubblico per lo
sviluppo. Ma una soluzione un po' scomoda, vi-
sto che ci costringerebbe a fare un deploy conti-
nuo delle modifiche che facciamo in locale. Un'al-
tra possibilit consiste nel creare un accesso su in-
ternet al nostro server locale. Anche in questo ca-
so ci sono diverse possibilit, noi utilizzeremo un
tunnel SSH.
Per fare questo necessario configurare un server
pubblico che riceve le richieste da internet e le inol-
tra nel "tunnel" che arriva fino alla nostra mac-
china. Non possedendo alcun server, possiamo
utilizzare il servizio www.tunnlr.comche stato crea-
to appositamente per facilitare lo sviluppo di ap-
plicazioni Facebook. Il servizio gratuito per 30
giorni, e successivamente richiede un pagamen-
to di 5$ al mese. La registrazione rapidissima:
aver inserito username, password ed e-mail, do-
po la consueta conferma dalla mail, ci ritroviamo
su una pagina che riporta tutte le informazioni ne-
cessarie per proseguire:
G La nostra URL pubblica (esempio http://web1.
tunnlr.com:10238 ma potrebbe essere per voi di-
versa)
G Il numero assegnato al nostro utente, lo vediamo
nell'indirizzo al quale punta il nostro browser.
G Una textarea con l'etichetta "Authorized keys"
dove dovremo inserire una chiave pubblica pre-
sente sul nostro computer.
Questo perch i dati che si spostano nel "tunnel
SSH" viaggiano su internet criptati da un algoritmo
a chiave pubblica e privata, dobbiamo quindi co-
municare al servizio la nostra chiave pubblica.
Il sito Tunnlr contiene informazioni dettagliate per
creare le chiavi e testare il tutto sia per Windows
che per Linux/Mac. Riporto qui di seguito la pro-
cedura per MAC OS X.
Da terminale, entrate nella cartella .ssh presente
nella home del vostro utente:
cd ~/.ssh
Questa cartella contiene tutte le chiavi ssh.
A questo punto dobbiamo creare una nuova chia-
ve con il comando seguente:
ssh-keygen -t dsa
il comando ci chieder il nome del file nel quale
salvare le chiavi, inseriamo tunnlr, poi ci chieder
una password, che possiamo non inserire.
A questo punto dovremmo avere due file: tunnlr
che contiene la nostra chiave privata, da non diffon-
dere mai, e tunnlr.pub che contiene la chiave pub-
blica. Quindi copiamo il contenuto di tunnlr.pubnel-
la clipboard con il comando seguente:
cat tunnlr.pub | pbcopy
ritorniamo all'interfaccia web e incolliamo nella
textarea Authorized Keys la nostra chiave pubbli-
ca. Possiamo quindi confermare con Save.
Arrivati a questo punto possiamo testare il nostro
tunnel. Baster attivarlo da console con il coman-
do seguente:
ssh -nNt -g -R :10238:0.0.0.0:3000
tunnlr001@ssh1.tunnlr.com
dove ssh1.tunnlr.come 10238 sono rispettivamen-
te host e porta della nostra URL pubblica; 0.0.0.0:3000
l'indirizzo al quale tutte le richieste devono essere
inviate e tunnlrXXX il nostro utente Tunnlr, do-
ve al posto di XXX dovete inserire il numero uten-
te.
A questo punto creiamo una nuova applicazione
Rails che chiameremo happyplanner e avviamo il
server:
rails happyplanner
cd happyplanner
script/server
Puntando il browser sulla URL pubblica dovremo
vedere il famoso "welcome aboard".
Se qualcosa andato storto, provate a ripercorre-
re il percorso relativo alle richieste che vengono
effettuate verso il vostro server locale.
NOTA
MYSPACE
MySpace stato acquistato
nel 2005 dal magnate
televisivo Rupert Murdoch
per 580 milioni di dollari, e
ad ottobre 2007 seguendo
l'esempio di Facebook ha
annunciato la propria
piattaforma di sviluppo
MySpace Developer.
094-097:072-080 30-09-2008 15:59 Pagina 95
ht t p: / / www. i opr ogr ammo. i t
SISTEMA M
G
96
/Novembre 2008
Sviluppare applicazioni per Facebook
Se siete connessi ad internet tramite un router, ad
esempio wi-fi, ricordatevi di impostare il port
forwarding per le porte che ci interessano. Nel no-
stro caso i pacchetti ricevuti sulla porta 3000 de-
vono essere sempre re-inviati (forward) sulla por-
ta 3000, all'indirizzo ip assegnato alla nostra mac-
china. Inoltre, se possedete un firewall assicurate-
vi che l'applicazione Ruby possa ricevere e inviare
sulla porta 3000.
SETUP DI UNA NUOVA
APPLICAZIONE SU
FACEBOOK DEVELOPER
Ora che la nostra macchina di sviluppo pu esse-
re raggiunta dall'esterno, dobbiamo comunicare
a Facebook la nostra intenzione di creare una nuo-
va applicazione con tutti i dettagli necessari.
Per fare questo necessario aggiungere al proprio
profilo Facebook l'applicazione "Developer".
Quindi, entrando in tale applicazione fare clic su "set-
up new application".
Come si potr osservare il form da compilare ab-
bastanza ricco di informazioni, ma per i nostri sco-
pi sufficiente compilare solo i campi fondamentali:
1. Application Name: questo il nome descrittivo
della nostra applicazione, non necessario che
sia unico
2. Callback URL: inserire qui la URL pubblica ri-
cevuta dal servizio Tunnlr. Attenzione, im-
portante digitare la barra finale nella url.
3. Canvas Page URL: questa la URL Facebook
dalla quale sar possibile accedere alla nostra
applicazione, deve essere necessariamente uni-
ca.
4. Can users add application: selezionare yes.
5. Developers: qui possibile aggiungere altri svi-
luppatori che potranno vedere l'applicazione
6. Developer Mode: spuntando questa opzione,
l'applicazione sar visibile solo agli utenti pre-
senti nell'elenco precedente.
quindi clic su Submit.
Abbiamo quindi un riepilogo dell'applicazione
creata, con due informazioni fondamentali: "API Key"
e "Secret". Queste due informazioni funzionano
come uno username e password della nostra ap-
plicazione. Mediante un algoritmo, noto come fir-
ma digitale, applicato ai parametri delle richieste
HTTP, Facebook si assicura che le richieste pro-
vengano dal nostro server e che non siano state
manomesse.
UTILIZZO DELLA
LIBRERIA FACEBOOKER
Per nostra fortuna non dobbiamo preoccuparci
dell'API di basso libello di Facebook e possiamo
concentrarci sulla nostra applicazione. Utilizzere-
mo infatti il plugin Facebooker per Rails, che fa un
ottimo lavoro nell'astrarre e rendere rails-friendly
tutte le funzionalit e i punti di integrazione con
Facebook. Aggiungiamo la libreria alla nostra ap-
plicazione:
cd happyplanner
script/plugin install
git://github.com/mmangino/facebooker.git
Questa libreria far per noi tutte le operazioni di
firma digitale necessarie per comunicare con fa-
cebook, per questo motivo, dobbiamo aggiungere
al file config/facebooker.yml API Key e Secret, oltre che
la canvas page name e la callback url:
development:
api_key: 1104966....273
secret_key: 65b60f....6fb80b
canvas_page_name: happyplanner
callback_url: http://web1.tunnlr.com:10238/
tunnel:
public_host_username:
public_host:
Fig. 2: Creazione di una nuova applicazione
Fig. 3: Riepilogo informazioni essenziali
NOTA
SOCIAL NETWORK
Il traffico generato dagli
utenti dei social network
supera quello generato dei
siti per soli adulti (fonte Clic:
What Millions of People Are
Doing Online and Why it
Matters di Bill Tancer)
NOTA
FBML
Indispensabile rifermento al
Facebook Markup
Language (FBML) il wiki
http://wiki.developers.fa
cebook.com/index.php/F
BML
094-097:072-080 30-09-2008 15:59 Pagina 96
ht t p: / / www. i opr ogr ammo. i t
M SISTEMA
Novembre 2008/
97
G
Sviluppare applicazioni per Facebook
public_port:
local_port:
in secondo luogo la libreria ci regala la possibilit
di attivare il tunneling SSH con un comodo task
rake, basta completare lo stesso file con i parame-
tri gi visti in precedenza:
tunnel:
public_host_username: tunnlr158
public_host: ssh1.tunnlr.com
public_port: 10238
local_port: 3000
quindi d'ora in poi possiamo attivare il tunnel con:
rake facebooker:tunnel:start
L'APPLICAZIONE
HAPPYPLANNER
In questa serie di articoli svilupperemo un'appli-
cazione per organizzare una serata happyhour tra
il vostro gruppo di ex compagni di scuola o tra vec-
chi colleghi di lavoro. Utilizzeremo il maggior nu-
mero di integrazioni possibili con Facebook come in-
viti, news feed e notifiche. Ogni persona invitata al-
l'happy hour potr a sua volta estendere l'invito e
ognuno potr esprimere la sua preferenza sulla da-
ta e luogo. Iniziamo quindi con il primo controller:
script/generate controller welcome
Il primo passo attivare la libreria facebooker in
tutti i controller che andremo a creare.
Per fare ci modifichiamo il file app/controlles/ap -
plication.rbaggiungendo la riga seguente:
before_filter :ensure_authenticated_to_facebook,
:set_facebook_session
Questa riga assicura che, prima di processare qual-
siasi richiesta, vengano fatti i controlli di autenti-
cazione e che venga impostato l'oggetto face-
book_sessionche contiene le informazioni relative
all'utente che accede alla nostra applicazione.
Possiamo quindi iniziare familiarizzare con l'oggetto
facebook_session, completando il file app/controllers/
welcome_controller.rbcome segue:
class WelcomeController < ApplicationController
def index
@first_name =
facebook_session.user.first_name
end
end
e creando la vista corrispondente app/views/welcome
/index.fbml.erb:
Hello <%= @first_name %>
TEST DELL'AMBIENTE
DI SVILUPPO
Possiamo quindi far partire il tunnel SSH e il no-
stro server locale rails:
rake facebooker:tunnel:start
script/server
quindi puntare il browser sulla canvas page URL
http://apps.facebook.com/happyplanner/welcome
Dovremmo quindi vedere il nostro nome, recupe-
rato dall'oggetto facebook_session. possibile re-
cuperare diverse informazioni, da tale oggetto, ma
licenza Facebook vieta di memorizzare su un no-
stro database tali informazioni. Molte applicazio-
ni usano queste informazioni, mostrando contenuti
specifici a seconda delle preferenze dell'utente.
Da questo momento in poi, possiamo iniziare a
sperimentare a volont. Ad esempio aggiungendo
al nostro controller le righe seguenti possiamo leg-
gere le preferenze cinematografiche dell'utente:
facebook_session.user.populate(:movies)
@movies = facebook_session.user.movies
Possiamo anche iniziare a sperimentare qualche
tag FBML, ad esempio potremmo dare a Facebook
il compito di recuperare e renderizzare l'immagine
dell'utente nella pagina di benvenuto.
Nel controller recuperiamo un'informazione fon-
damentale, il facebook_id dell'utente:
@user_facebook_id =
facebook_session.user.facebook_id
E nella vista aggiungiamo il seguente tag speciale
FBML:
<fb:profile-pic uid="<%= @user_facebook_id %>" />
Notate che in questo caso, noi non conosciamo
l'immagine dell'utente, Facebook che sostitui-
sce a questo tag speciale l'HTML necessario a vi-
sualizzare l'immagine utente con tanto di colle-
gamento al profilo. Nel prossimo articolo rende-
remo funzionate l'applicazione happyplanner
creando le pagine FBML per invitare nuovi amici e
consentirgli di indicare la loro preferenza su data
e luogo.
Giancarlo Valente
NOTA
RECUPERARE
GLI USER DATA
Alcuni esempi di user data
che possiamo leggere
tramite l'oggetto
facebook_session.user
sono: books, interests,
music, religion, political.
La lista completa
recuperabile nella
documentazione di
Facebooker
http://facebooker.rubyfor
ge.org/
094-097:072-080 30-09-2008 15:59 Pagina 97
ht t p: / / www. i opr ogr ammo. i t
DATABASE M
G
98
/Novembre 2008
Da tabelle, righe e colonne al Domain Model
L'
approccio tradizionale al design e allo
sviluppo della applicazioni sempre
stato quello di effettuare l'analisi del
problema per poi procedere con la progettazione
del database, spesso il vero cuore dell'applicazio-
ne. Tuttavia la progettazione e lo sviluppo del
codice, ormai da diversi anni a questa parte,
sono attuati con linguaggi di programmazione
fortemente basati sulla programmazione ad
oggetti. Tale approccio consente di trattare in
modo pi naturale il problema. Ad esempio, la
tipica fattura non sar pi gestita come righe
sparse su un database relazionale pi o meno
relazionate tra loro, con salti mortali per aggan-
ciare le varie parti del singolo dato per effetto
delle doverose normalizzazioni del database. Un
formato gerarchico come XML o un modello
concettuale basato sulla classe Fattura che espo-
ne un elenco di classi RigaFattura e che ha come
cliente una propriet Cliente di tipo Anagrafica
ben pi naturale perch pi si avvicina alla
maniera di concettualizzare la realt per oggetti
indipendenti, autoconsistenti e dotati di com-
portamento specifico che operata dal cervello
umano.
IL PROBLEMA DELLA
IMPEDANCE MISMATCH
Tale modalit di progettazione basata su classi di
entit davvero comoda, ma si rivela fragile nel
momento in cui le classi, che rappresentano il
cosidetto Domain Model, devono essere persisti-
te o rilette da un database relazionale, e cio
quando dal modello concettuale (le entit) biso-
gna passare al modello fisico (il database). Esiste
una teoria che da anni studia la difficolt (che
talvolta diventa l'impossibilit) di passare da un
modello all'altro che va sotto il nome di
Impedance Mismatch; esiste una documentazio-
ne e una bibliografia molto vasta sull'argomento.
Gli argomenti cardine di questa teoria sono:
G l'incapsulamento tipico dell'approccio OOP
che garantisce una consistenza dei dati molto
inferiore all'integrit referenziale tipica dei
database relazionali;
G i database relazionali sono privi di concetti
quali classi di oggetti (ad esempio, tipi diversi
di fatture con un numero differente di campi e
di relazioni a seconda del tipo), ereditariet e
polimorfismo;
G differenze nei tipi di dati e cio la necessit di
mappare i tipi di dati dei database relazioni su
quelli del linguaggio di programmazione e del
type system dell'ambiente di esecuzione;
G differenze strutturali, a partire dal differente
approccio tra il modello gerarchico OOP con
livelli di nidificazione dei dati molto pronun-
ciati e il modello relazionale che , invece,
tipicamente orizzontale;
G il modello di interrogazione dei dati che, nei
relazionali semplice e potente (SQL), ma che
nel modello OOP spesso del tutto assente o
comunque ben pi limitato, tuttavia negli
ultimi anni si sono affacciate soluzioni per
ovviare al problema e Linq forse la pi nota;
G il concetto di transazionalit nativo nei
database relazionali e il suo uso talmente
naturale che non farne uso forse pi difficile
che il suo contrario; nel mondo OOP della
programmazione per domain model questo
supporto del tutto assente se non costruen-
do soluzioni ad hoc, spesso posticce e poco
efficaci.
GLI ORM
E I MULINI A VENTO
Date le premesse, sembrerebbe quasi che non ci
sia modo di lavorare fattivamente con un
domain model e che si debba, ahinoi, restare
sotto il giogo di tabelle, righe, colonne, relazioni
ipernormalizzate e quant'altro per realizzare le
nostre brave applicazioni gestionali. Tuttavia gli
INTRODUZIONE
A NHIBERNATE
GLI OBJECT-RELATIONAL MAPPING, SONO SISTEMI SOFTWARE IN GRADO DI EFFETTUARE
AUTOMATICAMENTE LA MAPPATURA TRA UN DB E LE STRUTTURE TIPICHE DI UN
LINGUAGGIO DI PROGRAMMAZIONE. UNO DI QUESTI, PER .NET, NHIBERNATE
J CD J WEB
nhibernate.zip
cdrom.ioprogrammo.it
Conoscenze richieste
.NET livello intermedio
Software
Microsoft Visual Studio
Express 2008
Impegno
Tempo di realizzazione
REQUISITI
098-103:101-105 30-09-2008 16:54 Pagina 98
ht t p: / / www. i opr ogr ammo. i t
M DATABASE
Novembre 2008/
99
G
Da tabelle, righe e colonne al Domain Model
informatici sono tipi tenaci e portati alle sfide
impossibili: ed ecco la trovata degli ORM, gli
Object-Relational Mapping, cio sistemi software
in grado di effettuare automaticamente la map-
patura tra i due mondi risolvendo, cos, la mag-
gior parte dei problemi enunciati nel triste elen-
co precedente. Il loro principio di funzionamen-
to molto semplice: si scrive il proprio sistema di
class entit (il domain model), si disegna il data-
base relazionale che ne gestir la persistenza e si
istruisce l'ORM su come mappare i due mondi.
Tale operazione tipicamente avviene definendo
la mappatura, cio una sorta di metadefinizione,
spesso in formato XML, di come passare dal
mondo fisico al mondo concettuale. In taluni
casi opzionalmente possibile saltare la defini-
zione del modello fisico perch, definendo il
sistema di classi e la mappatura, alcuni ORM
sono in grado di generare lo schema fisico in
modo automatico e di aggiornarlo a seguito dei
mutamenti del modello di mappature.
NHIBERNATE:
IL PRINCIPE DEGLI ORM
NEL MONDO .NET
NHibernate forse il pi sofisticato ORM per
.NET attualmente disponibile: derivato dai
concetti e dagli algoritmi del suo celebre padre
Hibernate per il mondo Java, ma soprattutto con
la versione 1.2 e l'imminente 2.0, ha subto una
riprogettazione per sfruttare pienamente le
caratteristiche native e talvolta uniche del
mondo .NET. Dunque non si tratta di uno dei
tanti porting di codice da Java in circolazione ma
di un prodotto vero per .NET. Microsoft sta da
anni lavorando ai suoi tanti ORM: Linq for SQL,
ADO Entity Framework, ecc.., ma forse il pi
significativo che rapprsenter forse la prima vera
alternativa ad NHibernate sar proprio
l'imminente ADO Entity Framework.
Ma concentriamoci su NHibernate partendo dal
suo link: http://www.hibernate.org/343.html, dove
possibile scaricarne l'ultima versione stabile (al
momento in cui scriviamo), la 1.2.1.G.A. del
26/11/2007. La sua installazione richiede la pre-
senza del Microsoft .NET Framework 1.1 o suc-
cessivi, ma consigliabile la presenza almeno
della versione 2.0 per sfruttarne i potenti generi-
ci. Non viene installato nulla nella GAC e per uti-
lizzarlo nelle nostre applicazioni sufficiente
riferire una manciata di assembly.
Tutta questa potenza di fuoco gratuita perch il
progetto rilasciato, anche in sorgente, con
licenza LGPL. Il mantainer del progetto Jboss,
la societ dell'omonimo application server J2EE,
di recente passata ad un nuovo proprietario: Red
Hat, l'azienda della famosa distribuzione Linux.
UN ESEMPIO REALE
Dopo tante chiacchiere, passiamo a studiare un
esempio reale osservando il case study per eccel-
lenza fornito dai contributor di Nhibernate:
l'applicazione Order System. Useremo tutti pro-
dotti completamente gratuiti per lo sviluppo del-
l'esempio:
G Microsoft Visual C# 2008 Express Edition
(download da http://www.microsoft.com/Express/);
G Microsoft SQL Server 2005 Express Edition
(stesso link);
G NHibernate 1.2.1.G.A.
L'applicazione fornisce un semplice gestionale
fatto di anagrafiche fornitori, prodotti e ordini e
si bassa su cinque entit: Supplier, Important -
Supplier, Manufacturer, Product e Sale.
Osserviamo la definizione di tali classi, a comin-
ciare dalla classe fornitore (Supplier) e della sua
specializzazione ImportantSupplier:
//entit fornitore
public class Supplier
{
private int supplierId;
//Id del fornitore
public virtual int SupplierId
{
get { return this.supplierId; }
set { this.supplierId = value; }
}
private System.Collections.IList productList;
//elenco dei prodotti offerti dal fornitore
public virtual System.Collections.IList ProductList
{
get { return this.productList; }
}
private string name;
//nome del fornitore
public virtual string Name
{
get { return this.name; }
set { this.name = value; }
}
public Supplier()
{
this.supplierId = 0;
098-103:101-105 30-09-2008 16:54 Pagina 99
ht t p: / / www. i opr ogr ammo. i t
this.productList = new
System.Collections.ArrayList();
this.name = string.Empty;
}
}
//entit derivata dei fornitori, con dati addizionali
public class ImportantSupplier : Supplier
{
private string address;
public virtual string Address
{
get { return this.address; }
set { this.address = value; }
}
}
Possiamo notare che si tratta di normali classi C#
senza particolari fronzoli. Da rilevare la forte
gerarchizzazione della struttura: Supplier, ad
esempio, espone un ArrayList di Product.
Possiamo scrivere questa propriet anche usan-
do un generico e cio come:
private IList<Product> productList;
public virtual IList<Product> ProductList
{
get { return this.productList; }
}
In tal caso, per, instanzieremo una relativa List
generica:
this.productList = new List<Product>();
Si noti che ImportantSupplier una specializza-
zione di Supplier a cui aggiunge alcuni campi.
Dal punto di vista del database relazionale que-
sto il tipico caso da risolvere con due tabelle,
una principale (SUPPLIER) ed una con i campi
aggiuntivi della specializzazione (IMPOR-
TANTSUPPLIER) in relazione uno a uno tra loro.
Ed proprio quello che faremo con il nostro
database SQL Server di esempio, come mostrato
in Fig. 1.
IL MAPPING
DI NHIBERNATE
Non ci resta che osservare come mappare queste
due entit con i file di mapping di NHibernate.
Tanto per cominciare ogni entit ha il suo file di
mappatura corrispondente che per convenzione
si chiama come l'entit stessa seguito dall'esten-
sione .hbm.xml (ad esempio Supplier.hbm.xml):
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-
mapping-2.2" auto-import="true" default-
access="nosetter.camelcase">
<class name="OrderSystem.Objects.Supplier,
OrderSystem.Objects" table="SUPPLIER" lazy="true"
mutable="true">
<id name="SupplierId" unsaved-value="0"
column="ID_SUPPLIER" type="System.Int32">
<generator class="hilo"/>
</id>
<property name="Name" column="NAME" not-
null="true" type="System.String" />
<bag name="ProductList" table="PRODUCT"
cascade="all" fetch="join" inverse="true">
<key column="FK_SUPPLIER" />
<one-to-many class="OrderSystem.
Objects.Product, OrderSystem.Objects" />
</bag>
</class>
</hibernate-mapping>
Lo scopo dell'articolo non descrivere approfon-
ditamente ogni singolo aspetto e attributo di
NHibernate e delle sue mappature, dato che esi-
ste una documentazione molto ricca che assolve
a questo compito.
Ci concentreremo sugli aspetti pi importanti.
Per cominciare possiamo notare il nodo class che
riporta alcuni interessanti attributi:
G name, che rappresenta il full qualified name
dell'entit e l'assembly in cui definita, sepa-
rate da virgola;
DATABASE M
G
100
/Novembre 2008
Da tabelle, righe e colonne al Domain Model
Fig. 1: Il diagramma del database Order System
098-103:101-105 30-09-2008 16:54 Pagina 100
ht t p: / / www. i opr ogr ammo. i t
G table, il nome della tabella fisica sul database
relazione usata per persistere i dati di questa
entit;
G lazy, un attributo booleano che, se attivo, con-
sente il caricamento on demand dei dati dalla
tabella, una sorta di cursore lato server, in
modo da migliorare nettamente le prestazioni
perch si evita il precaricamento di tutti i dati
all'inizio della richiesta.
Sebbene siano consentite chiavi composite,
fortemente consigliato, se non quasi obbligatorio
in NHibernate (ma anche come buona regola di
normalizzazione dei database), che ciascuna
entit abbia come chiave primaria un campo
numerico. Tale campo definito attraverso il tag
id. Nell'esempio viene individuato con l'at -
tributo name il campo SupplierId dell'entit
come chiave e l'equivante campo ID_SUPPLIER
della tabella SUPPLIER attraverso l'attributo col-
umn.
L'attributo type, infine, descrive il tipo del campo
usando il type system di .NET.
Si noti che presente il sottonodo generator a
indicare l'algoritmo di generazione. In tal caso
viene usato hilo, un sistema basato su una
tabella contatore (HIBERNATE_UNIQUE_KEY)
che eroga id univoci per ciascuna entit. Tuttavia
si pu sempre optare per la pi classica identity
di SQL Server, in tal caso il generator diventa:
<generator class="native" />
Tutte le propriet scalari dell'entit, che quindi
coincidono con i campi fisici della tabella, sono
invece identificati nella mappatura attraverso il
nodo property, in cui ritroviamo in modo abba-
stanza intuitivo gli attributi name, column, not-
null e type, rispettivamente ad indicare il nome
della propriet della classe, il nome della colon-
na nella tabella, la possibilit che il valore sia null
e il suo tipo.
Il nodo bag introduce la prima relazione tra
entit e la sua mappatura sul modello fisico.
In particolare si tratta di una relazione uno a
molti. Nello specifico la propriet ProductList,
introdotta in precedenza. Un Supplier fornisce
un elenco di Product e pertanto nella tabella
PRODUCT (attributo table) vi una foreign key
verso la tabella SUPPLIER con il campo
FK_SUPPLIER (attributo column del sottonodo
key). La morfologia dell'entit Product non
chiaramente indicata, se non il full qualified
name (tag one-to-many) perch la sua definizio-
ne presente nel file Product.hbm.xml.
Con la mappature ImportantSupplier.hbm.xml si
definisce l'entit ImportantSupplier, derivata da
Supplier i cui campi aggiuntivi sono presenti
nella tabella IMPORTANT_SUPPLIER.
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-
mapping-2.2" auto-import="true" default-
access="nosetter.camelcase">
<joined-subclass
name="OrderSystem.Objects.ImportantSupplier,
OrderSystem.Objects"
extends="OrderSystem.Objects.Supplier,
OrderSystem.Objects" table="IMPORTANT_SUPPLIER"
lazy="true">
<key column="ID_IMPORTANT_SUPPLIER" />
<property name="Address" column="ADDRESS"
not-null="true" type="System.String" />
</joined-subclass>
</hibernate-mapping>
Questa relazione, che dal punto di vista del
domain model di ereditariet/specializzazione
e dal punto di vista del modello fisico un rela-
zione uno a uno tra due tabelle, rappresentata
con il tag joined-subclass che definisce gli attri-
buti name (la classe e l'assembly dell'entit deri-
vata) ed extends (la classe e l'assembly dell'entit
padre).
Per il resto la mappatura ricorda quella vista in
precedenza, con i suoi tag key property, bag, etc...
Per completezza riportiamo l'entit Product e
ometteremo Manufacturer e Sale che per pos-
sono essere consultate direttamente dai sorgenti
allegati all'articolo.
//entit Prodotto
public class Product
{
private int productId;
public virtual int ProductId
{
get { return this.productId; }
set { this.productId = value; }
}
private Iesi.Collections.ISet manufacturerSet;
//elenco dei produttori che producono questo
M DATABASE
Novembre 2008/
101
G
Da tabelle, righe e colonne al Domain Model
Fig. 2: Un esempio di mappatura visto da Visual C# 2008 Express Edition
098-103:101-105 30-09-2008 16:54 Pagina 101
ht t p: / / www. i opr ogr ammo. i t
prodotto
public virtual Iesi.Collections.ISet ManufacturerSet
{
get {
return this.manufacturerSet;
}
}
private string name;
public virtual string Name
{
get {
return this.name;
}
set {
this.name = value;
}
}
private Supplier supplier;
//fornitore del prodotto
public virtual Supplier Supplier
{
get {
return this.supplier;
}
set {
this.supplier = value;
}
}
}
Si osservino la propriet Supplier, di tipo
Supplier, che nel modello fisico rappresentato
con una relazione molti a uno tra SUPPLIER e
PRODUCT attraverso il campo FK_SUPPLIER
presente nella seconda tabella e che di fatto
una foreign key verso SUPPLIER.
Tali informazioni sono definite nella mappatura
attraverso il tag many-to-one.
Ecco di seguito il mapping di Product:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-
mapping-2.2" auto-import="true" default-
access="nosetter.camelcase">
<class name="OrderSystem.Objects.Product,
OrderSystem.Objects" table="PRODUCT" lazy="true"
mutable="true">
<id name="ProductId" unsaved-value="0"
column="ID_PRODUCT" type="System.Int32">
<generator class="hilo"/>
</id>
<set name="ManufacturerSet"
table="PRODUCT_X_MANUFACTURER"
cascade="save-update" lazy="true" inverse="true">
<key column="FK_PRODUCT" />
<many-to-many
class="OrderSystem.Objects.Manufacturer,
OrderSystem.Objects" column=
"FK_MANUFACTURER" />
</set>
<property name="Name" column="NAME" not-
null="true" type="System.String" />
<many-to-one name="Supplier"
column="FK_SUPPLIER" not-null="true"
fetch="join" cascade="save-update" />
</class>
</hibernate-mapping>
Un ultima relazione da osservare forse anche la
pi sorprendente che riesca a gestire
NHibernate, la many-to-many, cio la molti a
molti che nel modello fisico si rappresenta con
una tabella di relazione.
Nel caso specifico le tabelle interessate sono
PRODUCT e MANUFACTURER e la tabella di
relazione PRODUCT_X_MANUFACTURER.
Tra le varie relazioni del modello relazionale que-
sta probabilmente la meno naturale, la pi
distante rispetto a quanto si faccia con il domain
model. Infatti, se osserviamo le nostre entit,
non esiste alcuna entit ProductManufacturer,
ma l'entit Product espone semplicemente una
propriet Manufacturer Set che altro non che
una collezione di Manufacturer, implicitando
cos la tabella di relazione.
Tutto questo possibile a costo zero grazie ad
NHibernate.
CASCADE E FETCH
I vari tag di relazione espongono tutti quanti una
coppia di tag fetch e cascade. Essi sono molto
interessanti.
Il primo pu assumere i valori select e join.
Nel primo caso viene eseguita una select specifi-
ca per recuperare l'entit in relazione, nel secon-
do caso viene effettua semplicemente un'unica
select tra la tabella dell'entit principale e quella
dell'entit in relazione ritornando tutti i campi
dell'una e dell'altra e consentendo, cos, di popo-
lare le due entit con un solo accesso fisico al
database.
Il secondo tag cascade, indica invece le tipologie
di operazioni che possono essere effettuate sulla
tabella in relazione a partire dall'entit padre.
Il concetto ricorda da vicino quello dell'omoni-
ma funzionalit presente nei database relaziona-
li. E quindi la possibilit di riportare in cascata
update, delete, rimozione di righe orfane e altro
ancora.
DATABASE M
G
102
/Novembre 2008
Da tabelle, righe e colonne al Domain Model
098-103:101-105 30-09-2008 16:54 Pagina 102
ht t p: / / www. i opr ogr ammo. i t
Si raccomanda la consultazione della documen-
tazione specifica per un approfondimento.
CODICE DI ESEMPIO
Il seguente codice mostra come configurare ogni
aspetto di NHibernate per l'uso nella nostra
applicazione. I commenti aiutano la compren-
sione del codice:
private static NHibernate.Cfg.Configuration cfg = null;
private static NHibernate.ISessionFactory
sessionFactory = null;
static NHibernateSessionFactory()
{
//oggetto di configurazione di NHibernate
cfg = new NHibernate.Cfg.Configuration();
// assegnazione dei provider di accesso ai dati
cfg.Properties.Add(NHibernate.Cfg.Environment.
ConnectionProvider,
"NHibernate.Connection.DriverConnectionProvider");
cfg.Properties.Add(NHibernate.Cfg.Environment.
ConnectionDriver,
"Nhibernate.Driver.SqlClientDriver");
//versione di SQL Server da utilizzare
cfg.Properties.Add(NHibernate.Cfg.Environment.
Dialect,
"Nhibernate.Dialect.MsSql2000Dialect");
// consente un illimitato numero di outer join
cfg.Properties.Add(NHibernate.Cfg.Environment.
MaxFetchDepth, "-1");
// connection string
cfg.Properties.Add(NHibernate.Cfg.Environment.
ConnectionString,
"Server=vex;initial catalog=OrderSystem;User
Id=sa;Password=atreides");
// assembly delle entit
cfg.AddAssembly(Assembly.LoadFrom("OrderSystem.
Objects.dll"));
sessionFactory = cfg.BuildSessionFactory();
}
public static NHibernate.ISession OpenSession()
{
// apertura della sessione
return sessionFactory.OpenSession();
}
Ma veniamo finalmente agli aspetti pi interes-
santi della soluzione e cio osserviamo quanto
sia semplice e naturale creare e modificare entit
demandando completamente all'ORM ogni ac -
ces so fisico al database per la loro persistenza:
public int SaveObjectAndReturnSupplier(bool
makeImportant, int prodCount)
{
//creazione della sessione di NHibernate
ISession session =
NHibernateSessionFactory.OpenSession();
ITransaction transaction =
session.BeginTransaction();
string timeStamp =
DateTime.Now.ToString("yyyyMMdd-HHmmss");
// crea un ImportantSupplier
Supplier s = makeImportant ? new
ImportantSupplier("supplier " + timeStamp, "n/a") :
new Supplier("supplier " + timeStamp);
// crea un Manufacturer
Manufacturer m = new
Manufacturer("manufacturer " + timeStamp);
// crea i prodotti da legare al produttore
for (int i = 0; i < prodCount; i++)
{
Product p = new Product();
p.Name = "product " + timeStamp;
p.Supplier = s; s.ProductList.Add(p);
// sync both ways
m.ProductSet.Add(p);
p.ManufacturerSet.Add(m);
}
try
{
Console.WriteLine("Salvataggio modifiche...");
// tutti i cambiamenti vengono persistiti perch
si impostato un Cascade di tipo all
session.Save(s);
transaction.Commit();
Console.WriteLine("Save succeded");
}
catch {
transaction.Rollback();
Console.WriteLine("Save failed"); throw;
}
finally {
session.Close();
}
return s.SupplierId;
}
Ed ora, invece, vediamo come rileggere le entit
dal database:
public void ListSupplierProductsAndManufacturers(int
supplierId)
{
ISession session =
NhibernateSessionFactory.OpenSession();
//recupero di un Supplier con un Id specifico dal
database
Supplier s =
(Supplier)session.Get(typeof(Supplier), supplierId);
//NHibernate si preoccupa di verificare che si tratti
M DATABASE
Novembre 2008/
103
G
Da tabelle, righe e colonne al Domain Model
098-103:101-105 30-09-2008 16:54 Pagina 103
ht t p: / / www. i opr ogr ammo. i t
di un Supplier semplice o di un
//ImportantSupplier verificando semplicemente
l'esistenza di una riga relazionata nella tabella
//IMPORTANT_SUPPLIER, in tal caso viene
istanziata l'entit derivata e la select eseguita sul
database
//recupera anche tali informazioni e li inserisce
nelle propriet specifiche dell'entit derivata
Console.Write(s.Name + " is ");
Console.WriteLine(s is ImportantSupplier ?
"important" : "normal");
Console.WriteLine("Products...");
foreach(Product p in s.ProductList)
{
Console.WriteLine("\t" + p.Name);
foreach (Manufacturer m in p.ManufacturerSet)
Console.WriteLine("\t\t" + m.Name);
}
session.Close();
}
Ed ecco come modificare entit esistenti relazio-
nandole ad altre nuove o pre-esistenti:
public void AddSaleForEachProduct(int supplierId)
{
ISession session =
NhibernateSessionFactory.OpenSession();
//caricamento del Supplier dall'id specificato
Supplier s =
(Supplier)session.Load(typeof(Supplier), supplierId);
Console.WriteLine("Adding sales...");
//per ogni prodotto fornito dal Supplier e quindi
presente nella collezione ProductList
//si procede alla creazione di un'entit di vendita
(Sale) ad esso relazionata
foreach (Product p in s.ProductList)
{
Sale sale = new Sale(new Random().Next(), p,
1, DateTime.Now);
//l'entit viene salvata nella transazione
corrente
session.Save(sale);
}
Console.WriteLine("Saving sales");
session.Flush();
}
QUERY COMPLESSE CON
MODELLO A OGGETTI
DEI CRITERIA
I Criteria sono un importante funzionalit di
NHibernate che consente di effettuare interroga-
zioni complesse sul domain model.
importante comprendere che l'interrogazione
viene effettuata dal programmatore sul modello
concettuale di entit e che poi l'ORM a preoc-
cuparsi di trasformare tali richieste in tradiziona-
li query SQL atte al popolamento delle entit
richieste dall'utente.
L'esempio commentato di seguito offre un inte-
ressante caso di studio:
public void SimpleExamples()
{
ISession session =
NHibernateSessionFactory.OpenSession();
ICriteria criteria; IList result;
//creazione del Criteria sull'entit Sale
criteria = session.CreateCriteria(typeof(Sale));
// aggiunta delle condizioni sull'interrogazione che
sono espresse direttamente sulle propriet dell'entit
// e delle sotto entit del grafo principale. Tali
relazioni sui sottografi devono essere espressamente
// indicate creando dei sub Criteria
ICriteria subCriteria = criteria
.CreateCriteria("Product")
.CreateCriteria("Supplier")
.Add(Expression.Like("Name","supplier%"));
// ordinamento del risultato
criteria.AddOrder(NHibernate.Expression.
Order.Asc("SaleDate"));
//paginazione
int currentPage = 0, pageSize = 5;
criteria
.SetFirstResult(currentPage * pageSize)
.SetMaxResults(pageSize);
// eventuale modalit di lock in caso di
aggiornamento
criteria.SetLockMode(LockMode.Upgrade);
// esecuzione dell'interrogazione
result = criteria.List();
// NHibernate provveder a generare ed eseguire
per noi una query SQL piuttosto complessa in grado
di soddisfare l'interrogazione
/*
* SELECT top 5 this.ID_SALE as ID_SALE2_,
this.FK_PRODUCT as FK_PRODUCT2_, this.PRICE as
PRICE2_, this.QUANTITY as QUANTITY2_,
this.SALE_DATE as SALE_DATE2_, x0_.ID_PRODUCT
as ID_PRODUCT0_, x0_.FK_SUPPLIER as
FK_SUPPL3_0_, x0_.NAME as NAME0_,
x1_.ID_SUPPLIER as ID_SUPPL1_1_, case when
[x1__1_].ID_IMPORTANT_SUPPLIER is not null then 1
when x1_.ID_SUPPLIER is not null then 0 end as
clazz_1_, x1_.NAME as NAME4_1_,
[x1__1_].ADDRESS as ADDRESS5_1_
* FROM SALE this
* inner join PRODUCT x0_ on
this.FK_PRODUCT=x0_.ID_PRODUCT
DATABASE M
G
104
/Novembre 2008
Da tabelle, righe e colonne al Domain Model
098-103:101-105 30-09-2008 16:54 Pagina 104
ht t p: / / www. i opr ogr ammo. i t
* inner join SUPPLIER x1_ on
x0_.FK_SUPPLIER=x1_.ID_SUPPLIER
* left outer join IMPORTANT_SUPPLIER
[x1__1_] on x1_.ID_SUPPLIER=[x1__1_].
ID_IMPORTANT_SUPPLIER
* WHERE x1_.NAME like @p0
* ORDER BY this.SALE_DATE asc
*/
}
HQL: HIBERNATE
QUERY LANGUAGE
L'ORM offre una modalit pi comoda e natura-
le per effettuare le interrogazioni, un vero e pro-
prio linguggio simil SQL, ma ad oggetti perch
orientato a descrivere interrogazioni sul domain
model.
Tutto ci che si pu fare scrivendo codice di
Criteria possibile anche farlo scrivendo una
semplice query HQL.
concettualmente simile al nuovo linguaggio
Linq di Microsoft. Eccone una serie di esempi
convincenti tratti dall'applicazione didattica
Order System:
public void SimpleExamples()
{
ISession session =
NHibernateSessionFactory.OpenSession();
IQuery query;
IList result = null;
// semplice interrogazione su Sale per ricavare il
campo Product.Name con una condizione di like
sullo stesso campo
query = session.CreateQuery("select s.
Product.Name from Sale s where s.Product.Name
like:filterName");
query.SetString("filterName", "%");
query.SetFirstResult(5).SetMaxResults(10);
result = query.List();
Program.ListResult("IQuerySamples.
SimpleExamples1", result);
// esempio di join tra entit
query = session.CreateQuery("select p.Name from
Supplier s inner join s.ProductList p");
result = query.List();
Program.ListResult("IQuerySamples.
SimpleExamples2", result);
// query con HAVING COUNT
query = session.CreateQuery("from Sale s where
size(s.Product.ManufacturerSet) = 1");
result = query.List();
Program.ListResult("IQuerySamples.
SimpleExamples4", result);
// esempio complesso: recuperare tutti i
manufacturer i cui prodotti sono stati venduti
query = session.CreateQuery(
@"select distinct m.Name from Sale s inner
join s.Product.ManufacturerSet m
where s.SaleDate in (:allowedDates) order by
m.Name");
query.SetParameterList("allowedDates",
new DateTime[] { new DateTime(2000, 01,
01), DateTime.Now.Date });
result = query.List();
Program.ListResult("IQuerySamples.
SimpleExamples5", result);
// uso di funzioni statistiche nelle query HQL
query = session.CreateQuery("select max
( s.SaleDate ) from Sale s");
object uniqueResult = query.UniqueResult();
}
CONCLUSIONI
Molti concetti sono stati tralasciati e tante nuove
scoperte e funzionalit saranno magicamente a
vostra disposizione semplicemente studiando
un po' la documentazione ufficiale del prodotto
e, soprattutto, cominciando a usarlo per costrui-
re esempi via via pi complessi.
Gli ORM sono certamente la nuova frontiera
delle applicazioni gestionali e colmano final-
mente le lacune e le differenze, spesso concet-
tuali, tra il mondo fisico dei database relazionali
e il comodo mondo del disegno di applicazioni
per entit logiche.
Certo, la curva di apprendimento del prodotto
allinizio particolarmente ripida e manca la
tipica sensazione di sentirsi sempre a casa che si
prova quando si usano i ben integrati prodotti
Microsoft, ma i benefici che avrete dal suo studio
e dalla sua adozione vi ripagheranno in poco
tempo dello sforzo iniziale.
Vito Vessia
M DATABASE
Novembre 2008/
105
G
Da tabelle, righe e colonne al Domain Model
Fig. 3: Liste di entit popolate con interrogazioni all'ORM
098-103:101-105 30-09-2008 16:54 Pagina 105
ht t p: / / www. i opr ogr ammo. i t
SOFTWARE SUL CD M
G
106
/Novembre 2008
Librerie e Tool di sviluppo
OXYGEN CODE
GENERATOR 1.7
GENERATORE DI CODICE
PER .NET
Il modo migliore per sottrarsi allobbligo
di scrivere codice di routine. Particolar-
mente indicato per generare il codice ne-
cessario ad accedere e gestire database. Tra
le funzioni di output sono incluse stored
procedure e una classe data provider
che semplifica il collegamento a databa-
se. Versione trial.
Directory: OxyGen-
Code_TrialVersion_1_7_7.zip
CSHARP CODE
LIBRARY 1.9.0.146
UNA LIBRERIA IMPERDIBILE E
RICCHISSIMA DI CODICE PER C#
Una libreria di codice che comprende ol-
tre 50.000 righe, facilmente consultabile
grazie al motore di ricerca integrato. Par-
ticolarmente curata la sezione dedicata
alla stampa.
Directory: csharp_code_library.zip
VB 6 ANTIDECOM-
PILER 2.3.1
UNA UTILITY PER PROTEGGERE
I TUOI PROGRAMMI
Il programma ideale per gli sviluppatori
VB che hanno il timore che qualcuno co-
pi il codice da loro creato con tanto fa-
tica. VB 6 AntiDecompiler, infatti, di-
sabilita la decompilazione nativa di Vi-
sual Basic, che sar possibile effettua-
re in seguito soltanto con i tool messi a
disposizione dalla software house pro-
duttrice del programma. Una volta che
SOFTWARE
SUL CD
Tji Java IDE 1.6
F
esteggia dieci anni di attivit questo IDE per
programmare in Java che fa della semplicit
d'utilizzo uno dei suoi punti di forza. svilup-
pato direttamente in Java, per utilizzarlo c' bi-
sogno di avere installato almeno Java 1.6. Molto
personalizzabile, permette di salvare, compila-
re ed eseguire i programmi con pochi clic del
mouse. Integra uno swing GUI designer per fa-
cilitare la creazione di interfacce grafiche e un
utilissimo correttore della sinstassi in realtime,
che eviter errori proprio mentre si sviluppa.
L'autocompletamento permette di accelerare il la-
voro di stesura del codice, cos come
l'autoindentazione mantiene ordinate e ben leg-
gibili le migliaia di righe di codice sviluppate. Il
layout del programma molto personalizzabile,
per mettere quanto pi posssibile a proprio agio
l'utilizzatore.
106-108:106-108-software 30-09-2008 16:02 Pagina 106
ht t p: / / www. i opr ogr ammo. i t
M SOFTWARE SUL CD
Novembre 2008/
107
G
Librerie e Tool di sviluppo
VB Antidecompiler intervenuto su
un file eseguibile, l'output dell'even-
tuale decompilazione produrr sol-
tanto del confuso assembler, o addi-
rittura non produrr nessun output.
Directory: setup_vb6ad
VIRTUAL BOX 2.0.2
TESTARE I NOSTRI PROGRAMMI
SU TUTTI I SISTEMI OPERATIVI
Chi sviluppa per pi di un sistema operativo
ha spesso lesigenza di testare le proprie
applicazioni, ma pu non avere la possi-
bilit di installare pi di un SO, sulla pro-
pria macchina. Virtual Box permette di
utilizzare molti sistemi operativi sulla
stessa macchina, senza avere il bisogno
di partizionare lhard disk e configurare il
sistema in maniera particolare. Buona la
gestione delle porte USB della macchina
host, cos come anche quella della rete
virtuale, che pu essere bridged o anche
NAT.
Directory: VirtualBox-1.6.2-Win_x86.zip
ENTERPRISE
ARCHITECT 7.1
BUILD 832
PER PROGETTARE IN UML
LA TUA APPLICAZIONE
Questo programma sar certamente gra-
dito a chi utilizza UML durante lo svi-
lupppo delle sue applicazione e progetti.
Enterprise Architect permette, infatti, gra-
zie ad un'interfaccia molto semplice da
utilizzare, di modellare, disegnare e co-
struire un software o un progetto busi-
ness. Il programma riesce a generare il
codice in molti linguaggi, tra i quali Ac-
tionscript, Java, C#, C++ e molti altri, com-
presi Python e DDL). Grazie a questo
software pi semplice passare agevol-
mente dalla fase di progettazione a quel-
la di sviluppo, accelerando notevolmen-
te i tempi di lavorazione del progetto.
Directory: easetup.exe
SHOW ME THE
TEMPLATE
REALIZZARE INTERFACCE
FACILMENTE CON WPF
Un tool che permette di esplorare i tem-
plate e tutti i panel che possibile gesti-
re tramite WPF. Una ottima occasione per
studiare il funzionamento di WPF e non
solo: i template pre-impostati, consen-
tono di realizzare linterfaccia delle no-
stre applicazioni pi semplicemente e
senza dover partire da zero.
Directory: ShowMeTheTempla-
te.zip.zip
XF ULTRASCALE
2008 1
FORMATTAZIONE XML
ULTRAVELOCE
Un processore di XML capace di format-
tare migliaia di pagine al minuto, grazie al-
la particolare cura posta nellottimizza-
zione delle eleaborazioni. Tra i principa-
li vantaggi, oltra alla menzionata velo-
cit, si segnala la piccola impronta in me-
moria (generalmente sotto i 100 MB) e la
capacit di lavorare perfettamente in con-
testi ad elevato parallelismo.
Directory: XFUltrascale-Win32.1.0.0.zip
TIERDEVELOPER
.NET 6
SCRIVI LE TUE APPLICAZIONI
IN MET TEMPO
Un efficace generatore di codice capa-
ce di produrre sia applicazioni ASP.NET
sia Windows Forms. Copre un ampio
spettro di utilizzo e pu ridurre il tem-
po di sviluppo dal 50 al 70%.
Directory: Tierdevnetent.exe
EXCELSIOR JET 6.4
DA JAVA AD ESEGUIBILI
WINDOWS E LINUX
Una soluzione completa per accelerare
e proteggere il tuo codice Java. Permette
di generare codice applicazioni esegui-
bili per piattaforme Windows e Linux.
La virtual machine integrata fa s che sul
PC non debba essere preinstalla la JVM. Ver-
sione di prova a trenta giorni.
Directory: Jet-latest-eval-win32.exe
HEXASSISTANT 2.6
EDITOR ESADECIMALE
FULL OPTIONAL
Un editor esadecimale che permette
di effettuare il dump ed esportarne il
risultato nel codice sorgente C o in Ja-
va. Sono disponibili due visualizzato-
ri (Data Quick e Structure) che per-
mettono di visualizzare e interpretare
correttamente i dati ricavati.
Directory: HexAssistant_Setup26
NOTEPAD++ 4.7.5
PICCOLO E VELOCISSIMO
Un editor completamente gratuito (con
licenza GPL) che supporta numerosi
linguaggi di programmazione. C, C++,
Java, C#, XML, HTML, PHP, Javascript,
ASP, VB/VBS, SQL, ASP, VB/VBS, SQL e
altri ancora. Tra le caratteristiche pi
interessanti, un ottimo Syntax Highli-
ghting, la possibilit di effettuare ri-
cerche con le regular expression e la
capacit di stampare il codice con gli
stessi colori presentati a video.
Directory: npp.4.7.5.Installer.exe
JDK 6 UPDATE 7
LINDISPENSABILE AMBIENTE
DI RUN TIME JAVA
106-108:106-108-software 30-09-2008 16:02 Pagina 107
ht t p: / / www. i opr ogr ammo. i t
SOFTWARE SUL CD M
G
108
/Novembre 2008
Librerie e Tool di sviluppo
Il Java SE Development Kit (JDK) non
pu mancare nel PC del bravo svilup-
patore. Una volta installato, si disporr
del completo ambiente per eseguire le
applicazioni Java (il Java Runtime En-
vironment) oltre al solito, potentissi-
mo tool da linea di comando utile an-
che per testare le applet sviluppate per
i propri siti Web.
Directory: jdk-6u7-windows-i586-p.exe
A4DESK PRO
UN SITO FLASH PROFESSIONALE
IN POCHI CLIC
Potente e semplice da usare, A4DeskPro
un tool per la progettazione di siti web
in Flash, ricchi di animazioni, artico-
lati menu a scorrimento e tutti i tipici ele-
menti Flash che sono la gioia di molti
utenti (e il terrore di molti altri: atten-
ti al consumo di banda!). La program-
mazione avviene tutta per via visuale
e non richiede conoscenze specifiche.
Versione di prova.
Directory: a4deskpro_setup.exe
VOIP SDK
APPLICAZIONI CHE PARLANO
Una soluzione per includere nelle no-
stre applicazioni capacit di Voice Over
IP. LSDK include un client ad alta effi-
cienza che garantisce una ottima qua-
lit audio anche in caso di scarsit di
banda. Compatibile con il protocollo
H323, permette di realizzare applica-
zioni per chiacchierare sia in locale che
su scala mondiale, via internet.
Directory: H323_20.zip
PPL - POCKET
PROGRAMMING
LANGUAGE
PROGRAMMAZIONE MOBILE
PPL un linguaggio di programmazio-
ne orientate agli oggetti, molto sem-
plice da imparare. PPL gira su Smartpho-
ne, PocketPC e PC, cos i programmi
scritti in PPL sono compatibili al 100%
con tutte le piattaforme supportate.
PPL dotato di un completo ambiente
di sviluppo e fornisce anche degli stru-
menti verticali per la realizzazione di
applicazioni specifiche come i giochi. Tra
i grandi vantaggi di PPL c lestrema
leggerezza dellambiente di sviluppo:
anche un portatile non particolarmen-
te performante si dimostra pi che suf-
ficiente.
Directory: PPL152.exe
IRON SPEED
DESIGNER 5.2
UN MAGO PER GENERARE AP-
PLICAZIONI GESTIONALI IN .NET
Un generatore di applicazioni che per-
mette di creare in pochissimo tempo
gestionali a partire da database gi esi-
stenti: sufficiente indicare la base di da-
ti per avere in pochi istanti una appli-
cazione Web 2.0 perfettamente funzio-
nante. Lapproccio wizard-driven di
estrema semplicit e permette di en-
trare in confidenza con lapplicazione
in poco tempo.
Il codice .NET generato aperto al cen-
to per cento e pu dunque essere ulte-
riormente manipolato per ottenere ri-
sultati pi raffinati.
Directory: Iron_Speed_Designer_Setup.exe
ALTOVA DATABA-
SESPY 2008 REL. 2
PROGGETTARE E INTERROGARE DB
Dai creatori di XMLSpy, un comodissi-
mo tool per la progettazione e
linterrogazione di database. Si inter-
faccia con tutti i principali DB e sem-
plifica enormemente la scrittura di query
SQL. Versione di prova a trenta giorni.
Directory: databasespy2008.exe
ANDROID SDK
LA PIATTAFORMA DI SVILUPPO
PI ATTESA
Qualsiasi mossa di Google di fonda-
mentale importanza per chiunque ope-
ri nel campo dellinformatica, e per gli
sviluppatori in particolare. Android, la
piattaforma di Google per gli smartpho-
ne della prossima generazione, pro-
mette di rivoluzionare lintero settore.
Non facciamoci cogliere impreparati:
installiamo il kit di sviluppo, emulia-
mo la piattaforma e assimiliamo i con-
cetti fondamentali della programma-
zione.
Directory: android-sdk-windows-0.9_beta
106-108:106-108-software 30-09-2008 16:02 Pagina 108
ht t p: / / www. i opr ogr ammo. i t
SOLUZIONI M
G
110
/Novembre 2008
Algoritmi geometrici per la programmazione grafica raster
C
ontinuiamo lesplorazione dei metodi
alla base della grafica computazione.
Dopo aver trattato la rasterizzazione, ora
ci occuperemo di una tecnica altrettanto rilevan-
te: il clipping, ovvero il pre-trattamento applica-
to alle primitive grafiche, come ad esempio la
rappresentazione di un segmento, per
lindividuazione della parte che effettivamente
bisogna mostrare. In considerazione del fatto
che, la fase di modellazione potrebbe generare
primitive che risulterebbero totalmente o in
parte fuori dallarea di rappresentazione; come
per un segmento che solo parzialmente ricade
nella finestra grafica attiva. Come detto si tratta
di un procedimento che viene implementato a
monte della fase di rasterizzazione (scan conver-
sion) che cos risulta snellita. Un altro approccio,
che si occupa dello stesso problema, conosciuto
come scissoring, prevede lindividuazione delle
parti da rappresentare, punto per punto assieme
alla fase di scan conversion. stato mostrato che
conveniente, in termini computazionali, svi-
luppare prima il clipping, nellambito delle ela-
borazioni geometriche e successivamente la
scan conversion. Esamineremo in questo artico-
lo i principali metodi di clipping applicati a pri-
mitive elementari: punto, segmento e poligono.
PIPELINE GRAFICA
la scomposizione in fasi che realizza lintero
processo grafico dalla modellazione alla rappre-
sentazione. Essa consiste nellindividuazione
degli oggetti espressi da insiemi di primitive gra-
fiche che si vogliono rappresentare. In particola-
re verranno specificati insiemi di vertici che
descrivono gli oggetti. Tali oggetti vengono trat-
tati geometricamente per risolvere una serie di
problemi che usualmente si incontrano, data la
complessit del compito, tale fase stata scom-
posta in sotto fasi: clipping, trasformazioni
proiezioni, eliminazione superfici nascoste e
ombre. Il clipping, oggetto delle presenti analisi,
consiste nella rimozione di parti di primitive che
non appartengono alla finestra di visualizzazio-
ne. Una seconda sotto fase la trasformazione da
spazio 3D dove spesso un oggetto descritto in
spazio 2D dove deve essere rappresentato
(appunto perch disponiamo di schermi bidi-
mensionali!); fanno parte di questo stadio altre
trasformazioni come le proiezioni. La terza e la
quarta sotto fasi consistono nella rimozione
delle aree non visibili e la visualizzazione delle
ombre. A tale proposito, utile ricordare che un
oggetto nello spazio viene visto differentemente
e comunque parzialmente a seconda della pro-
spettiva dellosservatore. Anche la visione delle
ombre, ovviamente, sono differenti a seconda
della prospettiva. Le altre fasi della pipeline gra-
fica sono la rasterizzazione o scan conversion
che abbiamo esaminato negli scorsi numeri e
infine la rappresentazione vera e propria.
CLIPPING, I PRIMI PASSI
Abbiamo detto che le tecniche di clipping consi-
stono nella rimozione di primitive grafiche o
parti di esse, che non rientrano nellarea di rap-
presentazione conosciuta come finestra di clip-
ping. Le primitive sono semplici figure geometri-
che (le complesse si costruiscono come insieme
di primitive). Larea di clipping consiste in una
figura geometrica come un rettangolo, un esae-
dro o in generale un poligono convesso; nella
pratica si usa generalmente un rettangolo, come
uno schermo o una finestra canvas.
Nella nostra trattazione ci riferiremo sempre ad
un rettangolo. In Fig. 1 si comprende meglio cosa
intendiamo per clipping. Il rettangolo indica la
finestra di rappresentazione.
Supponiamo che il rettangolo di clipping sia
descritto da quattro valori, due delimitazioni per
le x (xmax e xmin) e due delimitazioni per le y
(ymax e ymin). Il caso pi semplice di clipping
IL CLIPPING
DI UNA IMMAGINE
SI TRATTA DI UNA FASE FONDAMENTALE NEL TRATTAMENTO GEOMETRICO DI UNIMMAGINE.
CONSENTE DI INDIVIDUARE LE PARTI DI OGGETTI CHE EFFETTIVAMENTE DEVONO ESSERE
TRACCIATE NELLA FINESTRA DI SCHERMO DISPONIBILE. ESAMINIAMONE IL FUNZIONAMENTO
Conoscenze richieste
Nozioni di base su:
linguaggi di
programmazione,
geometria e analisi
matematica.
Software
-
Impegno
Tempo di realizzazione
REQUISITI
110-114:110-113-Soluzioni mcd 30-09-2008 16:43 Pagina 110
ht t p: / / www. i opr ogr ammo. i t
M SOLUZIONI
Novembre 2008/
111
G
Algoritmi geometrici per la programmazione grafica raster
riguarda un singolo punto o pixel. Esso verr rap-
presentato se si trova allinterno del rettangolo.
Verificarlo banale. Devono essere soddisfatte
disequazioni elementari:
Il passo successivo consiste nel clipping di un
segmento. I casi che possono presentarsi sono
tre, a seconda se il segmento interno in tutte le
sue parti allarea, se completamente esterno o
se solo parzialmente presente nella finestra di
visualizzazione. Una classificazione pi interes-
sante fatta in funzione degli estremi del seg-
mento:
a Entrambi gli estremi sono interni allarea,
allora il segmento interno;
b Un estremo interno e laltro esterno, allo-
ra il segmento interseca un lato del rettangolo;
c Entrambi gli estremi sono esterni. In tal caso
possono aversi due situazioni: la prima (c.1)
che il segmento completamente esterno, la
seconda (c.2) che il segmento per una parte
interno. Vedremo che i vari algoritmi possono
distinguere ancora altri casi sulla base del
modello geometrico adottato per la descrizione
del problema. Le quattro differenti situazioni
sono rappresentate in Fig. 2: A) segmento con
entrambi i vertici interni; B) segmento con un
estremo interno e laltro esterno al rettangolo di
clipping; C.1) entrambi gli estremi sono esterni e
il segmento esterno; C.2) entrambi sono ester-
ni, ma il segmento, in parte, ricade nellarea di
visualizzazione
Alla base dei vari metodi che esamineremo per la
realizzazione del clipping vi la descrizione del
rettangolo di clipping come lintersezione di
quattro rette a coppie di due parallele allasse
delle ascisse e delle ordinate. Le quattro rette
citate hanno le seguenti equazioni:
y=ymax
y=ymin
x=xmax
x=xmin
In Fig. 3 possiamo vedere la rappresentazione
delle quattro rette.
Un primo metodo per individuare se il segmento
o parte di esso interno allarea di clipping, il
pi ovvio, ma come vedremo poco efficiente. Si
tratta di una soluzione analitica che prevede il
sistema tra lequazione della retta associata al
segmento e le quattro rette che delimitano larea
di clipping. Sicuramente si riscontreranno delle
intersezioni; bisogner per verificare se effetti-
vamente intersecano il rettangolo di clipping. Si
procede in questo modo. Si descrive la retta asso-
ciata ad un segmento A, B (ossia individuato da
due punti A e B) con le equazioni parametriche:
Con q appartenente allintervallo chiuso [0, 1].
Si tratta di sostituire le quattro coordinate cor-
rispondenti ai due punti A e B. Si ottengono
due equazioni: una per la x e una per la y; le
NOTA
POLIGONO
CONVESSO
Un poligono convesso se.
presi due qualsiasi punti, il
segmento che li unisce
ricade interamente nella
figura (poligono). Si tratta
del tipo di figura geometrica
pi facile da trattare
nellambito del clipping.
Fig. 1: A) oggetti da rappresentare prima del clipping;
B) oggetti rappresentati con il clipping
Fig. 2: Quattro differenti tipi di segmenti
Fig. 3: Descrizione dellarea di clipping come interse-
zione di quattro rette
110-114:110-113-Soluzioni mcd 30-09-2008 16:43 Pagina 111
ht t p: / / www. i opr ogr ammo. i t
SOLUZIONI M
G
112
/Gennaio 2006
Fisica
ht t p: / / www. i opr ogr ammo. i t
G
112
/Novembre 2008
Algoritmi geometrici per la programmazione grafica raster
indicheremo con equazioni 1. Andando a
sostituire ad x e y la prima volta x
max
e y
max
e
la seconda volta x
min
e y
min
si ottengono
valori di q che sostituiti nelle equazioni 1 indi-
viduano i punti di intersezione. Leventuale
parallelismo che non produce intersezione va
verificato prima che si imposti il sistema.
Bisogna poi verificare se i punti di intersezione
appartengono al rettangolo di clipping. Tale
metodo costoso in termini computazionali,
poich richiede delle divisioni in virgola mobi-
le. Per cui si preferiranno metodi, come quello
proposto di seguito, per i quali lintersezione
viene effettuata quando effettivamente indi-
spensabile.
METODO
COHEN SUTHERLAND
Lidea alla base del metodo omonimo ridurre al
minimo il calcolo delle intersezioni. Per farlo si
numerano le aree risultanti dal partizionamento
con una sequenza di bit, come possiamo vedere
in figura; essendo nove sono necessari 4 bit. La
numerazione avviene comparando la posizione
di un qualsiasi punto con le quattro rette che
delimitano il rettangolo. I valori sono descritti in
sintesi nella seguente tabella.
Ognuna delle nove aree sar quindi descritta da
una sequenza di bit cosi come si pu desumere
da quanto riportato in Fig. 4.
La sequenza prende il nome di outcode.
Si pu notare che il rettangolo di clipping carat-
terizzato da outcode di quattro zeri. Ad esempio:
tutte le tre aree sopra la retta di delimitazione
superiore, sono caratterizzate dal valore 1 del
primo bit. Si tratta di esaminare gli estremi del
segmenti. Con operazioni logiche booleane pos-
siamo stabilire per alcuni casi se il segmento
interno o esterno allarea di clipping. I restanti
casi di indeterminatezza possono essere trattati
analiticamente con il calcolo delle eventuali
intersezioni che consentiranno di accorciare il
segmento.
I passi dellalgoritmo sono riportati di seguito.
1. Calcolo degli outcode per gli estremi del seg-
mento;
2. Verifica se il segmento interno allarea di
clipping, si ha se lor logico dei due estremi
restituisce la sequenza nulla;
3. Verifica se il segmento esterno allarea di
clipping, si ha se land logico dei due estremi
diverso da zero, almeno in una componente;
4. Per i restanti casi, per i quali land logico ugua-
le a zero, necessario calcolare le eventuali
intersezioni e accorciare il segmento;
I vari casi che si possono verificare sono riporta-
ti sempre in Fig. 5, ad ogni categoria di segmenti
associato un colore. Il segmenti al punto 2,
ossia interni sono blu. I segmenti al punto 3,
ossia esterni sono rossi. Infine, i segmenti al
punto 4 che generano indeterminatezza, sono
riportati in arancio. Tutti i segmenti che hanno
un estremo allinterno dellarea di clipping e
laltro allesterno, ovviamente ricadono nel quar-
to caso, land sicuramente una sequenza di
zeri, essendo larea citata tutta nulla. Per il quar-
to caso bisogna parlare di eventuali intersezio-
ni proprio perch ci troviamo in una situazione
di indeterminatezza e quindi non siamo certi che
vi siano; infatti, tra i segmenti arancio ve ne sono
due che sono esterni. Merita approfondimento
limplementazione del punto quattro.
Esaminando i bit discordanti dalloperazione di
and logico si possono calcolare pi rapidamente
le intersezioni. Indichiamo i due punti con A e B,
e supponiamo di disporre di una funzione che
restituisce liesimo bit delloutcode per un gene-
rico punto P, del tipo outcode(i,P). Le rette sono
numerate. Con tali presupposti si procede con lo
sviluppo dellalgoritmo:
1. i <-- 1
2. Ripeti
3. Se outcode(i,A) <> outcode(i,B) calcola inter-
sezione con retta(i)
4. Accorcia il segmento con lintersezione otte-
nuta al punto precedente, ottieni il punto C;
BIT - VALORE BIT 1 0
b0 y>ymax y<=ymax
b1 y<ymin y>=ymin
b2 x>xmax x<=xmax
b3 x<xmin x>=xmin
Fig. 4: Suddivisione in nove aree e outcode relativi
110-114:110-113-Soluzioni mcd 30-09-2008 16:43 Pagina 112
ht t p: / / www. i opr ogr ammo. i t
5. Se outcode(j,C) OR outcode(j,B) = zero per j
che va da 1 a 4 allora esci altrimenti vai al
punto 6;
6. i <-- i+1
7. finch i>4
Alcune precisazioni. Il punto C ottenuto al passo
4, che ci consente di accorciare il segmento, va a
sostituire un estremo del segmento che potr
essere il punto A o il punto B a seconda della
intersezione ottenuta. Ad esempio, lintersezione
con la retta superiore y=ymax sostituisce A, men-
tre lintersezione con y=ymin sostituisce B.
Per semplicit nellalgoritmo, al punto successi-
vo era riportato al posto di A. Analogo ragiona-
mento per le rette verticali. Inoltre si suppone
sempre che i punti A e B vengano assegnati
seguendo alcune regole come ya>yb. Lalgoritmo
funziona anche se il segmento non ha intersezio-
ni con larea di clipping, in tal caso viene accor-
ciato a tal punto da essere annullato.
In definitiva, stabilito che lalgoritmo di Cohen
Sutherland sicuramente pi efficiente del
metodo puramente analitico, riporta buone
performance nei casi in cui molti segmenti siano
esterni.
METODO LIANG BARSKY
Questo secondo metodo affronta il problema
con una logica differente, che stato dimostrato
essere pi efficiente. Il primo passo esprimere il
segmento descritto dai due punti A e B, con una
retta nella forma parametrica che abbiamo
espresso per il metodo analitico.
Valori di q tra 0 e 1 ci fanno spostare nel segmen-
to da A a B. I punti del segmento interni al rettan-
golo di clipping soddisfano la relazione:
che in funzione della prima espressione ed espri-
mendo le differenze tra le x e y come incrementi
delta, possono essere riscritte:
Un modo sintetico per riscrivere queste quattro
disequazioni :
Definiamo le due serie di quattro valori u e v, che
verranno numerati con parametro k da 1 a 4.
Associate ai quattro vincoli, le quattro rette:
(1) (sinistra) x=x
min
(2) (destra) x=x
max
(3) (basso) y=y
min
(4) (alto) y=y
max
Le linee parallele sono individuate da alcuni
valori di u
k
pari a 0. Per capire se in questo caso
una linea interna o esterna baster osservare i
corrispondenti (rispetto a k) valori di v, se sono
minori di 0 il segmento completamente ester-
no, altrimenti interno. Esaminiamo il caso pi
comune di non parallelismo. I parametri u con-
tengono una preziosa informazione, se sono
negativi ci indicano che nel muoverci sul seg-
mento da A a B andiamo da dentro a fuori, men-
tre se sono positivi nello stesso movimento vuol
dire che si va da fuori a dentro.
A questo punto, per individuare leventuale seg-
mento di clipping, bisogna trovare due valori di
q, uno di entrata, laltro di uscita. Il primo, quello
di entrata, il valore massimo tra lo 0 i, qk cor-
rispondenti ad entrate (con valori di uk<0).
Lo 0 va considerato poich il valore minimo che
pu assumere q. Analogamente, il parametro q,
che indica la fine del segmento nella finestra di
visualizzazione, il minimo tra 1 e i valori di q
uscenti (corrispondenti a uk>0). Il valore massi-
mo che pu assumere q 1.
qe = max{0, tj con j corrispondente a uj<0}
qu = min{1, tj con j corrispondente a uj>0}
Siamo quasi al termine. Il segmento andr rap-
presentato solo se lentrata prima delluscita,
ovvero se qe<qu, in caso contrario il segmento
esterno e quindi non va visualizzato. In Fig. 5
mostrato il caso di segmento parzialmente inter-
no. Inoltre, sono riportati in grigio dei segmenti
che indicano i meccanismi di variazione del
parametro u.
Infine, solo se necessario, verranno calcolate le
intersezioni che si ottengono facilmente dal rap-
porto vk/uk. Tale metodo sembra complesso, ma
in realt non cos. Tale impressione potrebbe
M SOLUZIONI
Novembre 2008/
113
G
Algoritmi geometrici per la programmazione grafica raster
110-114:110-113-Soluzioni mcd 30-09-2008 16:43 Pagina 113
ht t p: / / www. i opr ogr ammo. i t
essere dovuta alla maggiore quantit di dati pro-
dotti, che in definitiva per semplificano il pro-
cedimento e soprattutto ne decretano rilevanti
prestazioni, tanto da preferirlo a quello di Cohen
Sutherland. Ad ogni modo una comprensione
maggiore si ottiene se si sviluppa, anche con
carta e penna un esempio completo, riportando
da prima i valori che delimitano la finestra di
clipping (xmin, xmax, ymin e ymax), poi indivi-
duando i due punti A e B e poi applicando il
metodo descritto.
SUTHERLAND
HODGAMAN
Il clipping di un poligono unoperazione pi
complessa, proprio perch ci troviamo di fronte
a una figura geometrica pi articolata. Inoltre, i
poligoni concavi possono produrre aree separate
di visualizzazione.
I due casi sono rappresentati in Fig. 6.
Con figure convesse, per risolvere il problema,
basterebbe applicare uno dei due metodi di clip-
ping lineari associati ai segmenti che delimitano il
poligono e riempire opportunamente le parti inter-
ne ad esso. Il problema si pone per le figure non
convesse. Per risolverlo Sutherland e Hodgaman
hanno messo a punto un algoritmo che propone
un nuovo approccio alla problematica. Si basa sulla
pi volte usata strategia di divide et impera. Si trat-
ta di risolvere diversi e circoscritti problemi che
associati ci forniscono la soluzione finale. Tale pro-
cedimento riportato in Fig. 7.
Il problema basilare il clipping rispetto ad un solo
lato descritto da una retta infinita. Si ripete cos per
i quattro lati una sorta di eliminazione delle parti
superflue. Ad ogni iterazione, per tale problema
basilare si individuano due semipiani, uno interno,
laltro esterno. Si confrontano in ordine gli n vertici
del poligono. Si costruisce una lista di output costi-
tuita da punti. Il confronto si effettua per coppie di
vertici che potranno trovarsi rispetto ai due piani
definiti:
- entrambi nel semipiano interno, allora il secondo
viene aggiunto nella lista di output;
- il primo interno e il secondo esterno, allora
lintersezione viene aggiunta alla lista di output;
- entrambi esterni, allora non si fa nulla;
- il primo esterno ed il secondo interno, allora ven-
gono aggiunti sia lintersezione sia il punto nella
lista di output.
Si costruisce cos un nuovo poligono per ogni lato
che viene esaminato. come se, ad ogni iterazione,
andassimo con una gomma a cancellare tutto ci
che nel lato esterno rispetto a quello che stiamo
esaminando. In alcuni casi il clipping d luogo a
pi componenti, generando falsi spigoli. Sar una
elaborazione a posteriori a risolvere questa situa-
zione. Anche per questo metodo consigliabile un
test anche solo con carta e penna, ci aiuter a capi-
re in profondit i meccanismi di funzionamento.
CONCLUSIONI
Unaltra importante tessera, che ci porter a una
completa e approfondita conoscenza dei metodi
geometrici usati per la computer grafica basilare,
stata mostrata.
Fabio Grimaldi
SOLUZIONI M
G
114
/Novembre 2008
Algoritmi geometrici per la programmazione grafica raster
Fig. 5: Rappresentazione dellalgoritmo di Liang
Barsky
Fig. 6: Clipping di poligoni: A) poligono convesso;
B) poligono non convesso, pu generare pi aree
Fig. 7: Clipping con metodologia divide et impera
110-114:110-113-Soluzioni mcd 30-09-2008 16:43 Pagina 114