Java Avancé - Cours 3


Plan

1 Rappel sur AWT

AWT (Abstract Window Toolkit) est l'ensemble des classes Java originales qui permet de faire des interfaces graphiques. Ces classes sont situées dans le package java.awt. AWT repose sur les widgets systèmes. Par widget on entend les boutons, les menus, etc...

Pour être portable au niveau également de l'apparence, et bien dégager la notion de modèle (par exemple une structure d'arbre) par rapport à son rendu (comment l'arbre va être affiché à l'écran), les classes Swing ont été créées. Ces classes sont dans le package javax.swing.

Le passage de AWT à Swing se fait facilement. Pour beaucoup de classes il suffit de rajouter un J devant le nom. Par exemple en AWT on dispose de Applet, Panel, Frame. Les classes équivalentes sont disponibles en Swing : JApplet, JPanel, JFrame. De plus on trouve des nouveaux composants très pratiques, comme par exemple : JTree pour afficher un arbre, JTabbedPane pour afficher des onglets.

2 Présentation de Swing

Dans cette section plusieurs images proviennent de l'URL:

http://java.sun.com/docs/books/tutorial/uiswing/components/components.html

2.1 Containers de haut niveau

JApplet : Cette classe est une sous classe de java.applet.Applet. Elle permet de faire des applets où les composants sont des widgets Swing et non AWT. Son usage est similaire à celui de java.applet.Applet.

JDialog : C'est la classe swing qui permet de créer des dialogues sur mesure. Pour des dialogues simples on préfèrera utiliser la classe JOptionPane. Typiquement l'image ci-contre est obtenue à l'aire de JOptionPane. Il existe une classe toute faite pour obtenir une boite de dialogue permettant de choisir un fichier JFileChooser.

\scalebox{.7}{\includegraphics{dialog.eps}}

JFrame : C'est la classe qui remplace Frame dans l'AWT. Il s'agit d'une fenêtre avec les boutons standards.

\scalebox{.7}{\includegraphics{frame.eps}}

2.2 Containers génériques

JPanel : Il s'agit d'une classe destinée à contenir d'autres classes. Les attributs généralement modifiés sont la couleur du fond, les bords, ainsi que l'appel à la méthode void setLayout(LayoutManager mgr) pour gérer la manière dont les composants sont placés.

\scalebox{.7}{\includegraphics{panel.eps}}

JScrollPane : Permet de visualiser un objet trop grand. Au lieu d'écrire directement dans un JPanel :

textArea = new JTextArea(5, 30);
add(textArea, BorderLayout.CENTER);
On peut le mettre préalablement dans un JScrollPane :
textArea = new JTextArea(5, 30);
JScrollPane scrollPane = new JScrollPane(textArea);
add(scrollPane, BorderLayout.CENTER);
\scalebox{.7}{\includegraphics{scrollpane.eps}}
La portion visualisée à l'écran est contenue dans un objet nommé viewport de type JViewPort :
\scalebox{.7}{\includegraphics{jscrollpane_and_viewport.eps}}

JTabbedPane : Permet de mettre plusieurs JPanel dans des onglets. La méthode pour ajouter des onglets est add :

JTabbedPane tabbedPane = new JTabbedPane();
ImageIcon icon = createImageIcon("images/middle.gif");
JComponent panel1 = makeTextPanel("Panel #1");
tabbedPane.addTab("One", icon, panel1, "Does nothing");
...
\scalebox{.7}{\includegraphics{tabbed.eps}}

JToolBar : Permet de créer une barre de boutons. Les boutons sont des instances de JButton. Un ActionListener leur est attribué. Il suffit de les ajouter dans la toolbar à l'aide de la commande add.

public class RecupereLesActions implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        ...
    }
}

JToolBar toolBar = new JToolBar();
JButton button = new JButton();
RecupereLesActions recupereLesActions = new RecupereLesActions();
button.addActionListener(recupereLesActions);
toolBar.add(button);
\scalebox{.7}{\includegraphics{toolbar.eps}}

JSplitPane : Permet d'affichier deux JPanels séparés verticalement ou horizontalement.

JSplitPane hSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                                       panel1, 
                                       panel2);
\scalebox{.7}{\includegraphics{splitplane.eps}}

2.3 Containers spécifiques

JInternalFrame : Permet d'affichier une fenêtre dans un JFrame.

