Académique Documents
Professionnel Documents
Culture Documents
Apprentissage de Java Serge Tahe
Apprentissage de Java Serge Tahe
Introduction
Ce document est un support de cours : ce n'est pas un cours complet. Des approfondissements ncessitent l'aide d'un enseignant.
L'tudiant y trouvera cependant une grande quantit d'informations lui permettant la plupart du temps de travailler seul. Ce
document comporte probablement des erreurs : toute suggestion constructive est la bienvenue l'adresse serge.tahe@istia.univangers.fr.
Il existe d'excellents livres sur Java. Parmi ceux-ci :
1.
2.
Le premier livre est un excellent ouvrage d'introduction pdagogique au langage Java. Une fois acquis son contenu, on pourra
passer au second ouvrage qui prsente des aspects plus avancs de Java (Java Beans, JDBC, Corba/Rmi). Il prsente une vue
industrielle de Java intressante. Pour approfondir Java dans diffrents domaines, on pourra se rfrer la collection "Java series"
chez O'Reilly. Pour une utilisation professionnelle de Java au sein d'une plate-forme J2EE on pourra lire :
3.
1.
1.1
1.2
1.2.1
1.2.2
1.2.3
1.2.4
1.2.5
1.3
1.3.1
1.3.2
1.3.3
1.3.4
1.4
1.4.1
1.4.2
1.4.3
1.4.4
1.5
1.6
1.7
1.8
1.9
1.10
2.
INTRODUCTION
LES DONNEES DE JAVA
LES TYPES DE DONNEES PREDEFINIS
NOTATION DES DONNEES LITTERALES
DECLARATION DES DONNEES
LES CONVERSIONS ENTRE NOMBRES ET CHAINES DE CARACTERES
LES TABLEAUX DE DONNEES
LES INSTRUCTIONS ELEMENTAIRES DE JAVA
ECRITURE SUR ECRAN
LECTURE DE DONNEES TAPEES AU CLAVIER
EXEMPLE D'ENTREES-SORTIES
AFFECTATION DE LA VALEUR D'UNE EXPRESSION A UNE VARIABLE
LES INSTRUCTIONS DE CONTROLE DU DEROULEMENT DU PROGRAMME
ARRET
STRUCTURE DE CHOIX SIMPLE
STRUCTURE DE CAS
STRUCTURE DE REPETITION
LA STRUCTURE D'UN PROGRAMME JAVA
LA GESTION DES EXCEPTIONS
COMPILATION ET EXECUTION D'UN PROGRAMME JAVA
ARGUMENTS DU PROGRAMME PRINCIPAL
PASSAGE DE PARAMETRES A UNE FONCTION
L'EXEMPLE IMPOTS
7
7
7
7
8
8
10
10
11
11
12
13
17
17
18
18
19
21
22
25
25
26
26
CLASSES ET INTERFACES
30
30
30
30
31
31
32
32
34
35
36
37
37
38
39
40
40
41
41
42
43
43
44
45
46
49
52
52
56
58
3
3.
62
3.1 LA DOCUMENTATION
3.2 LES CLASSES DE TEST
3.3 LA CLASSE STRING
3.4 LA CLASSE VECTOR
3.5 LA CLASSE ARRAYLIST
3.6 LA CLASSE ARRAYS
3.7 LA CLASSE ENUMERATION
3.8 LA CLASSE HASHTABLE
3.9 LES FICHIERS TEXTE
3.9.1 ECRIRE
3.9.2 LIRE
3.9.3 SAUVEGARDE D'UN OBJET PERSONNE
3.10 LES FICHIERS BINAIRES
3.10.1 LA CLASSE RANDOMACCESSFILE
3.10.2 LA CLASSE ARTICLE
3.10.3 ECRIRE UN ENREGISTREMENT
3.10.4 LIRE UN ENREGISTREMENT
3.10.5 CONVERSION TEXTE --> BINAIRE
3.10.6 CONVERSION BINAIRE --> TEXTE
3.10.7 ACCES DIRECT AUX ENREGISTREMENTS
3.11 UTILISER LES EXPRESSION REGULIERES
3.11.1 LE PAQUETAGE JAVA.UTIL.REGEX
3.11.2 VERIFIER QU'UNE CHAINE CORRESPOND A UN MODELE DONNE
3.11.3 TROUVER TOUS LES ELEMENTS D'UNE CHAINE CORRESPONDANT A UN MODELE
3.11.4 RECUPERER DES PARTIES D'UN MODELE
3.11.5 UN PROGRAMME D'APPRENTISSAGE
3.11.6 LA METHODE SPLIT DE LA CLASSE PATTERN
3.12 EXERCICES
3.12.1 EXERCICE 1
3.12.2 EXERCICE 2
3.12.3 EXERCICE 3
3.12.4 EXERCICE 4
3.12.5 EXERCICE 5
62
64
65
66
67
68
72
73
74
74
75
76
77
77
77
78
79
80
81
83
85
85
87
87
88
89
91
92
92
93
94
95
96
4.
98
INTERFACES GRAPHIQUES
4.1
4.1.1
4.1.2
4.1.3
4.1.4
4.1.5
4.1.6
4.1.7
4.2
4.2.1
4.2.2
4.2.3
4.2.4
4.2.5
4.2.6
4.2.7
4.3
4.3.1
4.3.2
4.3.3
4.3.4
4.4
4.4.1
4.4.2
Les bases
98
98
100
102
105
106
107
107
108
108
112
116
123
126
143
146
151
151
151
152
153
154
154
159
4
4.5
4.6
4.6.1
4.6.2
4.6.3
4.6.4
4.6.5
4.7
4.8
4.9
5.
200
GENERALITES
LES ETAPES IMPORTANTES DANS LEXPLOITATION DES BASES DE DONNEES
INTRODUCTION
LETAPE DE CONNEXION
MISSION DE REQUETES VERS LA BASE DE DONNEES
IMPOTS AVEC UNE BASE DE DONNEES
EXERCICES
EXERCICE 1
EXERCICE 2
EXERCICE 3
EXERCICE 4
200
201
201
203
205
214
220
220
220
220
225
6.1
6.2
6.3
6.4
6.5
6.6
6.6.1
6.6.2
6.6.3
6.6.4
7.
164
169
169
169
170
176
178
183
187
187
5.1
5.2
5.2.1
5.2.2
5.2.3
5.3
5.4
5.4.1
5.4.2
5.4.3
5.4.4
6.
229
INTRODUCTION
CREATION DE THREADS D'EXECUTION
INTERET DES THREADS
UNE HORLOGE GRAPHIQUE
APPLET HORLOGE
SYNCHRONISATION DE TACHES
UN COMPTAGE NON SYNCHRONISE
UN COMPTAGE SYNCHRONISE PAR METHODE
COMPTAGE SYNCHRONISE PAR UN OBJET
SYNCHRONISATION PAR EVENEMENTS
229
230
232
233
235
237
237
240
241
242
PROGRAMMATION TCP-IP
246
7.1 GENERALITES
7.1.1 LES PROTOCOLES DE L'INTERNET
7.1.2 LE MODELE OSI
7.1.3 LE MODELE TCP/IP
7.1.4 FONCTIONNEMENT DES PROTOCOLES DE L'INTERNET
7.1.5 LES PROBLEMES D'ADRESSAGE DANS L'INTERNET
7.1.6 LA COUCHE RESEAU DITE COUCHE IP DE L'INTERNET
7.1.7 LA COUCHE TRANSPORT : LES PROTOCOLES UDP ET TCP
7.1.8 LA COUCHE APPLICATIONS
7.1.9 CONCLUSION
7.2 GESTION DES ADRESSES RESEAU EN JAVA
7.2.1 DEFINITION
7.2.2 QUELQUES EXEMPLES
7.3 COMMUNICATIONS TCP-IP
7.3.1 GENERALITES
7.3.2 LES CARACTERISTIQUES DU PROTOCOLE TCP
7.3.3 LA RELATION CLIENT-SERVEUR
7.3.4 ARCHITECTURE D'UN CLIENT
7.3.5 ARCHITECTURE D'UN SERVEUR
7.3.6 LA CLASSE SOCKET
Les bases
246
246
246
247
249
250
253
254
255
256
256
256
257
258
258
258
259
259
259
260
5
262
264
264
267
269
274
280
282
284
289
289
292
295
300
8.
304
JAVA RMI
8.1
8.2
8.2.1
8.3
8.3.1
8.3.2
8.3.3
8.3.4
8.3.5
8.3.6
8.3.7
8.3.8
8.4
8.4.1
8.4.2
9.
INTRODUCTION
APPRENONS PAR LEXEMPLE
LAPPLICATION SERVEUR
DEUXIEME EXEMPLE : SERVEUR SQL SUR MACHINE WINDOWS
LE PROBLEME
TAPE 1 : LINTERFACE DISTANTE
TAPE 2 : CRITURE DU SERVEUR
CRITURE DU CLIENT RMI
TAPE 3 : CREATION DES FICHIERS .CLASS
TAPE 4 : TESTS AVEC SERVEUR & CLIENT SUR MEME MACHINE WINDOWS
TAPE 5 : TESTS AVEC SERVEUR SUR MACHINE WINDOWS ET CLIENT SUR MACHINE LINUX
CONCLUSION
EXERCICES
EXERCICE 1
EXERCICE 2
304
304
304
315
315
316
316
318
320
321
322
323
324
324
324
9.1
9.2
9.2.1
9.2.2
9.2.3
9.2.4
9.2.5
9.2.6
9.2.7
9.3
9.3.1
9.3.2
9.3.3
9.3.4
9.3.5
9.3.6
9.3.7
9.4
325
INTRODUCTION
PROCESSUS DE DEVELOPPEMENT DUNE APPLICATION CORBA
INTRODUCTION
CRITURE DE LINTERFACE DU SERVEUR
COMPILATION DE LINTERFACE IDL DU SERVEUR
COMPILATION DES CLASSES GENEREES A PARTIR DE LINTERFACE IDL
CRITURE DU SERVEUR
CRITURE DU CLIENT
TESTS
EXEMPLE 2 : UN SERVEUR SQL
INTRODUCTION
CRITURE DE LINTERFACE IDL DU SERVEUR
COMPILATION DE LINTERFACE IDL DU SERVEUR
CRITURE DU SERVEUR SQL
CRITURE DU PROGRAMME DE LANCEMENT DU SERVEUR SQL
CRITURE DU CLIENT
TESTS
CORRESPONDANCES IDL - JAVA
Les bases
325
325
325
325
326
327
327
330
332
333
333
333
334
335
337
338
341
343
des donnes
les instructions qui les manipulent
DONNEES
+--------------------
INSTRUCTIONS
+--------------------+
Codage
2 octets
4 octets
8 octets
1 octet
2 octets
4 octets
8 octets
1 bit
rfrence d'objet
rfrence d'objet
rfrence d'objet
rfrence d'objet
rfrence d'objet
rfrence d'objet
rfrence d'objet
rfrence d'objet
rfrence d'objet
Domaine
caractre Unicode
[-231, 231-1]
[-263, 263 -1]
[-27 , 27 -1]
[-215, 215-1]
[3.4 10-38, 3.4 10+38] en valeur absolue
[1.7 10-308 , 1.7 10+308] en valeur absolue
true, false
chane de caractres
date
char
int
long
byte
float
double
boolean
Les bases
entier
rel double
rel float
caractre
chane de caractres
boolen
date
La lecture du programme sera plus aise si l'on a donn la constante un nom significatif :
ex : final float taux_tva=0.186F;
2.
La modification du programme sera plus aise si la "constante" vient changer. Ainsi dans le cas prcdent, si le taux de
tva passe 33%, la seule modification faire sera de modifier l'instruction dfinissant sa valeur :
final float taux_tva=0.33F;
Si l'on avait utilis 0.186 explicitement dans le programme, ce serait alors de nombreuses instructions qu'il faudrait modifier.
"" + nombre
Integer.parseInt(chaine)
Long.parseLong(chaine)
Double.valueOf(chaine).doubleValue()
Float.valueOf(chaine).floatValue()
Voici un programme prsentant les principales techniques de conversion entre nombres et chanes de caractres. La conversion
d'une chane vers un nombre peut chouer si la chane ne reprsente pas un nombre valide. Il y a alors gnration d'une erreur
fatale appele exception en Java. Cette erreur peut tre gre par la clause try/catch suivante :
try{
appel de la fonction susceptible de gnrer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Si la fonction ne gnre pas d'exception, on passe alors instruction suivante, sinon on passe dans le corps de la clause catch puis
instruction suivante. Nous reviendrons ultrieurement sur la gestion des exceptions.
import java.io.*;
public class conv1{
public static void main(String arg[]){
String S;
final int i=10;
final long l=100000;
final float f=(float)45.78;
double d=-14.98;
java.lang.NumberFormatException: 10.67
java.lang.NumberFormatException: 10.675
java.lang.NumberFormatException: abcd
java.lang.NumberFormatException: abcd
10
1
2
Les instructions lmentaires apparaissent clairement lorsqu'on considre la structure d'un micro-ordinateur et de ses priphriques.
U. C
MEMOIRE
ECRAN
+-------------------+
+-------+
2 <-+-->
3
+-----------+
1
----+------+->
CLAVIER
+-----------+--------+-->
+-----------+
+-------------------+
+-------+
4^
\
\ 5
+-------+
\ ---->
DISQUE
+-------+
System.out crit dans un fichier texte qui est par dfaut l'cran. Il en est de mme pour System.err. Ces fichiers portent un
numro (ou descripteur) respectivement 1 et 2. Le flux d'entre du clavier (System.in) est galement considr comme un
fichier texte, de descripteur 0. Dos comme Unix supportent le tubage (pipe) de commandes :
commande1 | commande2
Tout ce que commande1 crit avec System.out est tub (redirig) vers l'entre System.in de commande2. Dit autrement,
commande2 lit avec System.in, les donnes produites par commande2 avec System.out qui ne sont donc plus affiches
l'cran. Ce systme est trs utilis sous Unix. Dans ce tubage, le flux System.err n'est lui pas redirig : il crit sur l'cran. C'est
pourquoi il est utilis pour crire les messages d'erreurs (d'o son nom err) : on est assur que lors d'un tubage de commandes,
les messages d'erreur continueront s'afficher l'cran. On prendra donc l'habitude d'crire les messages d'erreur l'cran avec
le flux System.err plutt qu'avec le flux System.out.
Nous n'expliquerons pas ici les dtails de cette instruction qui fait intervenir la notion de constructions d'objets. Nous
l'utiliserons telle-quelle.
La construction d'un flux peut chouer : une erreur fatale, appele exception en Java, est alors gnre. A chaque fois qu'une
Les bases
11
mthode est susceptible de gnrer une exception, le compilateur Java exige qu'elle soit gre par le programmeur. Aussi, pour
crer le flux d'entre prcdent, il faudra en ralit crire :
BufferedReader IN=null
null;
null
try{
try
IN=new
new BufferedReader(new
new InputStreamReader(System.in));
} catch (Exception e){
System.err.println("Erreur " +e);
System.exit(1);
}
De nouveau, on ne cherchera pas expliquer ici la gestion des exceptions. Une fois le flux IN prcdent construit, on peut lire
une ligne de texte par l'instruction :
String ligne;
ligne=IN.readLine();
La ligne tape au clavier est range dans la variable ligne et peut ensuite tre exploite par le programme.
Les instructions
Object obj=new Object();
System.out.println(""+obj);
System.out.println(obj.getClass().getName());
Les bases
12
ont pour but de montrer que n'importe quel objet peut faire l'objet d'un affichage. Nous ne chercherons pas ici expliquer la
signification de ce qui est affich. Nous avons galement l'affichage de la valeur d'un objet dans le bloc :
try{
La variable e est un objet de type Exception qu'on affiche ici avec l'appel affiche(e). Nous avions rencontr, sans en parler, cet
affichage de la valeur d'une exception dans le programme de conversion vu plus haut.
C'est ainsi que l'opration V1=V2=expression est lgale. A cause de la priorit, c'est l'oprateur = le plus droite qui va tre valu.
On a donc V1=(V2=expression). L'expression V2=expression est value et a pour valeur V. L'valuation de cette expression a
provoqu l'affectation de V V2. L'oprateur = suivant est alors valu sous la forme V1=V. La valeur de cette expression est
encore V. Son valuation provoque l'affectation de V V1. Ainsi donc, l'opration V1=V2=expression est une expression dont
l'valuation
1
2
addition
soustraction
multiplication
division : le rsultat est le quotient exact si l'un au moins des oprandes est rel. Si les deux oprandes sont entiers le
rsultat est le quotient entier. Ainsi 5/2 -> 2 et 5.0/2 ->2.5.
division : le rsultat est le reste quelque soit la nature des oprandes, le quotient tant lui entier. C'est donc l'opration
modulo.
racine carre
Cosinus
Sinus
Tangente
x la puissance y (x>0)
Exponentielle
Logarithme nprien
valeur absolue
13
etc...
Toutes ces fonctions sont dfinies dans une classe Java appele Math. Lorsqu'on les utilise, il faut les prfixer avec le nom de la
classe o elles sont dfinies. Ainsi on crira :
double x, y=4;
x=Math.sqrt(y);
// Methods
double a); 1.10.3
public static double abs(double
public static float abs(float
float a); 1.10.4
public static int abs(int
int a); 1.10.5
public static long abs(long
long a); 1.10.6
public static
double a); 1.10.7
static double acos(double
public static double asin(double
double a); 1.10.8
public static double atan(double
double a); 1.10.9
public static double atan2(double
double a, double b); 1.10.10
public static double ceil(double
double a); 1.10.11
public
double a); 1.10.12
public static double cos(double
public static double exp(double
double a); 1.10.13
public static double floor(double
double a); 1.10.14
public static double
1.10.15
IEEEremainder(double
double f1, double f2);
public
double a); 1.10.16
public static double log(double
public static double max(double
double a, double b); 1.10.17
public static float max(float
float a, float b); 1.10.18
public static int max(int
int a, int b); 1.10.19
public static long max(long
long a, long b); 1.10.20
public static double min(double
double a, double b); 1.10.21
public static float min(float
float a, float b); 1.10.22
public static int min(int
int a, int b); 1.10.23
public static long min(long
long a, long b); 1.10.24
public static double pow(double
double a, double b); 1.10.25
public static double random(); 1.10.26
public static double rint(double
double a); 1.10.27
public static long round(double
double a); 1.10.28
public static int round(float
float a); 1.10.29
public static double sin(double
double a); 1.10.30
public static double sqrt(double
double a); 1.10.31
public static double tan(double
double a); 1.10.32
boolean fin;
int x;
fin=x>4;
Les bases
14
!,
&&, ||
exemple :
int fin;
int x;
fin= x>2 && x<4;
Les oprateurs relationnels ont priorit sur les oprateurs && et ||.
Les bases
15
dcale i de n bits sur la gauche. Les bits entrants sont des zros.
dcale i de n bits sur la droite. Si i est un entier sign (signed char, int, long) le bit de signe
est prserv.
fait le ET logique de i et j bit bit.
fait le OU logique de i et j bit bit.
complmente i 1
fait le OU EXCLUSIF de i et j
Soit
int i=0x123F, k=0xF123;
unsigned j=0xF123;
opration
i<<4
i>>4
k>>4
i&j
i|j
~i
valeur
0x23F0
0x0123 le bit de signe est prserv.
0xFF12 le bit de signe est prserv.
0x1023
0xF33F
0xEDC0
1.3.4.9 L'oprateur ?
L'expression expr_cond ? expr1:expr2 est value de la faon suivante :
1
2
3
l'expression expr_cond est value. C'est une expression conditionnelle valeur vrai ou faux
Si elle est vraie, la valeur de l'expression est celle de expr1. expr2 n'est pas value.
Si elle est fausse, c'est l'inverse qui se produit : la valeur de l'expression est celle de expr2. expr1 n'est pas value.
Exemple
i=(j>4 ? j+1:j-1);
affectera la variable i :
j+1 si j>4, j-1 sinon
C'est la mme chose que d'crire if(j>4) i=j+1; else i=j-1; mais c'est plus concis.
Les bases
16
gd
dg
dg
gd
gd
gd
gd
gd
gd
gd
gd
gd
gd
dg
dg
gd indique qu'a priorit gale, c'est la priorit gauche-droite qui est observe. Cela signifie que lorsque dans une expression, l'on a
des oprateurs de mme priorit, c'est l'oprateur le plus gauche dans l'expression qui est valu en premier. dg indique une
priorit droite-gauche.
// priorit de () sur /
Ici il est ncessaire de changer le type de i ou j en rel sinon la division donnera le quotient entier et non rel.
i est une valeur code de faon exacte sur 2 octets
(float) i est la mme valeur code de faon approche en rel sur 4 octets
Il y a donc transcodage de la valeur de i. Ce transcodage n'a lieu que le temps d'un calcul, la variable i conservant toujours son type
int.
17
notes:
Dans l'exemple prcdent, le else se rapporte quel if ? La rgle est qu'un else se rapporte toujours au if le plus proche : if(n>6) dans
l'exemple. Considrons un autre exemple :
public static void main(void)
{ int n=0;
if(n>1)
if(n>6)
System.out.println(">6");
else;
// else du if(n>6) : rien faire
else System.out.println("<=1"); // else du if(n>1)
Ici nous voulions mettre un else au if(n>1) et pas de else au if(n>6). A cause de la remarque prcdente, nous sommes obligs de
mettre un else au if(n>6), dans lequel il n'y a aucune instruction.
18
. .. .. .. .. ..
default: actions_sinon;
notes
En Java
int choix, erreur;
switch(choix){
switch
case 0: System.exit(0);
case 1: M1();break
break;
break
case 2: M2();break
break;
break
default:
default erreur=1;
}
Notes
19
i
id
tantque i<=if
actions
i
i+ip
fintantque
On boucle tant que la condition est vrifie. La boucle peut ne jamais tre excute.
notes:
On boucle jusqu' ce que la condition devienne fausse ou tant que la condition est vraie. Ici la boucle est faite au moins une fois.
notes
Les bases
20
On boucle tant que la condition est vraie (value avant chaque tour de boucle). Instructions_dpart sont effectues avant d'entrer
dans la boucle pour la premire fois. Instructions_fin_boucle sont excutes aprs chaque tour de boucle.
notes
for(i=1,
somme=0;i<=n;i=i+1)
for
somme=somme+a[i];
3 i=1;somme=0;
while(i<=n)
while
{ somme+=i; i++; }
4 i=1; somme=0;
do somme+=i++;
while (i<=n);
La fonction main, appele aussi mthode est excute la premire lors de l'excution d'un programme Java. Elle doit avoir
obligatoirement la signature prcdente :
public static void main(String arg[]){
ou
Le nom de l'argument arg peut tre quelconque. C'est un tableau de chanes de caractres reprsentant les arguments de la ligne de
commande. Nous y reviendrons un peu plus loin.
Si on utilise des fonctions susceptibles de gnrer des exceptions qu'on ne souhaite pas grer finement, on pourra encadrer le code
du programme par une clause try/catch :
public class test1{
Les bases
21
Au dbut du code source et avant la dfinition de la classe, il est usuel de trouver des instructions d'importation de classes. Par
exemple :
import java.io.*;
public class test1{
public static void main(String arg[]){
code du programme
}// main
}// class
qui crit java l'cran. Il y a dans cette simple instruction beaucoup de choses :
System est une classe dont le nom complet est java.lang.System
out est une proprit de cette classe de type java.io.PrintStream, une autre classe
println est une mthode de la classe java.io.PrintStream.
Nous ne compliquerons pas inutilement cette explication qui vient trop tt puisqu'elle ncessite la comprhension de la notion de
classe pas encore aborde. On peut assimiler une classe une ressource. Ici, le compilateur aura besoin d'avoir accs aux deux
classes java.lang.System et java.io.PrintStream. Les centaines de classes de Java sont rparties dans des archives aussi appeles des
paquetages (package). Les instruction import places en dbut de programme servent indiquer au compilateur de quelles classes
externes le programme a besoin (celles utilises mais non dfinies dans le fichier source qui sera compil). Ainsi dans notre exemple,
notre programme a besoin des classes java.lang.System et java.io.PrintStream. On le dit avec l'instruction import. On pourrait crire en
dbut de programme :
import java.lang.System;
import java.io.PrintStream;
Un programme Java utilisant couramment plusieurs dizaines de classes externes, il serait pnible d'crire toutes les fonction import
ncessaires. Les classes ont t regroupes dans des paquetages et on peut alors importer le paquetage entier. Ainsi pour importer
les paquetages java.lang et java.io, on crira :
import java.lang.*;
import java.io.*;
Le paquetage java.lang contient toutes les classes de base de Java et il est import automatiquement par le compilateur. Aussi
finalement n'crira-t-on que :
import java.io.*;
Lorsqu'une fonction est susceptible de gnrer une exception, le compilateur Java oblige le programmeur grer celle-ci dans le but
d'obtenir des programmes plus rsistants aux erreurs : il faut toujours viter le "plantage" sauvage d'une application. Ici, la fonction
Les bases
22
readLine gnre une exception s'il n'y a rien lire parce que par exemple le flux d'entre a t ferm. La gestion d'une exception se
fait selon le schma suivant :
try{
appel de la fonction susceptible de gnrer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Si la fonction ne gnre pas d'exception, on passe alors instruction suivante, sinon on passe dans le corps de la clause catch puis
instruction suivante. Notons les points suivants :
e est un objet driv du type Exception. On peut tre plus prcis en utilisant des types tels que IOException, SecurityException,
ArithmeticException, etc : il existe une vingtaine de types d'exceptions. En crivant catch (Exception e), on indique qu'on veut
grer toutes les types d'exceptions. Si le code de la clause try est susceptible de gnrer plusieurs types d'exceptions, on peut
vouloir tre plus prcis en grant l'exception avec plusieurs clauses catch :
try{
appel de la fonction susceptible de gnrer l'exception
} catch (IOException e){
traiter l'exception e
}
} catch (ArrayIndexOutOfBoundsException e){
traiter l'exception e
}
} catch (RunTimeException e){
traiter l'exception e
}
instruction suivante
try{
appel de la fonction susceptible de gnrer l'exception
} catch (Exception e){
traiter l'exception e
}
finally{
code excut aprs try ou catch
}
instruction suivante
Ici, qu'il y ait exception ou pas, le code de la clause finally sera toujours excut.
La classe Exception a une mthode getMessage() qui rend un message dtaillant l'erreur qui s'est produite. Ainsi si on veut
afficher celui-ci, on crira :
catch (Exception ex){
System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
...
}//catch
La classe Exception a une mthode toString() qui rend une chane de caractres indiquant le type de l'exception ainsi que la
valeur de la proprit Message. On pourra ainsi crire :
catch (Exception ex){
System.err.println ("L'erreur suivante s'est produite : "+ex.toString());
...
}//catch
On peut crire aussi :
catch (Exception ex){
System.err.println ("L'erreur suivante s'est produite : "+ex);
...
}//catch
Nous avons ici une opration string + Exception qui va tre automatiquement transforme en string + Exception.toString() par le
compilateur afin de faire la concatnation de deux chanes de caractres.
L'exemple suivant montre une exception gnre par l'utilisation d'un lment de tableau inexistant :
// tableaux
// imports
import java.io.*;
Les bases
23
Voici un autre exemple o on gre l'exception provoque par l'affectation d'une chane de caractres un nombre lorsque la chane
ne reprsente pas un nombre :
// imports
import java.io.*;
public class console1{
public static void main(String[] args){
// cration d'un flux d'entre
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
}catch(Exception ex){}
// On demande le nom
System.out.print("Nom : ");
// lecture rponse
String nom=null;
try{
nom=IN.readLine();
}catch(Exception ex){}
// on demande l'ge
int age=0;
boolean ageOK=false;
while ( ! ageOK){
// question
System.out.print("ge : ");
// lecture-vrification rponse
try{
age=Integer.parseInt(IN.readLine());
ageOK=true;
}catch(Exception ex) {
System.err.println("Age incorrect, recommencez...");
}//try-catch
}//while
// affichage final
System.out.println("Vous vous appelez " + nom + " et vous avez " + age + " ans");
}//Main
}//classe
E:\data\serge\MSNET\c#\bases\1>console1
Nom : dupont
ge : xx
Age incorrect, recommencez...
ge : 12
Vous vous appelez dupont et vous avez 12 ans
Les bases
24
Le fichier source contenant la classe coucou prcdente doit obligatoirement s'appeler coucou.java :
E:\data\serge\JAVA\ESSAIS\intro1>dir
10/06/2002 08:42
228 coucou.java
La compilation et l'excution d'un programme Java se fait dans une fentre DOS. Les excutables javac.exe (compilateur) et java.exe
(interprteur) se trouvent dans le rpertoire bin du rpertoire d'installation du JDK :
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\bin\java?.exe"
07/02/2002 12:52
24 649 java.exe
07/02/2002 12:52
28 766 javac.exe
Le compilateur javac.exe va analyser le fichier source .java et produire un fichier compil .class. Celui-ci n'est pas immdiatement
excutable par le processeur. Il ncessite un interprteur Java (java.exe) qu'on appelle une machine virtuelle ou JVM (Java Virtual
Machine). A partir du code intermdiaire prsent dans le fichier .class, la machine virtuelle va gnrer des instructions spcifiques au
processeur de la machine sur laquelle elle s'excute. Il existe des machines virtuelles Java pour diffrents types de systmes
d'exploitation (Windows, Unix, Mac OS,...). Un fichier .class pourra tre excut par n'importe laquelle de ces machines virtuelles
donc sur n'importe que systme d'exploitation. Cette portabilit inter-systmes est l'un des atouts majeurs de Java.
Compilons le programme prcdent :
E:\data\serge\JAVA\ESSAIS\intro1>"e:\program files\jdk14\bin\javac" coucou.java
E:\data\serge\JAVA\ESSAIS\intro1>dir
10/06/2002 08:42
228 coucou.java
10/06/2002 08:48
403 coucou.class
On notera que dans la demande d'excution ci-dessus, on n'a pas prcis le suffixe .class du fichier coucou.class excuter. Il est
implicite. Si le rpertoire bin du JDK est dans le PATH de la machine DOS, on pourra ne pas donner le chemin complet des
excutables javac.exe et java.exe. On crira alors simplement
javac coucou.java
java coucou
25
import java.io.*;
public class param1{
public static void main(String[] arg){
int i;
System.out.println("Nombre d'arguments="+arg.length);
for (i=0;i<arg.length;i++)
System.out.println("arg["+i+"]="+arg[i]);
}
}
formel S=maman
effectif S=papa
formel a=30
effectif age=20
Les valeurs des paramtres effectifs "papa" et 20 ont t recopies dans les paramtres formels S et a. Ceux-ci ont t ensuite
modifis. Les paramtres effectifs ont t eux inchangs. On notera bien ici le type des paramtres effectifs :
S est une rfrence dobjet c.a.d. ladresse dun objet en mmoire
age est une valeur entire
on calcule le nombre de parts du salari nbParts=nbEnfants/2 +1 s'il n'est pas mari, nbEnfants/2+2 s'il est mari, o
nbEnfants est son nombre d'enfants.
s'il a au moins trois enfants, il a une demi-part de plus
on calcule son revenu imposable R=0.72*S o S est son salaire annuel
Les bases
26
0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
0.45
0.50
0.55
0.60
0.65
0
631
1290.5
2072.5
3309.5
4900
6898.5
9316.5
12106
16754.5
23147.5
30710
39312
49062
Chaque ligne a 3 champs. Pour calculer l'impt I, on recherche la premire ligne o QF<=champ1. Par exemple, si QF=23000 on
trouvera la ligne
24740 0.15 2072.5
L'impt I est alors gal 0.15*R - 2072.5*nbParts. Si QF est tel que la relation QF<=champ1 n'est jamais vrifie, alors ce sont les
coefficients de la dernire ligne qui sont utiliss. Ici :
0
0.65 49062
ce qui donne l'impt I=0.65*R - 49062*nbParts.
Le programme Java correspondant est le suivant :
import java.io.*;
public class impots{
// ------------ main
public static void main(String arg[]){
// donnes
// limites des tranches d'impts
double Limites[]={12620, 13190, 15640, 24740, 31810, 39970, 48360,55790, 92970, 127860, 151250,
172040, 195000, 0};
// coeff appliqu au nombre de parts
double Coeffn[]={0, 631, 1290.5, 2072.5, 3309.5, 4900, 6898.5, 9316.5,12106, 16754.5, 23147.5, 30710,
39312, 49062};
// le programme
// cration du flux d'entre clavier
BufferedReader IN=null
null;
null
try{
try
IN=new
new BufferedReader(new
new InputStreamReader(System.in));
}
catch(Exception
e){
catch
erreur("Cration du flux d'entre", e, 1);
}
// on rcupre le statut marital
boolean OK=false
false;
false
String reponse=null
null;
null
while(!
OK){
while
try{
try
System.out.print("Etes-vous mari(e) (O/N) ? ");
reponse=IN.readLine();
reponse=reponse.trim().toLowerCase();
if (! reponse.equals("o") && !reponse.equals("n"))
System.out.println("Rponse incorrecte. Recommencez");
else OK=true
true;
true
} catch(Exception
e){
catch
erreur("Lecture tat marital",e,2);
}
}
boolean Marie = reponse.equals("o");
// nombre d'enfants
OK=false
false;
false
int NbEnfants=0;
while(!
OK){
while
try{
try
System.out.print("Nombre d'enfants : ");
reponse=IN.readLine();
try{
try
NbEnfants=Integer.parseInt(reponse);
if(NbEnfants>=0)
OK=true
true;
if
true
else System.err.println("Rponse incorrecte. Recommencez");
} catch(Exception
catch
e){
Les bases
27
// salaire
OK=false
false;
false
long Salaire=0;
while(!
OK){
while
try{
try
System.out.print("Salaire annuel : ");
reponse=IN.readLine();
try{
try
Salaire=Long.parseLong(reponse);
if(Salaire>=0)
OK=true
true;
if
true
else System.err.println("Rponse incorrecte. Recommencez");
} catch(Exception
e){
catch
System.err.println("Rponse incorrecte. Recommencez");
}// try
} catch(Exception
e){
catch
erreur("Lecture Salaire",e,4);
}// try
}// while
// calcul du nombre de parts
double NbParts;
if(Marie)
NbParts=(double
double)NbEnfants/2+2;
if
double
else NbParts=(double
double)NbEnfants/2+1;
double
if (NbEnfants>=3) NbParts+=0.5;
// revenu imposable
double Revenu;
Revenu=0.72*Salaire;
// quotient familial
double QF;
QF=Revenu/NbParts;
// recherche de la tranche d'impots correspondant QF
int i;
int NbTranches=Limites.length;
Limites[NbTranches-1]=QF;
i=0;
while(QF>Limites[i])
i++;
while
// l'impt
long impots=(long
long)(i*0.05*Revenu-Coeffn[i]*NbParts);
long
// on affiche le rsultat
System.out.println("Impt payer : " + impots);
}// main
// ------------ erreur
private static void erreur(String msg, Exception e, int exitCode){
System.err.println(msg+"("+e+")");
System.exit(exitCode);
}// erreur
}// class
28
Salaire
Rponse
Salaire
Impt
Les bases
annuel : q
incorrecte. Recommencez
annuel : 1
payer : 0
29
2. Classes et interfaces
2.1 L' objet par l'exemple
2.1.1 Gnralits
Nous abordons maintenant, par l'exemple, la programmation objet. Un objet est une entit qui contient des donnes qui dfinissent
son tat (on les appelle des attributs ou proprits) et des fonctions (on les appelle des mthodes). Un objet est cr selon un
modle qu'on appelle une classe :
public class C1{
type1 p1;
//
type2 p2;
//
type3 m3(){ //
}
type4 m4(){ //
proprit p1
proprit p2
mthode m3
mthode m4
A partir de la classe C1 prcdente, on peut crer de nombreux objets O1, O2, Tous auront les proprits p1, p2, et les
mthodes m3, m4, Ils auront des valeurs diffrentes pour leurs proprits pi ayant ainsi chacun un tat qui leur est propre.
Par analogie la dclaration
int i,j;
cre deux objets (le terme est incorrect ici) de type (classe) int. Leur seule proprit est leur valeur.
Si O1 est un objet de type C1, O1.p1 dsigne la proprit p1 de O1 et O1.m1 la mthode m1 de O1.
Considrons un premier modle d'objet : la classe personne.
// mthode
public void initialise(String P, String N, int age){
this.prenom=P;
this
this.nom=N;
this
this.age=age;
this
}
// mthode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Nous avons ici la dfinition d'une classe, donc un type de donne. Lorsqu'on va crer des variables de ce type, on les appellera des
objets. Une classe est donc un moule partir duquel sont construits des objets.
Les membres ou champs d'une classe peuvent tre des donnes ou des mthodes (fonctions). Ces champs peuvent avoir l'un
des trois attributs suivants :
priv
public
Un champ priv (private) n'est accessible que par les seules mthodes internes de la classe
Un champ public est accessible par toute fonction dfinie ou non au sein de la classe
Classes et interfaces
30
protg
Un champ protg (protected) n'est accessible que par les seules mthodes internes de la classe ou d'un objet driv
(voir ultrieurement le concept d'hritage).
En gnral, les donnes d'une classe sont dclares prives alors que ses mthodes sont dclares publiques. Cela signifie que
l'utilisateur d'un objet (le programmeur)
a
b
Remarques
// mthode
public void initialise(String P, String N, int age){
this.prenom=P;
this
this.nom=N;
this
this.age=age;
this
}
// mthode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Quel est le rle de la mthode initialise ? Parce que nom, prenom et age sont des donnes prives de la classe personne, les
instructions
personne p1;
p1.prenom="Jean";
p1.nom="Dupont";
p1.age=30;
sont illgales. Il nous faut initialiser un objet de type personne via une mthode publique. C'est le rle de la mthode initialise. On
crira :
personne p1;
p1.initialise("Jean","Dupont",30);
31
dclare p1 comme une rfrence un objet de type personne. Cet objet n'existe pas encore et donc p1 n'est pas initialis. C'est comme
si on crivait :
personne p1=null;
o on indique explicitement avec le mot cl null que la variable p1 ne rfrence encore aucun objet.
Lorsqu'on crit ensuite
p1.initialise("Jean","Dupont",30);
on fait appel la mthode initialise de l'objet rfrenc par p1. Or cet objet n'existe pas encore et le compilateur signalera l'erreur.
Pour que p1 rfrence un objet, il faut crire :
personne p1=new personne();
Cela a pour effet de crer un objet de type personne non encore initialis : les attributs nom et prenom qui sont des rfrences d'objets
de type String auront la valeur null, et age la valeur 0. Il y a donc une initialisation par dfaut. Maintenant que p1 rfrence un objet,
l'instruction d'initialisation de cet objet
p1.initialise("Jean","Dupont",30);
est valide.
L'instruction this.prenom=P signifie que l'attribut prenom de l'objet courant (this) reoit la valeur P. Le mot cl this dsigne l'objet
courant : celui dans lequel se trouve la mthode excute. Comment le connat-on ? Regardons comment se fait l'initialisation de
l'objet rfrenc par p1 dans le programme appelant :
p1.initialise("Jean","Dupont",30);
C'est la mthode initialise de l'objet p1 qui est appele. Lorsque dans cette mthode, on rfrence l'objet this, on rfrence en fait
l'objet p1. La mthode initialise aurait aussi pu tre crite comme suit :
public void initialise(String P, String N, int age){
prenom=P;
nom=N;
this.age=age;
this
}
Lorsqu'une mthode d'un objet rfrence un attribut A de cet objet, l'criture this.A est implicite. On doit l'utiliser explicitement
lorsqu'il y a conflit d'identificateurs. C'est le cas de l'instruction :
this.age=age;
this
o age dsigne un attribut de l'objet courant ainsi que le paramtre age reu par la mthode. Il faut alors lever l'ambigut en
dsignant l'attribut age par this.age.
32
p1.identifie();
}
La classe personne est dfinie dans le fichier source personne.java et est compile :
E:\data\serge\JAVA\BASES\OBJETS\2>javac personne.java
E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002 09:21
473 personne.java
10/06/2002 09:22
835 personne.class
10/06/2002 09:23
165 test1.java
personne.java
personne.class
test1.java
test1.class
On peut s'tonner que le programme test1.java n'importe pas la classe personne avec une instruction :
import personne;
Lorsque le compilateur rencontre dans le code source une rfrence de classe non dfinie dans ce mme fichier source, il recherche
la classe divers endroits :
Dans notre exemple, le compilateur a t lanc depuis le rpertoire contenant le fichier personne.class, ce qui explique qu'il a trouv la
dfinition de la classe personne. Mettre dans ce cas de figure une instruction import provoque une erreur de compilation :
E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
test1.java:1: '.' expected
import personne;
^
1 error
Pour viter cette erreur mais pour rappeler que la classe personne doit tre importe, on crira l'avenir en dbut de programme :
// classes importes
// import personne;
Il est possible de rassembler plusieurs classes dans un mme fichier source. Rassemblons ainsi les classes personne et test1 dans le
fichier source test2.java. La classe test1 est renomme test2 pour tenir compte du changement du nom du fichier source :
// paquetages imports
import java.io.*;
class personne{
// attributs
private String prenom; // prnom de ma personne
private String nom;
// son nom
private int age;
// son ge
// mthode
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}//initialise
// mthode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}//identifie
Classes et interfaces
33
}//classe
public class test2{
public static void main(String arg[]){
personne p1=new personne();
p1.initialise("Jean","Dupont",30);
p1.identifie();
}
}
On notera que la classe personne n'a plus l'attribut public. En effer, dans un fichier source java, seule une classe peut avoir l'attribut
public. C'est celle qui a la fonction main. Par ailleurs, le fichier source doit porter le nom de cette dernire. Compilons le fichier
test2.java :
E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002 09:36
633 test2.java
E:\data\serge\JAVA\BASES\OBJETS\3>javac test2.java
E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002 09:36
633 test2.java
10/06/2002 09:41
832 personne.class
10/06/2002 09:41
418 test2.class
On remarquera qu'un fichier .class a t gnr pour chacune des classes prsentes dans le fichier source. Excutons maintenant le
fichier test2.class :
E:\data\serge\JAVA\BASES\OBJETS\2>java test2
Jean,Dupont,30
On a maintenant deux mthodes portant le nom initialise : c'est lgal tant qu'elles admettent des paramtres diffrents. C'est le cas
ici. Le paramtre est maintenant une rfrence P une personne. Les attributs de la personne P sont alors affects l'objet courant
(this). On remarquera que la mthode initialise a un accs direct aux attributs de l'objet P bien que ceux-ci soient de type private. C'est
toujours vrai : les mthodes d'un objet O1 d'une classe C a toujours accs aux attributs privs des autres objets de la mme classe C.
Voici un test de la nouvelle classe personne :
// import personne;
import java.io.*;
public class test1{
public static void main(String arg[]){
personne p1=new
new personne();
p1.initialise("Jean","Dupont",30);
System.out.print("p1=");
p1.identifie();
personne p2=new
new personne();
p2.initialise(p1);
System.out.print("p2=");
p2.identifie();
}
}
et ses rsultats :
p1=Jean,Dupont,30
p2=Jean,Dupont,30
Classes et interfaces
34
// constructeurs
public personne(String P, String N, int age){
initialise(P,N,age);
}
public personne(personne P){
initialise(P);
}
// mthode
public void initialise(String P, String N, int age){
this.prenom=P;
this
this.nom=N;
this
this.age=age;
this
}
public void initialise(personne P){
this.prenom=P.prenom;
this
this.nom=P.nom;
this
this.age=P.age;
this
}
// mthode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
Nos deux constructeurs se contentent de faire appel aux mthodes initialise correspondantes. On rappelle que lorsque dans un
constructeur, on trouve la notation initialise(P) par exemple, le compilateur traduit par this.initialise(P). Dans le constructeur, la
mthode initialise est donc appele pour travailler sur l'objet rfrenc par this, c'est dire l'objet courant, celui qui est en cours de
construction.
Voici un programme de test :
// import personne;
import java.io.*;
public class test1{
public static void main(String arg[]){
personne p1=new
new personne("Jean","Dupont",30);
System.out.print("p1=");
p1.identifie();
personne p2=new
new personne(p1);
System.out.print("p2=");
p2.identifie();
Classes et interfaces
35
}
}
p1 rfrence l'objet personne("Jean","Dupont",30) mais n'est pas l'objet lui-mme. En C, on dirait que c'est un pointeur, c.a.d. l'adresse
de l'objet cr. Si on crit ensuite :
p1=null
Ce n'est pas l'objet personne("Jean","Dupont",30) qui est modifi, c'est la rfrence p1 qui change de valeur. L'objet
personne("Jean","Dupont",30) sera "perdu" s'il n'est rfrenc par aucune autre variable.
Lorsqu'on crit :
personne p2=p1;
on initialise le pointeur p2 : il "pointe" sur le mme objet (il dsigne le mme objet) que le pointeur p1. Ainsi si on modifie l'objet
"point" (ou rfrenc) par p1, on modifie celui rfrenc par p2.
Lorsqu'on crit :
personne p3=new personne(p1);
il y a cration d'un nouvel objet, copie de l'objet rfrenc par p1. Ce nouvel objet sera rfrenc par p3. Si on modifie l'objet
"point" (ou rfrenc) par p1, on ne modifie en rien celui rfrenc par p3. C'est ce que montrent les rsultats obtenus.
Classes et interfaces
36
// accesseurs
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//modifieurs
public void setPrenom(String P){
this.prenom=P;
this
}
Classes et interfaces
37
Pour le rfrencer, on crit personne.nbPersonnes pour montrer que c'est un attribut de la classe personne elle-mme. Ici, nous avons
cr un attribut priv auquel on n'aura pas accs directement en-dehors de la classe. On cre donc une mthode publique pour
donner accs l'attribut de classe nbPersonnes. Pour rendre la valeur de nbPersonnes la mthode n'a pas besoin d'un objet particulier :
en effet nbPersonnes n'est pas l'attribut d'un objet particulier, il est l'attribut de toute une classe. Aussi a-t-on besoin d'une mthode de
classe dclare elle aussi static :
public static long getNbPersonnes(){
return nbPersonnes;
}
// attribut de classe
private static long nbPersonnes=0;
// attributs d'objets
// constructeurs
public personne(String P, String N, int age){
initialise(P,N,age);
nbPersonnes++;
}
public personne(personne P){
initialise(P);
nbPersonnes++;
}
// mthode
// mthode de classe
public static long getNbPersonnes(){
return nbPersonnes;
}
Classes et interfaces
38
}// class
objet
Recopie
R2
C'est ce que montre l'exemple suivant :
// import personne;
public class test1{
public static
static void main(String arg[]){
personne p1=new
new personne("Jean","Dupont",30);
System.out.print("Paramtre effectif avant modification : ");
p1.identifie();
modifie(p1);
System.out.print("Paramtre effectif aprs modification : ");
p1.identifie();
}// main
private static void modifie(personne P){
System.out.print("Paramtre formel avant modification : ");
P.identifie();
P.initialise("Sylvie","Vartan",52);
System.out.print("Paramtre formel aprs modification : ");
P.identifie();
}// modifie
}// class
La mthode modifie est dclare static parce que c'est une mthode de classe : on n'a pas la prfixer par un objet pour l'appeler. Les
rsultats obtenus sont les suivants :
Constructeur personne(String, String,
Paramtre effectif avant modification
Paramtre formel avant modification :
Paramtre formel aprs modification :
Classes et interfaces
int)
: Jean,Dupont,30
Jean,Dupont,30
Sylvie,Vartan,52
39
On voit qu'il n'y a construction que d'un objet : celui de la personne p1 de la fonction main et que l'objet a bien t modifi par la
fonction modifie.
La classe prcdente a un constructeur permettant d'initialiser un entier et deux mthodes permettant de lire et modifier la
valeur de cet entier. On teste cette classe avec le programme suivant :
// import entieres;
public class test2{
public static void main(String[] arg){
entieres I=new
new entieres(12);
System.out.println("I="+I.getValue());
change(I);
System.out.println("I="+I.getValue());
}
private static void change(entieres entier){
entier.setValue(15);
}
}
L'instruction personne[] amis=new personne[3]; cre un tableau de 3 lments de type personne. Ces 3 lments sont initialiss ici avec la
valeur null, c.a.d. qu'ils ne rfrencent aucun objet. De nouveau, par abus de langage, on parle de tableau d'objets alors que ce n'est
qu'un tableau de rfrences d'objets. La cration du tableau d'objets, tableau qui est un objet lui-mme (prsence de new) ne cre
donc en soi aucun objet du type de ses lments : il faut le faire ensuite.
On obtient les rsultats suivants :
---------------Classes et interfaces
40
// constructeur
public enseignant(String P, String N, int age,int
int section){
super(P,N,age);
super
this.section=section;
this
}
}
// accesseurs
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//modifieurs
public void setPrenom(String P){
this.prenom=P;
this
}
public void setNom(String N){
this.nom=N;
this
}
Classes et interfaces
41
La mthode identifie a t lgrement modifie pour rendre une chane de caractres identifiant la personne et porte maintenant le
nom identite. Ici la classe enseignant rajoute aux mthodes et attributs de la classe personne :
un attribut section qui est le n de section auquel appartient l'enseignant dans le corps des enseignants (une section par
discipline en gros)
un nouveau constructeur permettant d'initialiser tous les attributs d'un enseignant
L'instruction super(P,N,age) est un appel au constructeur de la classe parent, ici la classe personne. On sait que ce constructeur initialise
les champs prenom, nom et age de l'objet personne contenu l'intrieur de l'objet tudiant. Cela parat bien compliqu et on pourrait
prfrer crire :
// constructeur
public enseignant(String P, String N, int age,int
int section){
this.prenom=P;
this
this.nom=N
this
this.age=age
this
this.section=section;
this
}
C'est impossible. La classe personne a dclar privs (private) ses trois champs prenom, nom et age. Seuls des objets de la mme classe
ont un accs direct ces champs. Tous les autres objets, y compris des objets fils comme ici, doivent passer par des mthodes
publiques pour y avoir accs. Cela aurait t diffrent si la classe personne avait dclar protgs (protected) les trois champs : elle
autorisait alors des classes drives avoir un accs direct aux trois champs. Dans notre exemple, utiliser le constructeur de la classe
parent tait donc la bonne solution et c'est la mthode habituelle : lors de la construction d'un objet fils, on appelle d'abord le
constructeur de l'objet parent puis on complte les initialisations propres cette fois l'objet fils (section dans notre exemple).
Tentons un premier programme :
//
//
import personne;
import enseignant;
Ce programme ce contente de crer un objet enseignant (new) et de l'identifier. La classe enseignant n'a pas de mthode identit mais sa
classe parent en a une qui de plus est publique : elle devient par hritage une mthode publique de la classe enseignant.
Les fichiers source des classes sont rassembls dans un mme rpertoire puis compils :
E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002 10:00
765 personne.java
10/06/2002 10:00
212 enseignant.java
10/06/2002 10:01
192 test1.java
E:\data\serge\JAVA\BASES\OBJETS\4>javac *.java
E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002 10:00
765
10/06/2002 10:00
212
10/06/2002 10:01
192
10/06/2002 10:02
316
10/06/2002 10:02
1 146
10/06/2002 10:02
550
personne.java
enseignant.java
test1.java
enseignant.class
personne.class
test1.class
42
E:\data\serge\JAVA\BASES\OBJETS\4>java test1
personne(Jean,Dupont,30)
La mthode identite de la classe enseignant s'appuie sur la mthode identite de sa classe mre (super.identite) pour afficher sa partie
"personne" puis complte avec le champ section qui est propre la classe enseignant.
La classe enseignant dispose maintenant deux mthodes identite :
celle hrite de la classe parent personne
la sienne propre
Si E est un ojet enseignant, E.identite dsigne la mthode identite de la classe enseignant. On dit que la mthode identite de la classe mre
est "surcharge" par la mthode identite de la classe fille. De faon gnrale, si O est un objet et M une mthode, pour excuter la
mthode O.M, le systme cherche une mthode M dans l'ordre suivant :
dans la classe de l'objet O
dans sa classe mre s'il en a une
dans la classe mre de sa classe mre si elle existe
etc
L'hritage permet donc de surcharger dans la classe fille des mthodes de mme nom dans la classe mre. C'est ce qui permet
d'adapter la classe fille ses propres besoins. Associe au polymorphisme que nous allons voir un peu plus loin, la surcharge de
mthodes est le principal intrt de l'hritage.
Considrons le mme exemple que prcdemment :
// import personne;
// import
import enseignant;
public class test1{
public static void main(String arg[]){
System.out.println(new
new enseignant("Jean","Dupont",30,27).identite());
}
}
2.2.4 Le polymorphisme
Considrons une ligne de classes : C0
C1
C2
Cn
o Ci
Cj indique que la classe Cj est drive de la classe Ci. Cela entrane que la classe Cj a toutes les caractristiques de la classe Ci
plus d'autres. Soient des objets Oi de type Ci. Il est lgal d'crire :
Oi=Oj avec j>i
En effet, par hritage, la classe Cj a toutes les caractristiques de la classe Ci plus d'autres. Donc un objet Oj de type Cj contient en
lui un objet de type Ci. L'opration
Oi=Oj
Classes et interfaces
43
fait que Oi est une rfrence l'objet de type Ci contenu dans l'objet Oj.
Le fait qu'une variable Oi de classe Ci puisse en fait rfrencer non seulement un objet de la classe Ci mais en fait tout objet driv
de la classe Ci est appel polyporphisme : la facult pour une variable de rfrencer diffrents types d'objets.
Prenons un exemple et considrons la fonction suivante indpendante de toute classe :
public static void affiche(Object obj){
.
}
La classe Object est la "mre" de toutes les classes Java. Ainsi lorsqu'on crit :
public class personne
on crit implicitement :
public class personne extends Object
Ainsi tout objet Java contient en son sein une partie de type Object. Ainsi on pourra crire :
enseignant e;
affiche(e);
Le paramtre formel de type Object de la fonction affiche va recevoir une valeur de type enseignant. Comme enseignant drive de
Object, c'est lgal.
44
personne@1ee770
C'est dire nom_de_la_classe@adresse_de_l'objet. Comme ce n'est pas trs explicite, on est tent de dfinir une mthode toString pour
les classes personne et etudiant qui surchargeraient la mthode toString de la classe mre Object. Plutt que d'crire des mthodes qui
seraient proches des mthodes identite dj existantes dans les classes personne et enseignant, contentons-nous de renommer toString ces
mthodes identite :
public class personne{
...
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
...
}
class enseignant extends personne{
int section;
Avec le mme programme de test qu'auparavant, les rsultats obtenus sont les suivants :
enseignant(personne(Lucile,Dumas,56),61)
personne(Jean,Dupont,30)
45
La classe test1 contient la dfinition d'une autre classe, la classe article. On dit que article est une classe interne la classe test1. Cela
peut tre utile lorsque la classe interne n'a d'utilit que dans la classe qui la contient. Lors de la compilation du source test1.java cidessus, on obtient deux fichiers .class :
E:\data\serge\JAVA\classes\interne>dir
05/06/2002 17:26
1 362 test1.java
05/06/2002 17:26
941 test1$article.class
05/06/2002 17:26
1 020 test1.class
Un fichier test1$article.class a t gnr pour la classe article interne la classe test1. Si on excute le programme ci-dessus, on obtient
les rsultats suivants :
E:\data\serge\JAVA\classes\interne>java test1
art=article(a100,velo,1000.0,10,5)
Method Summary
boolean hasMoreElements()
Tests if this enumeration contains more elements.
Object nextElement()
Returns the next element of this enumeration if this enumeration object has at least one more element to
provide.
Toute classe implmentant cette interface sera dclare comme
public class C : Enumeration{
...
boolean hasMoreElements(){....}
Object nextElement(){...}
}
Nous dfinissons une classe notes rassemblant les notes de tous les lves dans une matire :
// classes importes
// import lve
Classes et interfaces
46
// classe notes
public class notes{
// attributs
protected String matire;
protected lve[] lves;
// constructeur
public notes (String MATIERE, lve[] ELEVES){
// mmorisation lves & matire
matire=MATIERE;
lves=ELEVES;
}//notes
// toString
public String toString(){
String valeur="matire="+matire +", notes=(";
int i;
// on concatne toutes les notes
for (i=0;i<lves.length-1;i++){
valeur+="["+lves[i].nom+","+lves[i].note+"],";
};
//dernire note
if(lves.length!=0){ valeur+="["+lves[i].nom+","+lves[i].note+"]";}
valeur+=")";
// fin
return valeur;
}//toString
}//classe
Les attributs matire et lves sont dclars protected pour tre accessibles d'une classe drive. Nous dcidons de driver la classe notes
dans une classe notesStats qui aurait deux attributs supplmentaires, la moyenne et l'cart-type des notes :
public class notesStats extends notes implements Istats {
// attributs
private double _moyenne;
private double _cartType;
Cela signifie que la classe notesStats doit avoir deux mthodes appeles moyenne et cartType avec la signature indique dans l'interface
Istats. La classe notesStats est la suivante :
//
//
//
//
classes importes
import notes;
import Istats;
import lve;
47
La moyenne _moyenne et l'cart-type _ecartType sont calculs ds la construction de l'objet. Aussi les mthodes moyenne et cartType
n'ont-elles qu' rendre la valeur des attributs _moyenne et _ecartType. Les deux mthodes rendent -1 si le tableau des lves est vide.
La classe de test suivante :
//
//
//
//
//
classes importes
import lve;
import Istats;
import notes;
import notesStats;
// classe de test
public class test{
public static void main(String[] args){
// qqs lves & notes
lve[] ELEVES=new lve[] { new lve("paul",14),new lve("nicole",16), new lve("jacques",18)};
// qu'on enregistre dans un objet notes
notes anglais=new notes("anglais",ELEVES);
// et qu'on affiche
System.out.println(""+anglais);
// idem avec moyenne et cart-type
anglais=new notesStats("anglais",ELEVES);
System.out.println(""+anglais);
}//main
}//classe
Les diffrentes classes de cet exemple font toutes l'objet d'un fichier source diffrent :
E:\data\serge\JAVA\interfaces\notes>dir
06/06/2002 14:06
707 notes.java
06/06/2002 14:06
878 notes.class
06/06/2002 14:07
1 160 notesStats.java
06/06/2002 14:02
101 Istats.java
06/06/2002 14:02
138 Istats.class
06/06/2002 14:05
247 lve.java
06/06/2002 14:05
309 lve.class
06/06/2002 14:07
1 103 notesStats.class
06/06/2002 14:10
597 test.java
06/06/2002 14:10
931 test.class
La classe notesStats aurait trs bien pu implmenter les mthodes moyenne et cartType pour elle-mme sans indiquer qu'elle
implmentait l'interface Istats. Quel est donc l'intrt des interfaces ? C'est le suivant : une fonction peut admettre pour paramtre
une donne ayant le type d'une interface I. Tout objet d'une classe C implmentant l'interface I pourra alors tre paramtre de cette
fonction. Considrons l'interface suivante :
// une interface Iexemple
public interface Iexemple{
int ajouter(int i,int j);
int soustraire(int i,int j);
}//interface
L'interface Iexemple dfinit deux mthodes ajouter et soustraire. Les classes classe1 et classe2 suivantes implmentent cette interface.
// classes importes
// import Iexemple;
public class classe1 implements Iexemple{
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
}//classe
Classes et interfaces
48
// classes importes
// import Iexemple;
public class classe2 implements Iexemple{
public int ajouter(int a, int b){
return a+b+100;
}
public int soustraire(int a, int b){
return a-b+200;
}
}//classe
Par souci de simplification de l'exemple les classes ne font rien d'autre que d'implmenter l'interface Iexemple. Maintenant
considrons l'exemple suivant :
// classes importes
// import classe1;
// import classe2;
// classe de test
public class test{
// une fonction statique
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//calculer
// la fonction main
public static void main(String[] arg){
// cration de deux objets classe1 et classe2
classe1 c1=new classe1();
classe2 c2=new classe2();
// appels de la fonction statique calculer
calculer(4,3,c1);
calculer(14,13,c2);
}//main
}//classe test
La fonction statique calculer admet pour paramtre un lment de type Iexemple. Elle pourra donc recevoir pour ce paramtre aussi
bien un objet de type classe1 que de type classe2. C'est ce qui est fait dans la fonction main avec les rsultats suivants :
17
21
127
201
On voit donc qu'on a l une proprit proche du polymorphisme vu pour les classes. Si donc un ensemble de classes Ci non lies
entre-elles par hritage (donc on ne peut utiliser le polymorphisme de l'hritage) prsentent un ensemble de mthodes de mme
signature, il peut tre intressant de regrouper ces mthodes dans une interface I dont hriteraient toutes les classes concernes.
Des instances de ces classes Ci peuvent alors tre utilises comme paramtres de fonctions admettant un paramtre de type I, c.a.d.
des fonctions n'utilisant que les mthodes des objets Ci dfinies dans l'interface I et non les attributs et mthodes particuliers des
diffrentes classes Ci.
Dans l'exemple prcdent, chaque classe ou interface faisait l'objet d'un fichier source spar :
E:\data\serge\JAVA\interfaces\oprations>dir
06/06/2002 14:33
128 Iexemple.java
06/06/2002 14:34
218 classe1.java
06/06/2002 14:32
220 classe2.java
06/06/2002 14:33
144 Iexemple.class
06/06/2002 14:34
325 classe1.class
06/06/2002 14:34
326 classe2.class
06/06/2002 14:36
583 test.java
06/06/2002 14:36
628 test.class
Notons enfin que l'hritage d'interfaces peut tre multiple, c.a.d. qu'on peut crire
public class classeDrive extends classeDeBase implements i1,i2,..,in{
...
}
49
Dans l'exemple prcdent, les classes classe1 et classe2 auraient pu ne pas tre dfinies explicitement. Considrons le programme
suivant qui fait sensiblement la mme chose que le prcdent mais sans la dfinition explicite des classes classe1 et classe2 :
// classes importes
// import Iexemple;
// classe de test
public class test2{
// une classe interne
private static class classe3 implements Iexemple{
public int ajouter(int a, int b){
return a+b+1000;
}
public int soustraire(int a, int b){
return a-b+2000;
}
};//dfinition classe3
// une fonction statique
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//calculer
// la fonction main
public static void main(String[] arg){
// cration de deux objets implmentant l'interface Iexemple
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
};//dfinition i1
Iexemple i2=new Iexemple(){
public int ajouter(int a, int b){
return a+b+100;
}
public int soustraire(int a, int b){
return a-b+200;
}
};//dfinition i2
// un autre objet Iexemple
Iexemple i3=new classe3();
// appels de la fonction statique calculer
calculer(4,3,i1);
calculer(14,13,i2);
calculer(24,23,i3);
}//main
}//classe test
On cre un objet i1 dont le seul rle est d'implmenter l'interface Iexemple. Cet objet est de type Iexemple. On peut donc crer des
objets de type interface. De trs nombreuses mthodes de classes Java rendent des objets de type interface c.a.d. des objets dont le
seul rle est d'implmenter les mthodes d'une interface. Pour crer l'objet i1, on pourrait tre tent d'crire :
Iexemple i1=new Iexemple()
Seulement une interface ne peut tre instantie. Seule une classe implmentant cette interface peut l'tre. Ici, on dfinit une telle
classe " la vole" dans le corps mme de la dfinition de l'objet i1 :
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
// dfinition de ajouter
}
public int soustraire(int a, int b){
// dfinition de soustraire
}
};//dfinition i1
Classes et interfaces
50
Dans l'exemple ci-dessus, on instantie bien une classe et non pas une interface. Une classe dfinie " la vole" est dite une classe
anonyme. C'est une mthode souvent utilise pour instantier des objets dont le seul rle est d'implmenter une interface.
L'excution du programme prcdent donne les rsultats suivants :
17
21
127
201
1047
2001
L'exemple prcent utilisait des classes anonymes pour implmenter une interface. Celles-ci peuvent tre utilises galement pour
driver des classes n'ayant pas de constructeurs avec paramtres. Considrons l'exemple suivant :
// classes importes
// import Iexemple;
class classe3 implements Iexemple{
public int ajouter(int a, int b){
return a+b+1000;
}
public int soustraire(int a, int b){
return a-b+2000;
}
};//dfinition classe3
public class test4{
// une fonction statique
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//calculer
// mthode main
public static void main(String args[]){
// dfinition d'une classe anonym drivant classe3
// pour redfinir soustraire
classe3 i1=new classe3(){
public int ajouter(int a, int b){
return a+b+10000;
}//soustraire
};//i1
// appels de la fonction statique calculer
calculer(4,3,i1);
}//main
}//classe
Nous y retrouvons une classe classe3 implmentant l'interface Iexemple. Dans la fonction main, nous dfinissons une variable i1 ayant
pour type, une classe drive de classe3. Cette classe drive est dfinie " la vole" dans une classe anonyme et redfinit la mthode
ajouter de la classe classe3. La syntaxe est identique celle de la classe anonyme implmentant une interface. Seulement ici, le
compilateur dtecte que classe3 n'est pas une interface mais une classe. Pour lui, il s'agit alors d'une drivation de classe. Toutes les
mthodes qu'il trouvera dans le corps de la classe anonyme remplaceront les mthodes de mme nom de la classe de base.
L'excution du programme prcdent donne les rsultats suivants :
E:\data\serge\JAVA\classes\anonyme>java test4
10007
Classes et interfaces
51
2001
Si nous regardons la dfinition de la classe System nous dcouvrons qu'elle s'appelle en fait java.lang.System :
au lieu de
java.lang.System.out.println("Coucou");
Parce que de faon implicite, il y a pour tout programme Java, une importation systmatique du "paquetage" java.lang. Ainsi tout se
passe comme si on avait au dbut de tout programme l'instruction :
import java.lang.*;
Que signifie cette instruction ? Elle donne accs toutes les classes du paquetage java.lang. Le compilateur y trouvera le fichier
System.class dfinissant la classe System. On ne sait pas encore o le compilateur trouvera le paquetage java.lang ni quoi un paquetage
ressemble. Nous y reviendrons. Pour crer une classe dans un paquetage, on crit :
package paquetage;
// dfinition de la classe
...
Pour l'exemple, crons dans un paquetage notre classe personne tudie prcdemment. Nous choisirons istia.st comme nom de
paquetage. La classe personne devient :
// nom du paquetage dans lequel sera cr la classe personne
package istia.st;
Classes et interfaces
52
// classe personne
public class personne{
// nom, prnom, ge
private String prenom;
private String nom;
private int age;
// constructeur 1
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// toString
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
}//classe
Cette classe est compile puis place dans un rpertoire istia\st du rpertoire courant. Pourquoi istia\st ? Parce que le paquetage
s'appelle istia.st.
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28
467 personne.java
06/06/2002 16:04
<DIR>
istia
E:\data\serge\JAVA\classes\paquetages\personne>dir istia
06/06/2002 16:04
<DIR>
st
E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002 16:28
675 personne.class
On remarquera que la classe personne est maintenant prfixe du nom de son paquetage istia.st. O le compilateur trouvera-t-il la
classe istia.st.personne ? Le compilateur cherche les classes dont il a besoin dans une liste prdfinie de rpertoires et dans une
arborescence partant du rpertoire courant. Ici, il cherchera la classe istia.st.personne dans un fichier istia\st\personne.class. C'est
pourquoi nous avons mis le fichier personne.class dans le rpertoire istia\st. Compilons puis excutons le programme de test :
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28
467 personne.java
06/06/2002 16:06
246 test.java
06/06/2002 16:04
<DIR>
istia
06/06/2002 16:06
738 test.class
E:\data\serge\JAVA\classes\paquetages\personne>java test
p1=personne(Jean,Dupont,20)
53
Nous avons mis le paquetage istia.st dans le rpertoire courant. Ce n'est pas obligatoire. Mettons-le dans un dossier appel mesClasses
toujours dans le rpertoire courant. Rappelons que les classes du paquetage istia.st sont places dans un dossier istia\st.
L'arborescence du rpertoire courant est la suivante :
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28
467 personne.java
06/06/2002 16:06
246 test.java
06/06/2002 16:06
738 test.class
06/06/2002 16:47
236 test2.java
06/06/2002 16:50
740 test2.class
06/06/2002 16:21
<DIR>
mesClasses
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses
06/06/2002 16:22
<DIR>
istia
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia
06/06/2002 16:22
<DIR>
st
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia\st
06/06/2002 16:01
1 153 personne.class
Le compilateur ne trouve plus le paquetage istia.st depuis qu'on l'a dplac. Remarquons qu'il le cherche cause de l'instruction
import. Par dfaut, il le cherche partir du rpertoire courant dans un dossier appel istia\st qui n'existe plus. Examinons les options
du compilateur :
E:\data\serge\JAVA\classes\paquetages\personne>javac
Usage: javac <options> <source files>
where possible options include:
-g
Generate all debugging info
-g:none
Generate no debugging info
-g:{lines,vars,source}
Generate only some debugging info
-O
Optimize; may hinder debugging or enlarge class file
-nowarn
Generate no warnings
-verbose
Output messages about what the compiler is doing
-deprecation
Output source locations where deprecated APIs are used
-classpath <path>
Specify where to find user class files
-sourcepath <path>
Specify where to find input source files
-bootclasspath <path>
Override location of bootstrap class files
-extdirs <dirs>
Override location of installed extensions
-d <directory>
Specify where to place generated class files
-encoding <encoding>
Specify character encoding used by source files
-source <release>
Provide source compatibility with specified release
-target <release>
Generate class files for specific VM version
-help
Print a synopsis of standard options
Classes et interfaces
54
Ici l'option -classpath peut nous tre utile. Elle permet d'indiquer au compilateur o chercher ses classes et paquetages. Essayons.
Compilons en disant au compilateur que le paquetage istia.st est dsormais dans le dossier mesClasses :
E:\data\serge\JAVA\classes\paquetages\personne>javac -classpath mesClasses test2.java
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:47
236 test2.java
06/06/2002 17:03
740 test2.class
06/06/2002 16:21
<DIR>
mesClasses
C'est maintenant au tour de la machine virtuelle Java de ne pas trouver la classe istia/st/personne. Elle la cherche dans le rpertoire
courant alors qu'elle est maintenant dans le rpertoire mesClasses. Regardons les options de la machine virtuelle Java :
E:\data\serge\JAVA\classes\paquetages\personne>java
Usage: java [-options] class [args...]
(to execute a class)
or java -jar [-options] jarfile [args...]
(to execute a jar file)
where options include:
-client
to select the "client" VM
-server
to select the "server" VM
-hotspot
is a synonym for the "client" VM
The default VM is client.
[deprecated]
On voit que la JVM a galement une option classpath comme le compilateur. Utilisons-la pour lui dire o se trouve le paquetage
istia.st :
E:\data\serge\JAVA\classes\paquetages\personne>java.bat -classpath mesClasses test2
Exception in thread "main" java.lang.NoClassDefFoundError: test2
On n'a pas beaucoup progress. C'est maintenant la classe test2 elle-mme qui n'est pas trouve. Pour la raison suivante : en
l'absence du mot cl classpath, le rpertoire courant est systmatiquement explor lors de la recherche de classes mais pas lorsqu'il est
prsent. Du coup, la classe test2.class qui se trouve dans le rpertoire courant n'est pas trouve. La solution ? Ajouter le rpertoire
courant au classpath. Le rpertoire courant est reprsent par le symbole .
E:\data\serge\JAVA\classes\paquetages\personne>java -classpath mesClasses;. test2
p1=personne(Jean,Dupont,20)
Pourquoi toutes ces complications ? Le but des paquetages est d'viter les conflits de noms entre classes. Considrons deux
entreprises E1 et E2 distribuant des classes empaquetes respectivement dans les paquetages com.e1 et com.e2. Soit un client C qui
achte ces deux ensembles de classes dans lesquelles les deux entreprises ont dfini toutes deux une classe personne. Le client C
rfrencera la classe personne de l'entreprise E1 par com.e1.personne et celle de l'entreprise E2 par com.e2.personne vitant ainsi un conflit
de noms.
Classes et interfaces
55
pour avoir accs toutes les classes du paquetage java.util, o celui-ci est-il trouv ? Nous avons dit que les paquetages taient
cherchs par dfaut dans le rpertoire courant ou dans la liste des rpertoires dclars dans l'option classpath du compilateur ou de la
JVM si cette option est prsente. Ils sont galement cherchs dans les rpertoires lib du rpertoire d'installation du JDK.
Considrons ce rpertoire :
Dans cet exemple, les arborescences jdk14\lib et jdk14\jre\lib seront explores pour y chercher soit des fichiers .class, soit des
fichiers .jar ou .zip qui sont des archives de classes. Faisons par exemple une recherche des fichiers .jar se trouvant sous le
rpertoire jdk14 prcdent :
Il y en a plusieurs dizaines. Un fichier .jar peut s'ouvrir avec l'utilitaire winzip. Ouvrons le fichier rt.jar ci-dessus (rt=RunTime). On y
trouve plusieurs centaines de fichiers .class dont celles appartenant au paquetage java.util :
Classes et interfaces
56
Une mthode simple pour grer les paquetages est alors de les placer dans le rpertoire <jdk>\jre\lib o <jdk> est le rpertoire
d'installation du JDK. En gnral, un paquetage contient plusieurs classes et il est pratique de rassembler celles-ci dans un unique
fichier .jar (JAR=Java ARchive file). L'excutable jar.exe se trouve dans le dossier <jdk>\bin :
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\bin\jar.exe"
07/02/2002 12:52
28 752 jar.exe
Une aide l'utilisation du programme jar peut tre obtenue en l'appelant sans paramtres :
E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar.exe"
Syntaxe : jar {ctxu}[vfm0M] [fichier-jar] [fichier-manifest] [rp -C] fichiers ...
Options :
-c crer un nouveau fichier d''archives
-t gnrer la table des matires du fichier d''archives
-x extraire les fichiers nomms (ou tous les fichiers) du fichier d''archives
-u mettre jour le fichier d''archives existant
-v gnrer des informations verbeuses sur la sortie standard
-f spcifier le nom du fichier d''archives
-m inclure les informations manifest provenant du fichier manifest spcifi
-0 stocker seulement ; ne pas utiliser la compression ZIP
-M ne pas crer de fichier manifest pour les entres
-i gnrer l''index pour les fichiers jar spcifis
-C passer au rpertoire spcifi et inclure le fichier suivant
Si un rpertoire est spcifi, il est trait rcursivement.
Les noms des fichiers manifest et d''archives doivent tre spcifis
dans l''ordre des indicateurs ''m'' et ''f''.
Exemple 1 : pour archiver deux fichiers de classe dans le fichier d''archives classes.jar :
jar cvf classes.jar Foo.class Bar.class
Exemple 2 : utilisez le fichier manifest existant ''monmanifest'' pour archiver tous les fichiers du
rpertoire foo/ dans ''classes.jar'':
jar cvfm classes.jar monmanifest -C foo/ .
57
Crons un fichier istia.st.jar archivant toutes les classes du paquetage istia.st donc toutes les classes de l'arborescence istia\st ci-dessus
:
E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar" cvf istia.st.jar
istia\st\*
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28
467 personne.java
06/06/2002 17:36
195 test.java
06/06/2002 16:04
<DIR>
istia
06/06/2002 16:06
738 test.class
06/06/2002 16:47
236 test2.java
06/06/2002 18:15
740 test2.class
06/06/2002 18:08
874 istia.st.jar
On remarque qu'on n'a eu qu' citer le nom de l'archive explorer sans avoir dire explicitement o elle se trouvait. Tous les
rpertoires de l'arborescence <jdk>\jre\lib sont explors pour trouver le fichier .jar demand.
on calcule le nombre de parts du salari nbParts=nbEnfants/2 +1 s'il n'est pas mari, nbEnfants/2+2 s'il est mari, o
nbEnfants est son nombre d'enfants.
s'il a au moins trois enfants, il a une demie part de plus
on calcule son revenu imposable R=0.72*S o S est son salaire annuel
on calcule son coefficient familial QF=R/nbParts
on calcule son impt I. Considrons le tableau suivant :
12620.0
13190
15640
24740
31810
39970
48360
55790
92970
127860
151250
172040
195000
0
0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
0.45
0.50
0.55
0.60
0.65
0
631
1290.5
2072.5
3309.5
4900
6898.5
9316.5
12106
16754.5
23147.5
30710
39312
49062
Chaque ligne a 3 champs. Pour calculer l'impt I, on recherche la premire ligne o QF<=champ1. Par exemple, si QF=23000 on
trouvera la ligne
24740 0.15 2072.5
Classes et interfaces
58
L'impt I est alors gal 0.15*R - 2072.5*nbParts. Si QF est tel que la relation QF<=champ1 n'est jamais vrifie, alors ce sont les
coefficients de la dernire ligne qui sont utiliss. Ici :
0
0.65 49062
ce qui donne l'impt I=0.65*R - 49062*nbParts.
La classe impots sera dfinie comme suit :
// cration d'une classe impots
public class impots{
// les donnes ncessaires au calcul de l'impt
// proviennent d'une source extrieure
private double[] limites, coeffR, coeffN;
// constructeur
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// on vrifie que les 3 tableaux ont la mme taille
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la mme taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// c'est bon
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//constructeur
// calcul de l'impt
public long calculer(boolean mari, int nbEnfants, int salaire){
// calcul du nombre de parts
double nbParts;
if (mari) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// calcul revenu imposable & Quotient familial
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// calcul de l'impt
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// retour rsultat
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calculer
}//classe
Un objet impots est cr avec les donnes permettant le calcul de l'impt d'un contribuable. C'est la partie stable de l'objet. Une
fois cet objet cr, on peut appeler de faon rpte sa mthode calculer qui calcule l'impt du contribuable partir de son statut
marital (mari ou non), son nombre d'enfants et son salaire annuel.
Un programme de test pourait tre le suivant :
//classes importes
// import impots;
import java.io.*;
public class test
{
public static void main(String[] arg) throws IOException
{
// programme interactif de calcul d'impt
// l'utilisateur tape trois donnes au clavier : mari nbEnfants salaire
// le programme affiche alors l'impt payer
final String syntaxe="syntaxe : mari nbEnfants salaire\n"
+"mari : o pour mari, n pour non mari\n"
+"nbEnfants : nombre d'enfants\n"
+"salaire : salaire annuel en F";
// tableaux de donnes ncessaires au calcul de l'impt
double[] limites=new double[]
{12620,13190,15640,24740,31810,39970,48360,55790,92970,127860,151250,172040,195000,0};
double[] coeffR=new double[] {0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65};
double[] coeffN=new double[]
{0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5,12106,16754.5,23147.5,30710,39312,49062};
// cration d'un flux de lecture
BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
// cration d'un objet impt
impots objImpt=null;
try{
objImpt=new impots(limites,coeffR,coeffN);
}catch (Exception ex){
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
System.exit(1);
Classes et interfaces
59
}//try-catch
// boucle infinie
while(true){
// on demande les paramtres du calcul de l'impt
System.out.print("Paramtres du calcul de l'impt au format mari nbEnfants salaire ou rien pour
arrter :");
String paramtres=IN.readLine().trim();
// qq chose faire ?
if(paramtres==null || paramtres.equals("")) break;
// vrification du nombre d'arguments dans la ligne saisie
String[] args=paramtres.split("\\s+");
int nbParamtres=args.length;
if (nbParamtres!=3){
System.err.println(syntaxe);
continue;
}//if
// vrification de la validit des paramtres
// mari
String mari=args[0].toLowerCase();
if (! mari.equals("o") && ! mari.equals("n")){
System.err.println(syntaxe+"\nArgument mari incorrect : tapez o ou n");
continue;
}//if
// nbEnfants
int nbEnfants=0;
try{
nbEnfants=Integer.parseInt(args[1]);
if(nbEnfants<0) throw new Exception();
}catch (Exception ex){
System.err.println(syntaxe+"\nArgument nbEnfants incorrect : tapez un entier positif ou nul");
continue;
}//if
// salaire
int salaire=0;
try{
salaire=Integer.parseInt(args[2]);
if(salaire<0) throw new Exception();
}catch (Exception ex){
System.err.println(syntaxe+"\nArgument salaire incorrect : tapez un entier positif ou nul");
continue;
}//if
// les paramtres sont corrects - on calcule l'impt
System.out.println("impt="+objImpt.calculer(mari.equals("o"),nbEnfants,salaire)+" F");
// contribuable suivant
}//while
}//main
}//classe
60
Classes et interfaces
61
3.1 La documentation
Si vous avez install le JDK de Sun dans le dossier <jdk>, la documentation est disponible dans le dossier <jdk>\docs :
Quelquefois on a un jdk mais sans documentation. Celle-ci peut tre trouve sur le site de Sun http://www.sun.com. Dans le dossier
docs on trouve un fichier index.html qui est le point de dpart de l'aide du JDK :
Le lien API & Language ci-dessus donne accs aux classes Java. Le lien Demos/Tutorials est particulirement utile pour avoir des
exemples de programmes Java. Suivons le lien API & Language :
62
Cette page est le vritable point de dpart de la documentation sur les classes. On pourra crer un raccourci dessus pour y avoir un
accs rapide. L'URL est <jdk>\docs\api\index.html. On y trouve des liens sur les centaines de classes Java du JDK. Lorsqu'on
dbute, la principale difficult est de savoir ce que font ces diffrentes classes. Dans un premier temps, cette aide n'a donc d'intrt
que si on connat le nom de la classe sur laquelle on veut des informations. On peut aussi se laisser guider par les noms des classes
qui indiquent normalement le rle de la classe.
Prenons un exemple et cherchons des informations sur la classe Vector qui implmente un tableau dynamique. Il suffit de chercher
dans la liste des classes du cadre de gauche le lien de la classe Vector :
63
On y trouve
- la hirarchie dans laquelle se trouve la classe, ici java.util.Vector
- la liste des champs (attributs) de la classe
- la liste des constructeurs
- la liste des mthodes
Par la suite, nous prsentons diverses classes. Nous invitons le lecteur systmatiquement vrifier la dfinition complte des classes
utilises.
La classe enseignant est drive de la classe personne et est dfinie comme suit :
class enseignant extends personne{
// attributs
private int section;
// constructeur
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
// toString
public String toString(){
return "etudiant("+super.toString()+","+section+")";
}
}
Nous utiliserons galement une classe etudiant drive de la classe personne et dfinie comme suit :
class etudiant extends personne{
String numero;
Classes d'usage courant
64
65
affiche("(\"
}//Main
abc
\").trim()=["+"
abc
".trim()+"]");
// affiche
public static void affiche(String msg){
// affiche msg
System.out.println(msg);
}//affiche
}//classe
66
for(int
for int i=0;i<V.size();i++){
p2=(personne) V.elementAt(i);
System.out.println("V["+i+"]="+p2.toString());
}
} // fin main
}// fin classe
Compilons ce programme :
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>dir
10/06/2002 10:41
1 134 personne.class
10/06/2002 10:41
619 enseignant.class
10/06/2002 10:41
610 etudiant.class
10/06/2002 10:42
1 035 test1.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>javac test1.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>dir
10/06/2002 10:41
1 134 personne.class
10/06/2002 10:41
619 enseignant.class
10/06/2002 10:41
610 etudiant.class
10/06/2002 10:42
1 035 test1.java
10/06/2002 10:43
1 506 test1.class
Par la suite, nous ne rpterons plus le processus de compilation et d'excution des programmes de tests. Il suffit de reproduire ce
qui a t fait ci-dessus.
67
// le polymorphisme
personne p2=(personne)en;
System.out.println("p2="+p2);
personne p3=(personne)et;
System.out.println("p3="+p3);
// un vecteur
ArrayList personnes=new ArrayList();
personnes.add(p);personnes.add(en);personnes.add(et);
System.out.println("Nombre de personnes = "+personnes.size());
for(int i=0;i<personnes.size();i++){
p2=(personne) personnes.get(i);
System.out.println("personnes["+i+"]="+p2);
}
} // fin main
}// fin classe
trie tableau en utilisant pour cela l'ordre implicite du type de donnes du tableau, nombre
ou chanes de caractres.
static void sort (Object[] tableau, Comparator C) trie trie tableau en utilisant pour comparer les lments la fonction de comparaison C
static int binarySearch(tableau,lment)
rend la position de lment dans tableau ou une valeur <0 sinon. Le tableau doit tre
auparavant tri.
static int binarySearch(Object[] tableau,Object idem mais utilise la fonction de comparaison C pour comparer deux lments du
lment, Comparator C)
tableau.
Voici un premier exemple :
import java.util.*;
public class sort2 implements Comparator{
// une classe prive interne
private class personne{
private String nom;
private int age;
public personne(String nom, int age){
this.nom=nom; // nom de la personne
this.age=age; // son ge
}
// rcuprer l'ge
public int getAge(){
return age;
}
// identit de la personne
public String toString(){
return ("["+nom+","+age+"]");
}
}; // classe personne
// constructeur
public sort2() {
// un tableau de personnes
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// tri du tableau de personnes
Arrays.sort(amis,this);
// vrification
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//constructeur
// la fonction qui compare des personnes
public int compare(Object o1, Object o2){
// doit rendre
// -1 si o1 "plus petit que" o2
// 0 si o1 "gal " o2
// +1 si o1 "plus grand que" o2
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//compare
Classes d'usage courant
68
// fonction de test
public static void main(String[] arg){
new sort2();
}//main
}//classe
Examinons ce programme. La fonction main cre un objet sort2. Le constructeur de la classe sort2 est le suivant :
// constructeur
public sort2() {
// un tableau de personnes
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// tri du tableau de personnes
Arrays.sort(amis,this);
// vrification
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//constructeur
Le tableau trier est un tableau d'objets personne. La classe personne est dfinie de faon prive (private) l'intrieur de la classe sort2.
La mthode statique sort de la classe Arrays ne sait pas comment trier un tableau d'objets personne, aussi est-on oblig ici d'utiliser la
forme void sort(Object[] obj, Comparator C). Comparator est une interface ne dfinissant qu'une mthode :
int compare(Object o1, Object o2)
et qui doit rendre 0 : si o1=o2, -1 : si o1<02, +1 : si o1>o2. Dans le prototype void sort(Object[] obj, Comparator C) le second
argument C doit tre un objet implmentant l'interface Comparator. Dans le constructeur sort2, on a choisi l'objet courant this :
// tri du tableau de personnes
Arrays.sort(amis,this);
2.
Pour comparer deux objets personne, on utilise ici l'ge (on aurait pu utiliser le nom).
Les rsultats de l'excution sont les suivants :
[tournesol,40]
[milou,80]
[tintin,100]
69
Le second paramtre de la mthode sort doit tre un objet implmentant l'interface Comparator. Ici nous crons un tel objet par new
java.util.Comparator() et le texte qui suit {.} dfinit la classe dont on cre un objet. On appelle cela une classe anonyme car elle ne
porte pas de nom. Dans cette classe anonyme qui doit implmenter l'interface Comparator, on dfinit la mthode compare de cette
interface. Celle-ci se contente d'appeler la mthode compare1 de la classe sort2. On est alors ramen au cas prcdent.
La classe sort2 n'implmente plus l'interface Comparator. Aussi sa dclaration devient-elle :
public class sort2 {
Maintenant nous testons la mthode binarySearch de la classe Arrays sur l'exemple suivant :
import java.util.*;
public class sort4 {
// une classe prive interne
private class personne{
// attributs
private String nom;
private int age;
// constructeur
public personne(String nom, int age){
this.nom=nom; // nom de la personne
this.age=age; // son ge
}
// rcuprer le nom
public String getNom(){
return nom;
}
// rcuprer l'ge
public int getAge(){
return age;
}
// identit de la personne
public String toString(){
return ("["+nom+","+age+"]");
}
}; // classe personne
Classes d'usage courant
70
// constructeur
public sort4() {
// un tableau de personnes
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// des comparateurs
java.util.Comparator comparateur1=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//compare
}//classe
;
java.util.Comparator comparateur2=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare2(o1,o2);
}//compare
}//classe
;
// tri du tableau de personnes
Arrays.sort(amis,comparateur1);
// vrification
for(int i=0;i<3;i++)
System.out.println(amis[i]);
// recherches
cherche("milou",amis,comparateur2);
cherche("xx",amis,comparateur2);
}//constructeur
// la fonction qui compare des personnes
public int compare1(Object o1, Object o2){
// doit rendre
// -1 si o1 "plus petit que" o2
// 0 si o1 "gal " o2
// +1 si o1 "plus grand que" o2
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//compare1
// la fonction qui compare une personne un nom
public int compare2(Object o1, Object o2){
// o1 est une personne
// o2 est un String, le nom nom2 d'une personne
// doit rendre
// -1 si o1.nom "plus petit que" nom2
// 0 si o1.nom "gal " nom2
// +1 si o1.nom "plus grand que" nom2
personne p1=(personne)o1;
String nom1=p1.getNom();
String nom2=(String)o2;
return nom1.compareTo(nom2);
}//compare2
public void cherche(String ami,personne[] amis, Comparator comparateur){
// recherche ami dans le tableau amis
int position=Arrays.binarySearch(amis,ami,comparateur);
// trouv ?
if(position>=0)
System.out.println(ami + " a " + amis[position].getAge() + " ans");
else System.out.println(ami + " n'existe pas dans le tableau");
}//cherche
// main
public static void main(String[] arg){
new sort4();
}//main
}//classe
Ici, nous avons procd un peu diffremment des exemples prcdents. Les deux objets Comparator ncessaires aux mthodes sort et
binarySearch ont t crs et affects aux variables comparateur1 et comparateur2.
// des comparateurs
java.util.Comparator comparateur1=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//compare
}//classe
;
java.util.Comparator comparateur2=
new java.util.Comparator(){
Classes d'usage courant
71
Une recherche dichotomique sur le tableau amis est faite deux fois dans le constructeur de sort4 :
// recherches
cherche("milou",amis,comparateur2);
cherche("xx",amis,comparateur2);
La mthode cherche reoit tous les paramtres dont elle a besoin pour appeler la mthode binarySearch :
public void cherche(String ami,personne[] amis, Comparator comparateur){
// recherche ami dans le tableau amis
int position=Arrays.binarySearch(amis,ami,comparateur);
// trouv ?
if(position>=0)
System.out.println(ami + " a " + amis[position].getAge() + " ans");
else System.out.println(ami + " n'existe pas dans le tableau");
}//cherche
La mthode binarySearch travaille avec le comparateur comparateur2 qui lui-mme fait appel la mthode compare2 de la classe sort4. La
mthode rend la position du nom cherch dans le tableau s'il existe ou un nombre <0 sinon. La mthode compare2 sert comparer
un objet personne un nom de type String.
// la fonction qui compare une personne un nom
public int compare2(Object o1, Object o2){
// o1 est une personne
// o2 est un String, le nom nom2 d'une personne
// doit rendre
// -1 si o1.nom "plus petit que" nom2
// 0 si o1.nom "gal " nom2
// +1 si o1.nom "plus grand que" nom2
personne p1=(personne)o1;
String nom1=p1.getNom();
String nom2=(String)o2;
return nom1.compareTo(nom2);
}//compare2
Contrairement la mthode sort, la mthode binarySearch ne reoit pas deux objets personne, mais un objet personne et un objet String
dans cet ordre. Le 1er paramtre est un lment du ableau amis, le second le nom de la personne cherche.
Voici un exemple :
// les classes importes
import java.util.*;
public class test1{
// le programme principal main - static - mthode de classe
public static void main(String arg[]){
// la cration des objets instances de classes
personne p=new
new personne("Jean","Dupont",30);
enseignant en=new
new enseignant("Paula","Hanson",56,27);
etudiant et=new
new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// le polymorphisme
personne p2=(personne)en;
Classes d'usage courant
72
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// un vecteur
Vector V=new
new Vector();
V.addElement(p);V.addElement(en);V.addElement(et);
System.out.println("Taille du vecteur V = "+V.size());
int i;
for(i=0;i<V.size();i++){
for
p2=(personne) V.elementAt(i);
System.out.println("V["+i+"]="+p2.toString());
}
// une numration
Enumeration E=V.elements();
i=0;
while(E.hasMoreElements()){
while
p2=(personne) E.nextElement();
System.out.println("V["+i+"]="+p2.toString());
i++;
}
}// fin main
}//fin classe
valeur
valeur1
valeur2
...
Les cls sont uniques, c.a.d. qu'il ne peut y avoir deux cls identiques. Les mthodes et proprits principales de la classe Hashtable
sont les suivantes :
public Hashtable()
public int size()
public Object put(Object key, Object value)
public Object get(Object key)
public boolean containsKey(Object key)
public boolean contains(Object value)
public Enumeration keys()
public Object remove(Object key)
public String toString()
Voici un exemple :
// les classes importes
import java.util.*;
public class test1{
// le programme principal main - static - mthode de classe
public static void main(String arg[]){
// la cration des objets instances de classes
personne p=new
new personne("Jean","Dupont",30);
enseignant en=new
new enseignant("Paula","Hanson",56,27);
etudiant et=new
new etudiant("Chris","Garot",22,"19980405");
Classes d'usage courant
73
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// le polymorphisme
personne p2=(personne)en;
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// un dictionnaire
Hashtable H=new
new Hashtable();
H.put("personne1",p);
H.put("personne2",en);
H.put("personne3",et);
Enumeration E=H.keys();
int i=0;
String cle;
while(E.hasMoreElements()){
while
cle=(String) E.nextElement();
p2=(personne) H.get(cle);
System.out.println("cl "+i+"="+cle+" valeur="+p2.toString());
i++;
}
}//fin main
}//fin classe
cre le fichier de nom fileName - on peut ensuite crire dedans - un ventuel fichier de mme
nom est cras
idem - un ventuel fichier de mme nom peut tre utilis en l'ouvrant en mode ajout
(append=true)
La classe FileWriter offre un certain nombre de mthodes pour crire dans un fichier, mthodes hrites de la classe Writer. Pour
crire dans un fichier texte, il est prfrable d'utiliser la classe PrintWriter dont les constructeurs souvent utiliss sont les suivants :
PrintWriter(Writer out)
PrintWriter(Writer out, boolean
autoflush)
l'argument est de type Writer, c.a.d. un flux d'criture (dans un fichier, sur le rseau, )
idem. Le second argument gre la bufferisation des lignes. Lorsqu'il est faux (son dfaut), les lignes
crites sur le fichier transitent par un buffer en mmoire. Lorsque celui-ci est plein, il est crit dans le
fichier. Cela amliore les accs disque. Ceci-dit quelquefois, ce comportement est indsirable,
notamment lorsqu'on crit sur le rseau.
74
// ouverture du fichier
PrintWriter fic=null;
try{
fic=new PrintWriter(new FileWriter("out"));
} catch (Exception e){
Erreur(e,1);
}
// criture dans le fichier
try{
fic.println("Jean,Dupont,27");
fic.println("Pauline,Garcia,24");
fic.println("Gilles,Dumond,56");
} catch (Exception e){
Erreur(e,3);
}
// fermeture du fichier
try{
fic.close();
} catch (Exception e){
Erreur(e,2);
}
}// fin main
private static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}//Erreur
}//classe
3.9.2 Lire
Pour lire le contenu d'un fichier, il faut disposer d'un flux de lecture associ au fichier. On peut utiliser pour cela la classe FileReader
et le constructeur suivant :
FileReader(String nomFichier)
ouvre un flux de lecture partir du fichier indiqu. Lance une exception si l'opration choue.
La classe FileReader possde un certain nombre de mthodes pour lire dans un fichier, mthodes hrites de la classe Reader. Pour
lire des lignes de texte dans un fichier texte, il est prfrable d'utiliser la classe BufferedReader avec le constructeur suivant :
BufferedReader(Reader in)
ouvre un flux de lecture bufferis partir d'un flux d'entre in. Ce flux de type Reader peut provenir
du clavier, d'un fichier, du rseau, ...
75
La mthode sauveAttributs reoit en unique paramtre le flux PrintWriter dans lequel elle doit crire. Un programme de test pourrait
tre le suivant :
// imports
import java.io.*;
// import personne;
public class sauver{
public static void main(String[] arg){
// ouverture du fichier
PrintWriter fic=null;
try{
fic=new PrintWriter(new FileWriter("out"));
} catch (Exception e){
Erreur(e,1);
}
// criture dans le fichier
try{
new personne("Jean","Dupont",27).sauveAttributs(fic);
new personne("Pauline","Garcia",24).sauveAttributs(fic);
new personne("Gilles","Dumond",56).sauveAttributs(fic);
} catch (Exception e){
Erreur(e,3);
}
// fermeture du fichier
try{
fic.close();
} catch (Exception e){
Erreur(e,2);
}
}// fin main
Classes d'usage courant
76
// Erreur
private static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}//Erreur
}//classe
TTT readTTT()
long length()
long getFilePointer()
void seek(long pos)
constructeur - ouvre le fichier indiqu dans le mode indiqu. Celui-ci prend ses
valeurs dans :
r : ouverture en lecture
rw : ouverture en lecture et criture
crit valeur dans le fichier. TTT reprsente le type de valeur. La reprsentation
mmoire de valeur est crite telle-quelle dans le fichier. On trouve ainsi
writeBoolean, writeByte, writeInt, writeDouble, writeLong, writeFloat,... Pour
crire une chane, on utilise writeBytes(String chaine).
lit et rend une valeur de type TTT. On trouve ainsi readBoolean, readByte, readInt,
readDouble, readLong, readFloat,... La mthode read() lit un octet.
taille du fichier en octets
position courante du pointeur de fichier
positionne le curseur de fichier l'octet pos
77
double prix;
int stockActuel;
int stockMinimum;
}//structure
78
C'est le programme suivant qui nous permet de vrifier que l'excution s'est correctement faite.
79
E:\data\serge\JAVA\random>java test2
code : a100
nom : velo
prix : 1000.8
Stock actuel : 100
Stock minimum : 10
80
}
// stock actuel
try{
art.stockActuel=Integer.parseInt(champs[3]);
} catch (Exception E){
erreur("Stock actuel erron en ligne "+ numLigne + " du fichier data.txt",5);
}
// stock actuel
try{
art.stockActuel=Integer.parseInt(champs[3]);
} catch (Exception E){
erreur("Stock actuel erron en ligne "+ numLigne + " du fichier data.txt",5);
}
// on crit l'enregistrement
try{
ecrire(dataBin,art);
} catch (IOException E){
erreur("Erreur lors de l'criture de l'enregistrement "+numLigne,7);
}
// on passe la ligne suivante
}// fin while
} catch (IOException E){
erreur("Erreur lors de la lecture du fichier data.txt aprs la ligne "+numLigne,8);
}
// c'est fini
try{
dataBin.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.bin",10);
}
try{
dataTxt.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.txt",11);
}
}// fin main
// mthode d'criture
public static void ecrire(RandomAccessFile fic, article art) throws IOException{
// code
fic.writeBytes(art.code);
// le nom limit 20 caractres
art.nom=art.nom.trim();
int l=art.nom.length();
int nbBlancs=20-l;
if(nbBlancs>0){
String blancs="";
for(int i=0;i<nbBlancs;i++) blancs+=" ";
art.nom+=blancs;
} else art.nom=art.nom.substring(0,20);
fic.writeBytes(art.nom);
// le prix
fic.writeDouble(art.prix);
// les stocks
fic.writeInt(art.stockActuel);
fic.writeInt(art.stockMinimum);
}// fin crire
// ------------------------erreur
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// fin erreur
}// fin class
C'est le programme suivant qui permet de vrifier que celui-ci a correctement fonctionn.
81
82
art.stockActuel=fic.readInt();
art.stockMinimum=fic.readInt();
}// fin crire
// ---------------------affiche
public static void affiche(article art){
System.out.println("code : "+art.code);
System.out.println("nom : "+art.nom);
System.out.println("prix : "+art.prix);
System.out.println("Stock actuel : "+art.stockActuel);
System.out.println("Stock minimum : "+art.stockMinimum);
}// fin affiche
// ------------------------erreur
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// fin erreur
}// fin class
83
84
E:\data\serge\JAVA\random>java.bat test6 20
Erreur lors de la lecture de l'enregistrement 20
^
$
*
+
?
.
(modle)
x|y
{n}
{n,}
{n,m}
[xyz]
[^xyz]
[a-z]
[^m-z]
\b
\B
\d
\D
\f
\n
\r
\s
Description
Marque le caractre suivant comme caractre spcial ou littral. Par exemple, "n" correspond au
caractre "n". "\n" correspond un caractre de nouvelle ligne. La squence "\\" correspond "\",
tandis que "\(" correspond "(".
Correspond au dbut de la saisie.
Correspond la fin de la saisie.
Correspond au caractre prcdent zro fois ou plusieurs fois. Ainsi, "zo*" correspond "z" ou
"zoo".
Correspond au caractre prcdent une ou plusieurs fois. Ainsi, "zo+" correspond "zoo", mais pas
"z".
Correspond au caractre prcdent zro ou une fois. Par exemple, "a?ve?" correspond "ve" dans
"lever".
Correspond tout caractre unique, sauf le caractre de nouvelle ligne.
Recherche le modle et mmorise la correspondance. La sous-chane correspondante peut tre extraite de
la collection Matches obtenue, l'aide d'Item [0]...[n]. Pour trouver des correspondances avec des
caractres entre parenthses ( ), utilisez "\(" ou "\)".
Correspond soit x soit y. Par exemple, "z|foot" correspond "z" ou "foot". "(z|f)oo" correspond
"zoo" ou "foo".
n est un nombre entier non ngatif. Correspond exactement n fois le caractre. Par exemple, "o{2}"
ne correspond pas "o" dans "Bob," mais aux deux premiers "o" dans "fooooot".
n est un entier non ngatif. Correspond au moins n fois le caractre. Par exemple, "o{2,}" ne
correspond pas "o" dans "Bob", mais tous les "o" dans "fooooot". "o{1,}" quivaut "o+" et
"o{0,}" quivaut "o*".
m et n sont des entiers non ngatifs. Correspond au moins n et au plus m fois le caractre. Par
exemple, "o{1,3}" correspond aux trois premiers "o" dans "foooooot" et "o{0,1}" quivaut "o?".
Jeu de caractres. Correspond l'un des caractres indiqus. Par exemple, "[abc]" correspond "a" dans
"plat".
Jeu de caractres ngatif. Correspond tout caractre non indiqu. Par exemple, "[^abc]" correspond
"p" dans "plat".
Plage de caractres. Correspond tout caractre dans la srie spcifie. Par exemple, "[a-z]" correspond
tout caractre alphabtique minuscule compris entre "a" et "z".
Plage de caractres ngative. Correspond tout caractre ne se trouvant pas dans la srie spcifie. Par
exemple, "[^m-z]" correspond tout caractre ne se trouvant pas entre "m" et "z".
Correspond une limite reprsentant un mot, autrement dit, la position entre un mot et un espace.
Par exemple, "er\b" correspond "er" dans "lever", mais pas "er" dans "verbe".
Correspond une limite ne reprsentant pas un mot. "en*t\B" correspond "ent" dans "bien entendu".
Correspond un caractre reprsentant un chiffre. quivaut [0-9].
Correspond un caractre ne reprsentant pas un chiffre. quivaut [^0-9].
Correspond un caractre de saut de page.
Correspond un caractre de nouvelle ligne.
Correspond un caractre de retour chariot.
Correspond tout espace blanc, y compris l'espace, la tabulation, le saut de page, etc. quivaut
85
\S
\t
\v
\w
\W
\num
\n
\xn
"[ \f\n\r\t\v]".
Correspond tout caractre d'espace non blanc. quivaut "[^ \f\n\r\t\v]".
Correspond un caractre de tabulation.
Correspond un caractre de tabulation verticale.
Correspond tout caractre reprsentant un mot et incluant un trait de soulignement. quivaut "[AZa-z0-9_]".
Correspond tout caractre ne reprsentant pas un mot. quivaut "[^A-Za-z0-9_]".
Correspond num, o num est un entier positif. Fait rfrence aux correspondances mmorises. Par
exemple, "(.)\1" correspond deux caractres identiques conscutifs.
Correspond n, o n est une valeur d'chappement octale. Les valeurs d'chappement octales doivent
comprendre 1, 2 ou 3 chiffres. Par exemple, "\11" et "\011" correspondent tous les deux un caractre
de tabulation. "\0011" quivaut "\001" & "1". Les valeurs d'chappement octales ne doivent pas
excder 256. Si c'tait le cas, seuls les deux premiers chiffres seraient pris en compte dans l'expression.
Permet d'utiliser les codes ASCII dans des expressions rgulires.
Correspond n, o n est une valeur d'chappement hexadcimale. Les valeurs d'chappement
hexadcimales doivent comprendre deux chiffres obligatoirement. Par exemple, "\x41" correspond
"A". "\x041" quivaut "\x04" & "1". Permet d'utiliser les codes ASCII dans des expressions
rgulires.
Un lment dans un modle peut tre prsent en 1 ou plusieurs exemplaires. Considrons quelques exemples autour du symbole \d
qui reprsente 1 chiffre :
modle
\d
\d?
\d*
\d+
\d{2}
\d{3,}
\d{5,7}
signification
un chiffre
0 ou 1 chiffre
0 ou davantage de chiffres
1 ou davantage de chiffres
2 chiffres
au moins 3 chiffres
entre 5 et 7 chiffres
Imaginons maintenant le modle capable de dcrire le format attendu pour une chane de caractres :
chane recherche
une date au format jj/mm/aa
une heure au format hh:mm:ss
un nombre entier non sign
un suite d'espaces ventuellement vide
un nombre entier non sign qui peut tre prcd ou suivi d'espaces
un nombre entier qui peut tre sign et prcd ou suivi d'espaces
un nombre rel non sign qui peut tre prcd ou suivi d'espaces
un nombre rel qui peut tre sign et prcd ou suivi d'espaces
une chane contenant le mot juste
modle
\d{2}/\d{2}/\d{2}
\d{2}:\d{2}:\d{2}
\d+
\s*
\s*\d+\s*
\s*[+|-]?\s*\d+\s*
\s*\d+(.\d*)?\s*
\s*[+|]?\s*\d+(.\d*)?\s*
\bjuste\b
signification
le modle commence la chane
le modle finit la chane
le modle commence et finit la chane
le modle est cherch partout dans la chane en commenant par le dbut de celle-ci.
chane recherche
une chane se terminant par un point d'exclamation
une chane se terminant par un point
une chane commenant par la squence //
une chane ne comportant qu'un mot ventuellement suivi ou prcd d'espaces
une chane ne comportant deux mot ventuellement suivis ou prcds d'espaces
une chane contenant le mot secret
Classes d'usage courant
modle
!$
\.$
^//
^\s*\w+\s*$
^\s*\w+\s*\w+\s*$
\bsecret\b
86
Les sous-ensembles d'un modle peuvent tre "rcuprs". Ainsi non seulement, on peut vrifier qu'une chane correspond un
modle particulier mais on peut rcuprer dans cette chane les lments correspondant aux sous-ensembles du modle qui ont t
entours de parenthses. Ainsi si on analyse une chane contenant une date jj/mm/aa et si on veut de plus rcuprer les lments
jj, mm, aa de cette date on utilisera le modle (\d\d)/(\d\d)/(\d\d).
avec : modle : le modle vrifier, chaine : la chane comparer au modle. Le rsultat est le boolen true si chaine correspond
modle, false sinon.
Voici un exemple :
import java.io.*;
import java.util.regex.*;
// gestion d'expression rgulires
public class regex1 {
public static void main(String[] args){
// une expression rgulire modle
String modle1="^\\s*\\d+\\s*$";
// comparer un exemplaire au modle
String exemplaire1=" 123 ";
if (Pattern.matches(modle1,exemplaire1)){
affiche("["+exemplaire1 + "] correspond au
}else{
affiche("["+exemplaire1 + "] ne correspond
}//if
String exemplaire2=" 123a ";
if (Pattern.matches(modle1,exemplaire2)){
affiche("["+exemplaire2 + "] correspond au
}else{
affiche("["+exemplaire2 + "] ne correspond
}//if
}//main
modle ["+modle1+"]");
pas au modle ["+modle1+"]");
modle ["+modle1+"]");
pas au modle ["+modle1+"]");
On notera que dans le modle "^\s*\d+\s*$" le caractre \ doit tre doubl cause de l'interprtation particulire que fait Java de
ce caractre. On crit donc : String modle1="^\\s*\\d+\\s*$";
Elle prend pour paramtre la chane du modle et rend un objet Pattern. Pour comparer le modle d'un objet Pattern une chane de
caractres on utilise la classe Matcher. Celle-ci permet la comparaison d'un modle une chane de caractres. A partir d'un objet
Pattern, il est possible d'obtenir un objet de type Matcher avec la mthode matcher :
public Matcher matcher(CharSequence
input)
matcher
Ainsi pour comparer le modle "\d+" la chane " 123 456 789 ", on pourra crer un objet Matcher de la faon suivante :
Classes d'usage courant
87
Pattern regex=Pattern.compile("\\d+");
Matcher rsultats=regex.matcher(" 123
456
789
");
A partir de l'objet rsultats prcdent, on va pouvoir rcuprer les diffrentes occurrences du modle dans la chane. Pour cela, on
utilise les mthodes suivantes de la classe Matcher :
public
public
public
public
boolean find()
find
String group()
group
int start()
start
Matcher reset()
reset
La mthode find recherche dans la chane explore la premire occurence du modle. Un second appel find recherchera
l'occurrence suivante. Et ainsi de suite. La mthode rend true si elle trouve le modle, false sinon. La portion de chaine
correspondant la dernire occurence trouve par find est obtenue avec la mthode group et sa position avec la mthode start. Ainsi,
si on poursuit l'exemple prcdent et qu'on veuille afficher toutes les occurences du modle "\d+" dans la chane " 123 456 789 "
on crira :
while(rsultats.find()){
System.out.println("squence " + rsultats.group() + " trouve en position " + rsultats.start());
}//while
La mthode reset permet de rinitialiser l'objet Matcher sur le dbut de la chane compare au modle. Ainsi la mthode find trouvera
ensuite de nouveau la premire occurence du modle.
Voici un exemple complet :
import java.io.*;
import java.util.regex.*;
// gestion d'expression rgulires
public class regex2 {
public static void main(String[] args){
// plusieurs occurrences du modle dans l'exemplaire
String modle2="\\d+";
Pattern regex2=Pattern.compile(modle2);
String exemplaire3=" 123 456 789";
// recherche des occurrences du modle dans l'exemplaire
Matcher matcher2=regex2.matcher(exemplaire3);
while(matcher2.find()){
affiche("squence " + matcher2.group() + " trouve en position " + matcher2.start());
}//while
}//Main
public static void affiche(String msg){
System.out.println(msg);
}//affiche
}//classe
88
Matcher rsultat=regex3.matcher(exemplaire4);
if (rsultat.find()){
// l'exemplaire correspond au modle
affiche("L'exemplaire ["+exemplaire4+"] correspond au modle ["+modle3+"]");
// on affiche les groupes
for (int i=0;i<=rsultat.groupCount();i++){
affiche("groupes["+i+"]=["+rsultat.group(i)+"] en position "+rsultat.start(i));
}//for
}else{
// l'exemplaire ne correspond pas au modle
affiche("L'exemplaire["+exemplaire4+" ne correspond pas au modle ["+modle3+"]");
}
}//Main
public static void affiche(String msg){
System.out.println(msg);
}//affiche
}//classe
La chane exemplaire4 est compare au modle regex3 au travers de la mthode find. Une occurence du modle regex3 est alors trouve
dans la chane exemplaire4. Si le modle comprend des sous-ensembles entours de parenthses ceux-ci sont disponibles grce
diverses mthodes de la classe Matcher :
public int groupCount()
groupCount
public String group(int
group)
group
public int start(int
group)
start
La mthode groupCount donne le nombre de sous-ensembles trouvs dans le modle et group(i) le sous-ensemble n i. Celui est
trouv dans la chane une position donne par start(i). Ainsi dans l'exemple :
L'exemplaire [Il est 18:05:49] correspond au modle [(\d\d):(\d\d):(\d\d)]
groupes[0]=[18:05:49] en position 7
groupes[1]=[18] en position 7
groupes[2]=[05] en position 10
groupes[3]=[49] en position 13
Le premier appel la mthode find va trouver la chane 18:05:49 et automatiquement crer les trois sous-ensembles dfinis par les
parenthses du modle, respectivement 18, 05 et 49.
89
Pattern regex=null;
BufferedReader IN=null;
Matcher rsultats=null;
int nbOccurrences=0;
// gestion des erreurs
try{
// on demande l'utilisateur les modles et les exemplaires comparer celui-ci
while(true){
// flux d'entre
IN=new BufferedReader(new InputStreamReader(System.in));
// on demande le modle
System.out.print("Tapez le modle tester ou fin pour arrter :");
modle=IN.readLine();
// fini ?
if(modle.trim().toLowerCase().equals("fin")) break;
// on cre l'expression rgulire
regex=Pattern.compile(modle);
// on demande l'utilisateur les exemplaires comparer au modle
while(true){
System.out.print("Tapez la chane comparer au modle ["+modle+"] ou fin pour arrter :");
chaine=IN.readLine();
// fini ?
if(chaine.trim().toLowerCase().equals("fin")) break;
// on cre l'objet matcher
rsultats=regex.matcher(chaine);
// on recherche les occurrences du modle
nbOccurrences=0;
while(rsultats.find()){
// on a une occurrence
nbOccurrences++;
// on l'affiche
System.out.println("J'ai trouv la correspondance ["+rsultats.group()
+"] en position "+rsultats.start());
// affichage des sous-lments
if(rsultats.groupCount()!=1){
for(int j=1;j<=rsultats.groupCount();j++){
System.out.println("\tsous-lment ["+rsultats.group(j)+"] en position "+
rsultats.start(j));
}//for j
}//if
// chane suivante
}//while(rsultats.find())
// a-t-on trouv au moins une occurrence ?
if(nbOccurrences==0){
System.out.println("Je n'ai pas trouv de correspondance au modle ["+modle+"]");
}//if
// modle suivant
}//while(true)
}//while(true)
}catch(Exception ex){
// erreur
System.err.println("Erreur : "+ex.getMessage());
// fin avec erreur
System.exit(1);
}//try-catch
// fin
System.exit(0);
}//Main
}//classe
90
La chane input est dcompose en champs, ceux-ci tant spars par un sparateur correspondant au modle de l'objet Pattern
courant. Pour rcuprer les champs d'une ligne dont le sparateur de champs est la virgule prcde ou suivie d'un nombre
quelconque d'espaces, on crira :
// une ligne
String ligne="abc ,, def , ghi";
// un modle
Pattern modle=Pattern.compile("\\s*,\\s*");
// dcomposition de ligne en champs
String[] champs=modle.split(ligne);
91
3.12 Exercices
3.12.1 Exercice 1
Sous Unix, les programmes sont souvent appels de la faon suivante :
$ pg -o1 v1 v2 ... -o2 v3 v4
o -oi reprsente une option et vi une valeur associe cette option. On dsire crer une classe options qui permettrait
d'analyser la chane d'arguments -o1 v1 v2 ... -o2 v3 v4 afin de construire les entits suivantes :
optionsValides
dictionnaire (Hashtable) dont les cls sont les options oi valides. La valeur associe
la cl oi est un vecteur (Vector) dont les lments sont les valeurs v1 v2
associes l'option -oi
dictionnaire (Hashtable) dont les cls sont les options oi invalides. La valeur associe
la cl oi est un vecteur (Vector) dont les lments sont les valeurs v1 v2
associes l'option -oi
chane (String) donnant la liste des valeurs vi non associes une option
entier valant 0 s'il n'y a pas d'erreurs dans la ligne des arguments, autre chose sinon :
1 : il y a des paramtres d'appel invalides
2 : il y a des options invalides
4 : il y a des valeurs non associes des options
S'il y a plusieurs types d'erreurs, ces valeurs se cumulent.
optionsInvalides
optionsSans
erreur
92
Quelques rsultats :
C:\Serge\java\options>java test1
Options : 1 2 3 -a a1 a2 -b b1 -c c1 c2 c3 -b b2 b3
Options acceptables: -a -b
Erreur 6
Options valides :(-b,b1,b2,b3) (-a,a1,a2)
Options invalides : (-c,c1,c2,c3)
Sans options : 1 2 3
3.12.2 Exercice 2
On dsire crer une classe stringtovector permettant de transfrer le contenu d'un objet String dans un objet Vector. Cette classe
serait drive de la classe Vector :
class stringtovector extends Vector
//
//
//
//
//
//
//
Cet attribut est positionn par le constructeur prcdent avec les valeurs suivantes :
0 : la construction s'est bien passe
4 : certains champs demands sont absents alors que strict=true
La classe aura galement deux mthodes :
Classes d'usage courant
93
qui affiche la valeur de l'objet sous la forme (erreur, lment 1, lment 2, ) o lments i sont les lments du vecteur construit
partir de la chane.
Un programme de test pourrait tre le suivant :
import java.io.*;
//import stringtovector;
public class essai2{
public static void main(String arg[]){
int[]
T1={1,3};
int
System.out.println(new
new stringtovector("a
int[]
T2={1,3,7};
int
System.out.println(new
new stringtovector("a
int [] T3={1,4,7};
System.out.println(new
new stringtovector("a
System.out.println(new
new stringtovector("a
System.out.println(new
new stringtovector("a
int[]
T4={1};
int
System.out.println(new
new stringtovector("a
int[]
T5=null
null;
int
null
System.out.println(new
new stringtovector("a
System.out.println(new
new stringtovector("a
int[]
T6=new
new int[0];
int
int
System.out.println(new
new stringtovector("a
int[]
T7={1,3,4};
int
System.out.println(new
new stringtovector("a
}
}
: b : c :d:e",":",T1,true
true).identite());
true
: b : c :d:e",":",T2,true
true).identite());
true
: b : c :d:e",":",T3,false
false).identite());
false
: b : c :d:e","",T1,false
false).identite());
false
: b : c :d:e",null
null,T1,false
false).identite());
null
false
: b : c :d:e","!",T4,true
true).identite());
true
: b : c :d:e",":",T5,true
true).identite());
true
: b : c :d:e",null
null,T5,true
true).identite());
null
true
: b : c :d:e","",T6,true
true).identite());
true
b
c d e"," ",T6,true
true).identite());
true
Les rsultats :
(0,a,c)
(4,a,c)
(0,a,d)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a,b,c,d,e)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a,b,c,d,e)
Quelques conseils :
1.
2.
3.
3.12.3 Exercice 3
On dsire ajouter la classe stringtovector le constructeur suivant :
public stringtovector(String S, String separateur, String sChampsVoulus,boolean
boolean strict){
//
//
//
//
//
//
//
La liste des champs dsirs est donc dans une chane (String) au lieu d'un tableau d'entiers (int[]). L'attribut priv erreur de la classe
peut se voir attribuer une nouvelle valeur :
2 : la chanes des index des champs dsirs est incorrecte
Voici un programme exemple :
Classes d'usage courant
94
import java.io.*;
//import
//import stringtovector;
public class essai1{
public static void main(String arg[]){
String champs=null
null;
null
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a: b :c
System.out.println(new
new stringtovector("a b c d
}
}
:d:e
:d:e
:d:e
:d:e
:d:e
:d:e
:d:e
:d:e
:d:e
:d:e
:d:e
",":","1 3",true
true).identite());
true
",":","1 3 7",true
true).identite());
true
",":","1 4 7",false
false).identite());
false
","","1 3",false
false).identite());
false
",null
null,"1
3",false
false).identite());
null
false
","!","1",true
true).identite());
true
",":","",true
true).identite());
true
",":",champs,true
true).identite());
true
",null
null,champs,true
true).identite());
null
true
","","",true
true).identite());
true
",":","1 !",true
true).identite());
true
e "," ","1 3",false
false).identite());
false
Quelques rsultats :
(0,a,c)
(4,a,c)
(0,a,d)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(0,a,b,c,d,e)
(0,a,b,c,d,e)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(2)
(0,a,c)
Quelques conseils :
1.
2.
Il faut se ramener au cas du constructeur prcdent en transfrant les champs de la chane sChampsVoulus dans un tableau
d'entiers. Pour cela, dcouper sChampsVoulus en champs avec un objet StringTokenizer dont l'attribut countTokens donnera le
nombre de champs obtenus. On peut alors crer un tableau d'entiers de la bonne dimension et le remplir avec les champs
obtenus.
Pour savoir si un champ est entier, utiliser la mthode Integer.parseInt pour transformer le champ en entier et grer
l'exception qui sera gnre lorsque cette conversion sera impossible.
3.12.4 Exercice 4
On dsire crer une classe filetovector permettant de transfrer le contenu d'un fichier texte dans un objet Vector. Cette classe serait
drive de la classe Vector :
class filetovector extends Vector
L'attribut erreur est positionn par le constructeur prcdent avec les valeurs suivantes :
0 : la construction s'est bien passe
Classes d'usage courant
95
qui affiche la valeur de l'objet sous la forme (erreur, lment 1 lment 2 ,(l1,l2,)) o lments i sont les lments du vecteur
construit partir du fichier et li les numros des lignes errones.
Voici un exemple de test :
import java.io.*;
//import filetovector;
public class test2{
public static void main(String arg[]){
int[]
T1={1,3};
int
System.out.println(new
new filetovector("data.txt",":",T1,false
false,"#").identite());
false
System.out.println(new
new filetovector("data.txt",":",T1,true
true,"#").identite());
true
System.out.println(new
new filetovector("data.txt","",T1,false
false,"#").identite());
false
System.out.println(new
new filetovector("data.txt",null
null,T1,false
false,"#").identite());
null
false
int[]
T2=null
null;
int
null
System.out.println(new
new filetovector("data.txt",":",T2,false
false,"#").identite());
false
System.out.println(new
new filetovector("data.txt",":",T2,false
false,"").identite());
false
int[]
T3=new
new int[0];
int
int
System.out.println(new
new filetovector("data.txt",":",T3,false
false,null
false null).identite());
null
}
}
Quelques conseils
1.
Le fichier texte est trait ligne par ligne. La ligne est dcoupe en champs grce la classe stringtovector tudie
prcdemment.
Les lments du vecteur consitu partir du fichier texte sont donc des objets de type stringtovector.
La mthode identite de filetovector pourra sappuyer sur la mthode stringtovector.identite() pour afficher ses
lments ainsi que sur la mthode Vector.toString() pour afficher les numros des ventuelles lignes errones.
2.
3.
3.12.5 Exercice 5
On dsire ajouter la classe filetovector le constructeur suivant :
public filetovector(String nomFichier, String separateur, String sChampsVoulus,
boolean strict, String tagCommentaire){
//
//
//
//
//
//
//
//
//
//
//
96
La liste des index des champs dsirs est maintenant dans une chane de caractres (String) au lieu dtre dans un tableau dentiers.
Lattribut priv erreur peut avoir une valeur supplmentaire :
2 : la chanes des index des champs dsirs est incorrecte
Voici un exemple de test :
import java.io.*;
//import
//import filetovector;
public class test1{
public static void main(String arg[]){
System.out.println(new
new filetovector("data.txt",":","1 3",false
false,"#").identite());
false
System.out.println(new
new filetovector("data.txt",":","1 3",true
true,"#").identite());
true
System.out.println(new
new filetovector("data.txt","","1 3",false
false,"#").identite());
false
System.out.println(new
new filetovector("data.txt",null
null,"
1 3",false
false,"#").identite());
null
false
String S2=null
null;
null
System.out.println(new
new filetovector("data.txt",":",S2,false
false,"#").identite());
false
System.out.println(new
new filetovector("data.txt",":",S2,false
false,"").identite());
false
String S3="";
System.out.println(new
new filetovector("data.txt",":",S3,false
false,null
false null).identite());
null
}
}
Les rsultats :
[0,(0,a,c) (0,1,3) (0,azerty,cvf) (0,s)][4,(0,a,c) (0,1,3) (0,azerty,cvf),[5]]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# commentaire) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# commentaire) (0,azerty,1,cvf,fff,qqqq) (0,s)]
Quelques conseils
1.
On transformera la chane sChampsVoulus en tableau dentiers tChampVoulus pour se ramener au cas du constructeur prcdent.
97
4. Interfaces graphiques
Nous nous proposons ici de montrer comment construire des interfaces graphiques avec JAVA. Nous voyons tout d'abord quelles
sont les classes de base qui nous permettent de construire une interface graphique. Nous n'utilisons dans un premier temps aucun
outil de gnration automatique. Puis nous utiliserons JBuilder, un outil de dveloppement de Borland/Inprise facilitant le
dveloppement d'applications Java et notamment la construction des interfaces graphiques.
La classe de base JFrame dfinit une fentre de base avec des boutons de fermeture, agrandissement/rduction, une taille ajustable,
etc ... et gre les vnements sur ces objets graphiques. Ici nous spcialisons la classe de base en lui fixant un titre et ses largeur (300
pixels) et hauteur (100 pixels). Ceci est fait dans son constructeur :
// le constructeur
public form1() {
// titre de la fentre
this.setTitle("Mon premier formulaire");
// dimensions de la fentre
this.setSize(new Dimension(300,100));
}//constructeur
Le titre de la fentre est fixe par la mthode setTitle et ses dimensions par la mthode setSize. Cette mthode accepte pour
paramtre un objet Dimension(largeur,hauteur) o largeur et hauteur sont les largeur et hauteur de la fentre exprimes en pixels.
La mthode main lance l'application graphique de la faon suivante :
Interfaces graphiques
98
new form1().setVisible(true);
Un formulaire de type form1 est alors cr (new form1()) et affich (setVisible(true)), puis l'application se met l'coute des vnements
qui se produisent sur le formulaire (clics, dplacements de souris, ...) et fait excuter ceux que le formulaire gre. Ici, notre
formulaire ne gre pas d'autres vnements que ceux grs par la classe de base JFrame (clics sur boutons fermeture,
agrandissement/rduction, changement de taille de la fentre, dplacement de la fentre, ...).
Lorsqu'on teste ce programme en le lanant partir d'une fentre Dos par :
java form1
pour excuter le fichier form1.class, on constate que lorsqu'on ferme la fentre qui a t affiche, on ne "rcupre pas la main" dans la
fentre Dos, comme si le programme n'tait pas termin. C'est effectivement le cas. L'excution du programme se fait de la faon
suivante :
- au dpart, un premier thread d'excution est lanc pour excuter la mthode main
- lorsque celle-ci cre le formulaire et l'affiche, un second thread est cr pour grer spcifiquement les vnements lis au
formulaire
- aprs cette cration et dans notre exemple, le thread de la mthode main se termine et seul reste alors le thread d'excution
de l'interface graphique.
- lorsqu'on ferme la fentre, celle-ci disparat mais n'interrompt pas le thread dans lequel elle s'excutait
- on est oblig pour l'instant d'arrter ce thread en faisant Ctrl-C dans la fentre Dos d'o a t lance l'excution du
programme.
Vrifions l'existence de deux threads spars, l'un dans lequel s'excute la mthode main, l'autre dans lequel s'excute la fentre
graphique :
// classes importes
import javax.swing.*;
import java.awt.*;
import java.io.*;
// la classe formulaire
public class form1 extends JFrame {
// le constructeur
public form1() {
// titre de la fentre
this.setTitle("Mon premier formulaire");
// dimensions de la fentre
this.setSize(new Dimension(300,100));
}//constructeur
// fonction de test
public static void main(String[] args) {
// suivi
System.out.println("Dbut du thread main");
// on affiche le formulaire
new form1().setVisible(true);
// suivi
System.out.println("Fin du thread main");
}//main
}//classe
On peut constater que le thread main est termin alors que la fentre est, elle, encore affiche. Le fait de fermer la fentre ne termine
pas le thread dans laquelle elle s'excutait. Pour arrter ce thread, on fait de nouveau Ctrl-C dans la fentre Dos.
Pour terminer cet exemple, on notera les paquetages imports :
- javax.swing pour la classe JFrame
- java.awt pour la classe Dimension
Interfaces graphiques
99
Method Summary
void windowActivated(WindowEvent e)
La fentre devient la fentre active
void windowClosed(WindowEvent e)
La fentre a t ferme
void windowClosing(WindowEvent e)
L'utilisateur ou le programme a demand la fermeture de la fentre
void windowDeactivated(WindowEvent e)
La fentre n'est plus la fentre active
void windowDeiconified(WindowEvent e)
La fentre passe de l'tat rduit l'tat normal
void windowIconified(WindowEvent e)
La fentre passe de l'tat normal l'tat rduit
void windowOpened(WindowEvent e)
La fentre devient visible pour la premire fois
Il y a donc sept vnements qui peuvent tre grs. Les gestionnaires reoivent tous en paramtre un objet de type WindowEvent que
nous ignorons pour l'instant. L'vnement qui nous intresse ici est la fermeture de la fentre, vnement qui devra tre trait par la
mthode windowClosing. Pour grer cet vnement, on pourra crer un objet Windowlistener l'aide d'une classe anonyme de la faon
suivante :
// cration d'un gestionnaire d'vnements
WindowListener win=new WindowListener(){
public void windowActivated(WindowEvent e){}
public void windowClosed(WindowEvent e){}
public void windowClosing(WindowEvent e){System.exit(0);}
public void windowDeactivated(WindowEvent e){}
public void windowDeiconified(WindowEvent e){}
public void windowIconified(WindowEvent e){}
public void windowOpened(WindowEvent e){}
};//dfinition win
Notre gestionnaire d'vnements implmentant l'interface WindowListener doit dfinir les sept mthodes de cette interface. Comme
nous ne voulons grer que la fermeture de la fentre, nous ne dfinissons du code que pour la mthode windowClosing. Lorsque les
autres vnements se produiront, nous en serons avertis mais nous ne ferons rien. Que ferons-nous lorsqu'on sera averti que la
fentre est en cours de fermeture (windowClosing) ? Nous arrterons l'application :
public void windowClosing(WindowEvent e){System.exit(0);}
Nous avons l un objet capable de grer les vnements d'une fentre en gnral. Comment l'associe-t-on une fentre particulire
? La classe JFrame a une mthode addWindowListener(WindowListener win) qui permet d'associer un gestionnaire d'vnements
"fentre" une fentre donne. Ainsi ici, dans le constructeur de la fentre nous crirons :
// cration d'un gestionnaire d'vnements
WindowListener win=new WindowListener(){
public void windowActivated(WindowEvent e){}
public void windowClosed(WindowEvent e){}
public void windowClosing(WindowEvent e){System.exit(0);}
public void windowDeactivated(WindowEvent e){}
public void windowDeiconified(WindowEvent e){}
public void windowIconified(WindowEvent e){}
public void windowOpened(WindowEvent e){}
Interfaces graphiques
100
};//dfinition win
// ce gestionnaire d'vnements va grer les vts de la fentre courante
this.addWindowListener(win);
Le paquetage java.awt.event contient l'interface WindowListener. Lorsqu'on excute ce programme et qu'on ferme la fentre qui s'est
afiche on constate dans la fentre Dos o a t lanc le programme, la fin d'excution du programme ce qu'on n'avait pas
auparavant.
Dans notre programme, la cration de l'objet charg de grer les vnements de la fentre est un peu lourde dans la mesure o on
est oblig de dfinir des mthodes mme pour des vnements qu'on ne veut pas grer. Dans ce cas, au lieu d'utiliser l'interface
WindowListener on peut utiliser la classe WindowAdapter. Celle-ci implmente l'interface WindowListener, avec sept mthodes vides.
En drivant la classe WindowAdapter et en redfinissant les seules mthodes qui nous intressent, nous arrivons au mme rsultat
qu'avec l'interface WindowListener mais sans avoir besoin de dfinir les mthodes qui ne nous intressent pas. La squence
- dfinition du gestionnaire d'vnements
- association du gestionnaire la fentre
peut se faire de la faon suivante dans notre exemple :
// cration d'un gestionnaire d'vnements
WindowAdapter win=new WindowAdapter(){
public void windowClosing(WindowEvent e){System.exit(0);}
};//dfinition win
// ce gestionnaire d'vnements va grer les vts de la fentre courante
this.addWindowListener(win);
Nous utilisons ici une classe anonyme qui drive la classe WindowAdapter et redfinit sa mthode windowClosing. Le programme
devient alors :
// classes importes
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;
// la classe formulaire
public class form2 extends JFrame {
// le constructeur
public form2() {
// titre de la fentre
this.setTitle("Mon premier formulaire");
Interfaces graphiques
101
// dimensions de la fentre
this.setSize(new Dimension(300,100));
// cration d'un gestionnaire d'vnements
WindowAdapter win=new WindowAdapter(){
public void windowClosing(WindowEvent e){System.exit(0);}
};//dfinition win
// ce gestionnaire d'vnements va grer les vts de la fentre courante
this.addWindowListener(win); }//constructeur
// fonction de test
public static void main(String[] args) {
// on affiche le formulaire
new form2().setVisible(true);
}//main
}//classe
Il donne les mmes rsultats que le prcdent programme mais est plus simple d'criture.
Une fentre JFrame a un conteneur dans lequel on peut dposer des composants graphiques (bouton, cases cocher, listes
droulantes, ...). Ce conteneur est disponible via la mthode getContentPane de la classe JFrame :
Container conteneur=null;
..........
// on rcupre le conteneur de la fentre
conteneur=this.getContentPane();
Tout composant est plac dans le conteneur par la mthode add de la classe Container . Ainsi pour dposer le composant C dans
l'objet conteneur prcdent, on crira :
conteneur.add(C)
Interfaces graphiques
102
O ce composant est-il plac dans le conteneur ? Il existe divers gestionnaires de disposition de composants portant le nom
XXXLayout, avec XXX gal par exemple Border, Flow, ... Chaque gestionnaire de disposition a ses particularits. Par exemple, le
gestionnaire FlowLayout dispose les composants en ligne en commenant par le haut du formulaire. Lorsqu'une ligne a t remplie,
les composants sont placs sur la ligne suivante. Pour associer un gestionnaire de mise en forme une fentre JFrame, on utilise la
mthode setLayout de la classe JFrame sous la forme :
setLayout(objet XXXLayout);
Ainsi dans notre exemple, pour associer un gestionnaire de type FlowLayout la fentre, on a crit :
// on choisit un gestionnaire de mise en forme des composants dans ce conteneur
conteneur.setLayout(new FlowLayout());
Dans ce cas, on devra donner les coordonnes prcises du composant dans le conteneur sous la forme (x,y,largeur,hauteur) o (x,y)
sont les coordonnes du coin suprieur gauche du composant dans le conteneur. C'est cette mthode que nous utiliserons le plus
souvent par la suite.
On sait maintenant comment sont dposs les composants au conteneur (add) et o (setLayout). Il nous reste connatre les
composants qu'on peut placer dans un conteneur. Ici nous plaons un bouton modlis par la classe javax.swing.JButton :
JButton btnTest=null;
..........
// on cre un bouton
btnTest=new JButton();
// on fixe son libell
btnTest.setText("Test");
// on ajoute le bouton au conteneur
conteneur.add(btnTest);
Si on redimensionne le formulaire ci-dessus, le gestionnaire de disposition du conteneur est automatiquement appel pour replacer
les composants :
C'est le principal intrt des gestionnaires de disposition : celui de maintenir une disposition cohrente des composants au fil des
modifications de la taille du conteneur. Utilisons le gestionnaire de disposition null pour voir la diffrence. Le bouton est
maintenant plac dans le conteneur par les instructions suivantes :
// on choisit un gestionnaire de mise en forme des composants dans ce conteneur
conteneur.setLayout(null);
// on cre un bouton
btnTest=new JButton();
// on fixe son libell
btnTest.setText("Test");
// on fixe son emplacement et ses dimensions
btnTest.setBounds(10,20,100,20);
// on ajoute le bouton au conteneur
conteneur.add(btnTest);
Interfaces graphiques
103
Ici, on place explicitement le bouton au point (10,20) du formulaire et on fixe ses dimensions 100 pixels pour la largeur et 20
pixels pour la hauteur. La nouvelle fentre devient la suivante :
Si on clique sur le bouton Test, il ne se passe rien. En effet, nous n'avons pas encore associ de gestionnaire d'vnements au
bouton. Pour connatre le type de gestionnaires d'vnements disponibles pour un composant donn, on peut chercher dans la
dfinition de sa classe des mthodes addXXXListener qui permettent d'associer au composant un gestionnaire d'vnements. La
classe javax.swing.JButton drive de la classe javax.swing.AbstractButton dans laquelle on trouve les mthodes suivantes :
Method Summary
void addActionListener(ActionListener l)
void addChangeListener(ChangeListener l)
void addItemListener(ItemListener l)
Ici, il faut lire la documentation pour savoir quel gestionnaire d'vnements gre le clic sur le bouton. C'est l'interface ActionListener.
Celle-ci ne dfinit qu'une mthode :
Method Summary
void actionPerformed(ActionEvent e)
La mthode reoit un paramtre ActionEvent que nous ignorerons pour le moment. Pour grer le clic sur le bouton btntest de notre
programme on lui associe d'abord un gestionnaire d'vnements :
btnTest.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt){
btnTest_clic(evt);
}
}//classe anonyme
);//gestionnaire d'evt
Ici, la mthode actionPerformed renvoie la mthode btnTest_clic que nous dfinissons de la faon suivante :
public void btnTest_clic(ActionEvent evt){
// suivi console
System.out.println("clic sur bouton");
}//btnTest_click
A chaque fois que l'utilisateur clique sur le bouton Test, on crit un message sur la console. C'est ce que montre l'excution suivante
:
Interfaces graphiques
104
105
Les tableaux suivants donnent une liste de quelques gestionnaires d'vnements et les vnements auxquels ils sont lis.
Gestionnaire
ActionListener
Composant(s)
JButton , JCheckbox,
JButtonRadio, JMenuItem
JTextField
Mthode d'enregistrement
public void addActionListener(ActionListener)
ItemListener
InputMethodListener
JComboBox, JList
JTextField, JTextArea
CaretListener
JTextField, JTextArea
AdjustmentListener
MouseMotionListener
WindowListener
MouseListener
JScrollBar
FocusListener
KeyListener
Composant
JButton
JCheckbox
JCheckboxMenuItem
JComboBox
Container
JComponent
JFrame
JList
JMenuItem
JPanel
JScrollPane
JScrollBar
JTextComponent
JTextArea
JTextField
JFrame
Evnement
clic sur le bouton, la case cocher,
le bouton radio, l'lment de menu
l'utilisateur a tap [Entre] dans la
zone de saisie
L'lment slectionn a chang
le texte de la zone de saisie a chang
ou le curseur de saisie a chang de
position
Le curseur de saisie a chang de
position
la valeur du variateur a chang
la souris a boug
vnement fentre
vnements
souris
(clic,
entre/sortie du domaine d'un
composant, bouton press, relche)
vnement focus (obtenu, perdu)
vnement clavier( touche tape,
presse, relache)
Tous les composants, sauf ceux du type TextXXX, tant drivs de la classe JComponent, possdent galement les mthodes
associes cette classe.
Interfaces graphiques
Mthodes
public void actionPerformed(ActionEvent)
public void adjustmentValueChanged(AdjustmentEvent)
public void componentHidden(ComponentEvent)
public void componentMoved(ComponentEvent)
public void componentResized(ComponentEvent)
public void componentShown(ComponentEvent)
public void componentAdded(ContainerEvent)
public void componentRemoved(ContainerEvent)
106
FocusListener
ItemListener
KeyListener
MouseListener
MouseMotionListener
TextListener
InputmethodListener
CaretLisetner
WindowListener
et crire :
public class gestionnaireSouris implements MouseListener{
// on crit les 5 mthodes de l'interface MouseListener
// mouseClicked, ..., mouseReleased)
}// fin classe
Comme on ne souhaite grer que les clics de souris, il est cependant prfrable d'crire :
public class gestionnaireSouris extends MouseAdapter{
// on crit une seule mthode, celle qui gre les clics de souris
public void mouseClicked(MouseEvent evt){
}
}// fin classe
Le tableau suivant donne les classes adapteurs des diffrents gestionnaires d'vnements :
Gestionnaire d'vnements
ComponentListener
ContainerListener
FocusListener
KeyListener
MouseListener
MouseMotionListener
WindowListener
Adaptateur
ComponentAdapter
ContainerAdapter
FocusAdapter
KeyAdapter
MouseAdapter
MouseMotionAdapter
WindowAdapter
4.1.7 Conclusion
Nous venons de prsenter les concepts de base de la cration d'interfaces graphiques en Java :
Interfaces graphiques
107
Maintenant, plutt que de construire des interfaces graphiques " la main" comme il vient d'tre fait nous allons utiliser Jbuilder, un
outil de dveloppement Java de Borland/Inprise dans sa version 4 et suprieure.Nous utiliserons les composants de la bibliothque
java.swing actuellement prconise par Sun, crateur de Java.
Lancez Jbuilder et prendre l'option Fichier/nouveau projet. La 1re page d'un assistant s'affiche alors :
2.
dbut
provoquera la cration d'un fichier projet dbut.jpr sous le dossier indiqu dans le champ "Nom du
rpertoire du projet"
indiquez le dossier sous lequel sera cr le dossier du projet que vous allez crer.
indiquez le nom du dossier o seront placs tous les fichiers du projet. Ce dossier sera cr sous le
rpertoire indiqu dans le champ "Chemin racine"
Les fichiers source (.java, .html, ...), les fichiers destinations (.class,..), les fichiers de sauvegarde pourraient aller dans des
rpertoires diffrents. En laissant leurs champs vides, ils seront placs dans le mme rpertoire que le projet.
Faites [suivant]
3.
Interfaces graphiques
108
Faites [suivant]
4.
Faites [terminer]
5.
Vrifier que l'option Voir/projet est coche. Vous devriez voir la structure de votre projet dans la fentre de gauche.
6.
Maintenant construisons une interface graphique dans ce projet. Choisissez l'option Fichier/Nouveau/Application :
Dans le champ classe, on met le nom de la classe qui va tre cre. Ici on a repris le mme nom que le projet.
Faites [suivant]
Interfaces graphiques
109
7.
Classe
Titre
interfaceDbut
C'est le nom de la classe correspondant la fentre qui va tre construite
C'est le texte qui apparatra dans la barre de titre de la fentre
Notez que ci-dessus, nous avons demand que la fentre soit centre sur l'cran au dmarrage de l'application.
Faites [Terminer]
8.
pour avoir accs au code gnr pour les deux classes, il suffit de double-cliquer sur le fichier .java correspondant.
Nous reviendrons ultrieurement sur le code gnr.
Vrifiez que l'option Voir/Structure est coche. Elle permet de voir la structure de la classe actuellement slectionne
(double clic sur le .java). Voici par exemple la structure de la classe dbut :
On apprend ici :
Interfaces graphiques
110
De plus, elle se ferme proprement lorsqu'on clique sur le bouton de fermeture de la fentre. Nous venons de construire notre
premire interface graphique. Nous pouvons sauvegarder notre projet par l'option Fichier/Fermer les projets :
En appuyant sur Tous, tous les projets prsents dans la fentre ci-dessus seront cochs. OK les fermera. Si nous avons la curiosit
d'aller, avec l'explorateur windows, dans le dossier de notre projet (celui qui a t indiqu l'assistant au dbut de la configuration
du projet), nous y trouvons les fichiers suivants :
Dans notre exemple, nous avions demand ce que tous les fichiers (source .java, destination .class, sauvegarde .jpr~) soient dans
le mme dossier que le projet .jpr.
Interfaces graphiques
111
4.2.2 Les fichiers gnrs par Jbuilder pour une interface graphique
Ayons maintenant la curiosit de regarder ce que Jbuilder a gnr comme fichiers source .java. Il est important de savoir lire ce qui
est gnr puisque la plupart du temps nous sommes amens ajouter du code ce qui a t gnr. Commenons par rcuprer
notre projet : Fichier/Ouvrir un projet et slectionnons le projet dbut.jpr. Nous retrouvons le projet construit prcdemment.
2);
frame.setVisible(true);
}
/**Mthode principale*/
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch(Exception e) {
e.printStackTrace();
}
new dbut();
}
}
la fonction main fixe l'aspect de la fentre (setLookAndFeel) et cre une instance de la classe dbut.
le constructeur dbut() est alors excut. Il cre une instance frame de la classe de la fentre (new interfaceDbut()). Celle-ci est
construite mais pas affiche.
La fentre est alors dimensionne selon les informations disponibles pour chacun des composants de la fentre
(frame.validate). Elle commence alors son existence spare en s'affichant et en rpondant aux sollicitations de l'utilisateur.
La fentre est centre sur l'cran ceci parce qu'on l'avait demand lors de la configuration de la fentre avec l'assistant.
Voyons ce qui serait obtenu si on rduisait le code dbut.java son strict minimum telle que nous l'avons fait en dbut de chapitre.
Crons une nouvelle classe. Prenez l'option Fichier/Nouvelle classe :
Interfaces graphiques
112
Nommez la nouvelle classe dbut2 et faites [Terminer]. Un nouveau fichier apparat dans le projet :
La fonction main cre une instance de la fentre interfaceDbut et l'affiche (show). Avant d'excuter notre projet, il nous faut signaler
que la classe contenant la fonction main excuter est maintenant la classe dbut2. Cliquez droit sur le projet dbut.jpr et choisissez
l'option proprits puis l'onglet Excution :
Interfaces graphiques
113
Ici, il est indiqu que la classe principale est dbut (1). Appuyez sur le bouton (2) pour choisir une autre classe principale :
Faites [OK] pour valider ce choix puis demandez l'excution du projet par Excuter/Excuter projet ou F9. Vous obtenez la mme
fentre qu'avec la classe dbut si ce n'est qu'elle n'est pas centre puisqu'ici cela n'a pas t demand. Par la suite, nous ne
Interfaces graphiques
114
prsenterons plus la classe principale gnre par Jbuilder car elle fait toujours la mme chose : crer une fentre. C'est sur la classe
de cette dernire que dsormais nous nous concentrerons.
Il s'agit des bibliothques java.awt, java.awt.event et javax.swing. Les deux premires taient les seules disponibles pour construire des
interfaces graphiques avec les premires versions de Java. La bibliothque javax.swing est plus rcente. Ici, elle est ncessaire pour la
fentre de type JFrame qui est utilise ici.
Les attributs
JPanel contentPane;
BorderLayout borderLayout1 = new BorderLayout();
JPanel est un type conteneur dans lequel on peut mettre des composants. BorderLayout est l'un des types de gestionnaire de mise en
forme disponibles pour placer les composants dans le conteneur. Dans tous nos exemples, nous n'utiliserons pas de gestionnaire de
mise en forme et placerons nous-mmes les composants un endroit prcis du conteneur. Pour cela, nous utiliserons le
gestionnaire de mise en forme null.
Le constructeur de la fentre
/**Construire le cadre*/
public interfaceDbut() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
/**Initialiser le composant*/
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
Interfaces graphiques
115
contentPane.setLayout(borderLayout1);
this.setSize(new Dimension(400, 300));
this.setTitle("Ma premire interface graphique avec Jbuilder");
}
1.
2.
3.
4.
5.
Le constructeur commence par dire qu'il va grer les vnements sur la fentre (enableEvents), puis il lance la mthode jbInit.
Le conteneur (JPanel) de la fentre (JFrame) est obtenu (getContentPane)
Le gestionnaire de mise en forme est fix (setLayout)
La taille de la fentre est fixe (setSize)
Le titre de la fentre est fix (setTitle)
Le gestionnaire d'vnements
/**Remplac, ainsi nous pouvons sortir quand la fentre est ferme*/
protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID() == WindowEvent.WINDOW_CLOSING) {
System.exit(0);
}
Le constructeur avait indiqu que la classe traiterait les vnements de la fentre. C'est la mthode processWindowEvent qui fait ce
travail. Elle commence par transmettre sa classe parent (JFrame) l'vnement WindowEvent reu puis si celui-ci est l'vnement
WINDOW_CLOSING provoqu par le clic sur le bouton de fermeture de la fentre, l'application est arrte.
Conclusion
Le code de la classe de la fentre est diffrent de celui prsent dans l'exemple de dbut de chapitre. Si nous utilisions un autre outil
de gnration Java que Jbuilder nous aurions sans doute un code encore diffrent. Dans la pratique, nous accepterons le code
produit par Jbuilder pour construire la fentre afin de nous concentrer uniquement sur l'criture des gestionnaires d'vnements de
l'interface graphique.
nom
type
rle
lblSaisie
JLabel
un libell
txtSaisie
JTextField une zone de saisie
cmdAfficher JButton
pour afficher dans une bote de dialogue le contenu de la zone de saisie txtSaisie
En procdant comme pour le projet prcdent, construisez le projet interface2.jpr sans mettre pour l'instant de composants dans la
fentre.
Interfaces graphiques
116
Dans la fentre ci-dessus, slectionnez la classe interface2.java de la fentre. A droite de cette fentre, se trouve un classeur onglets :
L'onglet Source donne accs au code source de la classe interface2.java. L'onglet Conception permet de construire visuellement la fentre.
Slectionnez cet onglet. Vous avez devant vous le conteneur de la fentre, qui va recevoir les composants que vous allez y dposer.
Il est pour l'instant vide. Dans la fentre de gauche est montre la structure de la classe :
this
contentPane
reprsente la fentre
son conteneur dans lequel on va dposer des composants ainsi que le mode de mise en forme de ces
composants dans le conteneur (BorderLayout par dfaut)
une instance du gestionnaire de mise en forme
borderLayout1
L'objet this tant toujours slectionn, on peut redimensionner le conteneur affich l'cran en tirant sur les points d'ancrage situs
autour du conteneur :
Interfaces graphiques
117
Nous sommes maintenant prts dposer des composants dans le conteneur ci-dessus. Auparavant, nous allons changer le
gestionnaire de mise en forme. Slectionnez l'objet contentPane dans la fentre de structure :
Puis dans la fentre de proprits de cet objet, slectionnez la proprit layout et choisissez parmi les valeurs possibles, la valeur null :
Cette absence de gestionnaire de mise en forme va nous permettre de placer librement les composants dans le conteneur. Il est
temps maintenant de choisir ceux-ci.
Lorsque le volet Conception est slectionn, les composants sont disponibles dans un classeur onglets en haut de la fentre de
conception :
1
Interfaces graphiques
118
Pour construire l'interface graphique, nous disposons de composants swing (1) et de composants awt (2). Nous allons utiliser ici les
composants swing. Dans la barre de composants ci-dessus, choisissez un composant JLabel (3), un composant JTextField (4) et un
composant JButton (5) et placez-les dans le conteneur de la fentre de conception.
et la structure suivante :
Nous pouvons excuter (F9) notre projet pour avoir un premier aperu de la fentre en action :
Fermez la fentre. Il nous reste crire la procdure lie un clic sur le bouton Afficher. Slectionnez le bouton pour avoir accs sa
fentre de proprits. Celle-ci a deux onglets : proprits et vnements. Choisissez vnements.
Interfaces graphiques
119
La colonne de gauche de la fentre liste les vnements possibles sur le bouton. Un clic sur un bouton correspond l'vnement
actionPerformed. La colonne de droite contient le nom de la procdure appele lorsque l'vnement correspondant se produit. Cliquez
sur la cellule droite de l'vnement actionPerformed :
Jbuilder gnre un nom par dfaut pour chaque gestionnaire d'vnement de la forme nomComposant_nomEvnement ici
cmdAfficher_actionPerformed. On pourrait effacer le nom propos par dfaut et en inscrire un autre. Pour avoir accs au code du
gestionnaire cmdAfficher_actionPerformed il suffit de double-cliquer sur son nom ci-dessus. On passe alors automatiquement au volet
source de la classe positionn sur le squelette du code du gestionnaire d'vnement :
void cmdAfficher_actionPerformed(ActionEvent e) {
}
Il ne nous reste plus qu' complter ce code. Ici, nous voulons prsenter une bote de dialogue avec dedans le contenu du champ
txtSaisie :
void cmdAfficher_actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(this, "texte saisi="+txtSaisie.getText(),
"Vrification de la saisie",JOptionPane.INFORMATION_MESSAGE);
}
JOptionPane est une classe de la bibliothque javax.swing. Elle permet d'afficher des messages accompagns d'une icne ou de
demander des informations l'utilisateur. Ici, nous utilisons une mthode statique de la classe :
parentComponent
message
title
messageType
Interfaces graphiques
java.awt.*;
java.awt.event.*;
javax.swing.*;
java.beans.*;
javax.swing.event.*;
Interfaces graphiques
121
if (e.getID() == WindowEvent.WINDOW_CLOSING) {
System.exit(0);
}
}
void cmdAfficher_actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(this, "texte saisi="+txtSaisie.getText(), "Vrification de la
saisie",JOptionPane.INFORMATION_MESSAGE);
}
}
Les attributs
JPanel contentPane;
JLabel lblSaisie = new JLabel();
JTextField txtSaisie = new JTextField();
JButton cmdAfficher = new JButton();
Le constructeur interface2 est semblable au constructeur de la prcdente interface graphique tudie. C'est dans la mthode jbInit
qu'on trouve des diffrences : le code de construction de la fentre dpend des composants qu'on y a placs. On peut reprendre le
code de jbInit en y mettant nos propres commentaires :
private void jbInit() throws Exception {
// la fentre elle-mme (taille, titre)
this.setSize(new Dimension(304, 129));
this.setTitle("Saisies & boutons - 1");
// le conteneur des composants
contentPane = (JPanel) this.getContentPane();
// pas de gestionnaire de mise en forme pour ce conteneur
contentPane.setLayout(null);
// l'tiquette lblSaisie (libell, position, taille)
lblSaisie.setText("Saisie");
lblSaisie.setBounds(new Rectangle(25, 23, 71, 21));
// le champ de saisie (position, taille)
txtSaisie.setBounds(new Rectangle(120, 21, 138, 24));
// le bouton Afficher (libell, position, taille)
cmdAfficher.setText("Afficher");
cmdAfficher.setBounds(new Rectangle(111, 77, 77, 20));
// le gestionnaire d'vt du bouton
cmdAfficher.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
cmdAfficher_actionPerformed(e);
}
});
// ajout des 3 composants au conteneur
contentPane.add(lblSaisie, null);
contentPane.add(txtSaisie, null);
contentPane.add(cmdAfficher, null);
Interfaces graphiques
122
}//jbInit
Une nouvelle instance de l'interface ActionListener est cre avec sa mthode actionPerformed dfinie dans la foule. Celle-ci se
contente d'appeler une mthode de la classe interface2. Tout ceci n'est qu'un artifice pour dfinir dans la mme classe que la fentre
les procdures de traitement des vnements des composants de cette fentre. On pourrait procder autrement :
cmdAfficher.addActionListener(this)
qui fait que la mthode actionPerformed sera cherche dans this c'est dire dans la classe de la fentre. Cette seconde mthode semble
plus simple mais la premire a sur elle un avantage : elle permet d'avoir des gestionnaires diffrents pour des boutons diffrents
alors que la seconde mthode ne le permet pas. Dans de dernier cas, en effet, l'unique mthode actionPeformed doit traiter les clics de
diffrents boutons et donc commencer par identifier lequel est l'origine de l'vnement avant de commencer travailler.
Les gestionnaires d'vnements
On retrouve ceux dj tudis :
/**Remplac, ainsi nous pouvons sortir quand la fentre est ferme*/
protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID() == WindowEvent.WINDOW_CLOSING) {
System.exit(0);
}
}
// clic sur bouton Afficher
void cmdAfficher_actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(this, "texte saisi="+txtSaisie.getText(), "Vrification de la
saisie",JOptionPane.INFORMATION_MESSAGE);
}
4.2.3.3 Conclusion
Des deux projets tudis, nous pouvons conclure qu'une fois l'interface graphique construite avec Jbuilder, le travail du dveloppeur
consiste crire les gestionnaires des vnements qu'il veut grer pour cette interface graphique.
Interfaces graphiques
123
Interfaces graphiques
124
Sommaire : Tutoriels
Si nous slectionnons l'option Tutoriels dans le sommaire ci-dessus, la fentre de droite nous prsente une liste de tutoriels
disponibles :
Les tutoriels de base sont particulirement intressants pour commencer prendre en main Jbuilder. Il en existe beaucoup d'autres
que ceux prsents ci-dessus et lorsqu'on veut dvelopper une application il peut tre utile de vrifier auparavant s'il n'y a pas un
tutoriel qui pourrait nous aider.
Interfaces graphiques
125
Sommaire : le JDK
En slectionnant l'option Java 2 JDK 1.3, on a accs toutes les bibliothques du JDK. Gnralement ce n'est pas l qu'il faut
chercher si on a besoin d'informations sur une classe prcise et qu'on ne sait pas dans quelle bibliothque elle se trouve. Par contre,
cette option prsente de l'intrt si on est intress par avoir une vue globale des bibliothques de Java.
Il vous reste double-cliquer sur l'entre qui vous intresse, ici JTextField class. L'aide sur cette classe s'affiche alors dans la fentre
de droite :
Interfaces graphiques
126
1
2
n
1
2
3
4
type
JTextField
JTextField
JButton
JButton
nom
txtSaisie
txtControle
cmdEffacer
cmdQuitter
rle
champ de saisie
affiche le texte de 1 en temps rel
pour effacer les champs 1 et 2
pour quitter l'application
Interfaces graphiques
127
...
...
CmdEffacer.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
CmdEffacer_actionPerformed(e);
}
});
CmdQuitter.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
CmdQuitter_actionPerformed(e);
}
});
....
}
/**Remplac, ainsi nous pouvons sortir quand la fentre est ferme*/
protected void processWindowEvent(WindowEvent e) {
...
}
void txtSaisie_caretUpdate(CaretEvent e) {
// le curseur de saisie a boug
txtControle.setText(txtSaisie.getText());
}
void CmdQuitter_actionPerformed(ActionEvent e) {
// on quitte l'application
System.exit(0);
}
void CmdEffacer_actionPerformed(ActionEvent e) {
// on efface le contenu du champ de saisie
txtSaisie.setText("");
}
}
Interfaces graphiques
128
Un composant JComboBox est une liste droulante double d'une zone de saisie : l'utilisateur peut soit choisir un lment dans (2)
soit taper du texte dans (1). Par dfaut, les JComboBox ne sont pas ditables. Il faut appeler explicitement la mthode setEditable(true)
pour qu'ils le deviennent. Pour dcouvrir la classe JComboBox, tapez JComboBox dans l'index de l'aide.
L'objet JComboBox peut tre construit de diffrentes faons :
new JComboBox()
new JComboBox (Object[] items)
new JComboBox(Vector items)
On peut s'tonner qu'un combo puisse contenir des objets alors qu'habituellement il contient des chanes de caractres. Au niveau
visuel, ce sera le cas. Si un JComboBox contient un objet obj, il affiche la chane obj.toString(). On se rappelle que tout objet a une
mthode toString hrite de la classe Object et qui rend une chane de caractres "reprsentative" de l'objet.
Les mthodes utiles de la classe JCombobox sont les suivantes :
void addItem(Object unObjet)
int getItemCount()
Object getItemAt(int i)
void insertItemAt(Object unObjet, int i)
int getSelectedIndex()
Object getSelectedItem()
void setSelectedIndex(int i)
void setSelectedItem(Object unObjet)
void removeAllItems()
void removeItemAt(int i)
void removeItem(Object unObjet)
void setEditable(boolean val)
Lors du choix d'un lment dans la liste droulante se produit l'vnement actionPerformed qui peut tre alors utilis pour tre averti
du changement de slection dans le combo. Dans l'application suivante, nous utilisons cet vnement pour afficher l'lment qui a
t slectionn dans la liste.
Interfaces graphiques
129
jComboBox1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
jComboBox1_actionPerformed(e);
}
});
....
void jComboBox1_actionPerformed(ActionEvent e) {
// un nouvel lment a t slectionn - on l'affiche
JOptionPane.showMessageDialog(this,jComboBox1.getSelectedItem(),
"actionPerformed",JOptionPane.INFORMATION_MESSAGE);
}
}
Interfaces graphiques
130
Dans le code source, la dfinition d'une liste peut se faire de la faon suivante :
// le vecteur des valeurs de la liste
DefaultlistModel valeurs=new DefaultListModel();
// la liste elle-mme laquelle on associe le vecteur de ses valeurs
JList jList1 = new JList(valeurs);
// le conteneur dfilant dans lequel on place la liste pour avoir une liste dfilante
JScrollPane jScrollPane1 = new JScrollPane(jList1);
Pour inclure la liste jList1 dans le conteneur jScrollPane1, le code gnr par JBuilder procde diffremment :
dclaration du conteneur dans les attributs de la fentre
Pour ajouter des valeurs la liste JList1 ci-dessus, il suffit de les ajouter son vecteur de valeurs valeurs :
// init liste
for(int i=0;i<10;i++)
valeurs.addElement(""+i);
Interfaces graphiques
131
....
// la liste jList1 est associ au conteneur jcrollPane1
jScrollPane1.getViewport().add(jList1, null);
}
/**Remplac, ainsi nous pouvons sortir quand la fentre est ferme*/
protected void processWindowEvent(WindowEvent e) {
....
}
}
Dcouvrons maintenant les principales mthodes de la classe JList en cherchant JList dans l'index de l'aide. L'objet JList peut tre
construit de diffrentes faons :
Une mthode simple est celle que nous avons utilise : crer un DefaultListModel V vide puis l'associer la liste crer par new
JList(V). Le contenu de la liste n'est pas gr par l'objet JList mais par l'objet contenant les valeurs de la liste. Si le contenu a t
construit l'aide d'un objet DefaultListModel qui repose sur la classe Vector, ce sont les mthodes de la classe Vector qui pourront tre
utilises pour ajouter, insrer et supprimer des lments de la liste. Une liste peut tre slection simple ou multiple. Ceci est fix
par la mthode setSelectionMode :
Le ou les lments slectionns peuvent tre obtenus via les mthodes suivantes :
Interfaces graphiques
132
Nous savons associer un vecteur de valeurs une liste avec le constructeur JList(DefaultListModel). Inversement nous pouvons
obtenir l'objet DefaultListModel d'une liste JList par :
4
3
2
5
6
type
JTextField
JList
JList
JButton
JButton
JButton
JButton
nom
txtSaisie
jList1
jList2
cmd1To2
cmd2To1
cmdRaz1
cmdRaz2
rle
champ de saisie
liste contenue dans un conteneur jScrollPane1
liste contenue dans un conteneur jScrollPane2
transfre les lments slectionns de liste 1 vers liste 2
fait l'inverse
vide la liste 1
vide la liste 2
L'utilisateur tape du texte dans le champ (1) qu'il valide. Se produit alors l'vnement actionPerformed sur le champ de saisie qui est
utilis pour ajouter le texte saisi dans la liste 1. Voici le code utile pour cette premire fonction :
public class interfaceAppli extends JFrame {
JPanel contentPane;
JLabel jLabel1 = new JLabel();
JLabel jLabel2 = new JLabel();
JLabel jLabel3 = new JLabel();
JTextField txtSaisie = new JTextField();
JButton cmd1To2 = new JButton();
JButton cmd2To1 = new JButton();
DefaultListModel v1=new DefaultListModel();
DefaultListModel v2=new DefaultListModel();
JList jList1 = new JList(v1);
JList jList2 = new JList(v2);
JScrollPane jScrollPane1 = new JScrollPane();
Interfaces graphiques
133
Le code de transfert des lments slectionns d'une liste vers l'autre est le suivant :
void cmd1To2_actionPerformed(ActionEvent e) {
// transfert des lments slectionns dans la liste 1 vers la liste 2
transfert(jList1,jList2);
}//cmd1To2
void cmd2To1_actionPerformed(ActionEvent e) {
// transfert des lments slectionns dans jList2 vers jList1
transfert(jList2,jList1);
}//cmd2TO1
private void transfert(JList L1, JList L2){
// transfert des lments slectionns dans la liste 1 vers la liste 2
// on rcupre le tableau des indices des lments slectionns dans L1
int[] indices=L1.getSelectedIndices();
// qq chose faire ?
if (indices.length==0) return;
// on rcupre les valeurs de L1
DefaultListModel v1=(DefaultListModel)L1.getModel();
// et celles de L2
DefaultListModel v2=(DefaultListModel)L2.getModel();
for(int i=indices.length-1;i>=0;i--){
// on ajoute L2 les valeurs slectionnes dans L1
v2.addElement(v1.elementAt(indices[i]));
// les lments de L1 copis dans L2 doivent tre supprims de L1
v1.removeElementAt(indices[i]);
}//for
}//transfert
Interfaces graphiques
134
1
2
type
JButtonRadio
JCheckBox
3
4
JList
ButtonGroup
nom
jButtonRadio1
jButtonRadio2
jButtonRadio3
jCheckBox1
jCheckBox2
jCheckBox3
jList1
buttonGroup1
rle
3 boutons radio faisant partie du groupe buttonGroup1
3 cases cocher
une liste dans un conteneur jScrollPane1
composant non visible - sert regrouper les 3 boutons radio afin que lorsque l'un d'eux
s'allume, les autres s'teignent.
Interfaces graphiques
135
On voit ci-dessus dans la branche Autre les attributs non visuels de la fentre. Une fois un groupe de boutons radio cr, on peut lui
associer chacun des boutons radio. Pour ce faire, on slectionne les proprits du bouton radio :
et dans la proprit buttonGroup du bouton radio, on met le nom du groupe dans lequel on veut mettre le bouton radio, ici
buttonGroup1. On rpte cette opration pour les 3 boutons radio.
La principale mthode des boutons radio et cases cocher est la mthode isSelected() qui indique si la case ou le bouton est coch. Le
texte associ au composant peut tre connu avec getText() et fix avec setText(String unTexte). La case/bouton radio peut tre coch
avec la mthode setSelected(boolean value).
Lors d'un clic sur un bouton radio ou case cocher, c'est l'vnement actionPerformed qui est dclench. Dans le code qui suit, nous
utilisons cet vnement pour suivre les changements de valeurs des boutons radio et cases cocher :
public class interfaceAppli extends JFrame {
JPanel contentPane;
JRadioButton jRadioButton1 = new JRadioButton();
JRadioButton jRadioButton2 = new JRadioButton();
JRadioButton jRadioButton3 = new JRadioButton();
JCheckBox jCheckBox1 = new JCheckBox();
JCheckBox jCheckBox2 = new JCheckBox();
JCheckBox jCheckBox3 = new JCheckBox();
ButtonGroup buttonGroup1 = new ButtonGroup();
JScrollPane jScrollPane1 = new JScrollPane();
DefaultListModel valeurs=new DefaultListModel();
JList jList1 = new JList(valeurs);
/**Construire le cadre*/
public interfaceAppli() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
/**Initialiser le composant*/
private void jbInit() throws Exception {
jRadioButton1.setSelected(true);
jRadioButton1.setText("un");
jRadioButton1.setBounds(new Rectangle(57, 31, 49, 23));
jRadioButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
afficheRadioButtons(e);
}
});
Interfaces graphiques
136
Interfaces graphiques
137
1
2
n
1
2
3
4
type
JScrollBar
JScrollBar
JTextField
JTextField
nom
jScrollBar1
jScrollBar2
txtvaleurHS
txtvaleurVS
rle
un variateur horizontal
un variateur vertical
affiche la valeur du variateur horizontal 1 - permet aussi de fixer cette valeur
affiche la valeur du variateur vertical 2 - permet aussi de fixer cette valeur
Un variateur JScrollBar permet l'utilisateur de choisir une valeur dans une plage de valeurs entires symbolise par la
"bande" du variateur sur laquelle se dplace un curseur.
Pour un variateur horizontal, l'extrmit gauche reprsente la valeur minimale de la plage, l'extrmit droite la valeur
maximale, le curseur la valeur actuelle choisie. Pour un variateur vertical, le minimum est reprsent par l'extrmit haute,
le maximum par l'extrmit basse. Le couple (min,max) vaut par dfaut (0,100).
Un clic sur les extrmits du variateur fait varier la valeur d'un incrment (positif ou ngatif) selon l'extrmit clique
appele unitIncrement qui est par dfaut 1.
Un clic de part et d'autre du curseur fait varier la valeur d'un incrment (positif ou ngatif) selon l'extrmit clique
appele blockIncrement qui est par dfaut 10.
Ces cinq valeurs (min, max, valeur, unitIncrement, blockIncrement) peuvent tre connues avec les mthodes getMinimum(),
getMaximum(), getValue(), getUnitIncrement(), getBlockIncrement() qui toutes rendent un entier et peuvent tre fixes par les
mthodes setMinimum(int min), setMaximum(int max), setValue(int val), setUnitIncrement(int uInc), setBlockIncrement(int bInc)
Il y a quelques points connatre dans l'utilisation des composants JScrollBar. Tout d'abord, on le trouve dans la barre des
composants swing :
Interfaces graphiques
138
Lorsqu'on le dpose sur le conteneur, il est par dfaut vertical. On le rend horizontal avec la proprit orientation ci-dessous:
Sur la feuille de proprits ci-dessus on voit qu'on a accs aux proprits minimum, maximum, value, unitIncrement, blockIncrement du
JScrollbar. On peut donc les fixer la conception. Lorsqu'on place un scrollbar sur le conteneur sa "bande de variation" n'apparat
pas :
On peut rgler ce problme en donnant une bordure au composant. Cela se fait avec sa proprit border qui peut avoir diffrentes
valeurs :
Interfaces graphiques
139
Lorsqu'on clique sur l'extrmit suprieure d'un variateur vertical, sa valeur diminue. Cela peut surprendre l'utilisateur moyen qui
s'attend normalement voir la valeur "monter". On rgle ce problme en donnant une valeur ngative unitIncrement et
blockIncrement.
Comment suivre les volutions d'un variateur ? Lorsque la valeur de celui-ci change, l'vnement adjustmentValueChanged se produit.
Il suffit d'associer une procdure cet vnement pour tre inform de chaque variation de la valeur du scrollbar.
Interfaces graphiques
140
}
});
txtValeurHS.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
txtValeurHS_actionPerformed(e);
}
});
jScrollBar2.setBounds(new Rectangle(39, 51, 30, 27));
jScrollBar2.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
jScrollBar2_adjustmentValueChanged(e);
}
});
jScrollBar2.setAutoscrolls(true);
jScrollBar2.setUnitIncrement(-1);
jScrollBar2.setBorder(BorderFactory.createRaisedBevelBorder());
txtValeurVS.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
txtValeurVS_actionPerformed(e);
}
});
......
}
/**Remplac, ainsi nous pouvons sortir quand la fentre est ferme*/
protected void processWindowEvent(WindowEvent e) {
...
}
void jScrollBar1_adjustmentValueChanged(AdjustmentEvent e) {
// la valeur du scrollbar 1 a chang
txtValeurHS.setText(""+jScrollBar1.getValue());
}
void jScrollBar2_adjustmentValueChanged(AdjustmentEvent e) {
// la valeur du scrollbar 2 a chang
txtValeurVS.setText(""+jScrollBar2.getValue());
}
void txtValeurHS_actionPerformed(ActionEvent e) {
// on fixe la valeur du scrollbar horizontal
setValeur(jScrollBar1,txtValeurHS);
}
void txtValeurVS_actionPerformed(ActionEvent e) {
// on fixe la valeur du scrollbar vertical
setValeur(jScrollBar2,txtValeurVS);
}
private void setValeur(JScrollBar jS, JTextField jT){
// fixe la valeur du scrollbar jS avec le texte du champ jT
int valeur=0;
try{
valeur=Integer.parseInt(jT.getText());
jS.setValue(valeur);
}
catch (Exception e){
// on affiche l'erreur
afficher(""+e);
}//try-catch
}//setValeur
void afficher(String message){
// affiche un message dans une bote
JOptionPane.showMessageDialog(this,message,"Menus",JOptionPane.INFORMATION_MESSAGE);
}//afficher
}
Interfaces graphiques
141
3
4
type
JTextArea
JButton
JButton
JTextField
JScrollPane
1
2
3
4
5
nom
txtTexte
cmdAfficher
cmdEffacer
txtAjout
jScrollPane1
rle
une zone de texte multilignes
affiche le contenu de 1 dans une bote de dialogue
efface le contenu de 1
texte ajout au texte de 1 lorsqu'il est valid par la touche Entre.
conteneur dfilant dans lequel on a plac la zone de texte 1 afin d'avoir une zone de texte
dfilante.
Interfaces graphiques
142
mouseClicked
mouseDragged
mouseEntered
mouseExited
mouseMoved
mousePressed
mouseReleased
clic de souris
la souris se dplace, bouton gauche appuy
la souris vient d'entrer dans la surface du conteneur
la souris vient de quitter la surface du conteneur
la souris bouge
Pression sur le bouton gauche de la souris
Relchement du bouton gauche de la souris
Voici un programme permettant de mieux apprhender quels moments se produisent les diffrents vnements souris :
Interfaces graphiques
143
type
JTextField
JList
JButton
1
2
3
nom
rle
txtPosition pour afficher la position de la souris dans le conteneur (vt MouseMoved)
lstAffichage pour afficher les vts souris autres que MouseMoved
cmdEffacer pour effacer le contenu de 2
Les vnements sont empils par le haut dans la liste. Aussi la copie d'cran ci-dessus indique qu'un clic provoque trois vnements,
dans l'ordre :
1. MousePressed lorsqu'on appuie sur le bouton
2. MouseReleased lorsqu'on le lche
3. MouseClicked qui indique que la succession des deux vnements prcdents est considre comme un clic. Ce pourrait tre
un double clic. Mais ci-dessus, l'information clickCount=1 indiqe que c'est un simple clic.
Maintenant si on appuie sur le bouton, dplace la souris et relche le bouton :
Dans les deux exemples ci-dessus, on voit qu'un vnement souris ramne avec lui diverses informations dont les coordonnes (x,y)
de la souris, par exemple (408,65) dans la premire ligne ci-dessus.
Interfaces graphiques
144
Si on continue ainsi, on dcouvre que l'vnement MouseExited est dclench ds que la souris quitte le conteneur ou passe sur l'un
des composants de celui-ci. Dans ce dernier cas, le conteneur reoit l'vnement MouseExited et le composant l'vnement
MouseEntered. L'inverse se produira lorsque la souris quittera le composant pour revenir sur le conteneur.
Que se passe-t-il lors d'un double clic ?
On a exactement les mmes vnements que pour un clic simple. Seulement l'vnement rapporte avec lui l'information
clickCount=2 (cf ci-dessus) indiquant qu'il y a eu en fait un double clic.
Le code utile de cette application est le suivant :
public class Cadre1 extends JFrame {
JPanel contentPane;
JLabel jLabel1 = new JLabel();
JTextField txtPosition = new JTextField();
JScrollPane jScrollPane1 = new JScrollPane();
DefaultListModel valeurs=new DefaultListModel();
JList lstAffichage = new JList(valeurs);
JButton cmdEffacer = new JButton();
/**Construire le cadre*/
public Cadre1() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
/**Initialiser le composant*/
private void jbInit() throws Exception {
contentPane.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
public void mouseMoved(MouseEvent e) {
contentPane_mouseMoved(e);
}
public void mouseDragged(MouseEvent e) {
contentPane_mouseDragged(e);
}
});
contentPane.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(MouseEvent e) {
contentPane_mouseEntered(e);
}
public void mouseExited(MouseEvent e) {
contentPane_mouseExited(e);
}
public void mousePressed(MouseEvent e) {
contentPane_mousePressed(e);
}
public void mouseClicked(MouseEvent e) {
contentPane_mouseClicked(e);
}
public void mouseReleased(MouseEvent e) {
contentPane_mouseReleased(e);
}
});
cmdEffacer.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
cmdEffacer_actionPerformed(e);
}
});
Interfaces graphiques
145
jScrollPane1.getViewport().add(lstAffichage, null);
...............
}
/**Remplac, ainsi nous pouvons sortir quand la fentre est ferme*/
protected void processWindowEvent(WindowEvent e) {
.........
}
void contentPane_mouseMoved(MouseEvent e) {
txtPosition.setText("("+e.getX()+","+e.getY()+")");
}
void contentPane_mouseDragged(MouseEvent e) {
afficher(e);
}
void contentPane_mouseEntered(MouseEvent e) {
afficher(e);
}
void afficher(MouseEvent e){
// affiche l'vt e dans la liste lstAffichage
valeurs.insertElementAt(e,0);
}
void contentPane_mouseExited(MouseEvent e) {
afficher(e);
}
void contentPane_mousePressed(MouseEvent e) {
afficher(e);
}
void contentPane_mouseClicked(MouseEvent e) {
afficher(e);
}
void contentPane_mouseReleased(MouseEvent e) {
afficher(e);
}
void cmdEffacer_actionPerformed(ActionEvent e) {
// efface la liste
valeurs.removeAllElements();
}
}
Crez un nouveau projet avec au dpart une fentre vide. Choisissez dans la liste des composants "Conteneurs Swing" le
composant JMenuBar (cf 1 ci-dessous) et dposez-le sur la fentre en cours de conception.
Interfaces graphiques
146
1
Rien n'apparat dans la fentre de conception mais le composant JMenuBar apparat dans la panneau de structure de votre fentre :
Double-cliquez sur l'lment jMenuBar1 ci-dessus pour avoir accs au menu en mode conception :
1
Une barre d'outils est disponible pour construire l'ensemble des options du menu :
1
2
3
4
5
6
7
Pour crer votre premier lment de menu, tapez "Options A" dans la case A ci-dessus, puis dessous dans l'ordre : A1, A2,
sparateur, A3, A4.
Puis ct :
Interfaces graphiques
147
Si nous excutons notre application maintenant, nous verrons une fentre vide sans menu. Il nous faut associer le menu cr
notre fentre. Pour ce faire, dans la structure de la fentre, slectionnez l'objet this :
L'une de celles-ci est JMenuBar qui sert fixer le menu qui sera associ la fentre. Cliquez dans la cellule droite de JMenuBar.
Tous les menus crs vous seront alors proposs. Ici, nous n'aurons que jMenuBar1. Slectionnez-le.
Lancez l'excution de l'application (F9) :
Interfaces graphiques
148
Maintenant, on a un menu mais les options ne font, pour l'instant, rien. Les options de menu sont traites comme des composants :
elles ont des proprits et des vnements. Dans la structure du menu, slectionnez l'option jMenuItem1 :
Slectionnez la page des vnements et cliquez sur la cellule droite de l'vnement actionPerformed : c'est l'vnement qui se produit
lorsqu'on clique sur un lment de menu. Une procdure de traitement vous est propose par dfaut. Double-cliquez dessus pour
avoir accs son code :
Interfaces graphiques
149
JOptionPane.showMessageDialog(this,message,"Menus",JOptionPane.INFORMATION_MESSAGE);
}//afficher
Interfaces graphiques
150
Lorsqu'on ferme cette fentre, elle disparat mais le thread d'excution dans laquelle elle s'excutait lui n'est pas arrt. Ce
phnomne ne se produit normalement pas. Les botes de dialogue sont utilises au sein d'une application qui un moment ou un
autre utilise une instruction System.exit(n) pour arrter tous les threads. On s'en souviendra dans les exemples venir tous btis sur le
mme modle. Sous Dos, l'application peut tre interrompue par Ctrl-C. Avec JBuilder, on utilisera l'option Excuter/Rinitialiser le
programme (Ctrl-F2). Par ailleurs, le premier argument de showMessageDialog est ici null. En gnral ce n'est pas le cas. C'est plutt this
o this dsigne la fentre principale de l'application.
La mthode setLookAndFeel de la classe UIManager (UI=User Interface) permet de fixer l'apparence des interfaces graphiques. La
classe UIManager dispose d'une mthode permettant de connatre les "apparences" possibles pour les interfaces :
static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels()
La mthode rend un tableau d'lments de type LookAndFeelInfo. Cette classe a une mthode :
String getClassName()
Interfaces graphiques
151
qui donne le nom de la classe "implmentant" une apparence donne. Essayons le programme suivant :
import javax.swing.*;
public class LookAndFeels {
// affiche les look and feels disponibles
public static void main(String[] args) {
// iste des look and feels installs
UIManager.LookAndFeelInfo[] lf=UIManager.getInstalledLookAndFeels();
// affichage
for(int i=0;i<lf.length;i++){
System.out.println(lf[i].getClassName());
}//for
}//main
}//classe
Il semble donc y avoir trois "apparences "diffrentes". Reprenons notre programme d'affichage de messages en essayant les
diffrentes apparences possibles :
import javax.swing.*;
public class LookAndFeel2 {
// affiche les look and feels disponibles
public static void main(String[] args) {
// liste des look and feels installs
UIManager.LookAndFeelInfo[] lf=UIManager.getInstalledLookAndFeels();
// affichage
for(int i=0;i<lf.length;i++){
// gestionnaire d'apparence
try{
UIManager.setLookAndFeel(lf[i].getClassName());
}catch(Exception ex){
System.err.println(ex.getMessage());
}//catch
// message
JOptionPane.showMessageDialog(null,"Un
message",lf[i].getClassName(),JOptionPane.INFORMATION_MESSAGE);
}//for
}//main
}//classe
title
optionType
le titre de la bote
JOptionPane.YES_NO_OPTION : boutons oui, non
JOptionPane.YES_NO_CANCEL_OPTION : boutons oui, non, annuler
Voici un exemple :
import javax.swing.*;
public class confirm1 {
public static void main(String[] args) {
// affiche des botes de confirmation
int rponse;
affiche(JOptionPane.showConfirmDialog(null,"Voulez-vous continuer
?","Confirmation",JOptionPane.YES_NO_OPTION));
affiche(JOptionPane.showConfirmDialog(null,"Voulez-vous continuer
?","Confirmation",JOptionPane.YES_NO_CANCEL_OPTION));
}//main
private static void affiche(int rponse){
// indique quel type de rponse on a eu
switch(rponse){
case JOptionPane.YES_OPTION :
System.out.println("Oui");
break;
case JOptionPane.NO_OPTION :
System.out.println("Non");
break;
case JOptionPane.CANCEL_OPTION :
System.out.println("Annuler");
break;
case JOptionPane.CLOSED_OPTION :
System.out.println("Fermeture");
break;
}//switch
}//affiche
}//classe
Interfaces graphiques
153
L'affichage console :
Chane saisie [dupont]
0
1
3
4
type
JScrollPane
JTextArea lui-mme dans
le JScrollPane
JButton
JButton
JButton
Interfaces graphiques
nom
jScrollPane1
txtTexte
rle
Conteneur dfilant pour la bote de texte 1
texte tap par l'utilisateur ou charg partir d'un fichier
154
Un contrle non visuel est utilis : jFileChooser1. Celui-ci est pris dans la palette des conteneurs swing de JBuilder :
Nous dposons le composant dans la fentre de conception mais en-dehors du formulaire. Il apparat dans la liste des composants :
Nous donnons maintenant le code utile du programme pour en avoir une vue d'ensemble :
import
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.filechooser.FileFilter;
java.io.*;
Interfaces graphiques
155
jFileChooser1.setAcceptAllFileFilterUsed(true);
// on fixe le rpertoire de dpart de la bote FileChooser au rpertoire courant
jFileChooser1.setCurrentDirectory(new File("."));
}
//Initialiser le composant
private void jbInit() throws Exception {
.......................
}
//Remplac, ainsi nous pouvons sortir quand la fentre est ferme
protected void processWindowEvent(WindowEvent e) {
......................
}
}
void btnCharger_actionPerformed(ActionEvent e) {
// choix d'un fichier l'aide d'un objet JFileChooser
// on fixe le filtre initial
jFileChooser1.setFileFilter(filtreTxt);
// on affiche la bote de slection
int returnVal = jFileChooser1.showOpenDialog(this);
// l'utilisateur a-t-il choisi qq chose ?
if(returnVal == JFileChooser.APPROVE_OPTION) {
// on met le fichier dans le champ texte
lireFichier(jFileChooser1.getSelectedFile());
}//if
}//btnCharger_actionPerformed
// lireFichier
private void lireFichier(File fichier){
// affiche le contenu du fichier texte fichier dans le champ texte
// on efface le champ texte
txtTexte.setText("");
// qqs donnes
BufferedReader IN=null;
String ligne=null;
try{
// ouverture fichier en lecture
IN=new BufferedReader(new FileReader(fichier));
// on lit le fichier ligne par ligne
while((ligne=IN.readLine())!=null){
txtTexte.append(ligne+"\n");
}//while
// fermeture fichier
IN.close();
}catch(Exception ex){
// une erreur s'est produite
txtTexte.setText(""+ex);
}//catch
}
// effacer
void btnEffacer_actionPerformed(ActionEvent e) {
// on efface la bote de texte
txtTexte.setText("");
}
// sauvegarder
void btnSauvegarder_actionPerformed(ActionEvent e) {
// sauvegarde le contenu de la bote de texte dans un fichier
// on fixe le filtre initial
jFileChooser1.setFileFilter(filtreTxt);
// on affiche la bote de slection de sauvegarde
int returnVal = jFileChooser1.showSaveDialog(this);
// l'utilisateur a-t-il choisi qq chose ?
if(returnVal == JFileChooser.APPROVE_OPTION) {
// on crit le contenu de la bote de texte dans fichier
crireFichier(jFileChooser1.getSelectedFile());
}//if
}
// lireFichier
private void crireFichier(File fichier){
// crit le contenu de la bote de texte dans fichier
// qqs donnes
PrintWriter PRN=null;
try{
Interfaces graphiques
156
Nous ne commenterons pas le code des mthodes btnEffacer_Click, lireFichier et crireFichier qui n'amnent rien qui n'a dj t vu.
Nous nous attarderons sur la classe JFileChooser et son utilisation. Cette classe est complexe, du genre "usine gaz". Nous n'utilisons
ici que les mthodes suivantes :
addChoosableFilter(FileFilter)
setAcceptAllFileFilterUsed(boolean)
File getSelectedFile()
int showSaveDialog()
setCurrentDirectory
setFileFilter(FileFilter)
1
2
3
4
liste droulante construite avec la mthode addChoosableFilter. Elle contient ce qu'on appelle des filtres de slection
reprsents par la classe FileFilter. C'est au dveloppeur de dfinir ces filtres et de les ajouter la liste 1.
dossier courant, fix par la mthode setCurrentDirectory si cette mthode a t utilise, sinon le rpertoire courant sera le
dossier "Mes Documents" sous windows ou le home Directory sous Unix.
nom du fichier choisi ou tap directement par l'utilisateur. Sera disponible avec la mthode getSelectedFile()
boutons Enregistrer/Annuler. Si le bouton Enregistrer est utilis, la mthode showSaveDialog rend le rsultat
jFileChooser.APPROVE_OPTION
Comment les filtres de fichiers de la liste droulante 1 sont-ils construits ? Les filtres sont ajouts la liste 1 l'aide de la mthode :
Interfaces graphiques
157
addChoosableFilter(FileFilter)
de la classe JFileChooser. Il nous reste connatre la classe FileFilter. C'est en fait la classe javax.swing.filechooser.FileFilter qui est une
classe abstraite, c.a.d. une classe qui ne peut tre instantie mais seulement drive. Elle est dfinie comme suit :
FileFilter()
boolean accept(File)
String getDescription()
constructeur
indique si le fichier f appartient au filtre ou non
chane de description du filtre
Prenons un exemple. Nous voulons que la liste droulante 1 offre un filtre pour slectionner les fichiers *.txt avec la description
"Fichiers texte (*.txt)".
il nous faut crer une classe drive de la classe FileFilter
utiliser la mthode boolean accept(File f) pour rendre la valeur true si le nom du fichier f se termine par .txt
utiliser la mthode String getDescription() pour rendre la description "Fichiers texte (*.txt)"
Ce filtre pourrait tre dfini dans notre application de la faon suivante :
javax.swing.filechooser.FileFilter filtreTxt = new javax.swing.filechooser.FileFilter(){
public boolean accept(File f){
// accepte-t-on f ?
return f.getName().toLowerCase().endsWith(".txt");
}//accept
public String getDescription(){
// description du filtre
return "Fichiers Texte (*.txt)";
}//getDescription
};
Ce filtre serait ajout la liste des filtres de l'objet jFileChooser1 par l'instruction :
// on ajoute le filtre *.txt
jFileChooser1.addChoosableFileFilter(filtretxt);
Tout ceci et quelques autres initialisations sont faites dans la mthode moreInit excute la construction de la fentre (cf
programme complet ci-dessus). Le code du bouton Sauvegarder est le suivant :
void btnSauvegarder_actionPerformed(ActionEvent e) {
// sauvegarde le contenu de la bote de texte dans un fichier
on fixe le filtre actif au filtre *.txt afin de permettre l'utilisateur de chercher de prfrence ce type de fichiers. Le filtre
"Tous les fichiers" est galement prsent. Il a t ajout dans la procdure moreInit. On a donc deux filtres.
la bote de slection de sauvegarde est affiche. Ici on perd la main, l'utilisateur utilisant la bote de slection pour dsigner
un fichier du systme de fichiers.
lorsqu'il quitte la bote de slection, on teste la valeur de retour pour voir si on doit ou non sauvegarder la bote de texte. Si
oui, elle doit l'tre dans le fichier obtenu par la mthode getSelectedFile.
Interfaces graphiques
158
Il y a deux diffrences :
pour afficher la bote de slection de fichiers, on utilise la mthode showOpenDialog au lieu de la mthode showSaveDialog. La
bote de slection affiche est analogue celle affiche par la mthode showSaveDialog.
si l'utilisateur a bien slectionn un fichier, on appelle la mthode lireFichier plutt que la mthode crireFichier.
N
6
7
type
JButton
JButton
nom
btnCouleur
btnPolice
rle
pour fixer la couleur des caractres du TextBox
pour fixer la police de caractres du TextBox
Le composant JColorChooser permettant d'afficher une bote de slection de couleur peut tre trouve dans la liste des composants
swing de JBuilder :
Nous dposons ce composant dans la fentre de conception, mais en-dehors du formulaire. Il est non visible dans le formulaire
mais nanmoins prsent dans la liste des composants de la fentre :
Interfaces graphiques
159
La classe JColorChooser est trs simple. On affiche la bote de slection des couleurs avec la mthode int showDialog :
static Color showDialog(Component component, String title, Color initialColor)
Component component
String title
Color intialColor
Si l'utilisateur choisit une couleur, la mthode rend une couleur sinon la valeur null. Le code li au bouton Couleur est le suivant :
void btnCouleur_actionPerformed(ActionEvent e) {
// choix d'une couleur de texte l'aide du composant JColorChooser
Color couleur;
if((couleur=jColorChooser1.showDialog(this,"Choix d'une couleur",Color.BLACK))!=null){
// on fixe la couleur des caractres de la bote de texte
txtTexte.setForeground(couleur);
}//if
}
La classe Color est dfinie dans java.awt.Color. Diverses constantes y sont dfinies dont Color.BLACK pour la couleur noire. La
bote de slection affiche est la suivante
Interfaces graphiques
160
Assez curieusement, la bibliothque swing n'offre pas de classe pour slectionner une police de caractres. Heureusement, il y a les
ressources Java d'internet. En effectuant une recherche sur le mot cl "JFontChooser" qui semble un nom possible pour une telle
classe on en trouve plusieurs. L'exemple qui va suivre va nous donner l'occasion de configurer JBuilder pour qu'il utilise des
paquetages non prvus dans sa configuration initiale.
Le paquetage rcupr s'appelle swingextras.jar et a t plac dans le dossier <jdk>\jre\lib\perso o <jdk> dsigne le rpertoire
d'installation du jdk utilis par JBuilder. Il aurait pu tre plac n'importe o ailleurs.
On y trouve le code source java des composants proposs dans le paquetage. On y trouve galement les classes elles-mmes :
Interfaces graphiques
161
De cette archive, on retiendra que la classe JFontChooser s'appelle en ralit com.lamatek.swingextras.JFontChooser. Si on ne souhaite pas
crire le nom complet, il nous faudra crire en dbut de programme :
import com.lamatek.swingextras.*;
Comment feront le compilateur et la machine virtuelle Java pour trouver ce nouveau paquetage ? Dans le cas d'une utilisation
directe du JDK cela a dj t expliqu et le lecteur pourra retrouver les explications dans le paragraphe sur les paquetages Java.
Nous dtaillons ici la mthode utiliser avec JBuilder. Les paquetages sont configurs dans le menu Options/Configurer les JDK :
6
4
3
1
2
3
4
Le bouton Modifier (1) permet d'indiquer JBuilder quel JDK utilis. Dans cet exemple, JBuilder avait amen avec lui le JDK
1.3.1. Lorsque le JDK 1.4 est sorti, il a t install sparment de JBuilder et on a utilis le bouton Modifier pour indiquer
JBuilder d'utiliser dsormais le JDK 1.4 en dsignant le rpertoire d'installation de ce dernier
rpertoire de base du JDK actuellement utilis par JBuilder
liste des archives java (.jar) utilises par JBuilder. On peut en ajouter d'autres avec le bouton Ajouter (4)
Le bouton Ajouter (4) permet d'ajouter de nouveaux paquetages que JBuilder utilisera la fois pour la compilation et l'excution
des programmes. On l'a utilis ici pour ajouter le paquetage SwingExtras.jar (5)
Interfaces graphiques
162
Maintenant JBuilder est correctement configur pour pouvoir utiliser la classe JFontChooser. Cependant, il nous faudrait avoir accs
la dfinition de cette classe pour l'utiliser correctement. L'archive swingextras.jar contient des fichiers html qu'on pourrait extraire
pour les exploiter. C'est inutile. La documentation java incluse dans les fichiers .jar est directement accessible de JBuilder. Il faut
pour cela configurer l'onglet Documentation (6) ci-dessus. On obtient la nouvelle fentre suivante :
Le bouton Ajouter nous permet d'indiquer que le fichier SwingExtras.jar doit tre explor pour la documentation. Cette dmarche
valide, on peut constater qu'on a effectivement accs la documentation de SwingExtras.jar. Cela se traduit par diverses facilits :
import com.lamatek.swingextras.*;
on fait F1 sur le mot cl JFontChooser, on obtient une aide sur cette classe :
1
On voit dans la barre d'tat 1 que c'est bien un fichier html du paquetage swingextras.jar qui est utilis. L'exemple prsent ci-dessus
est suffisament explicite pour qu'on puisse crire le code du bouton Police de notre application :
Interfaces graphiques
163
void btnPolice_actionPerformed(ActionEvent e) {
// choix d'une police de texte l'aide du composant JFontChooser
jFontChooser1 = new JFontChooser(new Font("Arial", Font.BOLD, 12));
if (jFontChooser1.showDialog(this, "Choix d'une police") == JFontChooser.ACCEPT_OPTION) {
// on change la police des caractres de la bote de texte
txtTexte.setFont(jFontChooser1.getSelectedFont());
}//if
}
2
3
4
5
type
JRadioButton
JRadioButton
JSpinner
Interfaces graphiques
nom
rdOui
rdNon
spinEnfants
rle
coch si mari
coch si pas mari
nombre d'enfants du contribuable
164
4
5
6
JTextField
JLabel
JTextField
txtSalaire
lblImpots
txtStatus
option
secondaire
nom
Initialiser
Calculer
mnuInitialiser
mnuCalculer
Effacer
Quitter
mnuEffacer
mnuQuitter
rle
charge les donnes ncessaires au calcul partir d'un fichier texte
calcule l'impt payer lorsque toutes les donnes ncessaires sont prsentes et
correctes
remet le formulaire dans son tat initial
termine l'application
Rgles de fonctionnement
le menu Calculer reste teint tant qu'il n'y a rien dans le champ du salaire
si lorsque le calcul est lanc, il s'avre que le salaire est incorrect, l'erreur est signale :
Le programme est donn ci-dessous. Il utilise la classe impots cre dans le chapitre sur les classes. Une partie du code produit
automatiquement pas JBuilder n'a pas t ici reproduit.
import
import
import
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.filechooser.FileFilter;
java.io.*;
java.util.*;
javax.swing.event.*;
Interfaces graphiques
165
................
}
//Remplac, ainsi nous pouvons sortir quand la fentre est ferme
protected void processWindowEvent(WindowEvent e) {
.....................
}
void mnuQuitter_actionPerformed(ActionEvent e) {
// on quitte l'application
System.exit(0);
}
void mnuInitialiser_actionPerformed(ActionEvent e) {
// on charge le fichier des donnes
// qu'on slectionne avec la bote de slection JFileChooser1
jFileChooser1.setFileFilter(filtreTxt);
if (jFileChooser1.showOpenDialog(this)!=JFileChooser.APPROVE_OPTION)
return;
// on exploite le fichier slectionn
Interfaces graphiques
166
try{
// lecture donnes
lireFichier(jFileChooser1.getSelectedFile());
// cration de l'objet impots
objImpots=new impots(limites,coeffr,coeffn);
// confirmation
txtStatus.setText("Donnes charges");
// le salaire peut tre modifi
txtSalaire.setEditable(true);
// plus de chgt possible
mnuInitialiser.setEnabled(false);
}catch(Exception ex){
// problme
txtStatus.setText("Erreur : " + ex.getMessage());
// fin
return;
}//catch
}
private void lireFichier(File fichier) throws Exception {
// les tableaux de donnes
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
String[] champs=null;
// ouverture fichier en lecture
BufferedReader IN=new BufferedReader(new FileReader(fichier));
// on lit le fichier ligne par ligne
// elles sont de la forme limite coeffr coeffn
String ligne=null;
int numLigne=0;
// n de ligne courannte
while((ligne=IN.readLine())!=null){
// une ligne de plus
numLigne++;
// on dcompose la ligne en champs
champs=ligne.split("\\s+");
// 3 champs ?
if(champs.length!=3)
throw new Exception("ligne " + numLigne + "errone dans fichier des donnes");
// on rcupre les trois champs
aLimites.add(champs[0]);
aCoeffR.add(champs[1]);
aCoeffN.add(champs[2]);
}//while
// fermeture fichier
IN.close();
// transfert des donnes dans des tableaux borns
int n=aLimites.size();
limites=new double[n];
coeffr=new double[n];
coeffn=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffr[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffn[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}
void mnuCalculer_actionPerformed(ActionEvent e) {
// calcul de l'impt
// vrification salaire
int salaire=0;
try{
salaire=Integer.parseInt(txtSalaire.getText().trim());
if(salaire<0) throw new Exception();
}catch (Exception ex){
// msg d'erreur
txtStatus.setText("Salaire incorrect. Recommencez");
// focus sur champ erron
txtSalaire.requestFocus();
// retour l'interface
return;
}
// nbre d'enfants
Integer InbEnfants=(Integer)spinEnfants.getValue();
// calcul de l'impt
lblImpots.setText(""+objImpots.calculer(rdOui.isSelected(),InbEnfants.intValue(),salaire));
// effacement msg d'tat
txtStatus.setText("");
}
void txtSalaire_caretUpdate(CaretEvent e) {
// le salaire a chang - on met jour le menu calculer
mnuCalculer.setEnabled(! txtSalaire.getText().trim().equals(""));
}
Interfaces graphiques
167
void mnuEffacer_actionPerformed(ActionEvent e) {
// raz du formulaire
rdNon.setSelected(true);
spinEnfants.getModel().setValue(new Integer(0));
txtSalaire.setText("");
mnuCalculer.setEnabled(false);
lblImpots.setText("");
}
}
Nous avons utilis ici un composant disponible qu' partir du JDK 1.4, le JSpinner. C'est un incrmenteur, qui permet ici
l'utilisateur de fixer le nombre d'enfants. Ce composant swing n'tait pas disponible dans la barre des composants swing de JBuilder
6 utilis pour le test. Cela n'empche pas son utilisation mme si les choses sont un peu plus compliques que pour les composants
disponibles dans la barre des composants. En effet, on ne peut dposer le composant JSpinner sur le formulaire lors de la
conception. Il faut le faire l'excution. Regardons le code qui le fait :
// spinner Enfants - entre 0 et 20 enfants
spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
spinEnfants.setBounds(new Rectangle(137,60,40,27));
contentPane.add(spinEnfants);
La premire ligne cre le composant JSpinner. Ce composant peut servir diverses choses et pas seulement un incrmenteur
d'entiers comme ici. L'argument du constructeur JSpinner est ici un modle d'incrmenteur de nombres acceptant quatre paramtres
(valeur, min, max, incrment). Le composant a la forme suivante :
valeur
min
max
incrment
La valeur du composant est obtenue via sa mthode getValue qui rend un type Object. D'o quelques transtypages pour avoir l'entier
dont on a besoin :
// nbre d'enfants
Integer InbEnfants=(Integer)spinEnfants.getValue();
// calcul de l'impt
lblImpots.setText(""+objImpots.calculer(rdOui.isSelected(),
InbEnfants.intValue(),salaire));
Avant toute chose, l'utilisateur doit utiliser l'option de menu Initialiser qui construit un objet impots avec le constructeur
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception
de la classe impots. On rappelle que celle-ci a t dfinie en exemple dans le chapitre des classes. On s'y reportera si besoin est. Les
trois tableaux ncessaires au constructeur sont remplis partir du contenu d'un fichier texte ayant la forme suivante :
12620.0
13190
15640
24740
31810
39970
48360
55790
92970
127860
151250
172040
0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
0.45
0.50
0.55
0
631
1290.5
2072.5
3309.5
4900
6898.5
9316.5
12106
16754.5
23147.5
30710
Interfaces graphiques
168
195000
0
0.60
0.65
39312
49062
Chaque ligne comprend trois nombres spars par au moins un espace. Ce fichier texte est slectionn par l'utilisateur l'aide d'un
composant JFileChooser. Une fois l'objet de type impots construit, il ne reste plus qu' laisser l'utilisateur donner les trois informations
dont on a besoin : mari ou non, nombre d'enfants, salaire annuel, et appeler la mthode calculer de la classe impots. L'opration peut
tre rpte plusieurs fois. L'objet de type impots n'est lui construit qu'une fois, lors de l'utilisation de l'option Initialiser.
4.6
Ecriture d'applets
4.6.1 Introduction
Lorsqu'on a crit une application avec interface graphique il est assez ais de la transformer en applet. Stocke sur une machine A,
celle-ci peut tre tlcharge par un navigateur Web d'une machine B de l'internet. L'application initiale est ainsi partage entre de
nombreux utilisateurs et c'est l le principal intrt de transformer une application en applet. Nammoins, toute application ne peut
tre ainsi transforme : pour ne pas nuire l'utilisateur qui utilise une applet dans son navigateur, l'environnement de l'applet est
rglement :
Ce sont des restrictions fortes. Elles impliquent par exemple qu'une application ayant besoin de lire des informations dans un
fichier ou une base de donnes devra les demander par une application relais situe sur le serveur d'o elle a t tlcharge.
La structure gnrale d'une application Web simple est la suivante :
Serveur Web
client
applet
donnes
le client demande un document HTML au serveur Web gnralement avec un navigateur. Ce document peut contenir une
applet qui fonctionnera comme une application graphique autonome au sein du document HTML affich par le
navigateur du client.
cette applet pourra avoir accs des donnes mais seulement celles situes sur le serveur web. Elle n'aura pas accs ni
aux ressources de la machine cliente qui l'excute ni celles d'autres machines du rseau autres que celle partir de
laquelle elle a t tlcharge.
Rle
dtruit l'instance d'Applet
rcupre le contexte d'excution de l'applet (document HTML dans lequel il
se trouve, autres applets du mme document, )
rend une chane de caractres donnant des informations sur l'applet
169
La classe JApplet a amen quelques amliorations la classe Applet, notamment la capacit contenir des composants JMenuBar
c.a.d. des menus, ce qui n'tait pas possible avec la classe Applet.
stop
Cette mthode est appele lors du chargement initial de l'applet. On y mettra donc les initialisations ncessaires
l'application.
Cette mthode est appele chaque fois que le document contenant l'applet devient le document courant du navigateur.
Ainsi lorsqu'un utilisateur charge une applet, les mthodes init et start vont tre excutes dans cet ordre. Lorsqu'il va
quitter le document pour en visualiser un autre, la mthode stop va tre excute. Lorsqu'il reviendra dessus plus tard, la
mthode start sera excute.
Cette mthode est appele chaque fois que l'utilisateur quitte le document contenant l'applet.
Pour beaucoup d'applets, seule la mthode init est ncessaire. Les mthodes start et stop ne sont ncessaires que si l'application
lance des tches (threads) qui tournent en parallle et en continu souvent l'insu de l'utilisateur. Lorsque celui-ci quitte le
document, la partie visible de l'application disparat, mais ces tches de fond continuent travailler. C'est souvent inutile. On profite
alors de l'appel du navigateur la mthode stop pour les arrter. Si l'utilisateur revient sur le document, on profite de l'appel du
navigateur la mthode start pour les relancer.
Prenons par exemple une applet qui a une horloge dans son interface graphique. Celle-ci est maintenue par une tche de fond
(thread). Lorsque dans le navigateur l'utilisateur quitte la page de l'applet, il est inutile de maintenir l'horloge qui est devenue
invisible : dans la mthode stop de l'applet, on arrtera le thread qui gre l'horloge. Dans la mthode start, on le relancera afin que
lorsque l'utilisateur revient sur la page de l'applet, il retrouve une horloge l'heure.
Interfaces graphiques
170
A titre d'exemple, nous revenons sur une application tudie : la gestion de listes.
4
3
2
5
6
type
JTextField
JList
JList
JButton
JButton
JButton
JButton
nom
txtSaisie
jList1
jList2
cmd1To2
cmd2To1
cmdRaz1
cmdRaz2
rle
champ de saisie
liste contenue dans un conteneur jScrollPane1
liste contenue dans un conteneur jScrollPane2
transfre les lments slectionns de liste 1 vers liste 2
fait l'inverse
vide la liste 1
vide la liste 2
Interfaces graphiques
171
modifier la classe interfaceAppli prcdente pour qu'elle drive non plus de JFrame mais de JApplet :
public class interfaceAppli extends JApplet {
supprimer l'instruction qui fixe le titre de la fentre JFrame. Une applet JApplet n'a pas de barre de titre
// this.setTitle("JList");
changer le constructeur en mthode init et dans cette mthode supprimer la gestion des vnements fentre
(WindowListener, ...). Une applet est un conteneur qui ne peut tre redimensionn ou ferm.
/**Construire le cadre*/
public init() {
// enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}//interfaceAppli
Interfaces graphiques
172
On donne titre d'exemple le code complet de l'applet sauf la partie gnre automatiquement par JBuilder
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class interfaceAppli extends JApplet {
JPanel contentPane;
JLabel jLabel1 = new JLabel();
JLabel jLabel2 = new JLabel();
JLabel jLabel3 = new JLabel();
JTextField txtSaisie = new JTextField();
JButton cmd1To2 = new JButton();
JButton cmd2To1 = new JButton();
DefaultListModel v1=new DefaultListModel();
DefaultListModel v2=new DefaultListModel();
JList jList1 = new JList(v1);
JList jList2 = new JList(v2);
JScrollPane jScrollPane1 = new JScrollPane();
JScrollPane jScrollPane2 = new JScrollPane();
JButton cmdRaz1 = new JButton();
JButton cmdRaz2 = new JButton();
JLabel jLabel4 = new JLabel();
/**Construire le cadre*/
public void init() {
// enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}//interfaceAppli
/**Initialiser le composant*/
private void jbInit() throws Exception {
//setIconImage(Toolkit.getDefaultToolkit().createImage(interfaceAppli.class.getResource("[Votre
icne]")));
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(null);
this.setSize(new Dimension(400, 308));
// this.setTitle("JList");
jScrollPane1.setBounds(new Rectangle(37, 133, 124, 101));
jScrollPane2.setBounds(new Rectangle(232, 132, 124, 101));
.............
}
void txtSaisie_actionPerformed(ActionEvent e) {
// le texte de la saisie a t valid
// on le rcupre dbarrass de ses espaces de dbut et fin
String texte=txtSaisie.getText().trim();
// s'il est vide, on n'en veut pas
if(texte.equals("")){
// msg d'erreur
JOptionPane.showMessageDialog(this,"Vous devez taper un texte",
"Erreur",JOptionPane.WARNING_MESSAGE);
// fin
return;
}//if
// s'il n'est pas vide, on l'ajoute aux valeurs de la liste 1
v1.addElement(texte);
// et on vide le champ de saisie
txtSaisie.setText("");
}
void cmd1To2_actionPerformed(ActionEvent e) {
// transfert des lments slectionns dans la liste 1 vers la liste 2
transfert(jList1,jList2);
}//cmd1To2
private void transfert(JList L1, JList L2){
// transfert des lments slectionns dans la liste 1 vers la liste 2
// on rcupre le tableau des indices des lments slectionns dans L1
int[] indices=L1.getSelectedIndices();
// qq chose faire ?
Interfaces graphiques
173
if (indices.length==0) return;
// on rcupre les valeurs de L1
DefaultListModel v1=(DefaultListModel)L1.getModel();
// et celles de L2
DefaultListModel v2=(DefaultListModel)L2.getModel();
for(int i=indices.length-1;i>=0;i--){
// on ajoute L2 les valeurs slectionnes dans L1
v2.addElement(v1.elementAt(indices[i]));
// les lments de L1 copis dans L2 doivent tre supprims de L1
v1.removeElementAt(indices[i]);
}//for
}//transfert
private void affiche(String message){
// affiche message
JOptionPane.showMessageDialog(this,message,
"Suivi",JOptionPane.INFORMATION_MESSAGE);
}// affiche
void cmd2To1_actionPerformed(ActionEvent e) {
// transfert des lments slectionns dans jList2 vers jList1
transfert(jList2,jList1);
}//cmd2TO1
void cmdRaz1_actionPerformed(ActionEvent e) {
// vide liste 1
v1.removeAllElements();
}//cmd Raz1
void cmdRaz2_actionPerformed(ActionEvent e) {
// vide liste 2
v2.removeAllElements();
}//cmd Raz2
}//classe
On peut compiler le source de cette applet. On le fait ici avec le JDK, sans JBuilder.
E:\data\serge\Jbuilder\interfaces\JApplets\1>javac.bat interfaceAppli.java
E:\data\serge\Jbuilder\interfaces\JApplets\1>dir
12/06/2002 16:40
6 148 interfaceAppli.java
12/06/2002 16:41
527 interfaceAppli$1.class
12/06/2002 16:41
525 interfaceAppli$2.class
12/06/2002 16:41
525 interfaceAppli$3.class
12/06/2002 16:41
525 interfaceAppli$4.class
12/06/2002 16:41
525 interfaceAppli$5.class
12/06/2002 16:41
4 759 interfaceAppli.class
L'application peut tre teste avec le programme AppletViewer du JDK qui permet d'excuter des applets ou un navigateur. Pour
cela, il faut crer le document HTML appli.htm qui aura en son sein l'applet :
<html>
<head>
<title>listes swing</title>
</head>
<body>
<applet
code="interfaceAppli.class"
width="400"
height="300">
</applet>
</body>
</html>
On a l, un document HTML classique si ce n'est la prsence du tag applet. Celui-ci a t utilis avec trois paramtres :
code="interfaceAppli.class"
width="400"
height="300"
Lorsque le fichier appli.htm a t crit, on peut le charger avec le programme appletviewer du JDK ou avec un navigateur. On tape
dans une fentre Dos la commande suivante dans le dossier du fichier appli.htm :
appletviewer appli.htm
Interfaces graphiques
174
On suppose ici que le rpertoire de l'excutable appletviewer.exe est dans le PATH de la fentre Dos, sinon il faudrait donner le
chemin complet de l'excutable appletviewer.exe. On obtient la fentre suivante :
On arrte l'excution de l'applet avec l'option Applet/Quitter. Testons maintenant l'applet avec un navigateur. Celui-ci doit utiliser
une machine virtuelle Java 2 pour pouvoir afficher les composants swing. Jusqu' rcemment (2001), cette contrainte posait
problme car Sun produisait des JDK que les diteurs de navigateurs ne suivaient qu'avec beaucoup de retard. Finalement Sun a mis
un terme cet obstacle en mettant disposition des utilisateurs une application appele "Java plugin" qui permet aux navigateurs
Internet Explorer et Netscape Navigator d'utiliser les tout derniers JRE produits par Sun (JRE=Java Runtime Environment). Les
tests qui suivent ont t faits avec IE 5.5 muni du plugin Java 1.4.
Une premire faon de tester l'applet interfaceAppli.class est de charger le fichier HTML appli.htm directement dans le navigateur en
double-cliquant dessus. Le navigateur affiche alors la page HTML et son applet sans l'aide d'un serveur Web :
Interfaces graphiques
175
On voit d'aprs l'URL (1) que le fichier a t charg dans le navigateur directement. Dans l'exemple suivant, le fichier appli.htm a t
demand un serveur Web Apache travaillant sur le port 81 de la machine locale :
176
Nous avons vu qu'au sein d'un document HTML, l'applet tait rfrence par la commande de mise en forme (tag) <applet>.
Cette commande peut avoir divers paramtres :
<applet
code=
width=
height=
codebase=
align=
hspace=
vspace=
alt=
>
<param name1=nom1 value=val1>
<param name2=nom2 value=val2>
texte
>
</applet>
La signification des paramtres est la suivante :
Paramtre
code
width
height
codebase
align
hspace
vspace
alt
param
texte
Signification
obligatoire - nom du fichier .class excuter
obligatoire - largeur de l'applet dans le document
obligatoire - hauteur
facultatif - URL du rpertoire contenant l'applet excuter. Si codebase n'est pas prcis, l'applet sera cherch dans le
rpertoire du document html qui la rfrence
facultatif - positionnement (LEFT, RiGHT, CENTER) de l'applet dans le document
facultatif - marge horizontale entourant l'applet exprime en pixels
facultatif - marge verticale entourant l'applet exprime en pixels
facultatif - texte crit en lieu et place de l'applet si le navigateur ne peut le charger
facultatif - paramtre pass l'applet prcisant son nom (name) et sa valeur (value). L'applet pourra rcuprer la valeur
du paramtre nom1 par val1=getParameter("nom1")
On peut utiliser autant de paramtres que l'on veut
facultatif - sera affich par tout navigateur incapable d'excuter une applet, par exemple parce qu'il ne possde pas de
machine virtuelle Java.
Le code Java de l'applet interfaceParams a t gnr comme indiqu prcdemment. On a construit une application JBuilder puis on
a fait les quelques transformations mentionnes plus haut :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class interfaceParams extends JApplet {
JPanel contentPane;
JScrollPane jScrollPane1 = new JScrollPane();
JLabel jLabel1 = new JLabel();
DefaultListModel params=new DefaultListModel();
JList lstParams = new JList(params);
//Construire le cadre
public void init() {
// enableEvents(AWTEvent.WINDOW_EVENT_MASK);
Interfaces graphiques
177
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// autres initialisations
moreInit();
}
// initialisations
public void moreInit(){
// affiche les valeurs des paramtres de l'applet
params.addElement("nom=param1 valeur="+getParameter("param1"));
params.addElement("nom=param2 valeur="+getParameter("param2"));
}//more Init
//Initialiser le composant
private void jbInit() throws Exception {
............
this.setSize(new Dimension(205, 156));
//this.setTitle("Paramtres d'une applet");
jScrollPane1.setBounds(new Rectangle(19, 53, 160, 73));
............
}
}
178
cre une instance d'URL partir d'une chane du type "protocol:port//machine/fichier" lance une exception si la syntaxe de la chane ne correspond pas une URL
ouvre la connexion avec la machine distante getHost() sur son port getPort() selon le
protocole getProtocol() en vue de lire le fichier getFile(). Lance une exception si la
connexion n'a pu tre ouverte
Interfaces graphiques
179
Aprs avoir vrifi qu'il y avait bien un argument, on tente de crer une URL avec celui-ci :
// cration de l'URL
URL url=new
new URL(arg[0]);
Cette cration peut chouer si l'argument ne respecte pas la syntaxe des URL protocole:port//machine/fichier. Si on a une URL
syntaxiquement correcte, on essaie de crer un flux d'entre partir duquel on pourra lire des lignes de texte. Le flux d'entre fourni
par une URL URL.openStream() fournit un flux de type InputStream qui est un flux orient caractres : la lecture se fait caractre par
caractre et il faut former les lignes soi-mme. Pour pouvoir lire des lignes de texte, il faut utiliser la mthode readLine des classes
BufferedReader ou DataInputStream.On a choisi ici BufferedReader. Il ne nous reste qu' transformer le flux InputStream de l'URL en flux
BufferedReader. Cela se fait en crant un flux intermdiaire InputStreamReader :
BufferedReader is=new
new BufferedReader(new
new InputStreamReader(url.openStream()));
Cette cration de flux peut chouer. On gre l'exception correspondante. Une fois le flux obtenu, il ne nous reste plus qu' y lire les
lignes de texte et afficher celles-ci l'cran.
String ligne;
while((ligne=is.readLine())!=null
null)
while
null
System.out.println(ligne);
L encore la lecture peut engendrer une exception qui doit tre gre. Voici un exemple d'excution du programme qui demande
une URL locale :
E:\data\serge\Jbuilder\interfaces\JApplets\3>java urlcontenu http://localhost
<html>
<head>
Interfaces graphiques
180
2
3
4
Numro
1
2
3
4
Nom
txtURL
btnCharger
JScrollPane1
lstURL
Type
JTextField
JButton
JScrollPane
JList
Rle
URL lire
bouton lanant la lecture de l'URL
panneau dfilant
liste affichant le contenu de l'URL demande
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.event.*;
java.net.*;
java.io.*;
Interfaces graphiques
181
Interfaces graphiques
182
Dans l'exemple ci-dessus, le navigateur a demand l'URL http://localhost:81/Japplets/2/appliURL.htm un serveur web apache
fonctionnant sur le port 81. L'applet a t alors affiche dans le navigateur. Dans celle-ci, on a demand de nouveau l'URL
http://localhost:81/Japplets/2/appliURL.htm pour vrifier qu'on obtenait bien le fichier appliURL.htm qu'on avait construit.
Maintenant essayons de charger une URL n'appartenant pas la machine qui a dlivr l'applet (ici localhost) :
Le chargement de l'URL a t refus. On retrouve ici la contrainte lie aux applets : elles ne peuvent accder qu'aux seules
ressources rseau de la machine partir de laquelle elles ont t tlcharges. Il y un moyen pour l'applet de contourner cette
contrainte qui est de dlguer les demandes rseau un programme serveur situ sur la machine d'o elle a t tlcharge. Ce
programme fera les demandes rseau la place de l'applet et lui renverra les rsultats. On appelle cela un programme relais.
183
Nous allons transformer maintenant l'application graphique IMPOTS en applet. Cela prsente un certain intrt : l'application
deviendra disponible tout internaute disposant d'un navigateur et d'un Java plugin 1.4 ( cause du composant JSpinner). Notre
application devient ainsi plantaire... Cette modification va ncessiter un peu plus que la simple transformation application
graphique --> applet devenue maintenant classique. L'application utilise une option de menu Initialiser dont le but est de lire le
contenu d'un fichier local. Or si on se reprsente une application Web, on voit que ce schma ne tient plus :
Serveur Web
client
applet
donnes
Il est vident que les donnes dfinissant les barmes de l'impt seront sur le serveur web et non pas sur chacune des machines
clientes. Le fichier lire sur le serveur web sera ici plac dans le mme dossier que l'applet et l'option Initialiser devra aller le
chercher l. Les exemples prcdents ont montr comment faire cela. Par ailleurs, pour slectionner le fichier des donnes
localement, on avait utilis un composant JFileChooser qui n'a plus lieu d'tre ici.
Le document HTML contenant l'applet sera le suivant :
<html>
<head>
<title>Applet de calcul d'impts</title>
</head>
<body>
<h2>Calcul d'impts</h2>
<applet
code="appletImpots.class"
width="320"
height="270"
>
<param name="data" value="impots.txt">
</applet>
</body>
</html>
On notera la paramtre data dont la valeur est le nom du fichier contenant les donnes du barme de l'impt. Le document HTML,
la classe appletImpots, la classe impots et le fichier impots.txt sont tous dans le mme dossier du serveur Web :
E:\data\serge\web\JApplets\impots>dir
12/06/2002 13:24
247
13/06/2002 10:17
654
13/06/2002 10:17
653
13/06/2002 10:17
653
13/06/2002 10:17
651
13/06/2002 10:06
651
13/06/2002 10:17
8 655
13/06/2002 10:17
657
13/06/2002 10:24
286
13/06/2002 10:17
1 305
impots.txt
appletImpots$2.class
appletImpots$3.class
appletImpots$4.class
appletImpots$5.class
appletImpots$6.class
appletImpots.class
appletImpots$1.class
appletImpots.htm
impots.class
Le code de l'applet est le suivant (nous n'avons mis en relief que les changements par rapport l'application graphique) :
...........
import java.net.*;
import java.applet.Applet;
public class appletImpots extends JApplet {
// les composants de la fentre
JPanel contentPane;
............................
// les attributs de la classe
double[] limites=null;
double[] coeffr=null;
double[] coeffn=null;
impots objImpots=null;
Interfaces graphiques
184
String urlDATA=null;
//Construire le cadre
public void init() {
//enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// autres initialisations
moreInit();
}
// initialisation formulaire
private void moreInit(){
// menu Calculer inhib
mnuCalculer.setEnabled(false);
................
// on rcupre le nom du fichier du barme des impts
String nomFichier=getParameter("data");
// erreur ?
if(nomFichier==null){
// msg d'erreur
txtStatus.setText("Le paramtre data de l'applet n'a pas t initialis");
// on bloque l'option Initialiser
mnuInitialiser.setEnabled(false);
// fin
return;
}//if
// on fixe l'URL des donnes
urlDATA=getCodeBase()+"/"+nomFichier;
}//moreInit
//Initialiser le composant
private void jbInit() throws Exception
...................
}
void mnuQuitter_actionPerformed(ActionEvent e) {
// on quitte l'application
System.exit(0);
}
void mnuInitialiser_actionPerformed(ActionEvent e) {
// on charge le fichier des donnes
try{
// lecture donnes
lireDATA();
// cration de l'objet impots
objImpots=new impots(limites,coeffr,coeffn);
................
}
private void lireDATA() throws Exception {
// les tableaux de donnes
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
String[] champs=null;
// ouverture fichier en lecture
BufferedReader IN=new BufferedReader(new InputStreamReader(new URL(urlDATA).openStream()));
// on lit le fichier ligne par ligne
....................
}
void mnuCalculer_actionPerformed(ActionEvent e) {
// calcul de l'impt
......................
}
void txtSalaire_caretUpdate(CaretEvent e) {
..........
}
void mnuEffacer_actionPerformed(ActionEvent e) {
...............
}
}
Interfaces graphiques
185
Nous ne commentons ici que les modifications amenes par le fait que le fichier des donnes lire est maintenant sur une machine
distante plutt que sur une machine locale :
Le nom de l'URL du fichier des donnes lire est obtenu par le code suivant :
// on rcupre le nom du fichier du barme des impts
String nomFichier=getParameter("data");
// erreur ?
if(nomFichier==null){
// msg d'erreur
txtStatus.setText("Le paramtre data de l'applet n'a pas t initialis");
// on bloque l'option Initialiser
mnuInitialiser.setEnabled(false);
// fin
return;
}//if
// on fixe l'URL des donnes
urlDATA=getCodeBase()+"/"+nomFichier;
Rappelons-nous que le nom du fichier "impots.txt" est pass dans le paramtre data de l'applet :
<param name="data" value="impots.txt">
Le code ci-dessus commence donc par rcuprer la valeur du paramtre data en grant une ventuelle erreur. Si l'URL du document
HTML contenant l'applet est http://localhost:81/JApplets/impots/appletImpots.htm, l'URL du fichier impots.txt sera
http://localhost:81/JApplets/impots/impots.txt. Il nous faut construire le nom de cette URL. La mthode getCodeBase() de l'applet donne
l'URL du dossier o a t rcupr le document HTML contenant l'applet, donc dans notre exemple
http://localhost:81/JApplets/impots. L'instruction suivante permet donc de construire l'URL du fichier des donnes :
urlDATA=getCodeBase()+"/"+nomFichier;
On trouve dans la mthode lireFichier() qui lit le contenu de l'URL urlData, la cration du flux d'entre qui va permettre de lire les
donnes :
// ouverture fichier en lecture
BufferedReader IN=new BufferedReader(new InputStreamReader(new URL(urlDATA).openStream()));
A partir de l, on ne peut plus distinguer le fait que les donnes viennent d'un fichier distant plutt que local. Voici un exemple
d'excution de l'applet :
Interfaces graphiques
186
4.8 Conclusion
Ce chapitre a prsent
une introduction la construction d'interfaces graphiques avec Jbuilder
les composants swing les plus courants
la construction d'applets
Nous retiendrons que
le code gnr par JBuilder peut tre crit la main. Une fois ce code obtenu d'une manire ou d'une autre, un simple
JDK suffit pour l'excuter et JBuilder n'est alors plus indispensable.
l'utilisation d'un outil tel que JBuilder peut amener des gains de productivit importants :
o s'il est possible d'crire la main le code gnr par JBuilder, cela peut prendre beaucoup de temps et ne prsente
que peu d'intrt, la logique de l'application tant en gnral ailleurs.
o le code gnr peut tre instructif. Le lire et l'tudier est une bonne faon de dcouvrir certaines mthodes et
proprits de composants qu'on utilise pour la premire fois.
o JBuilder est multi plate-formes. On conserve donc ses acquis en passant d'une plate-forme l'autre. Pouvoir
crire des programmes fonctionnant la fois sous windows et Linux est bien sr un facteur de productivit trs
important. Mais il est d Java lui-mme et non JBuilder.
Nous prsentons ci-dessous l'installation de JBuilder 4 Foundation sur une machine Linux. Son installation sur une machine Win9x
ne pose aucun problme et est analogue au processus qui va maintenant tre dcrit. L'installation des version sultrieures est sans
doute diffrente mais ce document peut nanmoins quelques informations utiles.
Jbuilder est disponible sur le site d''Inprise l'url http://www.inprise.com/jbuilder
Interfaces graphiques
187
On trouve sur cette page les liens pour notamment Windows et Linux. Nous dcrivons maintenant l'installation de JBuilder sur une
machine Linux ayant l'interface graphique KDE.
En suivant le lien Jbuilder 4 for Linux, un formulaire est prsent. On le remplit et au final on rcupre deux fichiers :
jb4docs_fr.tar.gz
la documentation et les exemples de Jbuilder4
jb4fndlinux_fr.tar.gz
Jbuilder4 Foundation. C'est une version limite du Jbuilder4 commercial mais suffisant dans un
contexte ducatif.
Une cl d'activation du logiciel vous est envoye par courrier lectronique. Elle vous permettra d'utiliser Jbuilder4 et vous sera
demande ds la 1re utilisation. Si vous perdez cette cl, vous pouvez revenir l'url ci-dessus et suivre le lien "get your activation key".
Nous supposons par la suite que vous tes root et que vous tes plac dans le rpertoire des deux fichiers tar.gz ci-dessus. On
dcompresse les deux fichiers :
[tar xvzf jb4fndlinux_fr.tar.gz]
[tar xvzf jb4docs_fr.tar.gz]
[ls -l]
drwxr-xr-x
drwxr-xr-x
3 nobody
3 nobody
nobody
nobody
[ls -l foundation]
-rw-r--r--rwxr-xr-x
drwxr-xr-x
-rw-r--r--rw-r--r--rw-r--r--rw-r--r--
1
1
2
1
1
1
1
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
1128
69035365
4096
15114
23779
75739
31902
dc
dc
dc
dc
dc
dc
dc
5
5
5
5
5
5
5
13:00
13:00
13:00
13:00
13:00
13:00
13:00
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
1128
40497874
4096
9210
23779
75739
31902
oct
oct
oct
oct
oct
oct
oct
10
10
10
10
10
10
10
2000
2000
2000
2000
2000
2000
2000
deploy.txt
fnd_linux_install.bin
images
index.html
license.txt
release_notes.html
whatsnew.html
[ls -l docs]
-rw-r--r--rwxr-xr-x
drwxr-xr-x
-rw-r--r--rw-r--r--rw-r--r--rw-r--r--
1
1
2
1
1
1
1
deploy.txt
doc_install.bin
images
index.html
license.txt
release_notes.html
whatsnew.html
Dans les deux rpertoires gnrs, le fichier .bin est le fichier d'installation. Par ailleurs, avec un navigateur affichez le fichier
index.html de chacun des rpertoires. Ils donnent la marche suivre pour installer les deux produits. Commenons par installer
Jbuilder Foundation :
[cd foundation]
[./fnd_linux_install.bin]
Interfaces graphiques
188
Faites [suivant].
Interfaces graphiques
189
Acceptez l'emplacement propos pour Jbuilder (notez le, vous en aurez besoin ultrieurement) et faites [suivant]. L'installation se
fait rapidement.
Nous sommes prts pour un premier test. Dans KDE, lancez le gestionnaire de fichiers pour aller dans le rpertoire des excutables
de Jbuilder : /opt/jbuilder4/bin (si vous avez install jbuilder dans /opt/jbuilder4) :
Interfaces graphiques
190
Cliquez sur l'icne jbuilder ci-dessus. Comme c'est la 1re fois que vous utilisez Jbuilder, vous allez devoir entrer votre cl
d'activation :
Remplissez les champs Nom et Socit. Faites [Ajouter] pour entrer votre cl d'activation. On rappelle que celle-ci a du vous tre
envoye par mail et que si vous l'avez perdue, vous pouvez l'avoir l'url o vous avez rcupr Jbuilder 4 (cf dbut de l'installation).
Une fois votre cl d'activation accepte, on vous rappellera les conditions de licence :
Interfaces graphiques
191
Faites OK pour terminer cette phase qui ne s'excute que lors de la 1re excution. Apparat ensuite, l'environnement de
dveloppement de Jbuilder :
Quittez Jbuilder pour installer maintenant la documentation. Celle-ci va installer un certain nombre de fichiers qui seront utiliss
dans l'aide de Jbuilder, aide qui pour l'instant est incomplte. Revenez dans le rpertoire o vous avez dcompress les fichiers
tar.gz de Jbuilder. Le programme d'installation est dans le rpertoire docs. Placez-vous dedans :
[cd docs]
[ls -l]
Interfaces graphiques
192
-rw-r--r--rwxr-xr-x
drwxr-xr-x
-rw-r--r--rw-r--r--rw-r--r--rw-r--r--
1
1
2
1
1
1
1
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
nobody
1128
40497874
4096
9210
23779
75739
31902
oct
oct
oct
oct
oct
oct
oct
10
10
10
10
10
10
10
2000
2000
2000
2000
2000
2000
2000
deploy.txt
doc_install.bin
images
index.html
license.txt
release_notes.html
whatsnew.html
Le fichier d'installation est doc_install.bin mais on ne peut le lancer immdiatement. Si on le fait, l'installation choue sans qu'on
comprenne pourquoi. Lisez le fichier index.html avec un navigateur pour une description prcise du mode d'installation.
L'installateur a besoin d'une machine virtuelle java, en fait un programme appel java et celui-ci doit tre dans le PATH de votre
machine. On rappelle que PATH est une variable Unix dont la valeur de la forme rep1:rep2:...:repn indique les rpertoires repi qui
doivent tre explors dans la recherche d'un excutable. Ici, l'installateur demande l'excution d'un programme java. Vous pouvez
avoir plusieurs versions de ce programme selon le nombre de machines virtuelles Java que vous avez installes ici ou l. Assez
logiquement, nous utiliserons celui amen par Jbuilder4 et qui se trouve dans /opt/jbuilder4/jdk1.3/bin. Pour mettre ce rpertoire
dans la variable PATH :
[echo $PATH]
/usr/local/sbin:/usr/sbin:/sbin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/
usr/bin:/usr/X11R6/bin:/root/bin
[export PATH=/opt/jbuilder4/jdk1.3/bin/:$PATH]
[echo $PATH]
/opt/jbuilder4/jdk1.3/bin/:/usr/local/sbin:/usr/sbin:/sbin:/usr/local/sbin:/usr/local
/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin
Nous pouvons maintenant lancer l'installation de la documentation :
[./doc_install.bin]
Preparing to install...
C'est une installation graphique qui va se faire :
Elle est trs analogue celle de Jbuilder Foundation. Aussi ne la dtaillerons-nous pas. Il y a un cran qu'il ne faut pas rater :
Interfaces graphiques
193
L'installateur doit normalement trouver tout seul o vous avez install Jbuilder Foundation. Ne changez donc pas ce qui vous est
propos sauf bien sr si c'tait faux.
Une fois la documentation installe, nous sommes prts pour un nouveau test de Jbuilder. Lancez l'application comme il a t
montr plus haut. Une fois Jbuilder prsent, prenez l'option Aide/Rubrique d'aide. Dans le cadre de gauche, cliquez sur le lien
Tutoriels :
Jbuilder est livr avec un ensemble de tutoriels qui vous permettront de dmarrer en programmation Java. Ci-dessous on a suivi le
tutoriel "Construction d'une application" ci-dessus.
Interfaces graphiques
194
Nous allons crer une simple fentre avec le titre "Bonjour tout le monde". Nous appelons ce projet coucou. L'exemple a t excut
par root. Jbuilder propose alors de rassembler tous les projets dans un rpertoire jbproject du rpertoire de connexion de root. Le
projet coucou sera plac dans le rpertoire coucou (Champ Nom du rpertoire du projet) de jbproject. Faites [suivant].
Interfaces graphiques
195
Ce second cran est un rsum des diffrents chemins de votre projet. Il n'y a rien modifier. Faites [suivant].
Le 3ime cran vous demande de personnaliser votre projet. Compltez et faites [Terminer].
Faites maintenant Fichier/Nouveau :
Interfaces graphiques
196
Le champ paquet reprend le nom du projet. Le champ classe demande le nom qui sera donn la classe principale du projet. Prenez
le nom ci-dessus et faites [suivant] :
Notre application comporte une seconde classe Java pour la fentre de l'application. Donnez un nom cette classe. Le champ Titre
est le titre que vous voulez donner cette fentre. Le cadre options vous propose des options pour votre fentre. Prenez-les toutes et
faites [Terminer]. Vous revenez alors l'environnement Jbuilder qui s'est enrichi des informations que vous avez donnes pour
votre projet :
Interfaces graphiques
197
Dans la fentre de gauche/haut (1), vous avez la liste des fichiers qui composent votre projet. On trouve notamment les deux
classes .java dont nous venons de donner le nom. Dans la fentre de droite (2) vous avez le code Java de la classe coucouCadre. Dans
la fentre de gauche/bas, vous avez la structure (classes, mthodes, attributs) de votre projet. Il est prt tre excut. Appuyez sur
le bouton 4 ci-dessus ou faites Excuter/Excuter le projet ou faites F9. Vous devriez obtenir la fentre suivante :
En cliquant sur l'option Aide, vous devriez retrouver des informations que vous avez donnes aux assistants de cration. Nous
n'irons pas plus loin. Il est temps maintenant de vous plonger dans les tutoriels.
Avant de terminer, montrons simplement que vous pouvez utiliser non pas Jbuilder et son interface graphique mais le jdk qu'il a
amen avec lui et qu'il a plac dans /opt/jbuilder4/jdk1.3. Construisez le fichier essai1.java suivant :
import java.io.*;
public class essai1{
public static void main(String args[]){
System.out.println("coucou");
System.exit(0);
}
}
9 21:57 essai1.java
198
[/opt/jbuilder4/jdk1.3/bin/java essai1]
coucou
Interfaces graphiques
199
Une application sappuyant sur ces pilotes peut utiliser nimporte quelle bases de donnes ci-dessus sans r-criture.
Application
Pilote ODBC
Base de
donnes
Afin que les applications Java puissent tirer parti elles-aussi de linterface ODBC, Sun a cr lintervace JDBC (Java DataBase
Connectivity) qui va sintercaler entre lapplication Java et linterface ODBC :
JDBC
200
Application
Java
Interface
JDBC-ODBC
Interface
ODBC
Base de
donnes
Les tapes 2 et 3 sont ralises de faon rpte, la fermeture de connexion nayant lieu qu la fin de lexploitation de la base. Cest
un schma relativement classique dont vous avez peut-tre lhabitude si vous avez exploit une base de donnes de faon
interactive. Nous allons dtailler chacune de ces tapes sur un exemple. Nous considrons une base de donnes ACCESS appele
ARTICLES et ayant la structure suivante :
nom
code
nom
prix
stock_actu
stock_mini
type
code de larticle sur 4 caractres
son nom (chane de caractres)
son prix (rel)
son stock actuel (entier)
le stock minimum (entier) en-dea duquel il faut rapprovisionner larticle
Cette base ACCESS est dfinie comme source de donnes utilisateur dans le gestionnaire des bases ODBC :
JDBC
201
JDBC
202
Cette configuration consiste essentiellement associer la base ARTICLES, le fichier Access articles.mdb correspondant cette base.
Ceci fait, la base ARTICLES est accessible aux applications utilisant linterface ODBC.
avec
DriverManager
Connection
URL
id
mdp
JDBC
203
Pour que la connexion une base de donnes soit possible, il faut disposer du pilote adquat. Dans nos exemples, il sagira du
pilote ODBC capable de grer la base de donnes demande. Sil faut que ce pilote soit disponible dans la liste des pilotes ODBC
prsents sur la machine, il faut galement disposer de la classe JAVA qui fera linterface avec lui. Pour ce faire, lapplication va
requrir la classe qui lui est ncessaire de la faon suivante :
Class.forName(String nomClasse)
La classe Class nest en rien lie linterface JDBC. Cest une classe gnrale de gestion des classes. Sa mthode statique forName
permet de charger dynamiquement une classe et donc de bnficier de ses attributs et mthodes statiques. La classe faisant
linterface avec les pilotes ODBC de MS Windows sappelle sun.jdbc.odbc.JdbcOdbcDriver . On crira donc (la mthode peut
gnrer une exception) :
try{
Class.forName( sun.jdbc.odbc.JdbcOdbcDriver ) ;
} catch (Exception e){
// traiter lexception (classe inexistante)
}
Les classes ncessaires linterface JDBC se trouvent dans le package java.sql. On crira donc en dbut de programme :
import java.sql.*;
JDBC
204
Une fois obtenu un objet Statement, on peut mettre des requtes SQL. Cela se fera diffremment selon que la requte est une
requte dinterrogation ou de mise jour de la base.
Seuls les mots cls de la premire ligne sont obligatoires, les autres sont facultatifs. Il existe dautres mots cls non prsents ici.
1.
2.
3.
4.
Une jointure est faite avec toutes les tables qui sont derrire le mot cl from
Seules les colonnes qui sont derrire le mot cl select sont conserves
Seules les lignes vrifiant la condition du mot cl where sont conserves
Les lignes rsultantes ordonnes selon lexpression du mot cl order by forment le rsultat de la requte.
Le rsultat dun select est une table. Si on considre la table ARTICLES prcdente et quon veuille les noms des articles dont le
stock actuel est au-dessous du seuil minimal, on crira : select nom from articles where stock_actu<stock_mini. Si on les veut par ordre
alphabtique des noms, on crira : select nom from articles where stock_actu<stock_mini order by nom
Pour excuter ce type de requte, la classe Statement offre la mthode executeQuery :
ResultSet executeQuery(String requte)
205
Un objet de type ResultSet reprsente une table, cest dire un ensemble de lignes et de colonnes. A un moment donn, on na accs
qu une ligne de la table appele ligne courante. Lors de la cration initiale du ResultSet, la ligne courante est la ligne n 1 si le
ResultSet nest pas vide. Pour passer la ligne suivante, la classe ResultSet dispose de la mthode next :
boolean next()
Cette mthode tente de passer la ligne suivante du ResultSet et rend true si elle russit, false sinon. En cas de russite, la ligne
suivante devient la nouvelle ligne courante. La ligne prcdente est perdue et on ne pourra revenir en arrire pour la rcuprer. La
table du ResultSet a des colonnes col1, col2,.... Pour exploiter les diffrents champs de la ligne courante, on dispose des mthodes
suivantes :
Type getType("coli")
pour obtenir le champ coli de la ligne courante. Type dsigne le type du champ coli. On utilise assez souvent la mthode getString
sur tous les champs, ce qui permet dobtenir le contenu du champ en tant que chane de caractres. On convertit ensuite si
ncessaire. Si on ne connat pas le nom de la colonne, on peut utiliser les mthodes
Type getType(i)
206
JDBC
207
// excution de la requte
RS=S.executeQuery(select);
// nombre de colonnes
nbColonnes=RS.getMetaData().getColumnCount();
// exploitation de la table des rsultats
System.out.println("Rsultats obtenus\n\n");
while(RS.next()){
// tant qu'il y a une ligne exploiter
// on l'affiche l'cran
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// ligne suivante
// requte suivante
System.out.print("Requte : ");
select=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// fermeture de la base et du flux d'entre
try{
connect.close();
System.out.println("Base " + DB + " ferme");
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// classe
g457,panthre,800000,1,1
z400,lopard,500000,1,1
f807,cachalot,200000,0,0
d600,arc,5000,10,2
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
d800,cano,1502,12,6
a300,vlo,1202,30,2
f450,essai3,3,3,3
Requte : select nom, prix from articles where prix >10000 order by prix desc
Rsultats obtenus
panthre,800000
lopard,500000
cachalot,200000
JDBC
208
vlo,30
JDBC
209
arc,10
cano,12
fusil,10
skis nautiques,3
essai3,3
cachalot,0
lopard,1
panthre,1
Vrification :
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES tablie
Requte : select nom,stock_actu from articles
Rsultats obtenus
vlo,31
arc,10
cano,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
lopard,1
panthre,1
Vrification :
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES tablie
Requ_te : select nom,stock_actu from articles
Rsultats obtenus
vlo,31
arc,10
cano,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
lopard,1
panthre,1
nouveau,20
Vrification :
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES tablie
Requte : select nom,stock_actu from articles
Rsultats obtenus
JDBC
210
vlo,31
arc,10
cano_,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
lopard,1
panthre,1
Le rsultat rendu est le boolen true si la requte a rendu un ResultSet (executeQuery), false si elle a rendu un nombre (executeUpdate). Le
ResultSet obtenu peut tre rcupr avec la mthode getResultSet et le nombre de lignes mises jour, par la mthode
getUpdateCount. Ainsi on crira :
Statement S=...;
ResultSet RS=...;
int nbLignes;
String requte=...;
// excution dune requte SQL
if (S.execute(requte)){
// on a un resultset
RS=S.getResultSet();
// exploitation du ResultSet
...
} else {
// ctait une requte de mise jour
nbLignes=S.getUpdateCount();
...
}
// vrification du nb d'arguments
if(arg.length<2
if
|| arg.length>4)
erreur(syntaxe,1);
// init paramtres de la connexion
Connection connect=null
null;
null
String uid="";
String mdp="";
if(arg.length>=3)
uid=arg[2];
if
if(arg.length==4)
if
mdp=arg[3];
// autres donnes
Statement S=null
null;
null
ResultSet RS=null
null;
null
String sqlText;
JDBC
211
int nbLignes;
int nbColonnes;
* from articles
articles set stock_mini=stock_mini+5 where stock_mini<5
nom,stock_mini from articles
into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10)
* from articles
from articles where code='x400'
* from articles
JDBC
212
Le programme prend donc ses entres dans le fichier requetes et met ses sorties dans le fichier results. Les rsultats obtenus sont les
suivants :
Connexion avec la base jdbc:odbc:articles tablie
Requte : (requete 1 du fichier des requetes : select * from articles)
Rsultats obtenus
----------------a300,vlo,1202,31,3
d600,arc,5000,10,3
d800,cano,1502,13,7
x123,fusil,3000,10,3
s345,skis nautiques,1800,3,3
f450,essai3,3,3,4
f807,cachalot,200000,0,1
z400,lopard,500000,1,2
g457,panthre,800000,1,2
Nouvelle Requte : (requete 2 du fichier des requetes : update articles set stock_mini=stock_mini+5 where
stock_mini<5)
8 ligne(s) ont t mises jour
Nouvelle Requte : (requete 3 du fichier des requetes : select nom,stock_mini from articles)
Rsultats obtenus
----------------vlo,8
arc,8
cano,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
lopard,7
panthre,7
Nouvelle Requte : (requete 4 du fichier des requetes : insert into articles
(code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))
1 ligne(s) ont t mises jour
Nouvelle Requte : (requete 5 du fichier des requetes : select * from articles)
Rsultats obtenus
----------------a300,vlo,1202,31,8
d600,arc,5000,10,8
d800,cano,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,lopard,500000,1,7
g457,panthre,800000,1,7
x400,nouveau,100,20,10
Nouvelle Requte : (requete 6 du fichier des requtes : delete from articles where code='x400')
JDBC
213
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,lopard,500000,1,7
g457,panthre,800000,1,7
JDBC
214
type
JTextField
JScrollPane
JTextArea
nom
txtConnexion
JScrollPane1
txtStatus
rle
chane de connexion la base de donnes ODBC
conteneur pour le Textarea 3
affiche des messages d'tat notamment des messages d'erreurs
La base de donnes dbimpots a t cre la main avec MySQL. On la transforme en source de donnes ODBC de la faon suivante
:
on lance le gestionnaire des sources de donnes ODBC 32 bits
JDBC
on utilise le bouton [Add] pour ajouter une nouvelle source de donnes ODBC
215
1
2
3
4
5
Une fois la source de donnes ODBC dfinie, on peut tester notre programme :
JDBC
216
Examinons le code qui, compar la version graphique sans base de donnes a t modifi. Rappelons le code de la classe impots
utilise jusqu'ici :
// cration d'une classe impots
public class impots{
// les donnes ncessaires au calcul de l'impt
// proviennent d'une source extrieure
private double[] limites, coeffR, coeffN;
// constructeur
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// on vrifie que les 3 tableaux ont la mme taille
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la mme taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// c'est bon
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//constructeur
// calcul de l'impt
public long calculer(boolean mari, int nbEnfants, int salaire){
// calcul du nombre de parts
double nbParts;
if (mari) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// calcul revenu imposable & Quotient familial
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// calcul de l'impt
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// retour rsultat
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calculer
}//classe
Cette classe construit les trois tableaux limites, coeffR, coeffN partir de trois tableaux passs en paramtres son constructeur. On
dcide de lui adjoindre un nouveau constructeur permettant de construire les trois mmes tableaux partir d'une base de donnes :
public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
JDBC
217
Pour l'exemple, nous dcidons de ne pas implmenter ce nouveau constructeur dans la classe impots mais dans une classe drive
impotsJDBC :
// paquetages imports
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// rajout d'un constructeur permettant de construire
// les tableaux limites, coeffr, coeffn partir de la table
// impots d'une base de donnes
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS : nom DSN de la base de donnes
// userIMPOTS, mdpIMPOTS : login/mot de passe d'accs la base
// les tableaux de donnes
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// connexion la base
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// cration d'un objet Statement
Statement S=connect.createStatement();
// requte select
String select="select limites, coeffr, coeffn from impots";
// excution de la requte
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// exploitation de la ligne courante
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// ligne suivante
// fermeture ressources
RS.close();
S.close();
connect.close();
// transfert des donnes dans des tableaux borns
int n=aLimites.size();
limites=new double[n];
coeffR=new double[n];
coeffN=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}//constructeur
}//classe
Le constructeur lit le contenu de la table impots de la base qu'on lui a pass en paramtres et remplit les trois tableaux limites, coeffR,
coeffN. Un certain nombre d'erreurs peuvent se produire. Le constructeur ne les gre pas mais les "remonte" au programme appelant
:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
Si on prte attention au code prcdent, on verra que la classe impotsJDBC utilise directement les champs limites, coeffR, coeffN de sa
classe de base impots. Ceux-ci tant dclars privs :
private double[] limites, coeffR, coeffN;
la classe impotsJDBC n'a pas d'accs ces champs directement. Nous faisons donc une premire modification la classe de base en
crivant :
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
JDBC
218
L'attribut protected permet aux classes drives de la classe impots d'avoir un accs direct aux champs dclars avec cet attribut. Il nous
faut faire une seconde modification. Le constructeur de la classe fille impotsJDBC est dclare comme suit :
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
On sait qu'avant de construire un objet d'une classe fille, on doit d'abord construire un objet de la classe parent. Pour ce faire, le
constructeur de la classe fille doit appeler explicitement le constructeur de la classe parent avec une instruction super(....). Ici ce n'est
pas fait car on ne voit pas quel constructeur de la classe parent on pourrait appeler. Il n'y en a pour l'instant qu'un et il ne convient
pas. Le compilateur va alors chercher dans la classe parent un constructeur sans paramtres qu'il pourrait appeler. Il n'en trouve pas
et cela gnre une erreur la compilation. Nous ajoutons donc un constructeur sans arguments notre classe impots :
// constructeur vide
protected impots(){}
Nous le dclarons "protected" afin qu'il ne puisse tre utilis que par des classes filles. Le squelette de la classe impots est devenu
maintenant le suivant :
public class impots{
// les donnes ncessaires au calcul de l'impt
// proviennent d'une source extrieure
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// constructeur vide
protected impots(){}
// constructeur
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
}//constructeur
// calcul de l'impt
public long calculer(boolean mari, int nbEnfants, int salaire){
.............
}//calculer
}//classe
Une fois l'objet objImpots cr, l'application est identique l'application graphique dj crite. Le lecteur est invit s'y reporter.
JDBC
219
5.4 Exercices
5.4.1 Exercice 1
Fournir une interface graphique au programme sql3 prcdent.
5.4.2 Exercice 2
Une applet Java ne peut accder une base de donnes que par lintermdiaire du serveur partir duquel elle a t charge. En
effet, une applet nayant pas accs au disque de la machine sur laquelle elle est excutz, la base de donnes ne peut tre sur la
machine du client utilisant lapplet. On est donc dans la situation suivante :
Applet
Java
Serveur
Base de donnes
La machine qui excute lapplet, le serveur et la machine qui dtient la base de donnes peuvent tre trois machines diffrentes. On
suppose ici que la base de donnes se trouve sur le serveur.
Problme 1
crire en Java lapplication serveur suivante :
lapplication serveur travaille sur un port qui lui est pass en paramtre
lorsquun client se connecte, lapplication serveur envoie le message
200 - Bienvenue - Envoyez votre requte
le client envoie alors les paramtres ncessaires la connexion une base de donnes et la requte quil veut excuter
sous la forme :
Pilote Java/URL base/UID/MDP/requte SQL
Les paramtres sont spars par la barre oblique. Les quatre premiers paramtres sont ceux du programme sql3 dcrit dans
ce chapitre.
Lapplication serveur cre alors une connexion avec la base de donnes prcise qui doit se trouver sur la mme
machine quelle et excute la requte SQL dessus. Les rsultats sont renvoys au client sous la forme :
100 - ligne1
100 - ligne2
...
sil sagit du rsultat dune requte Select ou
101 - nbLignes
pour rendre le nombre de lignes affectes par une requte de mise jour. Si une erreur de connexion la base ou
dexcution de la requte se produit, lapplication renvoie
500 - Message derreur
une fois, la requte excute, lapplication serveur ferme la connexion.
Problme 2
Crez une applet Java permettant dinterroger le serveur prcdent. On pourra sinspirer de linterface graphique de lexercice 1
prcdent. Comme le port du serveur peut varier, celui-ci fera lobjet dune saisie dans linterface de lapplet. Il en sera de mme
pour tous les paramtres ncessaires lenvoi de la ligne :
Pilote Java/URL base/UID/MDP/requte SQL
que le client doit envoyer au serveur.
5.4.3 Exercice 3
JDBC
220
Le texte suivant expose un problme destin initialement tre trait en Visual Basic. Adaptez-le pour un traitement en Java dans
une applet. Cette dernire sappuiera sur le serveur de lexercice 2. Linterface graphique pourra tre modifie pour tenir compte du
nouveau contexte dexcution.
On se propose de crer une application mettant en lumire les diffrentes oprations de mise jour possibles dune table dune base
de donnes ACCESS. La base de donnes ACCESS sappelle articles.mdb. Elle a une unique table nomme articles qui rpertorie
les articles vendus par une entreprise. Sa structure est la suivante :
nom
code
nom
prix
stock_actu
stock_mini
type
code de larticle sur 4 caractres
son nom (chane de caractres)
son prix (rel)
son stock actuel (entier)
le stock minimum (entier) en-dea duquel il faut rapprovisioner larticle
1
2
4
8
10
11
12
13
JDBC
n
1
type
textbox
nom
fiche
2
3
4
5
6
7
textbox
textbox
textbox
textbox
textbox
data
code
nom
prix
actuel
minimum
data1
8
9
10
11
12
HScrollBar
button
button
frame
textbox
position
OK
Annuler
frame1
basename
Fonction
numro de la fiche visualise
enabled est false
code de larticle
nom de larticle
prix de larticle
stock actuel de larticle
stock minimum de larticle
Contrle data associ la base de donnes
databasename=chemin du fichier articles.mdb
recordsource=articles
connect=access
permet de naviguer dans la table
permet de valider une mise jour - napparat que lors de celle-ci
permet dannuler une mise jour - napparat que lors de celle-ci
pour faire joli
nom de la base ouverte
221
13
textbox
sourcename
contrle
data1
code
nom
prix
actuel
minimum
Particularits
les champs databasename et recordsource sont renseigns. databasename doit dsigner la base Access
articles.mdb de votre rpertoire et recordsource la table articles.
cest un textbox quon veut lier au champ code de lenregistrement courant de data1. Pour cela on
renseigne deux champs :
datasource : on met data1 pour indiquer que le textbox est li la table associe data1
datafield : on choisit le champ code de la table articles
Aprs ces oprations, le textbox code contiendra toujours le champ code de lenregistrement courant de
data1. Inversement, modifier le contenu de ce textbox modifiera le champ code de la fiche courante.
On fait de mme pour les autres textbox
datasource : data1 datafield : nom
datasource : data1 datafield : prix
datasource : data1 datafield : stock_actu
datasource : data1 datafield : stock_mini
Les menus
Parcourir
Prcdent
Suivant
Premier
Dernier
Quitter
name
mnuajouter
mnusupprimer
mnumodifier
mnuprecedent
mnusuivant
mnupremier
mnudernier
mnuquitter
fonction
pour ajouter un nouvel enregistrement la table articles
pour supprimer de la table articles, lenregistrement actuellement visualis
pour modifier dans la table articles, lenregistrement actuellement visualis
pour passer lenregistrement prcdent
pour passer lenregistrement suivant
pour passer au premier enregistrement
pour passer au dernier enregistrement
pour quitter lapplication
Chargement de la feuille
Lors de lvnement form_load, la table associe data1 est ouverte (data1.refresh). Si louverture choue, un message derreur est
affich et le programme se termine (end). Sinon, le formulaire est affich avec visualis, le premier enregistrement de la table articles.
Aucune saisie dans les champs nest possible (proprit enabled false). Cette saisie nest possible quavec les options Ajouter et
Modifier. Les boutons OK et Annuler sont cachs (visible=false).
Partie 1
On se propose de construire les procdures lies aux diffrentes options du menu ainsi quaux boutons OK et Annuler. Dans un
premier temps, on ignorera les points suivants :
.
.
lautorisation/inhibition de certaines options du menu : par exemple, loption Suivant doit tre inhibe si on est positionn sur
le dernier enregistrement de la table
la gestion du scroller horizontal
menu Parcourir/Suivant
JDBC
222
fait passer lenregistrement suivant (data1.RecordSet.MoveNext) si on nest pas en fin de fichier (data1.RecordSet.EOF).
Met jour le textbox fiche (data1.recordset.absoluteposition/ data1.recordset.recordcount).
menu Parcourir/Prcdent
fait passer lenregistrement prcdent (data1.RecordSet.MovePrevious) si on nest pas en dbut de fichier
(data1.RecordSet.BOF). Met jour le textbox fiche.
menu Parcourir/Premier
fait passer au premier enregistrement (data1.RecordSet.MoveFirst) si le fichier nest pas vide
(data1.recordset.recordcount=0). Met jour le textbox fiche.
menu Parcourir/Dernier
fait passer au dernier enregistrement (data1.RecordSet.MoveLast) si le fichier nest pas vide
(data1.recordset.recordcount=0). Met jour le textbox fiche.
menu Edition/Ajouter
- permet dajouter un enregistrement la table
- met en mode Ajout denregistrement (data1.recordset.addnew)
- autorise les saisies sur les 5 champs code, nom, prix,... (enabled=true)
- inhibe les menus Edition, Parcourir, Quitter (enabled=false)
- affiche les boutons OK et Annuler (visible=true)
bouton OK
- valide une modification denregistrement (data1.recordset.Update)
- cache les boutons OK et Annuler (visible=false)
- autorise les options Edition, Parcourir, Quitter (enabled=true)
- met jour le textbox fiche
bouton Annuler
- invalide une modification denregistrement (data1.recordset.CancelUpdate)
- cache les boutons OK et Annuler (visible=false)
- autorise les options Edition, Parcourir, Quitter (enabled=true)
- met jour le textbox fiche
menu Edition/Modifier
- permet de modifier lenregistrement visualis sur le formulaire
- met en mode Edition denregistrement (data1.recordset.edit)
- autorise les saisies sur les 4 champs nom, prix,... (enabled=true) mais pas sur le champ code (enabled=false)
- inhibe les menus Edition, Parcourir, Quitter (enabled=false)
- affiche les boutons OK et Annuler (visible=true)
menu Edition/Supprimer
- permet de supprimer (data1.recordset.delete) lenregistrement visualis de la table
- passe la fiche suivante (data1.recordset.movenext)
menu Quitter
- dcharge la feuille (unload me)
vnement form_unload (cancel as integer)
- activ par lopration unload me ou la fermeture du formulaire par Alt-F4 ou un double-clic sur la case systme, donc
pas forcment par loption quitter.
- pose la question Voulez-vous vraiment quitter lapplication avec deux boutons Oui/Non (msgbox avec
style=vbyes+vbno)
- si la rponse est Non (=vbno) on met cancel -1 et on quitte la procdure form_unload. cancel -1 indique que la
fermeture de la fentre est refuse.
- si la rponse est oui (=vbyes), la base est ferme (data1.recordset.close, data1.database.close).
Partie 2 - Gestion des menus
Ici, on sintresse lautorisation/inhibition des menus. Aprs chaque opration changeant la fiche courante on appellera une
procdure quon pourra appeler Oueston. Celle-ci vrifiera les conditions suivantes :
. si le fichier est vide, on
inhibera les menus Parcourir, Edition/Supprimer,
autorisera les autres
. si la fiche courante est la premire fiche, on
inhibera Parcourir/Prcdent
autorisera le reste
. si la fiche courante est la dernire, on
inhibera Parcourir/Suivant
autorisera le reste
JDBC
223
JDBC
224
5.4.4 Exercice 4
On prsente ici une application Web sappuyant sur le serveur de lexercice 2. Cest un embryon dapplication de commerce
lectronique.
Le client commande des articles grce linterface Web suivante :
JDBC
225
Le bilan permet lutilisateur de connatre le dtail de sa facture. Lutilisateur a accs des dtails concernant un article slectionn
dans la liste droulante avec le bouton Informations :
Lorsque lutilisateur a demand le bilan de sa facture, il peut la valider avec la page suivante. Pour cela, il doit donner son adresse
lectronique et confirmer sa commande avec le bouton adquat.
JDBC
226
Une fois la commande confirme, lapplication la traite et envoie une page de confirmation :
JDBC
227
En ralit, lapplication ne traite pas la commande. Elle se contente denvoyer un courrier lectronique lutilisateur lui demandant
de rgler le montant des achats :
Cher client,
Vous trouverez ci-dessous le dtail de votre commande au magasin SuperPrix. Elle vous sera livre aprs
rception de votre chque tabli l'ordre de SuperPrix et envoyer l'adresse suivante :
SuperPrix
ISTIA
62 av Notre-Dame du Lac
49000 Angers
France
Nous vous remercions vivement de votre commande
---------------------------------------Votre commande
---------------------------------------article, quantit, prix unitaire, total
========================================
vlo, 2, 1202.00 F, 2404.00 F
skis nautiques, 3, 1800.00 F, 5400.00 F
Total payer : 7804 F
Question : Crer lquivalent de cette application Web avec une applet Java.
JDBC
228
cre une rfrence sur une tche asynchrone. Celle-ci est encore inactive. La tche cre doit
possder la mthode run : ce sera le plus souvent une classe drive de Thread qui sera utilise.
Thread(Runnable object) idem mais c'est l'objet Runnable pass en paramtre qui implmente la mthode run.
Regardons une premire application mettant en vidence l'existence d'un thread principal d'excution, celui dans lequel s'excute la
fonction main d'une classe :
// utilisation de threads
import java.io.*;
import java.util.*;
public class thread1{
public static void main(String[] arg)throws Exception {
// init thread courant
Thread main=Thread.currentThread();
// affichage
System.out.println("Thread courant : " + main.getName());
// on change le nom
main.setName("myMainThread");
// vrification
System.out.println("Thread courant : " + main.getName());
// boucle infinie
while(true){
// on rcupre l'heure
Calendar calendrier=Calendar.getInstance();
String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
+calendrier.get(Calendar.MINUTE)+":"
+calendrier.get(Calendar.SECOND);
// affichage
System.out.println(main.getName() + " : " +H);
// arrt temporaire
Thread.sleep(1000);
}//while
}//main
}//classe
Threads
229
1.
Dans l'exemple qui suit, les threads sont construits l'aide d'une classe anonyme drivant la classe Thread :
// on cre le thread i
tches[i]=new Thread() {
public void run() {
affiche();
}
};//df tches[i]
L'excution du thread T est lanc par T.start() : cette mthode appartient la classe Thread et opre un certain nombre
d'initialisations puis lance automatiquement la mthode run du Thread ou de l'interface Runnable. Le programme qui excute
l'instruction T.start() n'attend pas la fin de la tche T : il passe aussitt l'instruction qui suit. On a alors deux tches qui
s'excutent en parallle. Elles doivent souvent pouvoir communiquer entre elles pour savoir o en est le travail commun
raliser. C'est le problme de synchronisation des threads.
3.
Une fois lanc, le thread s'excute de faon autonome. Il s'arrtera lorsque la fonction run qu'il excute aura fini son travail.
4.
On peut attendre la fin de l'excution du Thread T par T.join(). On a l une instruction bloquante : le programme qui
l'excute est bloqu jusqu' ce que la tche T ait termin son travail. C'est galement un moyen de synchronisation.
Threads
230
tches[i]=new Thread() {
public void run() {
affiche();
}
};//df tches[i]
// on fixe le nom du thread
tches[i].setName(""+i);
// on lance l'excution du thread i
tches[i].start();
}//for
// fin de main
System.out.println("fin du thread " +main.getName());
}//Main
public static void affiche() {
// on rcupre l'heure
Calendar calendrier=Calendar.getInstance();
String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
+calendrier.get(Calendar.MINUTE)+":"
+calendrier.get(Calendar.SECOND);
// affichage dbut d'excution
System.out.println("Dbut d'excution de la mthode affiche dans le Thread " +
Thread.currentThread().getName()+ " : " + H);
// mise en sommeil pendant 1 s
try{
Thread.sleep(1000);
}catch (Exception ex){}
// on rcupre l'heure
calendrier=Calendar.getInstance();
H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
+calendrier.get(Calendar.MINUTE)+":"
+calendrier.get(Calendar.SECOND);
// affichage fin d'excution
System.out.println("Fin d'excution de la mthode affiche dans le Thread "
+Thread.currentThread().getName()+ " : " + H);
}// affiche
}//classe
Le thread principal, celui qui excute la fonction main, cre 5 autres threads chargs d'excuter la mthode statique affiche. Les
rsultats sont les suivants :
dbut du thread myMainThread
Dbut d'excution de la mthode affiche dans le Thread
fin du thread myMainThread
Dbut d'excution de la mthode affiche dans le Thread
Dbut d'excution de la mthode affiche dans le Thread
Dbut d'excution de la mthode affiche dans le Thread
Dbut d'excution de la mthode affiche dans le Thread
Fin d'excution de la mthode affiche dans le Thread 0
Fin d'excution de la mthode affiche dans le Thread 1
Fin d'excution de la mthode affiche dans le Thread 2
Fin d'excution de la mthode affiche dans le Thread 3
Fin d'excution de la mthode affiche dans le Thread 4
0 : 15:48:3
1
2
3
4
:
:
:
:
:
: 15:48:3
: 15:48:3
: 15:48:3
: 15:48:3
15:48:4
15:48:4
15:48:4
15:48:4
15:48:4
on voit tout d'abord que le lancement de l'excution d'un thread n'est pas bloquante. La mthode main a lanc l'excution
de 5 threads en parallle et a termin son excution avant eux. L'opration
// on lance l'excution du thread i
tches[i].start();
Threads
lance l'excution du thread tches[i] mais ceci fait, l'excution se poursuit immdiatement avec l'instruction qui suit sans
attendre la fin d'excution du thread.
tous les threads crs doivent excuter la mthode affiche. L'ordre d'excution est imprvisible. Mme si dans l'exemple,
l'ordre d'excution semble suivre l'ordre de lancement des threads, on ne peut en conclure de gnralits. Le systme
d'exploitation a ici 6 threads et un processeur. Il va distribuer le processeur ces 6 threads selon des rgles qui lui sont
propres.
on voit dans les rsultats une consquence de la mthode sleep. Dans l'exemple, c'est le thread 0 qui excute le premier la
mthode affiche. Le message de dbut d'excution est affich puis il excute la mthode sleep qui le suspend pendant 1
seconde. Il perd alors le processeur qui devient ainsi disponible pour un autre thread. L'exemple montre que c'est le thread
1 qui va l'obtenir. Le thread 1 va suivre le mme parcours ainsi que les autres threads. Lorsque la seconde de sommeil du
thread 0 va tre termine, son excution peut reprendre. Le systme lui donne le processeur et il peut terminer l'excution
de la mthode affiche.
231
Modifions notre programme pour le terminer la mthode main par les instructions :
// fin de main
System.out.println("fin du thread " +main.getName());
// arrt de l'application
System.exit(0);
affiche
affiche
affiche
affiche
dans
dans
dans
dans
le
le
le
le
Thread
Thread
Thread
Thread
0
1
2
3
:
:
:
:
16:5:45
16:5:45
16:5:45
16:5:45
elle arrte tous les threads de l'application et non simplement le thread main. La mthode main pourrait vouloir attendre la fin
d'excution des threads qu'elle a crs avant de se terminer elle-mme. Cela peut se faire avec la mthode join de la classe Thread :
// attente de tous les threads
for(int i=0;i<tches.length;i++){
// on attend le thread i
tches[i].join();
}//for
// fin de main
System.out.println("fin du thread " +main.getName());
// arrt de l'application
System.exit(0);
0
1
2
3
4
:
:
:
:
:
: 16:11:9
: 16:11:9
: 16:11:9
: 16:11:9
: 16:11:9
16:11:10
16:11:10
16:11:10
16:11:10
16:11:10
C2
Cn
Nous utilisons tous les jours des applications de l'internet correspondant ce schma : services Web, messagerie lectronique,
consultation de forums, transfert de fichiers... Dans le schma ci-dessus, le serveur S1 doit servir les clients Ci de faon simultane.
Threads
232
Si nous prenons l'exemple d'un serveur FTP (File Transfer Protocol) qui dlivre des fichiers ses clients, nous savons qu'un
transfert de fichier peut prendre parfois plusieurs heures. Il est bien sr hors de question qu'un client monopolise tout seul le
serveur une telle dure. Ce qui est fait habituellement, c'est que le serveur cre autant de threads d'excution qu'il y a de clients.
Chaque thread est alors charg de s'occuper d'un client particulier. Le processeur tant partag cycliquement entre tous les threads
actifs de la machine, le serveur passe alors un peu de temps avec chaque client assurant ainsi la simultanit du service.
Serveur
Clients
thread 1
client 1
thread 2
client 2
thread n
client n
Pour que l'horloge vive, il faut qu'un processus s'occupe de changer l'heure toutes les secondes. En mme temps, il faut surveiller
les vnements qui se produisent dans la fentre : lorsque l'utilisateur cliquera sur le bouton "Arrter", il faudra stopper l'horloge.
On a l deux tches parallles et asynchrones : l'utilisateur peut cliquer n'importe quand.
Considrons le moment o l'horloge n'a pas encore t lance et o l'utilisateur clique sur le bouton "Lancer". On a l un
vnement classique et on pourrait penser qu'une mthode du thread dans lequel s'excute la fentre peut alors grer l'horloge.
Seulement lorsqu'une mthode de l'application graphique s'excute, le thread de celle-ci n'est plus l'coute des vnements de
l'interface graphique. Ceux-ci se produisent et sont mis dans une file d'attente pour tre traits par l'application lorsque la mthode
actuellement en cours d'excution sera acheve. Dans notre exemple d'horloge, la mthode sera toujours en cours d'excution
puisque seul le clic sur le bouton "Arrter" peut la stopper. Or cet vnement ne sera trait que lorsque la mthode sera acheve.
On tourne en rond.
La solution ce problme serait que lorsque l'utilisateur clique sur le bouton "Lancer", une tche soit lance pour grer l'horloge
mais que l'application puisse continuer couter les vnements qui se produisent dans la fentre. On aurait alors deux tches
distinctes qui s'excuteraient en parallle :
gestion de l'horloge
coute des vnements de la fentre
Revenons notre horloge graphique :
n
1
type
JTextField
(Editable=false)
JButton
nom
txtHorloge
rle
affiche l'heure
btnGoStop
Threads
233
java.awt.*;
java.awt.event.*;
javax.swing.*;
java.util.*;
Threads
234
Lorsque l'utilisateur clique sur le bouton "Lancer", un thread est cr l'aide d'une classe anonyme :
Thread thHorloge=new Thread(){
public void run(){
runHorloge();
}
La mthode run du thread renvoie la mthode runHorloge de l'application. Ceci fait, le thread est lanc :
// on lance le thread
thHorloge.start();
Lorsque nous chargeons directement ce document dans IE en double-cliquant dessus, nous obtenons l'affichage suivant :
Threads
235
Tous les lments ncessaires l'applet sont dans cet exemple dans le mme dossier :
E:\data\serge\Jbuilder\horloge\1>dir
13/06/2002 12:17
3 174
13/06/2002 12:17
658
13/06/2002 12:17
512
13/06/2002 12:20
245
appletHorloge.class
appletHorloge$1.class
appletHorloge$2.class
appletHorloge.htm
Notre applet peut tre amliore. Nous avons dit qu'au chargement de l'applet, la mthode init tait excute puis ensuite la
mthode start si elle existe. De plus, lorsque l'utilisateur quitte la page, la mthode stop est excute si elle existe. Lorsqu'il revient sur
la page de l'applet, la mthode start est de nouveau appele. Lorsqu'une applet met en uvre des threads d'animation visuelle, on
utilise souvent les mthodes start et stop de l'applet pour lancer et arrter les threads. Il est en effet inutile qu'un thread d'animation
visuelle continue travailler en arrire-plan alors que l'animation est cache.
Nous ajoutons donc notre applet les mthodes start et stop suivantes :
public void stop(){
// la page est cache
// suivi
System.out.println("Page stop");
// la page est cache - on arrte le thread
finHorloge=true;
}
public void start(){
// la page rapparat
// suivi
System.out.println("Page start");
// on relance un nouveau thread horloge si ncessaire
if(btnGoStop.getText().equals("Arrter")){
// on change le libell
btnGoStop.setText("Lancer");
// et on fait comme si l'utilisateur avait cliqu dessus
btnGoStop_actionPerformed(null);
}//if
}//start
Par ailleurs, nous avons ajout un suivi dans la mthode run du thread pour savoir quand il dmarre et s'arrte :
private void runHorloge(){
// suivi
System.out.println("Thread horloge lanc");
// on boucle tant qu'on nous a pas dit d'arrter
while( ! finHorloge){
// on rcupre l'heure
Calendar calendrier=Calendar.getInstance();
String H=calendrier.get(Calendar.HOUR_OF_DAY)+":"
+calendrier.get(Calendar.MINUTE)+":"
+calendrier.get(Calendar.SECOND);
// on l'affiche dans le champ T
txtHorloge.setText(H);
// attente d'une seconde
try{
Thread.sleep(1000);
} catch (Exception e){
// sortie avec erreur
return;
Threads
236
}//try
}// while
// suivi
System.out.println("Thread horloge termin");
}// runHorloge
Avec AppletViewer, l'vnement start se produit lorsque la fentre d'AppletViewer est visible et l'vnement stop lorsqu'on la met en
icne. Les rsultats ci-dessus montrent que lorsque le document HTML est cach, le thread est bien arrt s'il tait actif.
1
4
2
3
n
1
2
3
4
type
JTextField
JTextfield
(non ditable)
JTextField
(non ditable)
JButton
nom
rle
txtAGnrer indique le nombre de threads gnrer
txtGnres indique le nombre de threads gnrs
txtStatus
donne des informations sur les erreurs rencontres et sur l'application elle-mme
237
Les threads gnrs se partagent une ressource : la valeur du champ 2. Nous cherchons montrer ici les problmes que l'on
rencontre dans une telle situation. Voici un exemple d'excution :
On voit qu'on a demand la gnration de 1000 threads et qu'il en a t compt que 7. Le code utile de l'application est le suivant :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class interfaceSynchro extends JFrame {
JPanel contentPane;
JLabel jLabel1 = new JLabel();
JTextField txtAGnrer = new JTextField();
JButton btnGnrer = new JButton();
JTextField txtStatus = new JTextField();
JTextField txtGnrs = new JTextField();
JLabel jLabel2 = new JLabel();
// variables d'instance
Thread[] tches=null;
int[] compteurs=null;
// les threads
// les compteurs
//Construire le cadre
public interfaceSynchro() {
..........
}
//Initialiser le composant
private void jbInit() throws Exception
......................
}
Threads
238
return;
}//catch
// au dpart pas de threads gnrs
txtGnrs.setText("0"); // cpteur de tches 0
// on gnre et on lance les threads
tches=new Thread[nbThreads];
compteurs=new int[nbThreads];
for(int i=0;i<tches.length;i++){
// on cre le thread i
tches[i]=new Thread() {
public void run() {
incrmente();
}
};//thread i
// on dfinit son nom
tches[i].setName(""+i);
// on lance son excution
tches[i].start();
}//for
}//gnrer
// incremente
private void incrmente(){
// on rcupre le n du thread
int iThread=0;
try{
iThread=Integer.parseInt(Thread.currentThread().getName());
}catch(Exception ex){}
// on lit la valeur du compteur de tches
try{
compteurs[iThread]=Integer.parseInt(txtGnrs.getText());
} catch (Exception e){}
// on l'incrmente
compteurs[iThread]++;
// on patiente 100 millisecondes - le thread va alors perdre le processeur
try{
Thread.sleep(100);
} catch (Exception e){
System.exit(0);
}
// on affiche le nouveau compteur
txtGnrs.setText("");
txtGnrs.setText(""+compteurs[iThread]);
// suivi
System.out.println("Thread " + iThread + " : " + compteurs[iThread]);
}// incremente
}// classe
Dtaillons le code :
// les threads
// les compteurs
Le tableau tches sera le tableau des threads gnrs. Le tableau compteurs sera associ au tableau tches. Chaque tche aura un
compteur propre pour rcuprer la valeur du champ txtGnrs de l'interface graphique.
lors d'un clic sur le bouton Gnrer, la mthode btnGnrer_actionPerformed est excute.
celle-ci commence par rcuprer le nombre de threads gnrer. Au besoin, une erreur est signale si ce nombre n'est pas
exploitable. Elle gnre ensuite les threads demands en prenant soin de noter leurs rfrences dans un tableau et en donnant
chacun d'eux un numro. La mthode run des threads gnrs renvoie sur la mthode incrmente de la classe. Les threads sont
tous lancs (start). Le tableau des compteurs associs aux threads est galement cr.
la mthode incrmente :
lit la valeur actuelle du champ txtGnrs et la stocke dans le compteur appartenant au thread en cours d'excution
s'arrte 100 ms, ceci afin de perdre volontairement le processeur
Threads
239
Le mot cl synchronized signifie qu'un seul thread la fois peut excuter la mthode incrmente. Considrons les notations
suivantes :
l'objet fentre F qui cre les threads dans btnGnrer_actionPerformed
deux threads T1 et T2 crs par F
Les deux threads sont crs par F puis lancs. Ils vont donc tous deux excuter la mthode F.run. Supposons que T1 arrive le
premier. Il excute F.run puis F.incremente qui est une mthode synchronise. Il lit la valeur 0 du compteur, l'incrmente puis s'arrte
100 ms. Le processeur est alors donn T2 qui son tour excute F.run puis F.incremente. Et l il est bloqu car le thread T1 est en
cours d'excution de F.incremente et le mot cl synchronized assure qu'un seul thread la fois peut excuter F.incremente. T2 perd
alors le processeur son tour sans avoir pu lire la valeur du compteur. Au bout des 100 ms, T1 rcupre le processeur, affiche la
valeur 1 du compteur, quitte F.incremente puis F.run et se termine. T2 rcupre alors le processeur et peut cette fois excuter
F.incremente car T1 n'est plus en cours d'excution de cette mthode. T2 lit alors la valeur 1 du compteur, l'incrmente et s'arrte 100
ms. Au bout de 100 ms, il rcupre le processeur, affiche la valeur 2 du compteur et se termine lui aussi. Cette fois, la valeur
obtenue est correcte. Voici un exemple test :
Threads
240
On pourrait aussi dire que la ressource critique est l'objet F lui-mme. C'est plus strict que dans le cas o la ressource critique est
F.incremente. En effet, dans ce dernier cas, si un thread T1 execute F.incremente, un thread T2 ne pourra pas excuter F.incremente mais
pourra excuter une autre mthode de l'objet F qu'elle soit synchronise ou non. Dans le cas ou l'objet F est lui-mme la ressource
critique, lorsque un thread T1 excute une section synchronise de cet objet, toutre autre section synchronise de l'objet devient
inaccessible pour les autres threads. Ainsi si un thread T1 excute la mthode synchronise F.incremente, un thread T2 ne pourra pas
excuter non seulement F.incremente mais galement toute autre section synchronise de F, ceci mme si aucun thread ne l'utilise.
C'est donc une mthode plus contraignante.
Supposons donc que la fentre devienne la ressource critique. On crira alors :
// incremente
private void incrmente(){
// section critique
synchronized(this){
// on rcupre le n du thread
int iThread=0;
try{
iThread=Integer.parseInt(Thread.currentThread().getName());
}catch(Exception ex){}
// on lit la valeur du compteur de tches
try{
compteurs[iThread]=Integer.parseInt(txtGnrs.getText());
} catch (Exception e){}
// on l'incrmente
compteurs[iThread]++;
// on patiente 100 millisecondes - le thread va alors perdre le processeur
try{
Thread.sleep(100);
} catch (Exception e){
System.exit(0);
}
// on affiche le nouveau compteur
txtGnrs.setText("");
txtGnrs.setText(""+compteurs[iThread]);
}//synchronized
}// incremente
Tous les threads utilisent la fentre this pour se synchroniser. A l'excution, on obtient les mmes rsultats corrects que
prcdemment. On peut en fait se synchroniser sur n'importe quel objet connu de tous les threads. Voici par exemple une autre
mthode qui donne les mmes rsultats :
// variables d'instance
Thread[] tches=null;
// les threads
int[] compteurs=null;
// les compteurs
Object synchro=new Object(); // un objet de synchronisation de threads
// incremente
private void incrmente(){
// section critique
synchronized(synchro){
..............
}//synchronized
}// incremente
La fentre cre un objet de type Object qui servira la synchronisation des threads. Cette mthode est meilleure que celle qui se
synchronise sur l'objet this parce que moins contraignante. Ici, si un thread T1 est dans la section synchronise de incrmente et qu'un
thread T2 veuille excuter une autre section synchronise du mme objet this mais synchronise par un autre objet que synchro, il le
pourra.
Threads
241
La premire instruction o un thread boucle en attendant que peutPasser passe vrai est maladroite : le thread occupe le processeur
inutilement. On parle d'attente active. On peut amliorer l'criture comme suit :
while(!
peutPasser){
// on attend que peutPasser passe vrai
while
Thread.sleep(100); // arrt pendant 100 ms
}
peutPasser=false
false;
// aucun autre thread ne doit pas passer
false
section critique;
// ici le thread est tout seul
peutPasser=true
true;
// un autre thread peut passer dans la section critique
true
La boucle d'attente est ici meilleure : si le thread ne peut pas passer, il se met en sommeil pendant 100 ms avant de vrifier de
nouveau s'il peut passer ou non. Le processeur va entre-temps tre attribu d'autres threads du systme.
Ces deux mthodes sont en fait incorrectes : elle n'empche pas deux threads de s'engouffrer en mme temps dans la section
critique. Supposons qu'un thread T1 dtecte que peutPasser est vrai. Il va alors passer l'instruction suivante o il remet peutPasser
faux pour bloquer les autres Threads. Seulement, il peut trs bien tre interrompu ce moment, soit parce que sa part de temps du
processeur est puise, soit parce qu'une tche plus prioritaire a demand le processeur ou pour une autre raison. Le rsultat est
qu'il perd le processeur. Il le retrouvera un peu plus tard. Entre-temps d'autres tches vont obtenir le processeur dont peut-tre un
thread T2 qui boucle en attendant que peutPasser passe vrai. Lui aussi va dcouvrir que peutPasser est vrai (le premier thread n'a
pas eu le temps de le mettre faux) et va passer lui-aussi dans la section critique. Ce qu'il ne fallait pas.
La squence
while(!
peutPasser){
// on attend que peutPasser passe vrai
while
try{
try
Thread.sleep(100); // arrt pendant 100 ms
} catch (Exception e) {}
}// while
peutPasser=false
false;
// aucun autre thread ne doit passer
false
est une squence critique qu'il faut protger par une synchronisation. S'inspirant de l'exemple prcdent, on peut crire :
synchronized(synchro){
while(! peutPasser){
// on attend que peutPasser passe vrai
try{
Thread.sleep(100); // arrt pendant 100 ms
} catch (Exception e) {}
}//while
peutPasser=false;
// aucun autre thread ne doit pas passer
}// synchronized
section critique;
// ici le thread est tout seul
peutPasser=true;
// un autre thread peut passer dans la section critique
Cet exemple fonctionne correctement. On peut l'amliorer en vitant l'attente semi-active du thread lorsqu'il surveille rgulirement
la valeur du boolen peutPasser. Au lieu de se rveiller rgulirement toutes les 100 ms pour vrifier l'tat de peutPasser, il peut
s'endormir et demander ce qu'on le rveille lorsque peutPasser sera vrai. On crit cela de la faon suivante :
synchronized(synchro){
if (! peutPasser) {
try{
synchro.wait();
// si on ne peut pas passer alors on attend
} catch (Exception e){
}
}
peutPasser=false;
// aucun autre thread ne doit pas passer
}// synchronized
L'opration synchro.wait() ne peut tre faite que par un thread "propritaire" momentan de l'objet synchro. Ici, c'est la squence :
synchronized(synchro){
Threads
242
}// synchronized
qui assure que le thread est propritaire de l'objet synchro. Par l'opration synchro.wait(), le thread cde la proprit du verrou de
synchronisation. Pourquoi cela ? En gnral parce qu'il lui manque des ressources pour continuer travailler. Alors plutt que de
bloquer les autres threads en attente de la ressource synchro, il la cde et se met en attente de la ressource qui lui manque. Dans notre
exemple, il attend que le boolen peutPasser passe vrai. Comment sera-t-il averti de cet vnement ? De la faon suivante :
synchronized(synchro){
if (! peutPasser) {
try{
synchro.wait();
// si on ne peut pas passer alors on attend
} catch (Exception e){
}
}
peutPasser=false;
// aucun autre thread ne doit passer
}// synchronized
section critique...
synchronized(synchro){
synchro.notify();
}
Considrons le premier thread qui passe le verrou de synchronisation. Appelons le T1. Imaginons qu'il trouve le boolen peutPasser
vrai puisqu'il est le premier. Il le passe donc faux. Il sort ensuite de la section critique verrouille par l'objet synchro. Un autre
thread pourra alors entrer dans la section critique pour tester la valeur de peutPasser. Il le trouvera faux et se mettra alors en attente
d'un vnement (wait). Ce faisant, il cde la proprit de l'objet synchro. Un autre thread peut alors entrer dans la section critique : lui
aussi se mettra en attente car peutPasser est faux. On peut donc avoir plusieurs threads en attente d'un vnement sur l'objet synchro.
Revenons au thread T1 qui lui est pass. Il excute la section critique puis va indiquer qu'un autre thread peut maintenant passer. Il
le fait avec la squence :
synchronized(synchro){
synchro.notify();
}
Il doit d'abord reprendre possession de l'objet synchro avec l'instruction synchronized. Ca ne doit pas poser de problme puisqu'il est
en comptition avec des threads qui, s'ils obtiennent momentanment l'objet synchro doivent l'abandonner par un wait parce qu'ils
trouvent peutPasser faux. Donc notre thread T1 va bien finir par obtenir la proprit de l'objet synchro. Ceci fait, il indique par
l'opration synchro.notify que l'un des threads bloqus par un synchro.wait doit tre rveill. Ensuite il abandonne de nouveau la
proprit de l'objet synchro qui va alors tre donne l'un des threads en attente. Celui-ci poursuit son excution avec l'instruction
qui suit le wait qui l'avait mis en attente. A son tour, il va excuter la section critique et excuter un synchro.notify pour librer un autre
thread. Et ainsi de suite.
Voyons ce mode de fonctionnement sur l'exemple du comptage dj tudi.
void btnGnrer_actionPerformed(ActionEvent e) {
//gnration des threads
// on lit le nombre de threads gnrer
int nbThreads=0;
try{
// lecture du champ contenant le nbre de threads
nbThreads=Integer.parseInt(txtAGnrer.getText().trim());
// positif >
if(nbThreads<=0) throw new Exception();
}catch(Exception ex){
//erreur
txtStatus.setText("Nombre invalide");
// on recommnece
txtAGnrer.requestFocus();
return;
}//catch
// RAZ compteur de tches
txtGnrs.setText("0"); // cpteur de tches 0
// 1er thread peut passer
peutPasser=true;
// on gnre et on lance les threads
tches=new Thread[nbThreads];
compteurs=new int[nbThreads];
for(int i=0;i<tches.length;i++){
// on cre le thread i
tches[i]=new Thread() {
public void run() {
Threads
243
synchronise();
}
};//thread i
// on dfinit son nom
tches[i].setName(""+i);
// on lance son excution
tches[i].start();
}//for
}//gnrer
Maintenant, les threads n'excutent plus la mthode incrmente mais la mthode synchronise suivante :
// tape de synchronisation des threads
public void synchronise(){
// on demande l'accs la section critique
synchronized(synchro){
try{
// peut-on passer ?
if(! peutPasser){
System.out.println(Thread.currentThread().getName()+ " en attente");
synchro.wait();
}
// on est pass - on interdit aux autres threads de passer
peutPasser=false;
} catch(Exception e){
txtStatus.setText(""+e);
return;
}//try
}// synchronized
// section critique
System.out.println(Thread.currentThread().getName()+ " pass");
incrmente();
// on a fini - on libre un ventuel thread bloqu l'entre de la section critique
peutPasser=true;
System.out.println(Thread.currentThread().getName()+ " termin");
synchronized(synchro){
synchro.notify();
}// synchronized
} // synchronise
La mthode synchronise a pour but de faire passer les threads un par un. Elle utilise pour cela une variable de synchronisation synchro.
La mthode incrmente n'est maintenant plus protge par le mot cl synchronized :
// incremente
private void incrmente(){
// on rcupre le n du thread
int iThread=0;
try{
iThread=Integer.parseInt(Thread.currentThread().getName());
}catch(Exception ex){}
// on lit la valeur du compteur de tches
try{
compteurs[iThread]=Integer.parseInt(txtGnrs.getText());
} catch (Exception e){}
// on l'incrmente
compteurs[iThread]++;
// on patiente 100 millisecondes - le thread va alors perdre le processeur
try{
Thread.sleep(100);
} catch (Exception e){
System.exit(0);
}
// on affiche le nouveau compteur
txtGnrs.setText("");
txtGnrs.setText(""+compteurs[iThread]);
}// incremente
pass
en attente
en attente
en attente
en attente
termin
pass
Threads
244
1
2
2
3
3
4
4
termin
pass
termin
pass
termin
pass
termin
Soient T0 T4 les 5 threads gnrs par l'application. T0 prend le premier la proprit du verrou synchro et trouve peutPasser vrai. Il
met peutPasser faux et passe : c'est le sens du premier message pass. Selon toute vraisemblance, il continue et excute la section
critique et notamment la mthode incrmente. Dans celle-ci, il va s'endormir pendant 100 ms (sleep). Il lche donc le processeur. Celuici est accord un autre thread, le thread T1 qui obtient alors la proprit de l'objet synchro. Il dcouvre qu'il ne peut pas passer et se
met en attente (wait). Il lche alors la proprit de l'objet synchro ainsi que le processeur. Celui-ci est accord au thread T2 qui subit le
mme sort. Pendant les 100 ms d'arrt de T0, les threads T1 T4 sont donc mis en attente. c'est le sens des 4 messages "en attente".
Au bout de 100 ms, T0 rcupre le processeur et termine son travail : c'est le sens du message "0 termin". Il libre ensuite l'un des
threads bloqus et se termine. Le processeur libr est affect alors un thread disponible : celui qui vient d'tre libr. Ici c'est T1.
Le thread T1 entre alors dans la section critique : c'est le sens du message "1 pass". Il fait ce qu'il a faire et va son tour s'arrter
100 ms. Le processeur est alors disponible pour un autre thread mais tous sont en attente d'un vnement : aucun d'eux ne peut
prendre le processeur. Au bout de 100 ms, le thread T1 rcupre le processeur et se termine : c'est le sens du message "1 termin".
Les Threads T1 T4 vont avoir le mme comportement que T1 : c'est le sens des trois sries de messages : "pass", "termin".
Threads
245
7. Programmation TCP-IP
7.1 Gnralits
7.1.1 Les protocoles de l'Internet
Nous donnons ici une introduction aux protocoles de communication de l'Internet, appels aussi suite de protocoles TCP/IP
(Transfer Control Protocol / Internet Protocol), du nom des deux principaux protocoles. Il est bon que le lecteur ait une comprhension
globale du fonctionnement des rseaux et notamment des protocoles TCP/IP avant d'aborder la construction d'applications
distribues.
Le texte qui suit est une traduction partielle d'un texte que l'on trouve dans le document "Lan Workplace for Dos - Administrator's
Guide" de NOVELL, document du dbut des annes 90.
Le concept gnral de crer un rseau d'ordinateurs htrognes vient de recherches effectues par le DARPA (Defense Advanced
Research Projects Agency) aux Etats-Unis. Le DARPA a dvelopp la suite de protocoles connue sous le nom de TCP/IP qui
permet des machines htrognes de communiquer entre elles. Ces protocoles ont t tests sur un rseau appel ARPAnet,
rseau qui devint ultrieurement le rseau INTERNET. Les protocoles TCP/IP dfinissent des formats et des rgles de
transmission et de rception indpendants de l'organisation des rseaux et des matriels utiliss.
Le rseau conu par le DARPA et gr par les protocoles TCP/IP est un rseau commutation de paquets. Un tel rseau
transmet l'information sur le rseau, en petits morceaux appels paquets. Ainsi, si un ordinateur transmet un gros fichier, ce dernier
sera dcoup en petits morceaux qui seront envoys sur le rseau pour tre recomposs destination. TCP/IP dfinit le format de
ces paquets, savoir :
.
.
.
.
origine du paquet
destination
longueur
type
|-------------------------------------|
|
Application
|
|-------------------------------------|
|
Prsentation
|
|-------------------------------------|
|
Session
|
|-------------------------------------|
|
Transport
|
|-------------------------------------|
|
Rseau
|
|-------------------------------------|
|
Liaison
|
|-------------------------------------|
|
Physique
|
|-------------------------------------|
Chaque couche reoit des services de la couche infrieure et offre les siens la couche suprieure. Supposons que deux applications
situes sur des machines A et B diffrentes veulent communiquer : elles le font au niveau de la couche Application. Elles n'ont pas
Programmation TCP-IP
246
besoin de connatre tous les dtails du fonctionnement du rseau : chaque application remet l'information qu'elle souhaite
transmettre la couche du dessous : la couche Prsentation. L'application n'a donc connatre que les rgles d'interfaage avec la
couche Prsentation.
Une fois l'information dans la couche Prsentation, elle est passe selon d'autres rgles la couche Session et ainsi de suite, jusqu' ce
que l'information arrive sur le support physique et soit transmise physiquement la machine destination. L, elle subira le
traitement inverse de celui qu'elle a subi sur la machine expditeur.
A chaque couche, le processus expditeur charg d'envoyer l'information, l'envoie un processus rcepteur sur l'autre machine
apartenant la mme couche que lui. Il le fait selon certaines rgles que l'on appelle le protocole de la couche. On a donc le
schma de communication final suivant :
7
6
5
4
3
2
1
Machine A
Machine B
+-------------------------------------+
+----------------------------+
Application
v
^
Application
+------------------------------------
+---------------------------
Prsentation
v
^
Prsentation
+------------------------------------
+---------------------------
Session
v
^
Session
+------------------------------------
+---------------------------
Transport
v
^
Transport
+------------------------------------
+---------------------------
Rseau
v
^
Rseau
+------------------------------------
+---------------------------
Liaison
v
^
Liaison
+------------------------------------
+---------------------------
Physique
v
^
Physique
+------------------------------------+
+---------------------------+
^
+-->------->------>-----+
Liaison de
donnes
Rseau
Transport
Session
Prsentation
Application
Assure la transmission de bits sur un support physique. On trouve dans cette couche des quipements
terminaux de traitement des donnes (E.T.T.D.) tels que terminal ou ordinateur, ainsi que des quipements
de terminaison de circuits de donnes (E.T.C.D.) tels que modulateur/dmodulateur, multiplexeur,
concentrateur. Les points d'intrt ce niveau sont :
. le choix du codage de l'information (analogique ou numrique)
. le choix du mode de transmission (synchrone ou asynchrone).
Masque les particularits physiques de la couche Physique. Dtecte et corrige les erreurs de transmission.
Gre le chemin que doivent suivre les informations envoyes sur le rseau. On appelle cela le routage :
dterminer la route suivre par une information pour qu'elle arrive son destinataire.
Permet la communication entre deux applications alors que les couches prcdentes ne permettaient que la
communication entre machines. Un service fourni par cette couche peut tre le multiplexage : la couche
transport pourra utiliser une mme connexion rseau (de machine machine) pour transmettre des
informations appartenant plusieurs applications.
On va trouver dans cette couche des services permettant une application d'ouvrir et de maintenir une
session de travail sur une machine distante.
Elle vise uniformiser la reprsentation des donnes sur les diffrentes machines. Ainsi des donnes
provenant d'une machine A, vont tre "habilles" par la couche Prsentation de la machine A, selon un format
standard avant d'tre envoyes sur le rseau. Parvenues la couche Prsentation de la machine destinatrice B
qui les reconnatra grce leur format standard, elles seront habilles d'une autre faon afin que l'application
de la machine B les reconnaisse.
A ce niveau, on trouve les applications gnralement proches de l'utilisateur telles que la messagerie
lectronique ou le transfert de fichiers.
Programmation TCP-IP
247
+-----------------------+
Application
+-----------------------
Prsentation
+-----------------------
Session
+-----------------------
Transport
+-----------------------
Rseau
+-----------------------
Liaison
+-----------------------
Physique
+-----------------------+
7
6
5
4
3
2
1
+-------------------------------------------------------+
DNS
Autres
+-------------------------+-----------------------------
TCP
UDP
+-------------------------------------------------------
IP
ICMP
ARP
RARP
+-------------------------------------------------------
MLID1
MLID2 MLID3
MLID4
+-------------------------------------------------------
Ethernet
Token-ring
Autres
+-------------------------------------------------------+
Couche Physique
En rseau local, on trouve gnralement une technologie Ethernet ou Token-Ring. Nous ne prsentons ici que la technologie
Ethernet.
Ethernet
C'est le nom donn une technologie de rseaux locaux commutation de paquets invente PARC Xerox au dbut des annes
1970 et normalise par Xerox, Intel et Digital Equipment en 1978. Le rseau est physiquement constitu d'un cble coaxial
d'environ 1,27 cm de diamtre et d'une longueur de 500 m au plus. Il peut tre tendu au moyen de rpteurs, deux machines ne
pouvant tre spares par plus de deux rpteurs. Le cble est passif : tous les lments actifs sont sur les machines raccordes au
cble. Chaque machine est relie au cble par une carte d'accs au rseau comprenant :
un transmetteur (transceiver) qui dtecte la prsence de signaux sur le cble et convertit les signaux analogiques en signaux
numrique et inversement.
un coupleur qui reoit les signaux numriques du transmetteur et les transmet l'ordinateur pour traitement ou
inversement.
Capacit de 10 Mgabits/seconde.
Topologie en bus : toutes les machines sont raccordes au mme cble
---------------------------------------------------------
+--------+
+--------+
+-----------+
+--------+
+--------+
+-----------+
Machine A
B
C
Rseau diffusant - Une machine qui met transfre des informations sur le cble avec l'adresse de la machine destinatrice.
Toutes les machines raccordes reoivent alors ces informations et seule celle qui elles sont destines les conserve.
La mthode d'accs est la suivante : le transmetteur dsirant mettre coute le cble - il dtecte alors la prsence ou non
d'une onde porteuse, prsence qui signifierait qu'une transmission est en cours. C'est la technique CSMA (Carrier Sense
Multiple Access). En l'absence de porteuse, un transmetteur peut dcider de transmettre son tour. Ils peuvent tre
plusieurs prendre cette dcision. Les signaux mis se mlangent : on dit qu'il y a collision. Le transmetteur dtecte cette
situation : en mme temps qu'il met sur le cble, il coute ce qui passe rellement sur celui-ci. S'il dtecte que
l'information transitant sur le cble n'est pas celle qu'il a mise, il en dduit qu'il y a collision et il s'arrtera d'mettre. Les
autres transmetteurs qui mettaient feront de mme. Chacun reprendra son mission aprs un temps alatoire dpendant
de chaque transmetteur. Cette technique est appele CD (Collision Detect). La mthode d'accs est ainsi appele
CSMA/CD.
un adressage sur 48 bits. Chaque machine a une adresse, appele ici adresse physique, qui est inscrite sur la carte qui la relie
au cble. On appelle cet adresse, l'adresse Ethernet de la machine.
Couche Rseau
Programmation TCP-IP
248
Nous trouvons au niveau de cette couche, les protocoles IP, ICMP, ARP et RARP.
IP (Internet Protocol)
ICMP
(Internet Control Message Protocol)
ARP
(Address Resolution Protocol)
RARP
(Reverse Address Resolution Protocol)
Couches Transport/Session
Dans cette couche, on trouve les protocoles suivants :
TCP (Transmission Control Protocol)
UDP (User Datagram Protocol)
Couches Application/Prsentation/Session
On trouve ici divers protocoles :
TELNET
TFTP (Trivial
Protocol)
File
SMTP (Simple Mail Transfer permet l'change de messages entre utilisateurs du rseau
protocol)
DNS (Domain Name System)
XDR
(eXternal
Representation)
Data cr par sun MicroSystems, il spcifie une reprsentation standard des donnes,
indpendante des machines
dfini galement par Sun, c'est un protocole de communication entre applications distantes,
indpendant de la couche transport. Ce protocole est important : il dcharge le
programmeur de la connaissance des dtails de la couche transport et rend les applications
portables. Ce protocole s'appuie sur sur le protocole XDR
toujours dfini par Sun, ce protocole permet une machine, de "voir" le systme de fichiers
d'une autre machine. Il s'appuie sur le protocole RPC prcdent
249
destinatrice o elle retraversera les mmes couches, en sens inverse cette fois-ci, jusqu' arriver l'application destinatrice des
informations envoyes. Le schma suivant montre le parcours de l'information :
+----------------+
+---------------------------+
Application
Application
+----------------+
+---------------------------+
<----------- messages ou streams ---------->
+----------------+
+---------------------------+
Transport
Transport
(Udp/Tcp)
(Udp/tcp)
+----------------+
+---------------------------+
<----------- datagrammes (UDP) ----------->
+----------------+
ou
+---------------------------+
Rseau (IP)
segments (TCP)
Rseau (IP)
+----------------+
+---------------------------+
<----------- datagrammes IP -------------->
+----------------+
+----------------------------+
Interface rseau
Interface rseau
+---------------+
+----------------------------+
<---------- trames rseau ------------->
+----------------------------------------------+
rseau physique
Prenons un exemple : l'application FTP, dfinie au niveau de la couche Application et qui permet des transferts de fichiers entre
machines.
.
.
.
.
.
.
.
.
.
.
250
Les adresses IP doivent tre toutes diffrentes. Des organismes officiels sont chargs de les distribuer. En fait, ces organismes
dlivrent une adresse pour des rseaux locaux, par exemple 193.49.144.0 pour le rseau de la facult des sciences d'Angers.
L'administrateur de ce rseau peut ensuite affecter les adresses IP 193.49.144.1 193.49.144.254 comme il l'entend. Cette adresse
est gnralement inscrite dans un fichier particulier de chaque machine relie au rseau.
l'adresse du rseau
l'adresse d'un noeud de ce rseau
Selon la taille de ces deux champs, les adresses IP sont divises en 3 classes : classes A, B et C.
Classe A
L'adresse IP : I1.I2.I3.I4 a la forme R1.N1.N2.N3 o
R1
N1.N2.N3
1 octet
3 octets
+-------------------------------------------------------------------------------+
0 adr. rseau
adresse noeud
+-------------------------------------------------------------------------------+
L'adresse rseau est sur 7 bits et l'adresse du noeud sur 24 bits. On peut donc avoir 127 rseaux de classe A, chacun comportant
jusqu' 224 noeuds.
Classe B
Ici, l'adresse IP : I1.I2.I3.I4 a la forme R1.R2.N1.N2 o
R1.R2
N1.N2
+-------------------------------------------------------------------------------+
L'adresse du rseau est sur 2 octets (14 bits exactement) ainsi que celle du noeud. On peut donc avoir 214 rseaux de classe B
chacun comportant jusqu' 216 noeuds.
Classe C
Dans cette classe, l'adresse IP : I1.I2.I3.I4 a la forme R1.R2.R3.N1 o
R1.R2.R3
N1
251
3 octets
1 octet
+-------------------------------------------------------------------------------+
110
adresse rseau
adr. noeud
+-------------------------------------------------------------------------------+
L'adresse rseau est sur 3 octets (moins 3 bits) et l'adresse du noeud sur 1 octet. On peut donc avoir 221 rseaux de classe C
comportant jusqu' 256 noeuds.
L'adresse de la machine Lagaffe de la facult des sciences d'Angers tant 193.49.144.1, on voit que l'octet de poids fort vaut 193,
c'est dire en binaire 11000001. On en dduit que le rseau est de classe C.
Adresses rserves
.
.
.
Certaines adresses IP sont des adresses de rseaux plutt que des adresses de noeuds dans le rseau. Ce sont celles, o
l'adresse du noeud est mise 0. Ainsi, l'adresse 193.49.144.0 est l'adresse IP du rseau de la Facult des Sciences d'Angers.
En consquence, aucun noeud d'un rseau ne peut avoir l'adresse zro.
Lorsque dans une adresse IP, l'adresse du noeud ne comporte que des 1, on a alors une adresse de diffusion : cette adresse
dsigne tous les noeuds du rseau.
Dans un rseau de classe C, permettant thoriquement 28=256 noeuds, si on enlve les deux adresses interdites, on n'a
plus que 254 adresses autorises.
Source
Destination
+---------------------------------------------------------------------------------------------+
Le paquet IP contient donc les adresses Internet des machines source et destination. Lorsque ce paquet va tre transmis la couche
charge de l'envoyer sur le rseau physique, d'autres informations lui sont ajoutes pour former la trame physique qui sera
finalement envoye sur le rseau. Par exemple, le format d'une trame sur un rseau Ethernet est le suivant :
.
<---- En-tte trame Ethernet
----------------------->
.
<-Donnes trame Ethernet->.
+----------------------------------------------------------------------------------------------------+
Info
Adresse Physique
Adresse Physique
longueur Paquet IP
Ethernet
Source
Destination
paquet
CRC
+----------------------------------------------------------------------------------------------------+
8 oct
6
6
2
46 1500
4
Dans la trame finale, il y a l'adresse physique des machines source et destination. Comment sont-elles obtenues ?
La machine expditrice connaissant l'adresse IP de la machine avec qui elle veut communiquer obtient l'adresse physique de celle-ci
en utilisant un protocole particulier appel ARP (Address Resolution Protocol).
.
.
.
.
Elle envoie un paquet d'un type spcial appel paquet ARP contenant l'adresse IP de la machine dont on cherche l'adresse
physique. Elle a pris soin galement d'y placer sa propre adresse IP ainsi que son adresse physique.
Ce paquet est envoy tous les noeuds du rseau.
Ceux-ci reconnaissent la nature spciale du paquet. Le noeud qui reconnat son adresse IP dans le paquet, rpond en
envoyant l'expditeur du paquet son adresse physique. Comment le peut-il ? Il a trouv dans le paquet les adresses IP et
physique de l'expditeur.
L'expditeur reoit donc l'adresse physique qu'il cherchait. Il la stocke en mmoire afin de pouvoir l'utiliser ultrieurement
si d'autres paquets sont envoyer au mme destinataire.
L'adresse IP d'une machine est normalement inscrite dans l'un de ses fichiers qu'elle peut donc consulter pour la connatre. Cette
adresse peut tre change : il suffit d'diter le fichier. L'adresse physique elle, est inscrite dans une mmoire de la carte rseau et ne
peut tre change.
Programmation TCP-IP
252
Lorsqu'un administrateur dsire d'organiser son rseau diffremment, il peut tre amen changer les adresses IP de tous les
noeuds et donc diter les diffrents fichiers de configuration des diffrents noeuds. Cela peut tre fastidieux et une occasion
d'erreurs s'il y a beaucoup de machines. Une mthode consiste ne pas affecter d'adresse IP aux machines : on inscrit alors un
code spcial dans le fichier dans lequel la machine devrait trouver son adresse IP. Dcouvrant qu'elle n'a pas d'adresse IP, la
machine la demande selon un protocole appel RARP (Reverse Address Resolution Protocol). Elle envoie alors sur un rseau un paquet
spcial appel paquet RARP, analogue au paquet ARP prcdent, dans lequel elle met son adresse physique. Ce paquet est envoy
tous les noeuds qui reconnaissent alors un paquet RARP. L'un d'entre-eux, appel serveur RARP, possde un fichier donnant la
correspondance adresse physique <--> adresse IP de tous les noeuds. Il rpond alors l'expditeur du paquet RARP, en lui
renvoyant son adresse IP. Un administrateur dsirant reconfigurer son rseau, n'a donc qu' diter le fichier de correspondances du
serveur RARP. Celui-ci doit normalement avoir une adresse IP fixe qu'il doit pouvoir connatre sans avoir utiliser lui-mme le
protocole RARP.
Source
Destination
+---------------------------------------------------------------------------------------------+
L'important est qu'outre les donnes transmettre, le datagramme IP contient les adresses Internet des machines source et
destination. Ainsi la machine destinatrice sait qui lui envoie un message.
A la diffrence d'une trame de rseau qui a une longueur dtermine par les caractristiques physiques du rseau sur lequel elle
transite, la longueur du datagramme IP est elle fixe par le logiciel et sera donc la mme sur diffrents rseaux physiques. Nous
avons vu qu'en descendant de la couche rseau dans la couche physique le datagramme IP tait encapsul dans une trame physique.
Nous avons donn l'exemple de la trame physique d'un rseau Ethernet :
. <---- En-tte trame Ethernet
-------------------------------->. <---Donnes trame Ethernet------>.
+----------------------------------------------------------------------------------------------------+
Info
Adresse Physique
Adresse Physique
Type du
Paquet IP
Ethernet
Source
Destination
paquet
CRC
+----------------------------------------------------------------------------------------------------+
Les trames physiques circulent de noeud en noeud vers leur destination qui peut ne pas tre sur le mme rseau physique que la
machine expditrice. Le paquet IP peut donc tre encapsul successivement dans des trames physiques diffrentes au niveau des
noeuds qui font la jonction entre deux rseaux de type diffrent. Il se peut aussi que le paquet IP soit trop grand pour tre
encapsul dans une trame physique. Le logiciel IP du noeud o se pose ce problme, dcompose alors le paquet IP en fragments
selon des rgles prcises, chacun d'eux tant ensuite envoy sur le rseau physique. Ils ne seront rassembls qu' leur ultime
destination.
7.1.6.1 Le routage
Le routage est la mthode d'acheminement des paquets IP leur destination. Il y a deux mthodes : le routage direct et le routage
indirect.
Routage direct
Le routage direct dsigne l'acheminement d'un paquet IP directement de l'expditeur au destinataire l'intrieur du mme rseau :
.
.
.
Routage indirect
Le routage indirect dsigne l'acheminement d'un paquet IP une destination se trouvant sur un autre rseau que celui auquel
appartient l'expditeur. Dans ce cas, les parties adresse rseau des adresses IP des machines source et destination sont diffrentes.
Programmation TCP-IP
253
La machine source reconnat ce point. Elle envoie alors le paquet un noeud spcial appel routeur (router), noeud qui connecte un
rseau local aux autres rseaux et dont elle trouve l'adresse IP dans ses tables, adresse obtenue initialement soit dans un fichier soit
dans une mmoire permanente ou encore via des informations circulant sur le rseau.
Un routeur est attach deux rseaux et possde une adresse IP l'intrieur de ces deux rseaux.
+------------+
rseau 2 routeur
rseau 1
----------------|193.49.144.6|-----------193.49.145.0
193.49.145.3 193.49.144.0
+------------+
Le routeur a pour rle de mettre le paquet IP qu'il reoit et qui est contenu dans une trame physique typique du rseau n 1, dans
une trame physique pouvant circuler sur le rseau n 2. Si l'adresse IP du destinataire du paquet est dans le rseau n 2, le routeur lui
enverra le paquet directement sinon il l'enverra un autre routeur, connectant le rseau n 2 un rseau n 3 et ainsi de suite.
+--------------------------------------------------------------------------------+
Ces datagrammes seront encapsuls dans des paquets IP, puis dans des trames physiques.
Programmation TCP-IP
254
Le processus qui souhaite mettre tablit tout d'abord une connexion avec le processus destinataire des informations qu'il
va mettre. Cette connexion se fait entre un port de la machine mettrice et un port de la machine rceptrice. Il y a entre
les deux ports un chemin virtuel qui est ainsi cr et qui sera rserv aux deux seuls processus ayant ralis la connexion.
. Tous les paquets mis par le processus source suivent ce chemin virtuel et arrivent dans l'ordre o ils
ont t mis ce qui n'tait pas garanti dans le protocole UDP puisque les paquets pouvaient suivre des
chemins diffrents.
.
.
.
.
.
L'information mise a un aspect continu. Le processus metteur envoie des informations son rhythme. Celles-ci ne sont
pas ncessairement envoyes tout de suite : le protocole TCP attend d'en avoir assez pour les envoyer. Elles sont stockes
dans une structure appele segment TCP. Ce segment une fois rempli sera transmis la couche IP o il sera encapsul dans
un paquet IP.
Chaque segment envoy par le protocole TCP est numrot. Le protocole TCP destinataire vrifie qu'il reoit bien les
segments en squence. Pour chaque segment correctement reu, il envoie un accus de rception l'expditeur.
Lorsque ce dernier le reoit, il l'indique au processus metteur. Celui-ci peut donc savoir qu'un segment est arriv bon
port, ce qui n'tait pas possible avec le protocole UDP.
Si au bout d'un certain temps, le protocole TCP ayant mis un segment ne reoit pas d'accus de rception, il retransmet le
segment en question, garantissant ainsi la qualit du service d'acheminement de l'information.
Le circuit virtuel tabli entre les deux processus qui communiquent est full-duplex : cela signifie que l'information peut
transiter dans les deux sens. Ainsi le processus destination peut envoyer des accuss de rception alors mme que le
processus source continue d'envoyer des informations. Cela permet par exemple au protocole TCP source d'envoyer
plusieurs segments sans attendre d'accus de rception. S'il ralise au bout d'un certain temps qu'il n'a pas reu l'accus de
rception d'un certain segment n n, il reprendra l'mission des segments ce point.
255
La machine DPX2/320 de l'universit d'Angers a t nomme Lagaffe alors qu'un PC 486DX50 a t nomm liny. Comment
rfrencer ces machines de l'extrieur ? En prcisant la hirarchie des domaines auxquelles elles appartiennent. Ainsi le nom
complet de la machine Lagaffe sera :
Lagaffe.univ-Angers.fr
A l'intrieur des domaines, on peut utiliser des noms relatifs. Ainsi l'intrieur du domaine fr et en dehors du domaine univAngers, la machine Lagaffe pourra tre rfrence par
Lagaffe.univ-Angers
Enfin, l'intrieur du domaine univ-Angers, elle pourra tre rfrence simplement par
Lagaffe
Une application peut donc rfrencer une machine par son nom. Au bout du compte, il faut quand mme obtenir l'adresse
Internet de cette machine. Comment cela est-il ralis ? Suposons que d'une machine A, on veuille communiquer avec une
machine B.
.
.
si la machine B appartient au mme domaine que la machine A, on trouvera probablement son adresse IP dans un fichier de la
machine A.
sinon, la machine A trouvera dans un autre fichier ou le mme que prcdemment, une liste de quelques serveurs de noms
avec leurs adresses IP. Un serveur de noms est charg de faire la correspondance entre un nom de machine et son adresse IP. La
machine A va envoyer une requte spciale au premier serveur de nom de sa liste, appel requte DNS incluant donc le nom
de la machine recherche. Si le serveur interrog a ce nom dans ses tablettes, il enverra la machine A, l'adresse IP
correspondante. Sinon, le serveur trouvera lui aussi dans ses fichiers, une liste de serveurs de noms qu'il peut interroger. Il le
fera alors. Ainsi un certain nombre de serveurs de noms vont tre interrogs, pas de faon anarchique mais d'une faon
minimiser les requtes. Si la machine est finalement trouve, la rponse redescendra jusqu' la machine A.
7.1.9 Conclusion
Nous avons prsent dans cette introduction quelques grandes lignes des protocoles Internet. Pour approfondir ce domaine, on
pourra lire l'excellent livre de Douglas Comer :
Titre
Auteur
Editeur
Programmation TCP-IP
String getHostAddress()
String getHostName()
String toString()
InetAddress getByName(String
Host)
InetAddress getLocalHost()
Chaque machine a une adresse IP interne qui est 127.0.0.1. Lorsqu'un programme utilise cette adresse rseau, il utilise la machine
sur laquelle il fonctionne. L'intrt de cette adresse est qu'elle ne ncessite pas de carte rseau. On peut donc tester des programmes
rseau sans tre connect un rseau. Une autre faon de dsigner la machine locale est d'utiliser le nom localhost.
Programmation TCP-IP
257
nom : localhost
identit : localhost/127.0.0.1
Port PB
Rseau physique
Lorsque une application AppA d'une machine A veut communiquer avec une application AppB d'une machine B de l'Internet, elle
doit connatre plusieurs choses :
l'adresse IP ou le nom de la machine B
le numro du port avec lequel travaille l'application AppB. En effet la machine B peut supporter de nombreuses applications
qui travaillent sur l'Internet. Lorsqu'elle reoit des informations provenant du rseau, elle doit savoir quelle application sont
destines ces informations. Les applications de la machine B ont accs au rseau via des guichets appels galement des ports
de communication. Cette information est contenue dans le paquet reu par la machine B afin qu'il soit dlivr la bonne
application.
les protocoles de communication compris par la machine B. Dans notre tude, nous utiliserons uniquement les protocoles
TCP-IP.
le protocole de dialogue accept par l'application AppB. En effet, les machines A et B vont se "parler". Ce qu'elles vont dire va
tre encapsul dans les protocoles TCP-IP. Nammoins, lorsqu'au bout de la chane, l'application AppB va recevoir
l'information envoye par l'applicaton AppA, il faut qu'elle soit capable de l'interprter. Ceci est analogue la situation o deux
personnes A et B communiquent par tlphone : leur dialogue est transport par le tlphone. La parole va tre code sous
forme de signaux par le tlphone A, transporte par des lignes tlphoniques, arrive au tlphone B pour y tre dcode. La
personne B entend alors des paroles. C'est l qu'intervient la notion de protocole de dialogue : si A parle franais et que B ne
comprend pas cette langue, A et B ne pourront dialoguer utilement.
Aussi les deux applications communicantes doivent -elles tre d'accord sur le type de dialogue qu'elles vont adopter. Ainsi par
exemple, le dialogue avec un service ftp n'est pas le mme qu'avec un service pop : ces deux services n'acceptent pas les mmes
commandes. Elles ont un protocole de dialogue diffrent.
Programmation TCP-IP
258
Nous n'tudierons ici que des communications rseau utilisant le protocole de transport TCP. Rappelons ici, les caractristiques de
ce protocole :
.
.
.
.
.
.
.
Le processus qui souhaite mettre tablit tout d'abord une connexion avec le processus destinataire des informations qu'il
va mettre. Cette connexion se fait entre un port de la machine mettrice et un port de la machine rceptrice. Il y a entre
les deux ports un chemin virtuel qui est ainsi cr et qui sera rserv aux deux seuls processus ayant ralis la connexion.
Tous les paquets mis par le processus source suivent ce chemin virtuel et arrivent dans l'ordre o ils ont t mis
L'information mise a un aspect continu. Le processus metteur envoie des informations son rythme. Celles-ci ne sont
pas ncessairement envoyes tout de suite : le protocole TCP attend d'en avoir assez pour les envoyer. Elles sont stockes
dans une structure appele segment TCP. Ce segment une fois rempli sera transmis la couche IP o il sera encapsul dans
un paquet IP.
Chaque segment envoy par le protocole TCP est numrot. Le protocole TCP destinataire vrifie qu'il reoit bien les
segments en squence. Pour chaque segment correctement reu, il envoie un accus de rception l'expditeur.
Lorsque ce dernier le reoit, il l'indique au processus metteur. Celui-ci peut donc savoir qu'un segment est arriv bon
port.
Si au bout d'un certain temps, le protocole TCP ayant mis un segment ne reoit pas d'accus de rception, il retransmet le
segment en question, garantissant ainsi la qualit du service d'acheminement de l'information.
Le circuit virtuel tabli entre les deux processus qui communiquent est full-duplex : cela signifie que l'information peut
transiter dans les deux sens. Ainsi le processus destination peut envoyer des accuss de rception alors mme que le
processus source continue d'envoyer des informations. Cela permet par exemple au protocole TCP source d'envoyer
plusieurs segments sans attendre d'accus de rception. S'il ralise au bout d'un certain temps qu'il n'a pas reu l'accus de
rception d'un certain segment n n, il reprendra l'mission des segments ce point.
Le programme serveur traite diffremment la demande de connexion initiale d'un client de ses demandes ultrieures visant obtenir
un service. Le programme n'assure pas le service lui-mme. S'il le faisait, pendant la dure du service il ne serait plus l'coute des
demandes de connexion et des clients ne seraient alors pas servis. Il procde donc autrement : ds qu'une demande de connexion
est reue sur le port d'coute puis accepte, le serveur cre une tche charge de rendre le service demand par le client. Ce service
est rendu sur un autre port de la machine serveur appel port de service. On peut ainsi servir plusieurs clients en mme temps.
Programmation TCP-IP
259
public InetAddress
getInetAdress()
public InputStream
getInputStream()
rend un flux d'entre permettant de lire les donnes envoyes par le partenaire distant
public OutputStream
getOutputStream()
public
public
public
public
void shutdownInput()
void shutdownOutput()
void close()
String toString()
host, int
port);
cre une socket et la connecte la machine host sur le port port. Ce constructeur gnre une exception dans diffrents cas :
mauvaise adresse
mauvais port
demande refuse
Programmation TCP-IP
260
Si la demande de connexion russit, le client se voit localement attribuer un port pour communiquer avec la machine B. Une fois la
connexion tablie, on peut connatre ce port avec la mthode :
public int getLocalPort();
Si la connexion russit, nous avons vu que, de son ct, le serveur fait assurer le service par une autre tche travaillant sur un port
dit de service. Ce numro de port peut tre connu avec la mthode :
public int getPort();
Tout ce qui sera envoy dans ce flux sera reu sur le port de service de la machine serveur. De nombreuses applications ont un
dialogue sous forme de lignes de texte termines par un passage a ligne. Aussi la mthode println est-elle bien pratique dans ces cas
l. On transforme alors le flux de sortie OutputStream en flux PrintWriter qui possde la mthode println. L'criture peut gnrer une
exception.
Tout ce qui sera lu dans ce flux vient du port de service de la machine serveur. Pour les applications ayant un dialogue sous forme
de lignes de texte termines par un passage la ligne on aimera utiliser la mthode readLine. Pour cela on transforme le flux d'entre
InputStream en flux BufferedReader qui possde la mthode readLine(). La lecture peut gnrer une exception.
La mthode peut gnrer une exception. Les ressources utilises, notamment le port rseau, sont libres.
Programmation TCP-IP
261
}
// c'est fini
sClient.close();
} catch(Exception
e){
catch
// on gre l'exception
.
}
Nous n'avons pas cherch grer les diffrents types d'exception gnrs par le constructeur Socket ou les mthodes readline,
getInputStream, getOutputStream, close pour ne pas compliquer l'exemple. Tout a t runi dans une seule exception.
met le serveur en attente d'une connexion (opration bloquante). A l'arrive d'une connexion
cliente, rend une socket partir de laquelle sera rendu le service au client.
ferme la socket et ses flux d'E/S
rend une chane de caractres "reprsentant" la socket
ferme la socket de service et libre les ressources qui lui sont associes
port);
port, int
count);
port est le port d'coute du service : celui o les clients adressent leurs demandes de connexion. count est la taille maximale de la file
d'attente du service (50 par dfaut), celle-ci stockant les demandes de connexion des clients auxquelles le serveur n'a pas encore
rpondu. Lorsque la file d'attente est pleine, les demandes de connexion qui arrivent sont rejetes. Les deux constructeurs gnrent
une exception.
Cette mthode rend une instance de Socket : c'est la socket de service, celle travers laquelle le service sera rendu, le plus souvent
par une autre tche. La mthode peut gnrer une exception.
262
de la classe ServerSocket. Cela libre les ressources occupes, notamment le port d'coute. La mthode peut gnrer une exception.
// la socket de service
// constructeur
public Service(Socket S){
sService=S;
}
// run
public void run(){
try{
try
// on cre les flux d'entre-sortie
BufferedReader in=new
new BufferedReader(new
new InputStreamReader(sService.getInputStream()));
PrinttWriter out=new
new PrintWriter(sService.getOutputStream(),true);
// boucle demande - rponse
boolean fini=false
false;
false
String demande;
String rponse;
while (! fini){
// on lit la demande
demande=in.readLine();
// on la traite
// on prpare la rponse
rponse=
// on l'envoie
out.println(rponse);
Programmation TCP-IP
263
}
// c'est fini
sService.close();
} catch(Exception
e){
catch
// on gre l'exception
.
}// try
} // run
7.4 Applications
7.4.1 Serveur d'cho
Nous nous proposons d'crire un serveur d'cho qui sera lanc depuis une fentre DOS par la commande :
java serveurEcho port
Le serveur officie sur le port pass en paramtre. Il se contente de renvoyer au client la demande que celui-ci lui a envoye
accompagne de son identit (IP+nom). Il accepte 2 connexions dans sa liste d'attente. On a l tous les constituants d'un serveur
tcp. Le programme est le suivant :
// appel : serveurEcho port
// serveur d'cho
// renvoie au client la ligne que celui-ci lui a envoye
import java.net.*;
import java.io.*;
public class serveurEcho{
public final static String syntaxe="Syntaxe : serveurEcho port";
public final static int nbConnexions=2;
// programme principal
public static void main (String arg[]){
// y-a-t-il un argument
if(arg.length != 1)
erreur(syntaxe,1);
// cet argument doit tre entier >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// on cre la socket d'coute
ServerSocket ecoute=null;
try{
ecoute=new ServerSocket(port,nbConnexions);
} catch (Exception e){
erreur("Erreur lors de la cration de la socket d'coute ("+e+")",3);
}
// suivi
System.out.println("Serveur d'cho lanc sur le port " + port);
// boucle de service
boolean serviceFini=false;
Socket service=null;
while (! serviceFini){
// attente d'un client
try{
service=ecoute.accept();
} catch (IOException e){
erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
}
// on identifie la liaison
try{
Programmation TCP-IP
264
System.out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connect au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// le service est assur par une autre tche
new traiteClientEcho(service).start();
}// fin while
}// fin main
// affichage des erreurs
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// identifie
private static String identifie(InetAddress Host){
// identification de Host
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// fin class
// assure le service un client du serveur d'cho
class traiteClientEcho extends Thread{
private Socket service;
private BufferedReader in;
private PrintWriter out;
// socket de service
// flux d'entre
// flux de sortie
// constructeur
public traiteClientEcho(Socket service){
this.service=service;
}
// mthode run
public void run(){
// cration des flux d'entre et de sortie
try{
in=new BufferedReader(new InputStreamReader(service.getInputStream()));
} catch (IOException e){
erreur("Erreur lors de la cration du flux dentre de la socket de service ("+e+")",1);
}// fin try
try{
out=new PrintWriter(service.getOutputStream(),true);
} catch (IOException e){
erreur("Erreur lors de la cration du flux de sortie de la socket de service ("+e+")",1);
}// fin try
// l'identification de la liaison est envoye au client
try{
out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connect au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// boucle lecture demande/criture rponse
String demande,reponse;
try{
// le service s'arrte lorsque le client envoie une marque de fin de fichier
while ((demande=in.readLine())!=null){
// cho de la demande
reponse="["+demande+"]";
out.println(reponse);
// le service s'arrte lorsque le client envoie "fin"
if(demande.trim().toLowerCase().equals("fin")) break;
}// fin while
} catch (IOException e){
erreur("Erreur lors des changes client/serveur ("+e+")",3);
}// fin try
// on ferme la socket
try{
Programmation TCP-IP
265
service.close();
} catch (IOException e){
erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
}// fin try
}// fin run
// affichage des erreurs
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// fin erreur
// identifie
private String identifie(InetAddress Host){
// identification de Host
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// fin class
Les deux classes ncessaires au service ont t runies dans un mme fichier source. Seule l'une d'entre-elles, celle qui a la fonction
main a l'attribut public. La structure du serveur est conforme l'architecture gnrale des serveurs tcp. On y ajout une mthode
(identifie) permettant d'identifier la liaison entre le serveur et un client. Voici quelques rsultats :
Le serveur est lanc par la commande
java serveurEcho 187
Il affiche alors dans la fentre de contrle, le message suivant :
Serveur d'cho lanc sur le port 187
Pour tester ce serveur, on utilise le programme telnet qui existe la fois sous Unix et Windows. Telnet est un client tcp universel
adapt tous les serveurs qui acceptent des lignes de texte termines par une marque de fin de ligne dans leur dialogue. C'est le cas
de notre serveur d'cho. On lance un premier client telnet sous windows (2000 dans cet exemple) en tapant telnet dans une fentre
DOS :
DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numro 2195)
Client Telnet Microsoft
Client Telnet numro 5.00.99203.1
Le caractre d'chappement est 'CTRL+$'
Microsoft Telnet> help
Les commandes peuvent tre abrges. Les commandes prises en charge sont :
close
display
open
quit
set
status
unset
? ou help
Le programme telnet ne fait, par dfaut, pas l'cho des commandes que l'on tape au clavier. Pour avoir cet cho on met la
commande :
Microsoft Telnet> set local_echo
Programmation TCP-IP
266
Pour ouvrir une connexion avec le serveur, en lui prcisant le port du service d'cho (187) et l'adresse de la machine sur lequel il se
trouve (localhost) on met la commande :
Microsoft Telnet> open localhost 187
Ici tahe et localhost dsignent la mme machine. Dans la fentre du client telnet, on peut taper des lignes de texte. Le serveur les
renvoie en cho :
Client [127.0.0.1,tahe,1059] connect au serveur [127.0.0.1,tahe,187]
je suis l
[je suis l]
au revoir
[au revoir]
On notera que le port du client (1059) est bien dtect mais que le port de service (187) est identique au port d'coute (187), ce qui
est inattendu. On pouvait en effet s'attendre obtenir le port de la socket de service et non le port d'coute. Il faudrait vrifier si on
obtient les mmes rsultats sous Unix. Maintenant, lanons un second client telnet. La fentre du serveur devient :
Serveur d'cho lanc sur le port 187
Client [127.0.0.1,tahe,1059] connect au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connect au serveur [127.0.0.1,tahe,187]
Dans la fentre du second client, on peut aussi taper des lignes de texte :
Client [127.0.0.1,tahe,1060] connect au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]
On voit ainsi que le serveur d'cho peut servir plusieurs clients la fois. Les clients telnet peuvent tre termins en fermant la fentre
Dos dans laquelle ils s'excutent.
267
serveurAddress=InetAddress.getByName(machine);
} catch (Exception e){
erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
}
// le port doit tre entier >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\nPort incorrect ("+E+")",3);
// on se connecte au serveur
Socket sClient=null;
try{
sClient=new Socket(machine,port);
} catch (Exception e){
erreur("Erreur lors de la cration de la socket de communication ("+e+")",4);
}
// on identifie la liaison
try{
System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
sClient.getLocalPort()+"] connect au serveur [" + identifie (sClient.getInetAddress())
+ "," + sClient.getPort() + "]");
} catch (Exception e) {
erreur("identification liaison ("+e+")",5);
}
// cration du flux de lecture des lignes tapes au clavier
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
erreur("Cration du flux d'entre clavier ("+e+")",6);
}
// cration du flux d'entre associe la socket client
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
} catch (Exception e){
erreur("Cration du flux d'entre de la socket client("+e+")",7);
}
// cration du flux de sortie associe la socket client
PrintWriter out=null;
try{
out=new PrintWriter(sClient.getOutputStream(),true);
} catch (Exception e){
erreur("Cration du flux de sortie de la socket ("+e+")",8);
}
// boucle demandes - rponses
boolean serviceFini=false;
String demande=null;
String reponse=null;
// on lit le message envoy par le serveur juste aprs la connexion
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture rponse ("+e+")",4);
}
// affichage rponse
System.out.println("Serveur : " +reponse);
while (! serviceFini){
// lecture d'une ligne tape au clavier
System.out.print("Client : ");
try{
demande=IN.readLine();
} catch (Exception e){
erreur("Lecture ligne ("+e+")",9);
}
// envoi demande sur le rseau
try{
out.println(demande);
} catch (Exception e){
erreur("Envoi demande ("+e+")",10);
}
Programmation TCP-IP
268
// attente/lecture rponse
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture rponse ("+e+")",4);
}
// affichage rponse
System.out.println("Serveur : " +reponse);
// est-ce fini ?
if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
}
// c'est fini
try{
sClient.close();
} catch(Exception e){
erreur("Fermeture socket ("+e+")",11);
}
}// main
La structure de ce client est conforme l'architecture gnrale des clients tcp. Ici, on a gr les diffrentes exceptions possibles, une
par une, ce qui alourdit le programme. Voici les rsultats obtenus lorsqu'on teste ce client :
Client : Client [127.0.0.1,tahe,1045] connect au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connect au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis l
Serveur : [je suis l]
Client : fin
Serveur : [fin]
Les lignes commenant par Client sont les lignes envoyes par le client et celles commenant par Serveur sont celles que le serveur a
renvoyes en cho.
269
une boucle de lecture des commandes tapes au clavier pour tre envoyes au serveur. L'utilisateur signalera la fin des
commandes avec le mot cl fin.
une boucle de rception et d'affichage des rponses du serveur. Celle-ci sera une boucle infinie qui ne sera interrompue
que par la fermeture du flux rseau par le serveur ou par l'utilisateur au clavier qui tapera la commande fin.
Pour avoir ces deux boucles dissocies, il nous faut deux threads indpendants. Montrons un exemple d'exccution o notre client
tcp gnrique se connecte un service SMTP (SendMail Transfer Protocol). Ce service est responsable de l'acheminement du
courrier lectronique leurs destinataires. Il fonctionne sur le port 25 et a un protocole de dialogue de type changes de lignes de
texte.
Dos>java clientTCPgenerique istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test
ligne1
ligne2
ligne3
.
<-- 250
quit
<-- 221
[fin du
fin
[fin du
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
certains services ont une commande help donnant des indications sur les commandes utilisables avec le service. Ici ce n'est
pas le cas. Les commandes SMTP utilises dans l'exemple sont les suivantes :
o mail from: expditeur, pour indiquer l'adresse lectronique de l'expditeur du message
o rcpt to: destinataire, pour indiquer l'adresse lectronique du destinataire du message. S'il y a plusieurs destinataires,
on r-met autant de fois que ncessaire la commande rcpt to: pour chacun des destinataires.
o data qui signale au serveur SMTP qu'on va envoyer le message. Comme indiqu dans la rponse du serveur,
celui-ci est une suite de lignes termine par une ligne contenant le seul caractre point. Un message peut avoir
des enttes spars du corps du message par une ligne vide. Dans notre exemple, nous avons mis un sujet avec le
mot cl Subject:
une fois le message envoy, on peut indiquer au serveur qu'on a termin avec la commande quit. Le serveur ferme alors la
connexion rseau. Le thread de lecture peut dtecter cet vnement et s'arrter.
l'utilisateur tape alors fin au clavier pour arrter galement le thread de lecture des commandes tapes au clavier.
Programmation TCP-IP
270
On remarquera que le service SMTP ne peut dtecter si un expditeur est valide ou non. Aussi ne peut-on jamais faire confiance au
champ from d'un message. Ici l'expditeur machin@univ-angers.fr n'existait pas.
Ce client tcp gnrique peut nous permettre de dcouvrir le protocole de dialogue de services internet et partir de l construire des
classes spcialises pour des clients de ces services. Dcouvrons le protocole de dialogue du service POP (Post Office Protocol) qui
permet de retrouver ses mls stocks sur un serveur. Il travaille sur le port 110.
Dos> java clientTCPgenerique istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<-by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<-Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<-by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<-Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- -----------------------------------------------------------------------<-- NOC-RENATER2
Tl. : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 , Email : noc-r2@cssi.renater.fr
<-- -----------------------------------------------------------------------<-<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des rponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
user login, o on donne son login sur la machine qui dtient nos mls
pass password, o on donne le mot de passe associ au login prcdent
list, pour avoir la liste des messages sous la forme numro, taille en octets
retr i, pour lire le message n i
quit, pour arrter le dialogue.
Dcouvrons maintenant le protocole de dialogue entre un client et un serveur Web qui lui travaille habituellement sur le port 80 :
Dos> java clientTCPgenerique istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0
<-<-<-<-<-<-<-<-<-<-<-<--
HTTP/1.1 200 OK
Date: Mon, 13 May 2002 07:30:58 GMT
Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
ETag: "23432-2bf3-3c60f0ca"
Accept-Ranges: bytes
Content-Length: 11251
Connection: close
Content-Type: text/html
<html>
Programmation TCP-IP
271
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<-[fin du thread de lecture des rponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
qui demande au serveur l'URL /index.html et indique qu'il travaille avec le protocole HTTP version 1.0. La version la plus rcente
de ce protocole est 1.1. L'exemple montre que le serveur a rpondu en renvoyant le contenu du fichier index.html puis qu'il a ferm
la connexion puisqu'on voit le thread de lecture des rponses se terminer. Avant d'envoyer le contenu du fichier index.html, le
serveur web a envoy une srie d'enttes termine par une ligne vide :
<-<-<-<-<-<-<-<-<-<-<--
HTTP/1.1 200 OK
Date: Mon, 13 May 2002 07:30:58 GMT
Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
ETag: "23432-2bf3-3c60f0ca"
Accept-Ranges: bytes
Content-Length: 11251
Connection: close
Content-Type: text/html
<html>
La ligne <html> est la premire ligne du fichier /index.html. Ce qui prcde s'appelle des enttes HTTP (HyperText Transfer
Protocol). Nous n'allons pas dtailler ici ces enttes mais on se rappellera que notre client gnrique y donne accs, ce qui peut tre
utile pour les comprendre. La premire ligne par exemple :
<-- HTTP/1.1 200 OK
indique que le serveur Web contact comprend le protocole HTTP/1.1 et qu'il a bien trouv le fichier demand (200 OK), 200
tant un code de rponse HTTP. Les lignes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
disent au client qu'il va recevoir 11251 octets reprsentant du texte HTML (HyperText Markup Language) et qu' la fin de l'envoi,
la connexion sera ferme.
On a donc l un client tcp trs pratique. Il fait sans doute moins que le programme telnet que nous avons utilis prcdemment mais
il tait intressant de l'crire nous-mmes. Le programme du client tcp gnrique est le suivant :
// paquetages imports
import java.io.*;
import java.net.*;
public class clientTCPgenerique{
Programmation TCP-IP
272
//
//
//
//
//
//
//
//
// variable d'instance
private static Socket client;
public static void main(String[] args){
// syntaxe
final String syntaxe="pg serveur port";
// nombre d'arguments
if(args.length != 2)
erreur(syntaxe,1);
// on note le nom du serveur
String serveur=args[0];
// le port doit tre entier >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
client=null;
// il peut y avoir des problmes
try{
// on se connecte au service
client=new Socket(serveur,port);
}catch(Exception ex){
// erreur
erreur("Impossible de se connecter au service ("+ serveur
+","+port+"), erreur : "+ex.getMessage(),3);
// fin
return;
}//catch
// on cre les threads de lecture/criture
new ClientSend(client).start();
new ClientReceive(client).start();
// fin thread main
return;
}// main
// affichage des erreurs
public static void erreur(String msg, int exitCode){
// affichage erreur
System.err.println(msg);
// arrt avec erreur
System.exit(exitCode);
}//erreur
}//classe
class ClientSend extends Thread {
// classe charge de lire des commandes tapes au clavier
// et de les envoyer un serveur via un client tcp pass en paramtre
private Socket client; // le client tcp
// constructeur
public ClientSend(Socket client){
// on note le client tcp
this.client=client;
}//constructeur
// mthode Run du thread
public void run(){
// donnes locales
PrintWriter OUT=null;
// flux d'criture rseau
BufferedReader IN=null;
// flux clavier
String commande=null;
// commande lue au clavier
Programmation TCP-IP
273
274
Le programme est lanc par : java serveurTCPgenerique portEcoute, o portEcoute est le port sur lequel les clients doivent se
connecter. Le service au client sera assur par deux threads :
un thread se consacrant exclusivement la lecture des lignes de texte envoyes par le client
un thread se consacrant exclusivement la lecture des rponses tapes au clavier par l'utilisateur. Celui-ci signalera par la
commande fin qu'il clt la connexion avec le client.
Le serveur cre deux threads par client. S'il y a n clients, il y aura 2n threads actifs en mme temps. Le serveur lui ne s'arrte jamais
sauf par un Ctrl-C tap au clavier par l'utilisateur. Voyons quelques exemples.
Le serveur est lanc sur le port 100 et on utilise le client gnrique pour lui parler. La fentre du client est la suivante :
E:\data\serge\MSNET\c#\rseau\client tcp gnrique> java clientTCPgenerique localhost 100
Commandes :
commande 1 du client 1
<-- rponse 1 au client 1
commande 2 du client 1
<-- rponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les donnes de la connexion de transport.
[fin du thread de lecture des rponses du serveur]
[fin du thread d'envoi des commandes au serveur]
Les lignes commenant par <-- sont celles envoyes du serveur au client, les autres celles du client vers le serveur. La fentre du
serveur est la suivante :
Dos> java serveurTCPgenerique 100
Serveur gnrique lanc sur le port 100
Thread de lecture des rponses du serveur au client 1 lanc
1 : Thread de lecture des demandes du client 1 lanc
<-- commande 1 du client 1
rponse 1 au client 1
1 : <-- commande 2 du client 1
rponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des rponses du serveur au client 1]
Les lignes commenant par <-- sont celles envoyes du client au serveur. Les lignes N : sont les lignes envoyes du serveur au client
n N. Le serveur ci-dessus est encore actif alors que le client 1 est termin. On lance un second client pour le mme serveur :
Dos> java clientTCPgenerique localhost 100
Commandes :
commande 3 du client 2
<-- rponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les donnes de la connexion de transport.
[fin du thread de lecture des rponses du serveur]
[fin du thread d'envoi des commandes au serveur]
275
^C
Simulons maintenant un serveur web en lanant notre serveur gnrique sur le port 88 :
Dos> java serveurTCPgenerique 88
Serveur gnrique lanc sur le port 88
Prenons maintenant un navigateur et demandons l'URL http://localhost:88/exemple.html. Le navigateur va alors se connecter sur le
port 88 de la machine localhost puis demander la page /exemple.html :
On dcouvre ainsi les enttes HTTP envoys par le navigateur. Cela nous permet de dcouvrir peu peu le protocole HTTP. Lors
d'un prcdent exemple, nous avions cr un client Web qui n'envoyait que la seule commande GET. Cela avait t suffisant. On
voit ici que le navigateur envoie d'autres informations au serveur. Elles ont pour but d'indiquer au serveur quel type de client il a en
face de lui. On voit aussi que les enttes HTTP se terminent par une ligne vide.
Elaborons une rponse notre client. L'utilisateur au clavier est ici le vritable serveur et il peut laborer une rponse la main.
Rappelons-nous la rponse faite par un serveur Web dans un prcdent exemple :
<-<-<-<-<-<-<-<-<-<-<--
HTTP/1.1 200 OK
Date: Mon, 13 May 2002 07:30:58 GMT
Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
ETag: "23432-2bf3-3c60f0ca"
Accept-Ranges: bytes
Content-Length: 11251
Connection: close
Content-Type: text/html
<html>
Host: localhost:88
Connection: Keep-Alive
HTTP/1.1 200 OK
Server: serveur tcp generique
Connection: close
Content-Type: text/html
<html>
<head><title>Serveur generique</title></head>
<body>
Programmation TCP-IP
276
2 :
<center>
2 :
<h2>Reponse du serveur generique</h2>
2 :
</center>
2 :
</body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les donnes de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des rponses du serveur au client 2]
Les lignes commenant par 2 : sont envoyes du serveur au client n 2. La commande fin clt la connexion du serveur au client.
Nous nous sommes limits dans notre rponse aux enttes HTTP suivants :
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
Nous ne donnons pas la taille du fichier que nous allons envoyer (Content-Length) mais nous contentons de dire que nous allons
fermer la connexion (Connection: close) aprs envoi de celui-ci. Cela est suffisant pour le navigateur. En voyant la connexion ferme, il
saura que la rponse du serveur est termine et affichera la page HTML qui lui a t envoye. Cette dernire est la suivante :
2
2
2
2
2
2
2
2
: <html>
:
<head><title>Serveur generique</title></head>
:
<body>
:
<center>
:
<h2>Reponse du serveur generique</h2>
:
</center>
:
</body>
: </html>
L'utilisateur ferme ensuite la connexion au client en tapant la commande fin. Le navigateur sait alors que la rponse du serveur est
termine et peut alors l'afficher :
Programmation TCP-IP
277
278
279
}//run
}//classe
Nous allons crire un client Web auquel on passerait en paramtre une URL et qui afficherait l'cran le contenu de cette URL.
Nous supposerons que le serveur Web contact pour l'URL supporte le protocole HTTP 1.1. Des enttes prcdents, nous
n'utiliserons que les suivants :
<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
Si ci-dessus, nous remplaons GET par HEAD, le serveur ne nous enverra que les enttes HTTP et pas la page HTML.
Notre client web sera appel de la faon suivante : java clientweb URL cmd, o URL est l'URL dsire et cmd l'un des deux
mots cls GET ou HEAD pour indiquer si on souhaite seulement les enttes (HEAD) ou galement le contenu de la page (GET).
Regardons un premier exemple. Nous lanons le serveur IIS puis le client web sur la mme machine :
dos>java clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private
La rponse
HTTP/1.1 302 Object moved
signifie que la page demande a chang de place (donc d'URL). La nouvelle URL est donne par l'entte Location:
Location: /IISSamples/Default/welcome.htm
Programmation TCP-IP
280
Cache-control: private
<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
Nous obtenons le mme rsultat qu'avec HEAD avec de plus le corps de la page HTML. Le programme est le suivant :
// paquetages imports
import java.io.*;
import java.net.*;
public class clientweb{
// demande une URL
// affiche le contenu de celle-ci l'cran
public static void main(String[] args){
// syntaxe
final String syntaxe="pg URI GET/HEAD";
// nombre d'arguments
if(args.length != 2)
erreur(syntaxe,1);
// on note l'URI demande
String URLString=args[0];
String commande=args[1].toUpperCase();
// vrification validit de l'URI
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrecte
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// vrification de la commande
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// commande incorrecte
erreur("Le second paramtre doit tre GET ou HEAD",3);
}
// on extrait les infos utiles de l'URL
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// on peut travailler
Socket client=null;
// le client
BufferedReader IN=null;
// le flux de lecture du client
PrintWriter OUT=null;
// le flux d'criture du client
String rponse=null;
// rponse du serveur
try{
// on se connecte au serveur
client=new Socket(host,port);
// on cre les flux d'entre-sortie du client TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// on demande l'URL - envoi des enttes HTTP
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// on lit la rponse
while((rponse=IN.readLine())!=null){
// on traite la rponse
System.out.println(rponse);
}//while
// c'est fini
client.close();
} catch(Exception e){
// on gre l'exception
erreur(e.getMessage(),4);
}//catch
}//main
// affichage des erreurs
Programmation TCP-IP
281
La seule nouveaut dans ce programme est l'utilisation de la classe URL. Le programme reoit une URL (Uniform Resource Locator)
ou URI (Uniform Resource Identifier) de la forme http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... La classe URL nous
permet de dcomposer la chane de l'URL en ses diffrents lments. Un objet URL est construit partir de la chane URLstring
reue en paramtre :
// vrification validit de l'URL
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrecte
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
Si la chane URL reue en paramtre n'est pas une URL valide (absence du protocole, du serveur, ...), une exception est lance. Cela
nous permet de vrifier la validit du paramtre reu. Une fois l'objet URL construit, on a accs aux diffrents lments de celui-ci.
Ainsi si l'objet url du code prcdent a t construit partir de la chane
http://serveur:port/cheminPageHTML?param1=val1;param2=val2;...
on aura :
url.getHost()=serveur
url.getPort()=port ou -1 si le port n'est pas indiqu
url.getPath()=cheminPageHTML ou la chane vide s'il n'y a pas de chemin
url.getQuery()=param1=val1;param2=val2;... ou null s'il n'y a pas de requte
uri.getProtocol()=http
il lit la premire ligne des enttes HTTP envoys par le serveur pour vrifier si on y trouve la chane 302 Object moved qui
signale une redirection
il lit les enttes suivants. S'il y a redirection, il recherche la ligne Location: url qui donne la nouvelle URL de la page
demande et note cette URL.
il affiche le reste de la rponse du serveur. S'il y a redirection, les tapes 1 3 sont rptes avec la nouvelle URL. Le
programme n'accepte pas plus d'une redirection. Cette limite fait l'objet d'une constante qui peut tre modifie.
Voici un exemple :
Dos>java clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private
<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Programmation TCP-IP
282
// le client
// le flux de lecture du client
// le flux d'criture du client
// rponse du serveur
// pas plus d'une redirection accepte
// nombre de redirections en cours
// 1re ligne de la rponse
// indique s'il y a redirection ou non
// la chane URL d'une ventuelle redirection
283
client=new Socket(host,port);
// on cre les flux d'entre-sortie du client TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// on demande l'URL - envoi des enttes HTTP
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// on lit la premire ligne de la rponse
premireLigne=IN.readLine();
// cho cran
System.out.println(premireLigne);
// redirection ?
if(premireLigne.endsWith("302 Object moved")){
// il y a une redirection
redir=true;
nbRedirs++;
}//if
// enttes HTTP suivants jusqu' trouver la ligne vide signalant la fin des enttes
boolean locationFound=false;
while(!(rponse=IN.readLine()).equals("")){
// on affiche la rponse
System.out.println(rponse);
// s'il y a redirection, on recherche l'entte Location
if(redir && ! locationFound){
// on compare la ligne l'expression relationnelle location
Matcher rsultat=location.matcher(rponse);
if(rsultat.find()){
// si on a trouv on note l'URL de redirection
locationString=rsultat.group(1);
// on note qu'on a trouv
locationFound=true;
}//if
}//if
// entte suivant
}//while
// lignes suivantes de la rponse
System.out.println(rponse);
while((rponse=IN.readLine())!=null){
// on affiche la rponse
System.out.println(rponse);
}//while
// on ferme la connexion
client.close();
// a-t-on fini ?
if ( ! locationFound || nbRedirs>nbRedirsMax)
break;
// il y a une redirection oprer - on construit la nouvelle URL
URLString=protocol +"://"+host+":"+port+locationString;
url=new URL(URLString);
// suivi
System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
}//while
} catch(Exception e){
// on gre l'exception
erreur(e.getMessage(),4);
}//catch
}//main
// affichage des erreurs
public static void erreur(String msg, int exitCode){
// affichage erreur
System.err.println(msg);
// arrt avec erreur
System.exit(exitCode);
}//erreur
}//classe
Programmation TCP-IP
284
un constructeur qui on passe les trois tableaux de donnes ncessaires au calcul de l'impt
protected impots(){}
A partir de cette classe a t drive la classe impotsJDBC qui permet de remplir les trois tableaux limites, coeffR, coeffN partir du
contenu d'une base de donnes :
public class impotsJDBC extends impots{
// rajout d'un constructeur permettant de construire
// les tableaux limites, coeffr, coeffn partir de la table
// impots d'une base de donnes
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS : nom DSN de la base de donnes
// userIMPOTS, mdpIMPOTS : login/mot de passe d'accs la base
Une application graphique avait t crite. L'application utilisait un objet de la classe impotsJDBC. L'application et cet objet taient
sur la mme machine. Nous nous proposons de mettre le programme de test et l'objet impotsJDBC sur des machines diffrentes.
Nous aurons une application client-serveur o l'objet impotsJDBC distant sera le serveur. La nouvelle classe s'appelle ServeurImpots et
est drive de la classe impotsJDBC :
// paquetages imports
import java.net.*;
import java.io.*;
import java.sql.*;
public class ServeurImpots extends impotsJDBC {
// attributs
int portEcoute;
boolean actif;
// constructeur
public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
throws IOException, SQLException, ClassNotFoundException {
// construction parent
super(DSNimpots, USERimpots, MDPimpots);
// on note le port d'coute
this.portEcoute=portEcoute;
// pour l'instant inactif
actif=false;
// cre et lance un thread de lecture des commandes tapes au clavier
// le serveur sera gr partir de ces commandes
Thread admin=new Thread(){
public void run(){
try{
admin();
}catch (Exception ignored){}
}
};
admin.start();
}//ServeurImpots
Le seul paramtre nouveau dans le constructeur est le port d'coute des demandes des clients. Les autres paramtres sont passs
directement la classe de base impotsJDBC. Le serveur d'impts est contrl par des commandes tapes au clavier. Aussi cre-t-on
un thread pour lire ces commandes. Il y en aura deux possibles : start pour lancer le service, stop pour l'arrter dfinitivement. La
mthode admin qui gre ces commandes est la suivante :
Programmation TCP-IP
285
Si la commande tape au clavier est start, un thread d'coute des demandes clients est lanc. Si la commande tape est stop, tous les
threads sont arrts. Le thread d'coute excute la mthode ecoute :
public void ecoute(){
// thread d'coute des demandes des clients
// on cre le service d'coute
ServerSocket ecoute=null;
try{
// on cre le service
ecoute=new ServerSocket(portEcoute);
// suivi
System.out.println("Serveur d'impts lanc sur le port " + portEcoute);
// boucle de service
Socket liaisonClient=null;
while (true){ // boucle infinie
// attente d'un client
liaisonClient=ecoute.accept();
// le service est assur par une autre tche
new traiteClientImpots(liaisonClient,this).start();
// on retourne l'coute des demandes
}// fin while
}catch(Exception ex){
// on signale l'erreur
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}//thread d'coute
On retrouve un serveur tcp classique coutant sur le port portEcoute. Les demandes des clients sont traites par la mthode run du
thread traiteCientImpots au constructeur duquel on passe deux paramtres :
1. l'objet Socket liaisonClient qui va permettre d'atteindre le client
2. l'objet impotsJDBC this qui va donner accs la mthode this.calculer de calcul de l'impt.
// ------------------------------------------------------// assure le service un client du serveur d'impts
class traiteClientImpots extends Thread{
private Socket liaisonClient;
Programmation TCP-IP
286
// flux d'entre
// flux de sortie
// objet Impt
// constructeur
public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
this.liaisonClient=liaisonClient;
this.objImpots=objImpots;
}//constructeur
La mthode run traite les demandes des clients. Ce sont des lignes de texte qui peuvent avoir deux formes :
1. calcul mari(o/n) nbEnfants salaireAnnuel
2. fincalculs
La forme 1 permet le calcul d'un impt, la forme 2 clt la liaison client-serveur.
// mthode run
public void run(){
// rend le service au client
try{
// flux d'entre
IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
// flux de sortie
OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
// envoi d'un msg de bienvenue au client
OUT.println("Bienvenue sur le serveur d'impts");
// boucle lecture demande/criture rponse
String demande=null;
String[] champs=null; // les lments de la demande
String commande=null; // la commande du client : calcul ou fincalculs
while ((demande=IN.readLine())!=null){
// on dcompose la demande en champs
champs=demande.trim().toLowerCase().split("\\s+");
// deux demandes acceptes : calcul et fincalculs
commande=champs[0];
if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
// erreur client
OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
// commande suivante
continue;
}//if
if(commande.equals("calcul")) calculerImpt(champs);
if(commande.equals("fincalculs")){
// msg d'au-revoir au client
OUT.println("Au revoir...");
// libration des ressources
try{ OUT.close();IN.close();liaisonClient.close();}
catch(Exception ex){}
// fin
return;
}//if
//demande suivante
}//while
}catch (Exception e){
erreur("L'erreur suivante s'est produite ("+e+")",2);
}// fin try
}// fin Run
Le calcul de l'impt est effectu par la mthode calculerImpt qui reoit en paramtre le tableau des champs de la demande faite par le
client. La validit de la demande est vrifie et ventuellement l'impt calcul et renvoy au client.
// calcul d'impts
public void calculerImpt(String[] champs){
// traite la demande : calcul mari nbEnfants salaireAnnuel
// dcompose en champs dans le tableau champs
String mari=null;
int nbEnfants=0;
int salaireAnnuel=0;
// validit des arguments
try{
// il faut au moins 4 champs
if(champs.length!=4) throw new Exception();
// mari
mari=champs[1];
if (! mari.equals("o") && ! mari.equals("n")) throw new Exception();
// enfants
nbEnfants=Integer.parseInt(champs[2]);
// salaire
Programmation TCP-IP
287
salaireAnnuel=Integer.parseInt(champs[3]);
}catch (Exception ignored){
// erreur de format
OUT.println(" syntaxe : calcul mari(O/N) nbEnfants salaireAnnuel");
// fini
return;
}//if
// on peut calculer l'impt
long impot=objImpots.calculer(mari.equals("o"),nbEnfants,salaireAnnuel);
// on envoie la rponse au client
OUT.println(""+impot);
}//calculer
On passe au programme de test les donnes ncessaires la construction d'un objet ServeurImpots et partir de l il cre cet objet.
Tentons une premire excution :
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impts>start
Serveur d'impts>Serveur d'cho lanc sur le port 124
stop
La commande
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
cre un objet ServeurImpots qui n'coute pas encore les demandes des clients. C'est la commande start tape au clavier qui lance cette
coute. La commande stop arrte le serveur. Utilisons maintenant un client. Nous utiliserons le client gnrique cr
prcdemment. Le serveur est lanc :
Programmation TCP-IP
288
On voit que le client a bien rcupr le message de bienvenue du serveur. On envoie d'autres commandes :
x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<-- syntaxe : calcul mari(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des rponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
7.5 Exercices
7.5.1 Exercice 1 - Client TCP gnrique graphique
7.5.1.1 Prsentation de lapplication
On se propose de crer un programme capable de dialoguer sur lInternet avec les principaux services TCP. On lappellera un client
tcp gnrique. Lorsqu'on a compris cette application, on trouve que tous les clients tcp se ressemblent. La fentre du programme
est la suivante :
Programmation TCP-IP
289
1
2
3
nom
TxtRemoteHost
TxtPort
TxtSend
OptRCLF
OptLF
type
JTextField
JTextField
JTextField
JCheckBox
5
6
7
LstSuivi
LstDialogue
CmdAnnuler
JList
JList
JButton
rle
nom de la machine offrant le service dsir
port du service demand
texte du message qui sera envoy au serveur par le client
boutons permettant dindiquer comment se terminent les lignes dans le dialogue client/serveur
RCLF : retour chariot (#13) + passage la ligne (#10)
LF : passage la ligne (#10)
affiche des messages sur ltat de la communication entre le client & le serveur
affiche les messages changs par le client (->) et le serveur (<-)
cach - situ sous la liste de dialogue - Apparat lorsque la connexion est en cours et permet de
linterrompre si le serveur ne rpond pas
sous-options
Connecter
Dconnecter
Quitter
Envoyer
RazSuivi
RazDialogue
Auteur
rle
connecte le client au serveur
ferme la connexion
Termine le programme
Envoie le message du contrle TxtSend au serveur
Efface la liste LstSuivi
Efface la liste LstDialogue
affiche une bote de copyright
Programmation TCP-IP
290
Menu Connexion/Connecter
Cette option nest disponible que lorsque les champs Hte Distant et n de port sont non vides et qu'une connexion n'est pas
actuellement active. Un clic sur cette option entrane les oprations suivantes :
la validit du port est vrifie : ce doit tre un entier >0
un thread est lanc pour assurer la connexion au serveur
le bouton Annuler apparat afin de permettre lutilisateur dinterrompre la connexion en cours
toutes les options de menu sont dsactives sauf Quitter & Auteur
La connexion peut se terminer de plusieurs faons :
1. Lutilisateur a appuy sur le bouton Annuler : on arrte le thread de connexion et on remet le menu dans son tat de
dpart. On indique dans le suivi quil y a eu fermeture de la connexion par lutilisateur.
2. La connexion se termine avec une erreur : on fait la mme chose que prcdemment et de plus dans le suivi, on
indique la cause de lerreur.
3. La connexion se termine correctement : on enlve le bouton Annuler, indique dans le suivi que la connexion a t
faite, autorise le menu RazSuivi, inhibe le menu Connecter, autorise le menu Dconnecter
Menu Connexion/Dconnecter
Cette option nest disponible que lorsqu'une connexion avec le serveur existe. Lorsqu'elle est active elle clt la connexion avec le
serveur et remet le menu dans son tat de dpart. On indique dans le suivi que la connexion a t ferme par le client.
Menu Connexion/Quitter
Cette option clt une ventuelle connexion active avec le serveur et termine l'application.
Menu Messages/Envoyer
Cette option nest accessible que si les conditions suivantes sont runies :
- la connexion avec le serveur a t faite
- il y a un message envoyer
Si ces conditions sont runies, on envoie au serveur le texte prsent dans le champ TxtSend (3) termin par la squence RCLF si
l'option RCLF a t coche, la squence LF sinon. Une ventuelle erreur l'mission est signale dans la liste de suivi.
Menus RazSuivi et RazDialogue
Vident respectivement les listes LstSuivi et LstDialogue. Ces options sont inhibes lorsque les listes coorespondantes sont vides.
Le bouton Annuler
Ce bouton situ en bas du formulaire napparat que lorsque le client est en cours de connexion au serveur. Cette connexion peut ne
pas se faire parce que le serveur ne rpond pas ou mal. Le bouton Annuler donne alors lutilisateur, la possibilit dinterrompre la
demande de connexion.
Les listes de suivi
La liste LstSuivi (5) fait le suivi de la connexion. Elle indique les moments cls de la connexion :
- son ouverture par le client
- sa fermeture par le serveur ou le client
- toutes les erreurs qui peuvent se produire tant que la liaison est active
Programmation TCP-IP
291
La liste LstDialogue (6) fait le suivi du dialogue qui sinstaure entre le client et le serveur. Un thread surveille en tche de fond ce qui
arrive sur la socket de communication du client et l'affiche dans la liste 6.
Loption Auteur
Ce menu ouvre une fentre dite de Copyright :
Programmation TCP-IP
292
Menu secondaire
Lancer
Arrter
Quitter
Auteur
Rle
Lance le service tcp sur le port 864
Arrte le service
Termine lapplication
Informations de Copyright
Type
JList
JList
Rle
Liste des serveurs libres
Liste des serveurs occups
Programmation TCP-IP
293
Option Service/Arrter
Cette option interrompt le service :
la liste des serveurs occups est vide
la liste des serveurs libres est remplie avec le contenu du fichier Serveurs
le menu Lancer est autoris
le menu Arrter est inhib
Option Service/Quitter
Lapplication se termine.
Dialogue client/serveur
Le dialogue client/serveur se fait par change de lignes de texte termines par la squence RCLF. Le serveur GRC reconnat deux
commandes : getserveur et finservice. Nous dtaillons le rle de ces deux commandes :
1
getserveur
Le client demande sil y a un serveur de calcul disponible pour lui.
Le serveur GRC prend alors le premier serveur trouv dans sa liste de serveurs libres et renvoie son nom au client sous la forme
:
100-nom du serveur
Par ailleurs, il passe le serveur accord au client dans la liste des serveurs occups sous la forme :
serveur (IP du client)
comme le montre lexemple suivant o le serveur calcul1.istia.univ-angers.fr est occup servir le client dadresse IP 193.52.43.5 :
Un client ne peut envoyer une commande getserveur si un serveur de calcul lui a dj t affect. Ainsi avant de rpondre au
client, le serveur GRC vrifie que ladresse IP du client nest pas dj prsent parmi celles enregistres dans la liste des serveurs
occups. Si cest le cas, le serveur GRC rpond :
501-Vous avez actuellement une demande en cours
Enfin, il y a le cas o aucun serveur de calcul nest disponible : la liste des serveurs libres est vide. Dans ce cas, le serveur GRC
rpond :
502- Il ny a aucun serveur de calcul disponible
Dans tous les cas, aprs avoir rpondu au client, le serveur GRC clt la connexion avec celui-ci afin de pouvoir servir dautres
clients.
2 finservice
Programmation TCP-IP
294
Aprs lenvoi de la rponse, quelque soit celle-ci, le serveur GRC clt la connexion.
Programmation TCP-IP
295
Les changes client/serveur se font avec des lignes de texte termines par la squence RCLF.
Les rponses du service smtp sont de la forme :
numro-Message ou
numro Message
Le serveur smtp peut envoyer plusieurs lignes de rponse. La dernire ligne de la rponse est signale par un numro suivi dun espace alors
que pour les lignes prcdentes de la rponse, le numro est suivi dun tiret -.
Un numro suprieur ou gal 500 signale un message derreur.
// fin de commentaires
help
296
// commentaires
Dans les lignes de texte de la commande data, on peut mettre une ligne subject: pour prciser le sujet du courrier. Cette ligne doit tre suivie
dune ligne vide.
// rponse du serveur smtp
250 HAA11627 Message accepted for delivery
// nouvelle commande mise au clavier
quit
// commentaires
La commande quit clt la connexion au service smtp
// fin de commentaires
// rponse du serveur smtp
221 Istia.Istia.Univ-Angers.fr closing connection
1
2
3
4
6
7
Type
JTextField
JTextField
JTextField
JTextField
JTextArea
JList
JList
JButton
Programmation TCP-IP
Rle
Suite dadresses lectroniques spares par une virgule
Texte du sujet du message
Suite dadresses lectroniques spares par une virgule
Suite dadresses lectroniques spares par une virgule
Texte du message
liste de suivi
liste de dialogue
bouton Annuler non reprsent, apparaissant lorsque le client demande la connexion au serveur
SMTP. Permet lutilisateur dinterrompre cette demande si le serveur ne rpond pas.
297
Menu Principal
Courrier
Options
Rle
Envoyer
Quitter
Masquer Suivi
Raz Suivi
Masquer Dialogue
Raz Dialogue
Configurer
Sauvegarder...
Auteur
Les deux champs doivent tre remplis pour que le bouton OK soit actif. Les deux informations doivent tre mmorises dans des
variables globales afin dtre disponibles pour dautres modules.
Menu Courrier/Envoyer
Cette option nest accessible que si les conditions suivantes sont runies :
- la configuration a t faite
- il y a un message envoyer
- il y a un sujet
- il y a au moins un destinataire dans les champs 1, 3 et 4
Si ces conditions sont runies, la squence des vnements est la suivante :
- le formulaire est mis dans un tat o toutes les actions pouvant interfrer dans le dialogue client/serveur sont inhibes
- il y a connexion sur le port 25 du serveur prcis dans la configuration
- le client dialogue ensuite avec le serveur smtp selon le protocole dcrit plus haut
- le mail from: utilise ladresse lectronique de lexpditeur donne dans la configuration
- le rcpt to: sutilise pour chacune des adresses lectroniques trouves dans les champs 1, 3 et 4
- dans les lignes envoyes aprs la commande data, on trouvera les textes suivants :
- une ligne Subject: texte du sujet du contrle 2
- une ligne Cc: adresses du contrle 3
Programmation TCP-IP
298
Raz Suivi
Masquer Dialogue
Raz Dialogue
Rend invisible la liste de suivi 6 ainsi que le libell qui est au-dessus. Si la hauteur
occupe par ces deux contrles est H, tous les contrles situs dessous sont
remonts dune hauteur H et la taille totale du formulaire est diminue de H. Par
ailleurs, Masquer Suivi rend invisible loption RazSuivi ci-dessous.
Vide la liste de suivi 6
Rend invisible la liste de dialogue 7, le libell qui est dessus ainsi que loption de
menu RazDialogue ci-dessous. Comme pour Masquer Suivi, la position des
contrles situs dessous (bouton Annuler peut-tre) est recalcule et la taille de la
fentre diminue.
Vide la liste de dialogue 7
Loption Auteur
Ce menu ouvre une fentre dite de Copyright :
Programmation TCP-IP
299
SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1
Les lignes SmtpServer et ReplyAddress reprennent les deux informations acquises par loption Options/Configurer. Les lignes
Suivi et Dialogue donnent ltat des listes de Suivi et de Dialogue : 1 (prsente), 0 (absente).
Au chargement du programme, le fichier sendmail.ini est lu sil existe et le formulaire est configur en consquence. Si le fichier
sendmail.ini nexiste pas on fait comme si on avait :
SmtpServer=
ReplyAddress=
Suivi=1
Dialogue=1
Si le fichier sendmail.ini existe mais est incomplet (lignes manquantes), la ligne manquante est remplace par la ligne
correspondante ci-dessus. Ainsi la ligne Suivi=... est manquante, on fait comme si on avait Suivi=1.
Toutes les lignes ne correspondant pas au modle :
mot cl= valeur
sont ignores ainsi que celles o le mot cl est invalide. Le mot cl peut-tre en majuscules ou minuscules : cela nimporte pas.
Dans loption Options/Configurer, les valeurs SmtpServer et ReplyAddress actuellement en cours sont prsentes. Lutilisateur
peut alors les modifier sil le dsire.
Programmation TCP-IP
300
1
2
3
4
5
nom
txtRemoteHost
txtLogin
txtMdp
txtNewMdp
txtConfirmation
lstSuivi
lstDialogue
cmdAnnuler
type
JTextField
JTextField
JTextField
JTextField
JTextField
JList
JList
JButton
rle
nom du serveur
login de lutilisateur
Mot de Passe de lutilisateur
Nouveau mot de passe de lutilisateur
Confirmation du nouveau mot de passe
Messages du suivi de la connexion
Messages du dialogue Client/Serveur
non reprsent - Bouton apparaissant lorsque la connexion au serveur est en cours. Permet de
larrter .
Nom du contrle
mnuconnexion
mnuconnecter
mnuQuitter
mnuMessages
mnuRazSuivi
mnuRazDialogue
mnuAuteur
Rle
lance la connexion au serveur
quitte lapplication
efface la liste lstSuivi
efface la liste lstDialogue
affiche la bote de copyright
301
Lutilisateur a appuy sur le bouton Annuler : on arrte le thread de connexion et on remet le menu dans son tat de dpart.
On indique dans le suivi quil y a eu fermeture de la connexion par lutilisateur.
La demande de connexion est accepte par le serveur. On entame ensuite le dialogue avec le serveur pour changer le mot
de passe. Les changes de ce dialogue sont inscrits dans la liste LstDialogue. Une fois le dialogue termin, la connexion avec
le serveur est close et le menu du formulaire remis dans son tat initial.
Tant que le dialogue est actif, le bouton Annuler reste prsent afin de permettre l'utilisateur de clre la connexion s'il le
veut.
Si au cours de la communication se produit une erreur quelconque, la connexion est close et la cause de l'erreur affiche
dans la liste de suivi LstSuivi.
Menu Connexion/Quitter
Cette option clt une ventuelle connexion active avec le serveur et termine l'application.
Menus RazSuivi et RazDialogue
Vident respectivement les listes LstSuivi et LstDialogue. Ces options sont inhibes lorsque les listes coorespondantes sont vides.
Le bouton Annuler
Ce bouton situ en bas du formulaire napparat que lorsque le client est en cours de connexion ou connect au serveur. Le bouton
Annuler donne lutilisateur, la possibilit dinterrompre la communication avec le serveur.
Les listes de suivi
La liste LstSuivi (5) fait le suivi de la connexion. Elle indique les moments cls de la connexion :
- son ouverture par le client
- sa fermeture par le serveur ou le client
- toutes les erreurs de qui peuvent se produire tant que la liaison est active
La liste LstDialogue (6) fait le suivi du dialogue qui sinstaure entre le client et le serveur.
Loption Auteur
Ce menu ouvre une fentre dite de Copyright :
302
Les erreurs de communication sont signales dans la liste de suivi 6, celles lies au dialogue client/serveur dans la liste de dialogue 7.
Lors dune erreur de liaison, le dialogue client/serveur est ferm et le formulaire remis dans son tat initial prt pour une nouvelle
connexion.
Programmation TCP-IP
303
8. JAVA RMI
8.1 Introduction
Nous avons vu comment crer des applications rseau laide des outils de communication appels sockets. Dans une application
client/serveur btie sur ces outils, le lien entre le client et le serveur est le protocole de communication quils ont adopt pour
converser. Les deux applications peuvent tre crites avec des langages diffrents : Java par exemple pour le client, Perl pour le
serveur ou toute autre combinaison. On a bien deux applications distinctes relies par un protocole de communication connu des
deux. Par ailleurs, laccs au rseau via les sockets nest pas transparent pour une application Java : elle doit utiliser la classe Socket,
classe cre spcialement pour grer ces outils de communication que sont les sockets.
JAVA RMI (Remote Method Invocation) permet de crer des applications rseau aux caractristiques suivantes :
1. Les applications client/serveur sont des applications Java aux deux extrmits de la communication
2. Le client peut utiliser des objets situs sur le serveur comme sils taient locaux
3. La couche rseau devient transparente : les applications nont pas se soucier de la faon dont sont transportes les
informations dun point un autre.
Le dernier point est un facteur de portabilit : si la couche rseau dune application RMI venait changer, lapplication elle-mme
naurait pas tre r-crite. Ce sont les classes RMI du langage Java qui devront tre adaptes la nouvelle couche rseau.
Le principe dune communication RMI est le suivant :
1. Une application Java classique est crite sur une machine A. Elle va jouer le rle de serveur. Pour ce faire certains de ses objets
vont faire lobjet dune publication sur la machine A sur laquelle lapplication sexcute et deviennent alors des services.
2. Une application Java classique est crite sur une machine B. Elle va jouer le rle de client. Elle aura accs aux objets/services
publis sur la machine A, cest dire que via une rfrence distante, elle va pouvoir les manipuler comme sils taient locaux.
Pour cela, elle aura besoin de connatre la structure de lobjet distant auquel elle veut accder (mthodes & proprits).
// l'interface distante
public interface interEcho extends Remote{
public String echo(String msg) throws java.rmi.RemoteException;
}
Ici, on dclare donc une interface interEcho dclarant une mthode echo comme accessible distance. Cette mthode est susceptible
de gnrer une exception de la classe RemoteException, classe qui regroupe tous les erreurs lies au rseau.
Rmi
304
Rmi
305
// cration du service
public static
static void main (String arg[]){
try{
try
srvEcho serveurEcho=new
new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur dcho prt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur dcho ");
}
}// main
}// fin classe
Lorsquon lit le programme prcdent, on a limpression quil va sarrter aussitt aprs avoir cr et enregistr le service dcho. Ce
nest pas le cas. Parce que la classe srvEcho est drive de la classe UnicastRemoteObject, lobjet cr sexcute indfiniment : il coute
les demandes des clients sur un port anonyme cest dire choisi par le systme selon les circonstances. La cration du service est
asynchrone : dans lexemple, la mthode main cre le service et continue son excution : elle affichera bien Serveur dcho prt .
Rmi
306
Il ny a rien de bien particulier dans ce client si ce nest linstruction qui demande une rfrence du serveur :
serveur=(interEcho) Naming.lookup(urlService);
On se rappelle que notre service dcho a t enregistr dans lannuaire des services de la machine o il se trouve avec linstruction :
Naming.rebind("srvEcho",serveurEcho);
Le client utilise donc lui aussi une mthode de la classe Naming pour obtenir une rfrence du serveur quil veut utiliser. La mthode
lookup utilise admet comme paramtre lurl du service demand. Celle-ci a la forme dune url classique :
rmi://machine:port/nom_service
avec
rmi
machine
port
nom_service
Ce qui est rcupr, cest une instance de linterface distante interEcho. Si on suppose que le client et le serveur ne sont pas sur la
mme machine, lorsquon compile le client cltEcho.java, on doit disposer dans le mme rpertoire, du fichier interEcho.class, rsultat
de la compilation de linterface distante interEcho, sinon on aura une erreur de compilation sur les lignes qui rfrencent cette
interface.
158
759
09/03/99
09/03/99
15:06 interEcho.java
15:07 srvEcho.java
Aprs compilation de ces deux fichiers source, on a les fichiers .class suivants :
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA
INTERE~1 CLA
1 129
256
09/03/99
09/03/99
15:58 srvEcho.class
15:58 interEcho.class
1 427
09/03/99
16:08 cltEcho.java
256
09/03/99
15:59 interEcho.class
Rmi
307
CLTECH~1 CLA
INTERE~1 CLA
1 506
256
09/03/99
09/03/99
16:08 cltEcho.class
15:59 interEcho.class
Dans les deux cas, la machine virtuelle Java indique quelle na pas trouv la classe srvEcho_stub. Effectivement, nous navons encore
jamais entendu parler de cette classe. Dans le client, la localisation du serveur sest faite avec linstruction suivante :
serveur=(interEcho) Naming.lookup(urlService);
protocole rmi
machine o opre le serveur - ici la mme machine sur laquelle le client. La syntaxe est normalement
machine:port. En labsence du port, cest le port 1099 qui sera utilis par dfaut. A lcoute de ce port, se trouve
le service dannuaire du serveur.
cest le nom du service particulier demand
A la compilation, aucune erreur navait t signale. Il fallait simplement que le fichier interEcho.class de linterface distante soit
disponible.
A lexcution, la machine virtuelle rclame la prsence dun fichier srvEcho_stub.class si le service demand est le service srvEcho, de
faon gnrale un fichier X_stub.class pour un service X. Ce fichier nest ncessaire qu lexcution pas la compilation du client. Il
en est de mme pour le serveur. Quest-ce donc que ce fichier ?
Sur le serveur, se trouve la classe srvEcho.class qui est notre objet/service distant. Le client, sil na pas besoin de cette classe, a
nammoins besoin dune sorte dimage delle afin de pouvoir communiquer avec. En fait, le client nadresse pas directement ses
requtes lobjet distant : il les adresse son image locale srvEcho_stub.class situe sur la mme machine que lui. Cette image locale
srvEcho_stub.class dialogue avec une image de mme nature (srvEcho_stub.class) situe cette fois sur le serveur. Cette image est cre
partir du fichier .class du serveur avec un outil de Java appel rmic. Sous Windows, la commande :
E:\data\java\RMI\echo\serveur>j:\jdk12\bin\rmic srvEcho
3 264
1 736
09/03/99
09/03/99
16:57 srvEcho_Stub.class
16:57 srvEcho_Skel.class
Il y a bien l, le fichier srvEcho_stub.class dont le client et le serveur ont besoin lexcution. Il y a galement un fichier
srvEcho_Skel.class dont pour linstant on ignore le rle. On fait une copie du fichier srvEcho_stub.class dans le rpertoire du client et du
serveur et on supprime le fichier srvEcho_Skel.class. On a donc les fichiers suivants :
du ct serveur :
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA
INTERE~1 CLA
SRVECH~1 CLA
1 129
256
3 264
09/03/99
09/03/99
09/03/99
15:58 srvEcho.class
15:58 interEcho.class
16:01 srvEcho_Stub.class
du ct client :
Rmi
308
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA
INTERE~1 CLA
SRVECH~1 CLA
1 506
256
3 264
09/03/99
09/03/99
09/03/99
16:08 cltEcho.class
15:59 interEcho.class
16:01 srvEcho_Stub.class
rmiregistry est le service dannuaire. Il est ici lanc en tche de fond dans une fentre Dos de Windows par la commande start.
Lannuaire actif, on peut crer le service dcho et lenregistrer dans lannuaire des services. L encore, il est lanc en tche de fond
par une commande start :
E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho
Le serveur dcho sexcute dans une nouvelle fentre DOS et affiche comme on le lui avait demand :
Serveur dcho prt
10:02
10:01
10:02
10:02
10:02
.
..
cltEcho.class
interEcho.class
srvEcho_Stub.class
Rmi
309
On a donc une erreur : la machine virtuelle Java rclame apparemment le fichier srvEcho_skel.class qui avait t produit par lutilitaire
rmic mais qui jusqu maintenant navait pas servi. On le recre et on le transfre galement sur la machine linux :
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 11
drwxr-xr-x
2 serge
admin
1024 Mar 10
drwxr-xr-x
4 serge
admin
1024 Mar 10
-rw-r--r-1 serge
admin
1506 Mar 10
-rw-r--r-1 serge
admin
256 Mar 10
-rw-r--r-1 serge
admin
1736 Mar 10
-rw-r--r-1 serge
admin
3264 Mar 10
10:17
10:01
10:02
10:02
10:17
10:02
.
..
cltEcho.class
interEcho.class
srvEcho_Skel.class
srvEcho_Stub.class
On a la mme erreur que prcdemment... Alors on rflchit et on relit les docs sur RMI. On finit par se dire que cest peut-tre le
serveur lui-mme qui a besoin du fameux fichier srvEcho_Skel.class. On relance alors, sur la machine Windows, le serveur avec les
deux fichiers srvEcho_Stub.class et srvEcho_Skel.class prsents :
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1
INTERE~1
SRVECH~2
SRVECH~3
CLA
CLA
CLA
CLA
1 129
256
3 264
1 736
09/03/99
09/03/99
10/03/99
10/03/99
15:58
15:58
9:05
9:05
srvEcho.class
interEcho.class
srvEcho_Stub.class
srvEcho_Skel.class
puis sur la machine linux, on teste de nouveau le client et cette fois-ci, a marche :
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-1 serge
admin
1506 Mar 10 10:02 cltEcho.class
-rw-r--r-1 serge
admin
256 Mar 10 10:02 interEcho.class
-rw-r--r-1 serge
admin
3264 Mar 10 10:02 srvEcho_Stub.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Rponse serveur : [msg1]
Message : msg2
Rponse serveur : [msg2]
Message : fin
On en dduit donc, que ct serveur les deux fichiers srvEcho_Stub.class et srvEcho_Skel.class doivent tre prsents. Ct client, seul le
fichier srvEcho_Stub.class a t ncessaire jusqu maintenant. Il stait avr indispensable lorsque le client et le serveur taient sur la
mme machine Windows. Sous linux, on lenlve pour voir...
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-1 serge
admin
1506 Mar 10 10:02 cltEcho.class
-rw-r--r-1 serge
admin
256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:28)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
Rmi
310
On a une erreur intressante qui a lair de dire que la machine virtuelle Java a voulu charger la fameuse classe stub mais quil a
chou en labsence dun security manager . On se dit quon a vu quelque chose sur ce thme dans les docs. On sy replonge... et
on trouve que le serveur doit crer et installer un gestionnaire de scurit (security manager) qui garantisse aux clients qui
demandent le chargement de classes que celles-ci sont saines. En labsence de ce security manager, ce chargement de classes est
impossible. Ca semble coller : notre client linux a demand la classe srvEcho_stub.class dont il a besoin au serveur et celui-ci a refus
en disant quaucun gestionnaire de scurit navait t install. On modifie donc le code de la fonction main du serveur de la faon
suivante :
// cration du service
public static void main (String arg[]){
// installation d'un gestionnaire de scurit
System.setSecurityManager(new RMISecurityManager());
On compile et on produit les fichiers srvEcho_stub.class et srvEcho_Skel.class avec loutil rmic. On lance le service dannuaire (rmiregistry)
puis le serveur et on a une erreur quauparavant on navait pas !
Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099
connect,resolve) lors du lancement du serveur dcho
Le gestionnaire de scurit semble avoir t trop efficace. On lit de nouveau les docs... On saperoit que lorsquun gestionnaire de
scurit est actif, il faut prciser au lancement dun programme ses droits. Cela se fait avec loption suivante :
start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
o
java.security.policy est un mot cl
mypolicy est un fichier texte dfinissant les droits du programme. Ici, cest le suivant :
grant {
// Allow everything for now
permission java.security.AllPermission;
};
On en dduit que le rpertoire partir duquel le service dannuaire est lanc a de limportance. Ici, Java na pas trouv la classe
srvEcho_stub.class parce que le service dannuaire na pas t lanc du rpertoire du serveur. Lors du lancement du serveur, on peut
prciser dans quel rpertoire se trouvent les classes ncessaires au serveur :
Rmi
311
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
La commande est sur une unique ligne. Le mot cl java.rmi.server.codebase sert indiquer lURL du rpertoire contenant les
classes ncessaires au serveur. Ici, cette URL prcise le protocole file qui est le protocole daccs aux fichiers locaux et le
rpertoire contenant les fichiers .class du serveur. Si donc on procde comme suit :
arrt du service dannuaire
relancer le service dannuaire partir dun autre rpertoire que celui du serveur
dans le rpertoire du serveur, lancer celui-ci avec la commande (une seule ligne) :
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Le serveur est alors lanc correctement. On peut donc passer au client. On le teste :
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-1 serge
admin
1506 Mar 10 14:28 cltEcho.class
-rw-r--r-1 serge
admin
256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
On obtient la mme erreur signalant labsence dun gestionnaire de scurit. On se dit quon sest peut-tre tromp et que cest le
client qui doit se crer son gestionnaire de scurit. On laisse le serveur avec son gestionnaire de scurit mais on en cre un pour le
client galement. La fonction main du client cltEcho.java devient alors :
public static void main(String arg[]){
// syntaxe : cltEcho machine port
// machine : machine o opre le serveur d'cho
// port : port o opre l'annuaire des services sur la machine du service d'cho
// vrification des arguments
if(arg.length!=1){
if
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// installation d'un gestionnaire de scurit
System.setSecurityManager(new RMISecurityManager());
// dialogue client-serveur
String urlService=arg[0];
BufferedReader in=null
null;
null
String msg=null
null;
null
String reponse=null
null;
null
interEcho serveur=null
null;
null
try{
try
....
} catch (Exception e){
....
}// try
}// main
312
on recompile cltEcho.java
on transfre les fichiers .class sur la machine linux
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-1 serge
admin
1506 Mar 10 14:28 cltEcho.class
-rw-r--r-1 serge
admin
256 Mar 10 10:02 interEcho.class
on lance le client
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
java.io.FileNotFoundException: /e:/data/java/rmi/echo/serveur/srvEcho_Stub.class
at java.io.FileInputStream.<init>(FileInputStream.java)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:150)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:170)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:119)
at sun.applet.AppletClassLoader.findClass(AppletClassLoader.java:496)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:199)
at sun.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:159)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:97)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
File not found when looking for: srvEcho_Stub
Erreur : java.rmi.UnmarshalException: Return value class not found; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Malgr les apparences, on progresse : lerreur nest plus la mme. On constate que le client a pu demander au serveur la classe
srvEcho_Stub.class, mais que celui-ci ne la pas trouv. Donc, le client doit avoir un gestionnaire de scurit sil veut pouvoir
demander des classes au serveur.
Si on regarde lerreur prcdente, on voit que le fichier srvEcho_Stub.class a t cherch dans le rpertoire
e:/data/java/rmi/echo/serveur/ et quil na pas t trouv. Cest pourtant bien l quil est. Si on regarde plus prcisment,
la liste des mthodes impliques dans lerreur, on trouve celle-ci :
sun.net.www.protocol.file.FileURLConnection.getInputStream. Le client semble avoir ouvert un flux avec un objet de
type FileURLConnection. On se dit que tout a a un rapport avec la faon dont on a lanc notre serveur :
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Le message derreur semble faire rfrence la valeur du mot cl java.rmi.server.codebase. Lorsquon regarde les docs de nouveau,
on voit que la valeur de ce mot cl est, dans les exemples donns, toujours : http://.., c.a.d. que le protocole utilis est http. Il
napparat pas clairement comment le client demande et obtient ses classes auprs du serveur. Peut-tre les demande-t-il avec
lURL du mot cl java.rmi.server.codebase, URL prcise au lancement du serveur. On dcide donc de lancer le serveur par la
nouvelle commande suivante :
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Le protocole est maintenant http. On est oblig de dplacer les fichiers .class un endroit accessible au serveur http de la
machine sur laquelle seront stockes les classes. Dans notre exemple, le serveur tourne sur une machine windows avec un
serveur http PWS de Microsoft. La racine de ce serveur est d:\Inetpub\wwwroot. Aussi procde-t-on de la faon suivante :
Rmi
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA
INTERE~1 CLA
1 622
256
10/03/99
09/03/99
14:12 cltEcho.class
15:59 interEcho.class
8.2.1.8 Rsum
Ct serveur Windows :
le serveur a un gestionnaire de scurit
il a t lanc avec des options : start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
cltEcho
rmi://tahe.istia.univ-angers.fr/srvEcho
Rmi
314
-rw-r--r--rw-r--r--rw-r--r--
1 serge
1 serge
1 serge
admin
admin
admin
parce que la classe srvEcho_Stub.class va tre demande par les clients, le rpertoire choisi pour les classes du serveur est un
rpertoire accessible au serveur http de la machine Linux. Ici lURL de ce rpertoire est http://shiva.istia.univangers.fr/~serge/rmi/echo/serveur
le service dannuaire est lanc en tche de fond : /usr/local/bin/jdk/rmiregistry &
le serveur est lanc en tche de fond : /usr/local/bin/jdk/bin/java
-Djava.rmi.server.codebase=http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur/
srvEcho &
On peut tester les clients. Le client Windows dabord.
on passe dans le rpertoire du client sur la machine windows
on lance le client par la commande :
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho
rmi://shiva.istia.univ-angers.fr/srvEcho
Message : msg1
Rponse serveur : [msg1]
Message : fin
A noter que pour le client linux qui fonctionne sur la mme machine que le serveur dcho, il na pas t besoin de prciser de
machine dans lURL du service demand.
Machine Windows
Serveur RMI
Machine cliente
Client RMI
Base de donnes
ODBC
Rmi
315
// l'interface distante
public interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp)
throws java.rmi.RemoteException;
public String[] executeSQL(String requete, String separateur)
throws java.rmi.RemoteException;
public String close()
throws java.rmi.RemoteException;
}
le client se connecte une base de donnes distante dont il donne le pilote, lurl JDBC ainsi que son identit id
et son mot de passe mdp pour accder cette base. Le serveur lui rend une chane de caractres indiquant le
rsultat de la connexion :
200 - Connexion russie
500 - Echec de la connexion
executeSQL
le client demande lexcution dune requte SQL sur la base laquelle il est connect. Il indique le caractre
qui doit sparer les champs dans les rsultats qui lui sont renvoys. Le serveur renvoie un tableau de chanes :
100 n
pour une requte de mise jour de la base, n tant le nombre de lignes mises jour
500 msg derreur
si la requte a gnr une erreur
501 Pas de rsultats
si la requte na gnr aucun rsultat
101 ligne1
101 ligne2
101 ...
si la requte a gnr des rsultats. Les lignes ainsi renvoyes par le serveur sont les lignes rsultats de la
requte.
close
le client ferme sa connexion avec la base distante. Le serveur renvoie une chane indiquant le rsultat de cette
fermeture :
200 Base ferme
500 Erreur lors de la fermeture de la base (msg derreur)
Rmi
316
// packages imports
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;
// classe srvSQL
public class srvSQL extends UnicastRemoteObject implements
implements interSQL{
// donnes globales de la classe
private Connection DB;
// ------------- constructeur
public srvSQL() throws RemoteException{
super();
super
}
// --------------- connect
public String connect(String pilote, String url, String id,
String mdp) throws RemoteException{
// connexion la base url grce au driver pilote
// identification avec identit id et mot de passe mdp
String resultat=null
null;
// rsultat de la mthode
null
try{
try
// chargement du pilote
Class.forName(pilote);
// demande de connexion
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion russie";
} catch (Exception e){
// erreur
resultat="500 Echec de la connexion (" + e + ")";
}
// fin
return resultat;
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur)
throws RemoteException{
// excute une requte SQL sur la base DB
// et met les rsultats dans un tableau de chanes
// donnes ncessaires l'excution de la requte
Statement S=null
null;
null
ResultSet RS=null
null;
null
String[] lignes=null
null;
null
Vector resultats=new
new Vector();
String ligne=null
null;
null
try{
try
// cration du conteneur de la requte
S=DB.createStatement();
// excution requte
if (! S.execute(requete)){
// requte de mise jour
// on renvoie le nombre de lignes mises jour
lignes=new
new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// c'tait une requte d'interrogation
// on rcupre les rsultats
RS=S.getResultSet();
// nombre de champs du Resultset
int nbChamps=RS.getMetaData().getColumnCount();
// on les exploite
while(RS.next()){
while
// cration de la ligne des rsultats
ligne="101 ";
for (int
int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// ajout au vecteur des rsultats
resultats.addElement(ligne);
}// while
// fin de l'exploitation des rsultats
// on libre les ressources
RS.close();
S.close();
// on rend les rsultats
int nbLignes=resultats.size();
if (nbLignes==0){
Rmi
317
lignes=new
new String[1];
lignes[0]="501 Pas de rsultats";
} else {
lignes=new
new String[resultats.size()];
for(int
for int i=0;i<lignes.length;i++)
lignes[i]=(String) resultats.elementAt(i);
}//if
return lignes;
} catch (Exception e){
// erreur
lignes=new
new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- close
public String close() throws RemoteException {
// ferme la connexion la base de donnes
String resultat=null
null;
null
try{
try
DB.close();
resultat="200 Base ferme";
} catch (Exception e){
resultat="500 Erreur la fermeture de la base ("+e+")";
}
// renvoi du rsultat
return resultat;
}
// ----------- main
public static void main (String[] args){
// gestionnaire de scurit
System.setSecurityManager(new
new RMISecurityManager());
// lancement du service
srvSQL serveurSQL=null
null;
null
try{
try
// cration
serveurSQL=new
new srvSQL();
// enregistrement
Naming.rebind("srvSQL",serveurSQL);
// suivi
System.out.println("Serveur SQL prt");
} catch (Exception e){
// erreur
System.err.println("Erreur lors du lancement du serveur SQL ("+ e +")");
}// try-catch
}// main
}// classe
srvSQL
nom rmi du serveur SQL
sun.jdbc.odbc.JdbcOdbcDriver
318
urlBase
id
mdp
separateur
Une fois lanc avec les paramtres prcdents, le client suit les tapes suivantes :
il se connecte au serveur RMI srvSQL, donc un serveur RMI sur la mme machine que le client
il demande la connexion la base de donnes articles
connect(sun.jdbc.odbc.JdbcOdbcDriver, jdbc:odbc:articles, , )
il demande lutilisateur de taper une requte SQL au clavier
il lenvoie au serveur SQL
executeSQL(requete, ,);
il affiche lcran les rsultats renvoys par le serveur
il redemande lutilisateur de taper une requte SQL au clavier. Il sarrtera lorsque la requte est fin.
Le texte Java du client suit. Les commentaires devraient suffire sa comprhension.
import java.rmi.*;
import java.io.*;
public class cltSQL {
// vrification du nb d'arguments
if(arg.length!=6)
if
erreur(syntaxe,1);
// init paramtres de la connexion la base de donnes
String urlService=arg[0];
String pilote=arg[1];
String urlBase=arg[2];
String id, mdp, separateur;
if(arg[3].equals(
"null")) id=""; else id=arg[3];
if
if(arg[4].equals(
"null")) mdp=""; else mdp=arg[4];
if
if(arg[5].equals(
"null")) separateur=" "; else separateur=arg[5];
if
// installation d'un gestionnaire de scurit
System.setSecurityManager(new
new RMISecurityManager());
// dialogue client-serveur
String requete=null
null;
null
String reponse=null
null;
null
String[] lignes=null
null;
null
String codeErreur=null
null;
null
try{
try
// ouverture du flux clavier
in=new
new BufferedReader(new
new InputStreamReader(System.in));
// suivi
System.out.println("--> Connexion au serveur RMI en cours...");
// localisation du service
serveurSQL=(interSQL) Naming.lookup(urlService);
// suivi
System.out.println("--> Connexion la base de donnes en cours");
// demande de connexion initiale la base de donnes
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// suivi
Rmi
319
System.out.println("<-- "+reponse);
// analyse rponse
codeErreur=reponse.substring(0,3);
if(codeErreur.equals(
"500"))
if
erreur("Abandon sur erreur de connexion la base",3);
// boucle de lecture des requtes envoyer au serveur SQL
System.out.print("--> Requte : ");
requete=in.readLine().toLowerCase().trim();
while(!
requete.equals("fin")){
while
// envoi de la requte au serveur et rception de la rponse
lignes=serveurSQL.executeSQL(requete,separateur);
// suivi
afficheLignes(lignes);
// requte suivante
System.out.print("--> Requte : ");
requete=in.readLine().toLowerCase().trim();
}// while
// suivi
System.out.println("--> Fermeture de la connexion la base de donnes distante");
// on clt la connexion
reponse=serveurSQL.close();
// suivi
System.out.println("<-- " + reponse);
// fin
System.exit(0);
// gestion des erreurs
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// try
}// main
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int
int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ erreur
private static void erreur(String msg, int exitCode){
// affichage msg d'erreur
System.err.println(msg);
// libration ventuelle des ressources
try{
try
in.close();
serveurSQL.close();
} catch(Exception
e){}
catch
// on quitte
System.exit(exitCode);
}// erreur
}// classe
451
3 238
12/03/99
12/03/99
17:54 interSQL.class
17:54 srvSQL.class
INTERS~1
SRVSQL~1
SRVSQL~2
SRVSQL~3
Rmi
CLA
CLA
CLA
CLA
451
3 238
4 491
2 414
12/03/99
12/03/99
12/03/99
12/03/99
17:54
17:54
17:56
17:56
interSQL.class
srvSQL.class
srvSQL_Stub.class
srvSQL_Skel.class
320
JAV
CLA
CLA
CLA
3 486
451
4 491
2 414
11/03/99
11/03/99
11/03/99
11/03/99
11:39
10:55
13:19
13:19
cltSQL.java
interSQL.class
srvSQL_Stub.class
srvSQL_Skel.class
on compile le client
E:\data\java\RMI\sql\client>j:\jdk12\bin\javac cltSQL.java
E:\data\java\RMI\sql\client>dir *.class
INTERS~1
CLTSQL~1
SRVSQL~1
SRVSQL~2
CLA
CLA
CLA
CLA
451
2 839
4 491
2 414
11/03/99
12/03/99
11/03/99
11/03/99
10:55
18:00
13:19
13:19
interSQL.class
cltSQL.class
srvSQL_Stub.class
srvSQL_Skel.class
8.3.6 tape 4 : Tests avec serveur & client sur mme machine windows
le service dannuaire est lanc dans un rpertoire autre que celui du serveur et du client
F:\>start j:\jdk12\bin\rmiregistry
on lance le serveur
E:\data\java\RMI\sql\serveur>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/sql/serveur/ srvSQL
on lance le client
E:\data\java\RMI\sql\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltSQL srvSQL
sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
-->
-->
<--->
<-<-<-<-<-<-<-<-<--->
<--->
<-<-<-<-<-<-<-<--
Rmi
321
<--->
-->
<--
101 vlo,31
Requte : fin
Fermeture de la connexion la base de donnes distante
200 Base ferme
8.3.7 tape 5 : Tests avec serveur sur machine windows et client sur machine
linux
on arrte ventuellement le serveur et le service dannuaire
on transfre les fichiers .class du client sur une machine linux
shiva[serge]:/home/admin/serge/java/rmi/sql/client#
$ dir *.class
-rw-r--r-1 serge
admin
2839 Mar 11 14:37 cltSQL.class
-rw-r--r-1 serge
admin
451 Mar 11 14:37 interSQL.class
les fichiers du serveur sont mis dans un rpertoire accessible au serveur http de la machine windows
D:\Inetpub\wwwroot\rmi\sql>dir
INTERS~1
SRVSQL~1
SRVSQL~2
SRVSQL~3
MYPOLICY
CLA
CLA
CLA
CLA
451
3 238
4 491
2 414
81
11/03/99
11/03/99
11/03/99
11/03/99
08/06/98
10:55
13:19
13:19
13:19
15:01
interSQL.class
srvSQL.class
srvSQL_Stub.class
srvSQL_Skel.class
mypolicy
on relance le serveur avec des paramtres diffrents que ceux utiliss dans le test prcdent
D:\Inetpub\wwwroot\rmi\sql>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/sql/ srvSQL
Rmi
322
8.3.8 Conclusion
On a l une application intressante en ce quelle permet laccs une base de donnes partir dun poste quelconque du rseau.
On aurait trs bien pu lcrire de faon traditionnelle avec des sockets et ce qui est dailleurs demand dans un exercice du chapitre
sur les bases de donnes. Si on crivait cette application de faon traditionnelle :
on aurait un client et un serveur qui pourraient tre crits avec des langages diffrents
le client et le serveur communiqueraient par change de lignes de texte et auraient un dialogue qui pourrait tre du genre :
client : connect machine port pilote urlBase id mdp
o les deux premiers paramtres spcifient o trouver le serveur, les quatre suivants indiquant les paramtres de
connexion la base de donnes exploiter
Le serveur pourrait rpondre par quelque chose du genre :
200 - Connexion russie
500 - Echec de la connexion
client : executeSQL requete, separateur
pour demander au serveur dexcuter une requte SQL sur la base connecte au client. separateur tant le caractre
sparateur des champs dans les lignes de la rponse.
Le serveur pourrait rpondre quelque chose du genre
100 n
pour une requte de mise jour de la base, n tant le nombre de lignes mises jour
500 msg derreur
si la requte a gnr une erreur
501 Pas de rsultats
si la requte na gnr aucun rsultat
101 ligne1
101 ligne2
101 ...
si la requte a gnr des rsultats. Les lignes ainsi renvoyes par le serveur sont les lignes rsultats de la
requte.
client : close
pour fermer la connexion avec la base distante. Le serveur pourrait renvoyer une chane indiquant le rsultat de cette
fermeture :
200 Base ferme
500 Erreur lors de la fermeture de la base (msg derreur)
On voit ici que si on est capable de construire une application traditionnelle avec un protocole du genre prcdent, on peut en
dduire une structure possible du serveur RMI. L o dans le protocole on a une phrase du client vers le serveur du genre :
commande param1 param2 ... paramq
on pourra avoir au sein du serveur RMI une mthode
String commande(param1,param2,..., paramq)
et cette mthode accessible au client devra donc faire partie de linterface publie du serveur.
Pour en terminer l, notons que notre serveur ne gre actuellement quun client : il ne peut en grer plusieurs, tel quil est crit
actuellement. En effet, si un client se connecte une base B1, un objet Connection DB=DB1 est cr par le serveur. Si un second
client demande une connexion une base B2, le serveur le note en faisant Connection DB=DB2, cassant ainsi la connexion du
premier client la base B1.
Rmi
323
8.4 Exercices
8.4.1 Exercice 1
tendre le serveur SQL prcdent afin quil gre plusieurs clients.
8.4.2 Exercice 2
crire lapplet Java de commerce lectronique prsente dans les exercices du chapitre JDBC afin quelle travaille avec le serveur
RMI de lexercice prcdent.
Rmi
324
Client
C++
Client
Java
Serveur
C++
Serveur
Java
CORBA
Nous allons voir que la construction avec CORBA dune application distribue est proche de la mthode employe avec Java RMI :
les concepts se ressemblent. CORBA prsente lavantage de linteroprabilit avec des applications crites dans dautres langages.
Nous prenons comme premier exemple, celui du serveur dcho dj utilis dans le contexte RMI. Le lecteur pourra ainsi voir les
diffrences entre les deux mthodes.
Lapplication a t teste avec le jdk1.2.
Corba
325
};
};
La description de linterface sera stocke dans un fichier echo.idl. Elle est crite dans le langage IDL (Interface Definition Language) de
lOMG. Pour tre exploitable, elle doit tre analyse par un programme qui va crer des fichiers source dans le langage utilis pour
dvelopper lapplication CORBA. Ici, nous utiliserons le programme idltojava.exe qui partir de linterface prcdente va crer les
fichiers source .java ncessaires lapplication. Le programme idltojava.exe nest pas livr avec le JDK. On peut se le procurer sur le
site de Sun http://java.sun.com.
Analysons les quelques lignes de linterface idl prcdente :
module echo
est quivalent package echo de Java. La compilation de linterface va donner naissance au package java echo c.a.d. un
rpertoire contenant des classes Java.
interface iSrvEcho
est quivalent interface iSrvEcho de Java. Va donner naissance une interface Java.
string echo(in string msg)
est quivalent linstruction Java String echo(String msg). Les types du langage IDL ne correspondent pas exactement
ceux du langage Java. On trouvera les correspondances un peu plus loin dans ce chapitre. Dans le langage IDL, les
paramtres dune fonction peuvent tre des paramtres dentre (in), de sortie (out), dentre-sortie (inout). Ici, la mthode
echo reoit un paramtre dentre msg qui est une chane de caractres et renvoie une chane de caractres comme rsultat.
Linterface prcdente est celle de notre serveur dcho. On rappelle quune interface distante dcrit les mthodes de lobjet serveur
accessibles aux clients. Ici, seule la mthode echo sera disponible pour les clients.
IDL
78
15/03/99
13:56 ECHO.IDL
Loption -fno-cpp sert indiquer quil ny a pas utiliser de pr-processeur (utilis avec le C/C++ le plus souvent). La compilation du
fichier echo.idl produit un sous-rpertoire echo avec dedans les fichiers suivants :
E:\data\java\corba\ECHO>dir echo
_ISRVE~1
ISRVEC~1
ISRVEC~2
ISRVEC~3
_ISRVE~2
JAV
JAV
JAV
JAV
JAV
1 095
311
825
1 827
1 803
17/03/99
17/03/99
17/03/99
17/03/99
17/03/99
17:19
17:19
17:19
17:19
17:19
_iSrvEchoStub.java
iSrvEcho.java
iSrvEchoHolder.java
iSrvEchoHelper.java
_iSrvEchoImplBase.java
./ECHO/ISRVECHO.JAVA
ECHO.IDL
Mon Mar 15 13:56:08 1999
D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
package echo;
public interface iSrvEcho
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
String echo(String msg)
;
}
On voit que cest quasiment la traduction mot mot de linterface IDL. Si on a la curiosit de regarder le contenu des autres fichiers
.java, on trouvera des choses plus complexes. Voici ce que dit la documentation sur le rle de ces diffrents fichiers :
iSrvEcho.java
linterface du serveur
Corba
326
_iSrvEchoImplbase.java
implmente linterface iSrvEcho prcdente. Cest une classe abstraite, squelette du serveur fournissant au serveur les
fonctionnalits CORBA ncessaires lapplication distribue.
_iSrvEchoStub.java
Cest limage ( stub ) du serveur dont usera le client. Elle fournit au client les fonctionnalits CORBA pour atteindre le
serveur.
iSrvEchoHelper.java
Fournit les mthodes ncessaires la gestion des rfrences dobjets CORBA
iSrvEchoHolder.java
Fournit les mthodes ncessaires la gestion des paramtres dentre-sortie des mthodes de linterface.
JAV
JAV
JAV
JAV
JAV
CLA
CLA
CLA
CLA
CLA
1 095
311
825
1 827
1 803
2 275
1 383
251
2 078
858
17/03/99
17/03/99
17/03/99
17/03/99
17/03/99
18/03/99
18/03/99
18/03/99
18/03/99
18/03/99
17:19
17:19
17:19
17:19
17:19
11:25
11:25
11:25
11:25
11:25
_iSrvEchoStub.java
iSrvEcho.java
iSrvEchoHolder.java
iSrvEchoHelper.java
_iSrvEchoImplBase.java
_iSrvEchoImplBase.class
_iSrvEchoStub.class
iSrvEcho.class
iSrvEchoHelper.class
iSrvEchoHolder.class
Le code se comprend de lui-mme. Cette classe est enregistre dans le fichier srvEcho.java dans le rpertoire parent de celui du
package de linterface iSrvEcho.
On peut compiler pour vrifier :
E:\data\java\corba\ECHO>j:\jdk12\bin\javac srvEcho.java
E:\data\java\corba\ECHO>dir
ECHO
IDL
SRVECH~1 CLA
SRVECH~1 JAV
ECHO
78
488
252
<REP>
15/03/99
18/03/99
15/03/99
17/03/99
13:56
11:30
14:02
17:19
ECHO.IDL
srvEcho.class
srvEcho.java
echo
327
Comme pour une application client-serveur RMI, un serveur CORBA doit tre enregistr dans un annuaire pour tre accessible par
des clients. Cest cette procdure denregistrement qui, au niveau dveloppement, diffre selon quon a une application CORBA ou
RMI. Voici celle du serveur CORBA dcho enregistre dans le fichier serveurEcho.java :
// packages imports
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- classe serveurEcho
public class serveurEcho{
// ------- main : lance le serveur d'cho
// syntaxe pg machineAnnuaire portAnnuaire nomService
// machine : machine supportant l'annuaire CORBA
// port : port de l'annuaire CORBA
// nomService : nom du service enregistrer
public static void main(String arg[]){
// les arguments sont-ils l ?
if(arg.length!=3){
if
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomService");
System.exit(1);
}
// on rcupre les arguments
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
try{
try
Nous expliquons ci-aprs les grandes lignes du lancement du serveur sans entrer dans les dtails qui sont complexes au premier
abord. Il faut retenir de lexemple prcdent ses grandes lignes qui vont revenir dans tout serveur CORBA.
9.2.5.2.1 Les paramtres du serveur
Un serveur CORBA doit senregistrer auprs dun service dannuaire oprant sur une machine et un port donns. Notre application
recevra ces deux donnes en paramtres. Le service ainsi enregistr doit porter un nom, ce sera le troisime paramtre.
9.2.5.2.2 Crer lobjet daccs au service dannuaire CORBA
Pour atteindre le service dannuaire et enregistrer notre serveur dcho, nous avons besoin dun objet appel ORB (Object Request
Broker) obtenu avec la mthode de classe suivante :
ORB ORB.init(String [] args, Properties prop)
args
tableau de paires de chanes de caractres, chaque paire tant de la forme (paramtre,valeur)
Corba
328
prop
proprits de lapplication
Lexemple utilise la squence suivante pour obtenir lobjet ORB :
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port
port};
port
ORB orb=ORB.init(initORB,null
null);
null
ce couple prcise la machine ou opre lannuaire des services CORBA, ici la machine passe en paramtre au serveur.
)
ce couple prcise le port ou opre lannuaire des services CORBA, ici le port pass en paramtre au serveur.
("-ORBInitialPort",port
Le second paramtre de la mthode init est laiss null. Si on avait laiss le premier paramtre galement null, le couple
(machine,port) utilis aurait t par dfaut (localhost,900).
9.2.5.2.3 Enregistrer le serveur dans lannuaire des services CORBA
Lenregistrement du serveur dans lannuaire se fait avec les oprations suivantes :
// on met le service dans l'annuaire des services
// il s'appellera srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService
nomService,
nomService "");
NameComponent path[]={nc};
// on cre le serveur et on l'associe au service srvEcho
srvEcho serveurEcho=new
new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
La premire partie du code consiste prparer le nom du service. Ce nom est symbolis dans le code par la variable path. Le nom
dun service comprend plusieurs composantes :
une composante initiale objRef, objet gnrique qui doit tre ramen un type NamingContext, ici ncRef.
le nom du service, ici nomService qui a t pass en paramtre au serveur
Ces composantes du nom (NameComponent) sont rassembles dans un tableau, ici path. Cest ce tableau qui nomme de faon
prcise le service cr. Une fois le nom cr, il reste
lassocier un exemplaire du serveur (la classe srvEcho construite prcdemment)
srvEcho serveurEcho=new
new srvEcho();
Corba
IDL
CLA
JAV
CLA
JAV
78
1 793
1 806
488
252
<REP>
15/03/99
18/03/99
16/03/99
18/03/99
15/03/99
17/03/99
13:56
13:18
15:38
11:30
14:02
17:19
ECHO.IDL
serveurEcho.class
serveurEcho.java
srvEcho.class
srvEcho.java
echo
329
// ---------------------- getServeurEcho
private static iSrvEcho getServeurEcho(String machine,
machine String port,
port
Corba
330
String nomService){
nomService
A lissue de cette opration, le client dtient une rfrence du serveur dcho. Ensuite un client CORBA ne diffre pas dun client
RMI. La mthode prive qui assure la connexion au serveur est la suivante :
// ---------------------- getServeurEcho
private static iSrvEcho getServeurEcho(String machine, String port, String nomService){
// demande une rfrence du serveur d'cho
// suivi
System.out.println("--> Connexion au serveur CORBA en cours...");
// la rfrence du serveur d'cho
iSrvEcho serveurEcho=null
null;
null
try{
try
// on demande un objet CORBA pour travailler
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null
null);
null
// on construit le nom du serveur d'cho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// on demande le service
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));
} catch (Exception e){
System.err.println("Erreur lors de la localisation du serveur d'cho ("
+ e + ")");
System.exit(10);
}// try-catch
// on rend la rfrence au serveur
return serveurEcho;
}// getServeurEcho
Corba
331
on demande au service dannuaire une rfrence du service dcho (cest l quon diffre du serveur)
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));
9.2.6.3 Compilation
E:\data\java\corba\ECHO>j:\jdk12\bin\javac cltEcho.java
E:\data\java\corba\ECHO>dir
CLTECH~1
CLTECH~1
ECHO
SERVEU~1
SERVEU~1
SRVECH~1
SRVECH~1
ECHO
CLA
JAV
IDL
CLA
JAV
CLA
JAV
2 599
2 907
78
1 793
1 806
488
252
<REP>
18/03/99
16/03/99
15/03/99
18/03/99
16/03/99
18/03/99
15/03/99
17/03/99
13:51
16:15
13:56
13:18
15:38
11:30
14:02
17:19
cltEcho.class
cltEcho.java
ECHO.IDL
serveurEcho.class
serveurEcho.java
srvEcho.class
srvEcho.java
echo
9.2.7 Tests
9.2.7.1 Lancement du service dannuaire
Sur une machine Windows, nous lanons le service dannuaire de la faon suivante :
E:\data\java\corba\ECHO>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000
ce qui a pour effet de lancer le service dannuaire sur le port 1000 de la machine.
Le service dannuaire tnameserv produit un affichage dcran qui ressemble ce qui suit :
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
Cest peu lisible, mais on retiendra la dernire ligne : le service est actif sur le port 1000.
Le serveur affiche :
Serveur dcho prt
Corba
332
9.2.7.4 Lancement du client sur poste Windows autre que celui du serveur
E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho tahe.istia.univ-angers.fr 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : abcd
Rponse serveur : [abcd]
Message : efgh
Rponse serveur : [efgh]
Message : fin
Machine Windows
Machine cliente
Client
Serveur SQL
Base de donnes
ODBC
CORBA
// l'interface distante
public interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp)
throws java.rmi.RemoteException;
public String[] executeSQL(String requete, String separateur)
throws java.rmi.RemoteException;
public String close()
throws java.rmi.RemoteException;
}
Corba
le client se connecte une base de donnes distante dont il donne le pilote, lurl JDBC ainsi que son identit
id et son mot de passe mdp pour accder cette base. Le serveur lui rend une chane de caractres indiquant
le rsultat de la connexion :
333
le client demande lexcution dune requte SQL sur la base laquelle il est connect. Il indique le caractre
qui doit sparer les champs dans les rsultats qui lui sont renvoys. Le serveur renvoie un tableau de chanes :
100 n
pour une requte de mise jour de la base, n tant le nombre de lignes mises jour
500 msg derreur
si la requte a gnr une erreur
501 Pas de rsultats
si la requte na gnr aucun rsultat
101 ligne1
101 ligne2
101 ...
si la requte a gnr des rsultats. Les lignes ainsi renvoyes par le serveur sont les lignes rsultats de la
requte.
close
le client ferme sa connexion avec la base distante. Le serveur renvoie une chane indiquant le rsultat de cette
fermeture :
200 Base ferme
500 Erreur lors de la fermeture de la base (msg derreur)
La seule nouveaut par rapport ce quon a vu avec linterface IDL du serveur dcho est lutilisation du mot cl sequence. Ce mot
cl permet de dfinir un tableau une dimension. La dfinition se fait en deux tapes :
dfinition dun type pour dsigner le tableau, ici resultats :
typedef sequence<string> resultats;
Le mot cl typedef est bien connu des programmeurs C/C++ : il permet de dfinir un nouveau type. Ici, le type resultats est
dfini comme quivalent au type sequence<string>, c.a.d. un tableau dynamique (non dimensionn) de chanes de
caractres.
utilisation du nouveau type l o on en a besoin
resultats executeSQL(in string requete, in string separateur);
Corba
IDL
275
<REP>
19/03/99
19/03/99
9:59 srvSQL.idl
9:41 srvSQL
334
On constate que la compilation a donn naissance un rpertoire portant le nom du module de linterface IDL(srvSQL). Regardons
le contenu de ce rpertoire :
E:\data\java\corba\sql>dir srvSQl
RESULT~1
RESULT~2
_INTER~1
INTERS~1
INTERS~2
INTERS~3
_INTER~2
JAV
JAV
JAV
JAV
JAV
JAV
JAV
833
1 883
2 474
448
841
1 855
4 535
19/03/99
19/03/99
19/03/99
19/03/99
19/03/99
19/03/99
19/03/99
10:00
10:00
10:00
10:00
10:00
10:00
10:00
resultatsHolder.java
resultatsHelper.java
_interSQLStub.java
interSQL.java
interSQLHolder.java
interSQLHelper.java
_interSQLImplBase.java
On rappelle que les fichiers Helper et Holder sont des classes lies aux paramtres dentr-sortie et rsultats des mthodes de
linterface distante. Le rpertoire srvSQL contient tous les fichiers .java lis linterface interSQL dfinie dans le fichier .idl. In
contient galement des fichiers lis au type resultats cr dans linterface IDL.
Le fichier interSQL.java est le fichier Java de linterface de notre serveur. Il est important de vrifier que ce qui a t produit
automatiquement correspond notre attente. Le fichier interSQL.java gnr est ici le suivant :
/*
* File:
* From:
* Date:
*
By:
*/
./SRVSQL/INTERSQL.JAVA
SRVSQL.IDL
Fri Mar 19 09:59:48 1999
D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
package srvSQL;
public interface interSQL
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
String connect(String pilote, String urlBase, String id, String mdp)
;
String[] executeSQL(String requete, String separateur)
;
String close()
;
}
On constate quon a la mme interface que celle utilise pour le client-serveur RMI. On peut donc continuer. Compilons tous ces
fichiers .java :
E:\data\java\corba\sql\srvSQL>j:\jdk12\bin\javac *.java
Note: _interSQLImplBase.java uses or overrides a deprecated API.
"-deprecation" for details.
1 warning
Recompile with
E:\data\java\corba\sql\srvSQL>dir *.class
_INTER~1
_INTER~2
INTERS~1
INTERS~2
INTERS~3
RESULT~1
RESULT~2
CLA
CLA
CLA
CLA
CLA
CLA
CLA
3 094
1 953
430
2 096
870
2 047
881
19/03/99
19/03/99
19/03/99
19/03/99
19/03/99
19/03/99
19/03/99
10:01
10:01
10:01
10:01
10:01
10:01
10:01
_interSQLImplBase.class
_interSQLStub.class
interSQL.class
interSQLHelper.class
interSQLHolder.class
resultatsHelper.class
resultatsHolder.class
Corba
335
// --------------- connect
public String connect(String pilote, String url, String id,
String mdp){
// connexion la base url grce au driver pilote
// identification avec identit id et mot de passe mdp
String resultat=null
null;
// rsultat de la mthode
null
try{
try
// chargement du pilote
Class.forName(pilote);
// demande de connexion
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion russie";
} catch (Exception e){
// erreur
resultat="500 Echec de la connexion (" + e + ")";
}
// fin
return resultat;
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur){
// excute une requte SQL sur la base DB
// et met les rsultats dans un tableau de chanes
// donnes ncessaires l'excution de la requte
Statement S=null
null;
null
ResultSet RS=null
null;
null
String[] lignes=null
null;
null
Vector resultats=new
new Vector();
String ligne=null
null;
null
try{
try
// cration du conteneur de la requte
S=DB.createStatement();
// excution requte
if (! S.execute(requete)){
// requte de mise jour
// on renvoie le nombre de lignes mises jour
lignes=new
new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// c'tait une requte d'interrogation
// on rcupre les rsultats
RS=S.getResultSet();
// nombre de champs du Resultset
int nbChamps=RS.getMetaData().getColumnCount();
// on les exploite
while(RS.next()){
while
// cration de la ligne des rsultats
ligne="101 ";
for (int
int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// ajout au vecteur des rsultats
resultats.addElement(ligne);
}// while
// fin de l'exploitation des rsultats
// on libre les ressources
RS.close();
S.close();
// on rend les rsultats
int nbLignes=resultats.size();
if (nbLignes==0){
lignes=new
new String[1];
lignes[0]="501 Pas de rsultats";
} else {
lignes=new
new String[resultats.size()];
for(int
for int i=0;i<lignes.length;i++)
lignes[i]=(String) resultats.elementAt(i);
}//if
return lignes;
} catch (Exception e){
// erreur
lignes=new
new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
Corba
336
// --------------- close
public String close(){
// ferme la connexion la base de donnes
String resultat=null
null;
null
try{
try
DB.close();
resultat="200 Base ferme";
} catch (Exception e){
resultat="500 Erreur la fermeture de la base ("+e+")";
}
// renvoi du rsultat
return resultat;
}
}// classe SQLServant
Cette classe est place dans le fichier SQLServant.java que nous compilons :
E:\data\java\corba\sql>dir
SRVSQL
IDL
SRVSQL
SQLSER~1 JAV
275
<REP>
2 941
19/03/99
19/03/99
15/03/99
9:59 srvSQL.idl
9:41 srvSQL
9:09 SQLServant.java
E:\data\java\corba\sql>j:\jdk12\bin\javac SQLServant.java
E:\data\java\corba\sql>dir *.class
SQLSER~1 CLA
2 568
19/03/99
10:19 SQLServant.class
Corba
337
2 568
1 800
19/03/99
19/03/99
10:19 SQLServant.class
10:33 serveurSQL.class
Une fois lanc avec les paramtres prcdents, le client suit les tapes suivantes :
il se connecte la machine machine sur le port port pour demander le service CORBA srvSQL
Corba
338
srvSQL.*;
org.omg.CosNaming.*;
org.omg.CORBA.*;
java.io.*;
// vrification du nb d'arguments
if(arg.length!=8)
if
erreur(syntaxe,1);
// init paramtres de la connexion la base de donnes
String machine=arg[0];
String port=arg[1];
String service=arg[2];
String pilote=arg[3];
String urlBase=arg[4];
String id, mdp, separateur;
if(arg[5].equals(
"null")) id=""; else id=arg[5];
if
if(arg[6].equals(
"null")) mdp=""; else mdp=arg[6];
if
if(arg[7].equals(
"null")) separateur=" "; else separateur=arg[7];
if
// paramtres du service d'annuaire CORBA
String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
// client CORBA - on demande une rfrence du serveur SQL
interSQL serveurSQL=getServeurSQL(machine,port,service);
// dialogue client-serveur
String requete=null
null;
null
String reponse=null
null;
null
String[] lignes=null
null;
null
String codeErreur=null
null;
null
try{
try
// ouverture du flux clavier
in=new
new BufferedReader(new
new InputStreamReader(System.in));
// suivi
System.out.println("--> Connexion la base de donnes en cours");
// demande de connexion initiale la base de donnes
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// suivi
Corba
339
System.out.println("<-- "+reponse);
// analyse rponse
codeErreur=reponse.substring(0,3);
if(codeErreur.equals(
"500"))
if
erreur("Abandon sur erreur de connexion la base",3);
// boucle de lecture des requtes envoyer au serveur SQL
System.out.print("--> Requte : ");
requete=in.readLine().toLowerCase().trim();
while(!
requete.equals("fin")){
while
// envoi de la requte au serveur et rception de la rponse
lignes=serveurSQL.executeSQL(requete,separateur);
// suivi
afficheLignes(lignes);
// requte suivante
System.out.print("--> Requte : ");
requete=in.readLine().toLowerCase().trim();
}// while
// suivi
System.out.println("--> Fermeture de la connexion la base de donnes distante");
// on clt la connexion
reponse=serveurSQL.close();
// suivi
System.out.println("<-- " + reponse);
// fin
System.exit(0);
// gestion des erreurs
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// try
}// main
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int
int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ erreur
private
private static void erreur(String msg, int exitCode){
// affichage msg d'erreur
System.err.println(msg);
// libration ventuelle des ressources
try{
try
in.close();
serveurSQL.close();
} catch(Exception
e){}
catch
// on quitte
System.exit(exitCode);
}// erreur
// ---------------------- getServeurSQL
private static interSQL getServeurSQL(String machine, String port, String service){
// demande une rfrence du serveur SQL
// machine : machine de l'annuaire des services CORBA
// port : port de l'annuaire des services CORBA
// service : nom du service CORBA demander
// suivi
System.out.println("--> Connexion au serveur CORBA en cours...");
// la rfrence du serveur SQL
interSQL serveurSQL=null
null;
null
// paramtres du service d'annuaire CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
try
// on demande un objet CORBA pour travailler - on passe pour cela le port
// d'coute de l'annuaire des services CORBA
ORB orb=ORB.init(initORB,null
null);
null
// on utilise le service d'annuaire pour localiser le serveur SQL
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// le service recherch s'appelle srvSQL - on le demande
NameComponent nc= new NameComponent(service,"");
NameComponent path[]={nc};
serveurSQL=interSQLHelper.narrow(ncRef.resolve(path));
} catch (Exception e){
System.err.println("Erreur lors de la localisation du serveur SQL ("
+ e + ")");
System.exit(10);
}// try-catch
// on rend la rfrence au serveur
return serveurSQL;
}// getServeurSQL
}// classe
Corba
340
2 568
1 800
3 774
19/03/99
19/03/99
19/03/99
10:19 SQLServant.class
10:33 serveurSQL.class
10:45 clientSQL.class
9.3.7 Tests
9.3.7.1 Pr-requis
On suppose quune base ACSESS appele Articles est publiquement disponible sur la machine Windows du serveur SQL :
type
code de larticle sur 4 caractres
son nom (chane de caractres)
son prix (rel)
son stock actuel (entier)
le stock minimum (entier) en-dea duquel il faut rapprovisioner larticle
Corba
341
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
052800000018afabcafe000000027693d3fd000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
9.3.7.5 Lancement dun client sur une autre machine que celle du serveur
Voici les rsultats obtenus avec un client sur une autre machine que celle du serveur :
E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL tahe.istia.univ-angers.fr 1000 srvSQL
sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion la base de donnes en cours
<-- 200 Connexion russie
--> Requte : select * from articles
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,cano,1502,7,7
<-- 101 x123,fusil,3000,9,8
<-- 101 s345,skis nautiques,1800,13,8
<-- 101 f450,essai3,3,13,9
<-- 101 z400,lopard,500000,7,7
<-- 101 g457,panthre,800000,7,7
--> Requte : fin
--> Fermeture de la connexion la base de donnes distante
Corba
342
type IDL
boolean
char
wchar
octet
string
wstring
short
unsigned short
long
unsigned long
long long
unsigned long long
float
double
type Java
boolean
char
char
byte
java.lang.String
java.lang.String
short
short
int
int
long
long
float
double
Rappelons que pour dfinir un tableau dlments de type T dans linterface IDL, on utilise linstruction :
typedef sequence<T> nomType;
et quensuite on utilise nomType pour rfrencer le type du tableau. Ainsi, dans linterface du serveur SQL on a utilis la dclaration :
typedef sequence<string> resultats;
pour que resultats dsigne un tableau String[] sous Java.
Corba
343