\scalebox{.7}{\includegraphics{internalframe.eps}}

JLayeredPane : Permet d'afficher des JPanels à différents niveaux.

\scalebox{.7}{\includegraphics{layeredpane.eps}}

JRootPane : C'est la structure à la base des composants Swing pouvant contenir des menus. Le JRootPane n'est pas l'objet dans lequel on ajoute des composants rootPane. add(child); est impossible, à la place on fera rootPane. getContentPane(). add(child);. Les JRootPane ne sont pas crées tout seuls, mais viennent en général avec des composants existants.

\scalebox{.7}{\includegraphics{jrootpane_arch.eps}}
\scalebox{.7}{\includegraphics{rootpane.eps}}
Ainsi par exemple pour spécifier la mise en page sur un rootPane :
rootPane.getContentPane().setLayout(new BoxLayout());

2.4 Contrôles de base

JButton : représente les boutons. Cette classe partage la classe abstraite AbstractButton avec entre autres : JCheckBox, JRadioButton, JMenuButton, JCheckBoxMenuItem, JRadioButtonMenuItem.

public class ButtonDemo extends JPanel
                        implements ActionListener {
    protected JButton b1, b2, b3;

    public ButtonDemo() {
        // ...

        b1 = new JButton("MonBouton");
        b1.setActionCommand("disable");
        b1.setActionListener(this);
        add(b1);
    }

    public void actionPerformed(ActionEvent e) {
        if ("disable".equals(e.getActionCommand())) {
            ...
        }
    }
}
\scalebox{.7}{\includegraphics{buttons.eps}}

JComboBox : Permet de faire une liste dans laquelle l'utilisateur va pouvoir faire un seul choix. Il existe une forme éditable pour rajouter des items à la volée dans la liste.

\scalebox{.7}{\includegraphics{combobox.eps}}

JList : Permet d'afficher une liste d'objets, où l'utilisateur peut choisir plusieurs items.

\scalebox{.7}{\includegraphics{list.eps}}

JMenu : Permet de faire des menus. Les menus sont généralement ajoutés dans une barre de menus (JMenuBar) et comportent de items de type JMenuItem. L'exercice 1 détaille comment relier les menus aux actions à effectuer.

\scalebox{.7}{\includegraphics{menu.eps}}

JSlider : Permet de rentrer une valeur numérique comprise entre deux bornes.

\scalebox{.7}{\includegraphics{SliderDemo.eps}}

JSpinner : ressemble à une combo box mais sans partie dépliable qui peux recouvrir d'autres composants.

\scalebox{.7}{\includegraphics{spinner.eps}}

JTextField : permet de rentrer un champ texte.

\scalebox{.7}{\includegraphics{text.eps}}

2.5 Information non éditable

JLabel : permet d'afficher une chaîne de caractères.

\scalebox{.7}{\includegraphics{label.eps}}

JProgressBar : Affiche une barre de progression.

\scalebox{.7}{\includegraphics{progressbar.eps}}

JToolTip : Affiche des indications sur un composant.

\scalebox{.7}{\includegraphics{tooltip.eps}}

2.6 Représentations interactives

JColorChooser : Dialogue permettant de choisir une couleur.

\scalebox{.7}{\includegraphics{colorchooser.eps}}

JFileChooser : Dialogue permettant de choisir un fichier. Une utilisation de cette classe est demandée dans l'exercice 1.

\scalebox{.7}{\includegraphics{filechooser.eps}}

JTable : Permet d'afficher un tableau

\scalebox{.7}{\includegraphics{table.eps}}

JText :

\scalebox{.7}{\includegraphics{formatedtext.eps}}
\scalebox{.7}{\includegraphics{textcomp.eps}}

JTree :

\scalebox{.7}{\includegraphics{tree.eps}}

2.7 Présentation MVC

Plusieurs composants swing sont implémentés suivant l'architecture Model View Controler (MVC), c'est à dire que les fonctionnalités suivantes sont implémentées de manière séparées :

Les rôles view et controler sont souvent joués par les mêmes entités.

Le modèle s'obtient en général par la méthode getModel (par exemple dans JTree, JSlider, JTabbedPane ...).

Pour plus de détails :

http://java.sun.com/products/jfc/tsc/articles/architecture/

L'exercice 2 donne une illustration d'un usage fréquent de l'architecture MVC.

3 Evènements

3.1 Notion d'event listener

Pour pouvoir gérer les évènements qui se produisent il faut implémenter l'interface correspondant à l'évènement.

3.2 Exemple d'event listener

Ainsi par exemple pour être notifié des changements d'un JTextField, la classe Toto doit avoir le squelette suivant:
class Toto implements java.awt.event.ActionListener {
    public void actionPerformed(java.awt.event.ActionEvent e) {
        // ...
    }
}
et pour enregistrer une instance de Toto auprès d'une instance de JTextField :
  JTextField text = new JTextField();
  Toto toto = new Toto();
  text.addActionListener(toto);

3.3 Autre exemple d'event listener

Ainsi par exemple pour être notifié des changements d'un JSlider, la classe Toto doit avoir le squelette suivant:
class Toto implements javax.swing.event.ChangeListener {
    public void stateChanged(javax.swing.event.ChangeEvent e) {    
        // ...
    }
}
et pour enregistrer une instance de Toto auprès d'une instance de JSlider :
  JSlider slider = new JSlider(javax.swing.JSlider.HORIZONTAL,0,50,100);
  Toto toto = new Toto();
  slider.addChangeListener(toto);

3.4 Event Listener possibles

Voici une liste non exhaustive des évènements possibles:

Un tableau récapitulatif est présent à l'URL :

http://java.sun.com/docs/books/tutorial/uiswing/events/eventsandcomponents.html

4 Signature de fichier

4.1 Notions de base

Voici les notions cryptographiques de base utilisées dans ce cours :

4.2 Génération d'une clé

On va se servir de la classe KeyPairGenerator pour créer une clé RSA :

    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    keyGen.initialize(1024, new SecureRandom());
    KeyPair pair = keyGen.generateKeyPair();
Les clés publiques et privées sont alors :
    PublicKey pub = pair.getPublic();
    PrivateKey priv = pair.getPrivate();

4.3 Sauvegarde des clés

Pour sauver une clé (ou tout autre objet java sérialisable) on va utiliser un ObjectOutputStream dans le package java.io :

    PublicKey pub = .... ;
    FileOutputStream fout = new FileOutputStream("public.key");
    ObjectOutputStream oos = new ObjectOutputStream(fout);
    oos.writeObject(pub);
    oos.close();
Pour la manipulation inverse où l'on créé l'objet pub à partir du fichier public.key il suffit d'utiliser un ObjectInputStream dans le package java.io :

    FileInputStream fis = new FileInputStream("public.key");
    ObjectInputStream ois = new ObjectInputStream(fis);
    PublicKey pub = (PublicKey) ois.readObject();
    ois.close();

4.4 Signature du fichier

Maintenant que nous avons les clés il faut créer un objet signature basé sur cette clé :

            Signature rsa = Signature.getInstance("MD5withRSA"); 
            rsa.initSign(priv);
L'initialisation à l'aide de la méthode initSign est réalisée car on va signer le fichier. Si cela avait été pour vérifier la signature on aurait appelé initVerify(pub)pub est la clé publique correspondant à la clé privée générée.

Il faut alors faire ``passer'' tout le fichier à signer dans l'instance rsa :

            FileInputStream fis = new FileInputStream(new File("nomdufichier"));
            while (fis.available() != 0) {
                rsa.update(fis.read());
            }
            fis.close();

Le résultat de la signature est alors obtenu par la commande :

    byte [] sig = rsa.sign();

Pour l'exemple complet se réferrer à :

http://www.derepas.com/java/cours5_exemple_1.jar

5 Bac à sable

5.1 Motivation

Java est pratique : on peut facilement télécharger du bytecode. Cependant dès lors que l'on télécharge du code que l'on ne connais pas, il est pratique de pouvoir restreindre les droits de ce dernier.

5.2 Le fichier java.policy

Considérons le code suivant d'une classe OpenFile qui affiche le contenu du fichier build.xml :

            FileInputStream f = new FileInputStream ("build.xml");
            int i=0;
            byte [] b = new byte[1024];
            while ((i=f.read(b))!=-1) {
                System.out.println(new String(b));
            }
Une fois compilé, ce code s'exécute sans problème avec par exemple la commande : java OpenFile.

Rajoutons maintenant un fichier java.policy vide et lançons la ligne de commande :

java -Djava.security.manager -Djava.security.policy=java.policy OpenFile
On obtient alors l'erreur suivante :
java.security.AccessControlException: access denied 
                  (java.io.FilePermission build.xml read)

Explication : le fichier java.policy vide ne donne aucun droit au code qui s'exécute. On dit que le code est contraint de jouer dans son bac à sable (ou sand box en anglais).

Le code réalisant ce test ainsi que le fichier ant correspondant sont téléchargeables à l'url :

http://www.derepas.com/java/cours5_exemple_2.jar

5.3 Des informations sur son bac à sable

Le code qui s'exécute peut obtenir des informations sur le SecurityManager (i.e., le bac à sable) dans lequel il s'exécute :

            if (System.getSecurityManager()!=null) {
                // alors il y a un security manager
                java.security.AccessController.checkPermission
                    (new java.io.FilePermission("build.xml","write"));
            }
Le code suivant génère une exception de type java.security.AccessControlException si le code n'est pas autorisé à écrire dans le fichier build.xml.

5.4 Fixer la taille du bac à sable

Si par exemple dans l'exemple précédent on veut donner seulement le droit de lire le fichier build.xml voici le contenu du fichier java.policy :

grant {
  permission  java.io.FilePermission "build.xml", "read";
};
Si on a vraiment confiance dans des classes situées dans un répertoire donné on peut leur donner tous les privilèges :
grant codeBase "file:/chemin/vers/mes/classes/*" {
  permission java.security.AllPermission;
};

Remarque : sous windows les chemins ne sont pas spécifiés sous la forme file:/mon/chemin mais file:/C:/mon/chemin pour c:\mon\chemin.

Pour plus de détails sur le fichier java.policy se reporter à l'url :

http://java.sun.com/javase/6/docs/technotes/guides/security/PolicyFiles.html

6 Fichier jar signé

6.1 Keystore

Plutôt que de stocker les clés dans des fichiers comme nous avons fait précédemment, java propose une interface manipulable en ligne de commande ou par un programme.

Cet endroit ou nous allons stocker les clés se nomme le keystore.

6.2 Génération d'une clé dans le keystore

Il faut pour cela utiliser l'application keytool qui vient avec le JDK. L'option à rajouter sur la ligne de commande est -genkey, on peut en outre préciser le nom de clé générée (ici mykey), le type de clé (ici RSA), la taille (ici 1024) et le fichier de stockage (ici keystore) :

keytool -genkey -alias mykey -keyalg RSA -keysize 1024 -keystore keystore

Pour voir les clés qui sont stockées il suffit de taper :

keytool -list -keystore keystore

6.3 Signature du fichier jar

Si dans la liste on a une clé nommée mykey il suffit alors pour signer un fichier jar de taper :

jarsigner -keystore keystore monfichier.jar mykey
Les fichiers META-INF/MYKEY.SF et META-INF/MYKEY.DSA ont alors été rajoutés dans l'archive jar. Le fichier MANIFEST.MF a également été modifié, et contient en plus :
Name: ex1/ToDownload.class
SHA1-Digest: Q4wliYWpbRKBTzUICVWjuo0+6fc=
c'est à dire la liste des classes ainsi que leur signature par la clé mykey.

6.4 Création du certificat publique

Le certificat public peut être créé à l'aide de la commande :

keytool -export -keystore keystore -alias mykey -file derepas.cer

6.5 Politique de sécurité

Si l'on dispose d'une source de confiance nommée par exemple mykey, on a envie d'écrire un fichier java.policy de la forme :

grant signedBy "mykey" {
  permission java.security.AllPermission;
};
dont la sémantique est la suivante : fait entièrement confiance au code situé dans un fichier jar qui a été signé par la clé privée correspondant au certificat de la clé publique dans le registre de clé pour l'entrée nommée mykey.

6.6 Charger les bons certificats

Il faut charger le bon certificat. Pour cela récupérer le certificat public, puis l'importer dans le KeyStore :

keytool -import -alias mykey -file derepas.cer

Pour plus d'informations :

http://java.sun.com/docs/books/tutorial/security1.2/

7 Exercices

Exercice 3.1

Récupérer la classe ex1.Drawing à l'url :
http://www.derepas.com/java/cours3_exercice_1.jar
Il s'agit une petite classe (79 lignes) permettant de charger des images et de pouvoir dessiner dessus comme le programme paint. La classe vient sans menus ou outils pour choisir les couleurs, sauver les fichiers.
\scalebox{.7}{\includegraphics{hello.eps}}

Q1 Le but de cette question est de rajouter un menu qui permet de sortir. Il faut rajouter un menu Fichier avec un item Quitter qui permet de fermer.

On va pour cela créer une classe nommée DrawingPanel qui ``enrobe'' la classe Drawing. On utilisera le squelette suivant :

package ex1;

public class DrawingPanel extends javax.swing.JPanel
{
    Drawing drawing = null;

    class QuitAction extends javax.swing.AbstractAction {
        QuitAction() { super("quit"); }
        public void actionPerformed(java.awt.event.ActionEvent e) {
            quit();
        }
    }

    DrawingPanel() {
        // ...
    }

    public void quit() {
        System.exit(0);
    }

    public javax.swing.JMenuBar createMenuBar() {
        // ... 
    }

    public static void main(String [] argv) {
        javax.swing.JFrame frame = new javax.swing.JFrame("Drawing");
        frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
        DrawingPanel d = new DrawingPanel();
        frame.setContentPane((javax.swing.JComponent)d);
        frame.setJMenuBar(d.createMenuBar());
        frame.pack();
        frame.setVisible(true);
    }
}
Indications : dans la méthode createMenuBar on créé une instance de javax.swing.JMenuBar dans laquelle on a ajouté une instance de javax.swing.JMenu dans laquelle on a ajouté une instance de javax.swing.JMenuItem. On aura utilisé la méthde addActionListener sur l'instance de javax.swing.JMenuItem pour associer l'action correspondante.

Q2 Ajouter un item ouvrir dans le menu pour ouvrir un fichier. Indication : utiliser la classe javax.swing.JFileChooser pour choisir le fichier à ouvrir, appeler la méthode loadImage de la classe ex1.Drawing pour charger l'image.

Q3 Si cela n'a pas déjà été fait à la question 1, mettre l'instance de ex1.Drawing dans un javax.swing.JScrollPane pour pouvoir visualiser les images de taille importantes.

Exercice 3.2

L'idée de l'exercice est de montrer qu'il est facile de remplacer un modèle existant par un modèle ayant de nouvelles fonctionnalités.

Q1 Créer une classe ex2.CustomSlider qui permet de choisir un entier à l'aide d'une classe JSlider et affiche la valeur dans un JTextField, comme illustré ci-dessous.

\scalebox{.7}{\includegraphics{slider.eps}}

Q2 On souhaite qu'une modification du JTextField permette de modifier la position du slider. Indication : utiliser la méthode addActionListener sur le JTextField pour récupérer les changements.

Q3 On souhaite maintenant que la partie modèle stocke un double et non un int.

Téléchargez la classe CustomRangeModel à l'url :

http://www.derepas.com/java/CustomRangeModel.java
On remarquera que CustomRangeModel implémente BoundedRangeModel. On peut donc s'en servir comme modèle pour n'importe quel JSlider.

Replacer le modèle du JSlider grâce au constructeur JSlider(BoundedRangeModel brm). Adapter les méthodes précédemment écrites pour pouvoir rentrer des données de type double dans le JTextField.

Exercice 3.3

Q1 Ecrire une classe Signataire qui génère une clé RSA de 1024 bits et signe un fichier.

Q2 Ecrire une classe Chargeur qui vérifie la signature d'un fichier étant donné la clé publique utilisée pour générer la signature. Pour comparer les signatures on utilisera la méthode Signature.verify.

Ces classes peuvent ensuite être utilisées pour effectuer des téléchargements sûrs sur internet.

Exercice 3.4

Q1 Récupérer le certificat :

http://www.derepas.com/java/derepas.cer
et l'installer dans un keystore.

Q2 Téléchargez le fichier jar

http://www.derepas.com/java/cours5_exercice2.jar
l'exécuter sans politique de sécurité. L'exécuter avec une politique de sécurité interdisant tout.

Q3 Exécuter le fichier jar précédent avec une politique de sécurité autorisant l'écriture des classes certifiées par le certificat de la première question. Pour spécifier comment utiliser le keystore de la question 1, se reporter à l'url :

http://java.sun.com/javase/6/docs/technotes/guides/security/PolicyFiles.html



2008-04-04