dimanche 25 août 2019

Envoi d'un email en Java en utilisant JavaMail

L’envoi d’un email est le composant le plus simple à mettre en oeuvre pour contacter des acteurs en dehors de notre système d’information. Ces acteurs ne seront pas obligé d’ouvrir un compte ou d’installer des logiciels. Il suffit d’avoir un compte email.

Sous Java, l’API responsable de ce volet est JavaMail. Cette API garantit toutes les opérations liées aux emails : envoie, réception, lecture, classification, etc.

JavaMail est disponibles dans entrepôt GitHub accessible ici. Toutes les ressources nécessaires sont disponibles. Les exemples donnés (ici) sont aussi très claires et documentés.

La partie qui nous intéresse aujourd’hui est l’envoie d’un email. Le teste utilisera un compte Gmail pour envoyer un message texte simple.

Pour envoyer un email, il est nécessaire de manipuler les classes
suivantes :
  • Properties : cette classe permet de stocker des propriétés, elle n’est pas propre à l’API JavaMail disponible dans le package java.util).
  • Session : pour créer une sessiond d’envoie de messages. Session ici n’a pas le même sens de Session manipulée en J2EE.
  • Message : l’objet principal. Il permet de créer le message à envoyer et contiendra toutes les  information nécessaire (emetteur, destinataire, contenu, date, etc.). Transport : qui permet de se connecter au serveur et d’envoyer le message réellement.
Les informations de base sont :
  • Adresse email de l’expéditeur,
  • Adresse(s) email de(s) destinataire(s),
  • L’objet du message,
  • Le corps du message.
Dans le cas de Gmail :
  • Il est important de s’authentifié, ainsi, il faut :
    • Activiter l’authentification en ajoutant la propriété :
props.put("mail.smtp.auth", "true");
    • Il faut utiliser la classe transport SMTPTransport et de se connecter avant d’envoyer le message,
    • Il faut utiliser le protocole smtps et non pas smtp au moment de la création de la classe SMTPTransport comme suit :
SMTPTransport t = (SMTPTransport)session.getTransport("smtps");
  • Il faut activer l’option less secure apps sur le compte google à utiliser.
Ainsi, le code à utiliser sera le suivant (le mot de passe est lu à partir du clavier, il suffit d’appeler la méthode statique avec les trois paramètres nécessaires):


import com.sun.mail.smtp.SMTPTransport;
import java.util.Date;
import java.util.Properties;
import java.util.Scanner;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class MailService {

    public static String SERVER = "smtp.gmail.com";
    public static String FROM = "votre_compte@gmail.com";

    public static void sendMail(String to, String subject, String content) throws MessagingException {
        
        Properties props = System.getProperties();
        props.put("mail.smtp.host", SERVER);
        props.put("mail.smtp.auth", "true");

        Session session = Session.getInstance(props, null);
        session.setDebug(true);

        Message msg = new MimeMessage(session);

        msg.setFrom(new InternetAddress(FROM));

        msg.setRecipients(Message.RecipientType.TO,
                InternetAddress.parse(to, false));

        msg.setSubject(subject);
        msg.setText(content);

        msg.setHeader("X-Mailer", "msgsend");
        msg.setSentDate(new Date());

        SMTPTransport t = (SMTPTransport)session.getTransport("smtps");
        
        String password = readPassword();
        t.connect("smtp.gmail.com", FROM, password);

        t.sendMessage(msg, msg.getAllRecipients());
        
        System.out.println("Response: " + t.getLastServerResponse());

    }
    
    public static String readPassword(){
        Scanner in = new Scanner(System.in);
        System.out.println("Enter password :");
        return in.nextLine();
    }

}

Vous pouver trouver une version PDF de ce post : ici.

vendredi 16 août 2019

[défi] Greed

Le défi est proposé sur la plateforme Dev.to sur le lien suivant Greed.

""
Greed is a dice game played with five six-sided dice. Using an array containing five six-sided dice values, write a function that will score a throw according to the following rules:

Three 1's => 1000 points
Three 6's => 600 points
Three 5's => 500 points
Three 4's => 400 points
Three 3's => 300 points
Three 2's => 200 points
One 1 => 100 points
One 5 => 50 point

A single die can only be counted once in each roll. For example, a "5" can only count as part of a triplet (contributing to the 500 points) or alone (as 50 points), but not both in the same roll.

Example Scoring

5 1 3 4 1 => 50 + 2 * 100 = 250
1 1 1 3 1 => 1000 + 100 = 1100
2 4 4 5 4 => 400 + 50 = 450
""

Ma proposition (simplifiée) est la suivante :


public class Greed {

    public static int[] scores = new int[]{1000, 200, 300, 400, 500, 600};

    public static int checkOccurrences(int[] dicesValues, int valueToCheck) {
        int numberOfOccurrences = 0;

        for (int i = 0; i < dicesValues.length; i++) {
            numberOfOccurrences += (dicesValues[i] == valueToCheck) ? 1 : 0;
        }

        return numberOfOccurrences;
    }

    public static void eraseThreeOccurences(int[] dicesValues, int valueToCheck) {
        int index = 0;
        int erased = 0;

        while (index < dicesValues.length && erased < 3) {
            if (dicesValues[index] == valueToCheck) {
                dicesValues[index] = 0;
                erased++;
            }
            index++;
        }
    }

    public static int playExample(int[] dicesValues) {

        int score = 0;

        for (int i = 0; i < scores.length; i++) {
            if (checkOccurrences(dicesValues, (i + 1)) >= 3) {
                score += scores[i];
                eraseThreeOccurences(dicesValues, (i + 1));
            }
        }

        score += 100 * checkOccurrences(dicesValues, 1);
        score += 50 * checkOccurrences(dicesValues, 5);

        return score;

    }

    public static void main(String[] args) {

        
        int[] example1 = new int[]{5, 1, 3, 4, 1};
        int[] example2 = new int[]{1, 1, 1, 3, 1};
        int[] example3 = new int[]{2, 4, 4, 5, 4};

        System.out.println("Example 1 " + playExample(example1));
        System.out.println("Example 2 " + playExample(example2));
        System.out.println("Example 3 " + playExample(example3));

        
    }

}

Une version plus complète peut être trouvée ici.

jeudi 11 avril 2019

[Algorithmique][Java][Débutant] Tri Avec Visualisation

Ce code présente une variante de tri à bulles.

L'objectif n'est pas le tri en lui même mais la structuration du code d'une manière à séparer les responsabilités et de respecter le principe "SRP" (voir ici).

Ce code utilise le design pattern "Observer" pour garantir cette séparation. L'utilisation de ce design pattern permet à la classe Tri de ne faire que le tri; elle signale ses actions au Listener qui doit (dans ce cas) garantir l'affichage de l'état d'avancement de l'opération.

Un petit système d'événements a été créé pour rendre le code plus lisible.



Vous pouvez retrouver le code dans sur SourceForge.



mercredi 3 avril 2019

[Java][Débutant] Lire un fichier texte en une seule ligne de code en Java

La manipulation des fichiers textes est une tâche quotidienne. Ainsi, pouvoir lire un fichier texte en une seule ligne de code constitue un plus qu'une plateforme peut offrir à ses développeurs.
Java est l'une des plateformes connues pour compliquer les choses pour ses utilisateurs. En effet, Java est respecté pour être rigoureux, précis et fiable. Mais cela ne vient pas sans prix : pendant les première version, lire un fichier texte en une seule ligne de code était un peu compliqué et impliquait plusieurs objets.
Dans cet exemple, nous allons voir comment lire le contenu d'un fichier texte en une seule ligne de code et cela en utilisant deux API et en obtenant deux formats possibles.
Les deux API sont :
  • java.nio : disponible depuis Java 4 mais vraiment efficace depuis Java 8.
  • Apache Commons IO : créée pour faciliter les Entrées/Sorites sous Java.
Les deux formats sont :
  • Lire tout le contenu dans une seule chaîne (String),
  • Lire le contenu ligne par ligne (List<String>).

java.nio :

La lecture se base une méthode "static", cela minimise la création des objets. La classe d'entrée est la classe Files. Cette dernière offre un ensemble de méthodes statiques pour effectuer différentes opérations communes.
Néanmoins, cette classe n'offre pas une méthode pour lire directement en String, ainsi, il faut lire en Byte et faire la conversion. Le code pour la lecture de tout un fichier sous forme d'une seule chaîne de caractères sera :

public static String lireUneChaine(String fichier) throws IOException{
    return new String(Files.readAllBytes(Paths.get(fichier)));
}

La lecture sous forme d'une liste de chaîne de caractères est plus simple grâce à la méthode .readAllLines() :

public static List<String> lirePlusieursChaines(String fichier) throws IOException{
    return Files.readAllLines(Paths.get(fichier));
}

Apache Commons IO :

Apache Commons IO fait partie de la famille Apache Commons qui fournit des outils pour simplifier des tâches fréquentes en Java et que le JDK ne les offre (offrait) pas. La classe d'entrée est IOUtils, et comme pour Files, elle fournit un ensemble de méthodes "static" pour simplifier l'utilisation. Meilleur encore, elle se base sur le package java.io, ainsi, l'ancienne documentation et les anciennes appellations restent valables. Les deux approches de lectures (une seule chaîne ou bien une liste de chaînes) sont offertes par des méthodes dédiées :


public static String lireUneChaine(String fichier) throws IOException{
    return IOUtils.toString(new FileReader(new File(fichier)));
}



public static List<String> lirePlusieursChaines(String fichier) throws IOException{
    return IOUtils.readLines(new FileReader(new File(fichier)));
}

Le résultat d'exécution sera comme suit (un fichier Exemple.txt est fourni avec le code, cette capture montre la moitié exacte de l'exécution, notez la différence entre la première partie qui affiche une seule chaîne de caractères et la deuxième partie qui affiche une liste entre les [ ]) :


Vous pouvez télécharger le code complet, y compris le jar de lApache Commons IO (version actuelle : 2.6) et le fichier Exemple utilisé dans la capture ci-dessus par :



lundi 25 mars 2019

[Java][Débutant] setDefaultCloseOperation vs. la gestion des événements sur les JFrame

[Niveau Débutant][Pour les étudiants (ou personnes) qui prennent leurs premiers pas en Java].

La première chose que nous constatons après la création de notre toute première JFrame en Java est la continuation de l'exécution après la fermeture de l'unique fenêtre et même après la fin de la méthode "main". En effet, en créant une JFrame, nous créons un Thread séparé du Thread principal. En fermant la fenêtre, nous la détruisons mais pas le Thread qui l'a exécutée. Ainsi, il faut préciser à la JFrame qu'il faut quitter l'application lors de sa fermeture.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ExempleJFrame extends JFrame {
 
 public ExempleJFrame(){
  setTitle("Exemple");
  setSize(300, 200);
  
  JButton bouton = new JButton("Afficher");
  bouton.addActionListener(new ActionListener(){
   public void actionPerformed(ActionEvent e){
    System.out.println("Thread de la JFrame : " + Thread.currentThread().getName());
   }
  });
  
  getContentPane().setLayout(null);
  bouton.setBounds(100, 85, 100, 25);
  getContentPane().add(bouton);
  
  
 }
 
 public static void main (String args[]) {
  
  (new ExempleJFrame()).setVisible(true);
  System.out.println("Thread principal (méthode main) : " + Thread.currentThread().getName());
  
 }
}


La méthode la plus simple pour cela est l'utilisation de la méthode setDefaultCloseOperation(). Cette méthode est un bon exemple sur l’appellation des méthode suivie dans la JDK et confirme la réputation du Java sur cet axe par rapport à d'autres langages de programmation.

setDefaultCloseOperation() prend un entier qui précise l'action à prendre lors de la fermeture de la fenêtre. Les options disponibles sont :
  • DO_NOTHING_ON_CLOSE : ne rien faire,
  • HIDE_ON_CLOSE : chacher la fenêtre,
  • DISPOSE_ON_CLOSE : détruire la fenêtre,
  • EXIT_ON_CLOSE : quitter l'application.
Ces constants sont définies dans la classe WindowConstants et elles sont dupliquées (pour des raisons de simplification) dans la classe JFrame. ET là encore, le même principe d'une appellation détaillée est respecté.

On ajoute une seule ligne au code précédent :

setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

Maintenant, l'application rend la main au système après sa fermeture; il n'est plus nécessaire de forcer l'arrêt de l'application par la commande arrêt dans les EDI (un bouton rouge) ou bien Ctrl+C à partir de la ligne de commande.

Java offre aussi la possibilité de personnaliser l'action à prendre lors de la fermeture de la JFrame. Il est aussi possible de détecter tout sorte d'événements sur la fenêtre (modification de la taille, réduction, etc.). Cela est garanti par l'utilisation d'un écouteur pour la fenêtre : WindowListener.

Cet écouteur était l'unique méthode pour définir les actions à prendre lors de la fermeture dans AWT. setDefaultCloseOperation() n'a été introduite que plus tard avec Swing.

WindowListener est, comme tout autre écouteur,  une interface qui définit les méthodes de réception des événement. Et si ActionListener ne propose qu'une seule méthode (actionPerformed) parce qu'il n'écoute qu'un seul événement (le clique), WindowListener de son côté définit sept (07) fonctions. Son code source (officiel mais sans Javadoc) :


package java.awt.event; 
 
import java.util.EventListener; 
 
public interface WindowListener extends EventListener { 
 
    public void windowOpened(WindowEvent e); 
 
    public void windowClosing(WindowEvent e); 
 
    public void windowClosed(WindowEvent e); 
  
    public void windowIconified(WindowEvent e); 
  
    public void windowDeiconified(WindowEvent e); 
 
    public void windowActivated(WindowEvent e); 
 
    public void windowDeactivated(WindowEvent e); 

} 

Vous pouvez lire le code complet avec les commentaires Javadoc :

Ainsi, pour définir l'action à exécuter au moment de la fermeture, il faut implémenter les sept méthodes, laisser toutes les méthodes vides et remplir la méthode windowClosing().

Ainsi, le code de notre fenêtre devient :


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ExempleJFrame extends JFrame {
 
 public ExempleJFrame(){
  setTitle("Exemple");
  setSize(300, 200);
  
  addWindowListener(new MonWindowListener());
 }
 
 public static void main (String args[]) {
  (new ExempleJFrame()).setVisible(true);
 }
}

class MonWindowListener implements WindowListener {
 
    public void windowOpened(WindowEvent e){} 
 
    public void windowClosing(WindowEvent e){
  System.out.println("La fenêtre a été fermée");
  System.exit(0);
 } 
 
    public void windowClosed(WindowEvent e){}
  
    public void windowIconified(WindowEvent e){} 
  
    public void windowDeiconified(WindowEvent e){} 
 
    public void windowActivated(WindowEvent e){}
 
    public void windowDeactivated(WindowEvent e){} 

}

Avant la fermeture

Après la fermeture, l'application rend la main au système
( avec le code 0 passé comme paramètre à la fonction System.exit(0) )
Pour simplifier l'utilisation, une autre classe a été introduite, c'est la classe WindowAdapter. Cette classe ne fait rein de particulier; elle implémente toutes les méthodes des differentes interface liées aux JFrame (et Frame) mais avec des corps de méthodes vides. Son code (sans les Javadoc) :


public abstract class WindowAdapter 
    implements WindowListener, WindowStateListener, WindowFocusListener 
{ 
    public void windowOpened(WindowEvent e) {} 
 
    public void windowClosing(WindowEvent e) {} 
 
    public void windowClosed(WindowEvent e) {} 
 
    public void windowIconified(WindowEvent e) {} 

    public void windowDeiconified(WindowEvent e) {} 
 
    public void windowActivated(WindowEvent e) {} 

    public void windowDeactivated(WindowEvent e) {} 
 
    public void windowStateChanged(WindowEvent e) {} 
 
    public void windowGainedFocus(WindowEvent e) {} 

    public void windowLostFocus(WindowEvent e) {} 
} 

Son code source complet :

Ainsi, le code de la fenêtre devient (avec une exécution identique) :


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ExempleJFrame extends JFrame {
 
 public ExempleJFrame(){
  setTitle("Exemple");
  setSize(300, 200);
  
  addWindowListener(new MonWindowAdapter());
 }
 
 public static void main (String args[]) {
  (new ExempleJFrame()).setVisible(true);
 }
}

class MonWindowAdapter extends WindowAdapter {

 @Override
 public void windowClosing(WindowEvent e){
  System.out.println("La fenêtre a été fermée");
  System.exit(0);
 } 

}



mercredi 6 mars 2019

L'utilisation des modèles associés aux composants Swing : JList comme exemple

Contrairement aux composants AWT, les composants Swing sépare la représentation (Vue) des données (Modèle) dans une approche très similaire à l'approche (MVC). Cette série vise à mettre l'accent sur séparation et démontrer quelques possibilités offertes par l'hiérarchie des classes Java pour les composants Swing.

La série se compose de quatre (04) articles :

JList (4) : exemple d'un ListModel minimal

Ce code démontre l'utilisation de la classe AbstractListModel (ou même l'interface ListModel) pour implémenter un modèle personnalisé pour la JList.

Les trois exemples précédents (1, 2 et 3) se basaient sur l'utilisation de DefaultListModel. Cette dernière classe est une extension de la classe AbstractListModel qui utilise un Vector pour stocker le contenu de la liste. Il est possible de construire un modèle de données qui stocke le contenu dans une structure personnalisée.


Le problème avec cette approche est la duplication du référence. Les objets contenants les données seront doublement référencés. Ainsi, il est possible dans des cas particulier de définir un modèle qui ne stocke et qui ne garde aucune référence. Il va déléguer ces opérations à une structure déjà disponible dans l'application.

Ce cas est un cas extrême et est présenté ici juste pour donner exemple sur les possibilités obtenues si on opte pour l'utilisation de AbstractListModel au lieu de DefaultListModel.

La classe de donnée utilisée dans cet exemple est une classe très simple qui définit trois attributs (id, nom, prénom) et qui offre une méthode toString pour permettre l'affichage. Une petite méthode "static" est ajoutée pour simuler une source de données externe.

class Donnee {
  
 int id;
 String nom;
 String prenom;
 
 public Donnee(int id, String nom, String prenom){
  this.id = id;
  this.nom = nom;
  this.prenom = prenom;
 }
 
 public int getId(){ return id; }
 public String getNom(){ return nom; }
 public String getPrenom(){ return prenom; }
 
 public String toString(){
  return "(" + id + ") " + nom + " " + prenom;
 }
 
 public static List<Donnee> creerDonnees(){
  ...
 }
 
}

Les données sont exposées, par la suite, dans une liste "static" accessible pour tout le monde (comme j'ai précisé, c'est un cas extrême pour montrer les fonctionnalités possibles).

 public static List<Donnee> DONNEES = Donnee.creerDonnees();

Le modèle développé va déléguer ses fonctionnalités à cette liste

class MonModele extends AbstractListModel<Donnee> {
 
 public int getSize(){
  return ExempleSimpleListModel.DONNEES.size();
 }
 
 public Donnee getElementAt(int indice){
  return ExempleSimpleListModel.DONNEES.get(indice);
 } 
}

Le code complet sera :


import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.util.*;

public class ExempleSimpleListModel {
 
 public static List<Donnee> DONNEES = Donnee.creerDonnees();
 
 public static void main (String args[]) {
  
  (new MaFrame()).setVisible(true);
  
 }
}

class Donnee {
  
 int id;
 String nom;
 String prenom;
 
 public Donnee(int id, String nom, String prenom){
  this.id = id;
  this.nom = nom;
  this.prenom = prenom;
 }
 
 public int getId(){ return id; }
 public String getNom(){ return nom; }
 public String getPrenom(){ return prenom; }
 
 public String toString(){
  return "(" + id + ") " + nom + " " + prenom;
 }
 
 public static List<Donnee> creerDonnees(){
  List<Donnee> liste = new ArrayList<>();
  liste.add(new Donnee(1, "Wilhelm", "Conrad Röntgen"));
  liste.add(new Donnee(2, "Hendrik", "Lorentz"));
  liste.add(new Donnee(3, "Pieter", "Zeeman"));
  liste.add(new Donnee(4, "Antoine Henri", "Becquerel"));
  liste.add(new Donnee(5, "Piere", "Curie"));
  liste.add(new Donnee(6, "Marie", "Curie"));
  return liste;
 }
 
}

class MaFrame extends JFrame {
 
 JList liste;
 MonModele modele;
 
 public MaFrame(){
  
  setTitle("Exemple JList");
  setSize(250, 400);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  modele = new MonModele();
  liste = new JList(modele);
  
  getContentPane().add(new JScrollPane(liste));
  
 }
  
}

class MonModele extends AbstractListModel<Donnee> {
 
 public int getSize(){
  return ExempleSimpleListModel.DONNEES.size();
 }
 
 public Donnee getElementAt(int indice){
  return ExempleSimpleListModel.DONNEES.get(indice);
 } 
}

Résultat :


mardi 5 mars 2019

JList (3) : utilisation d'un écouteur (Listener) sur le modèle

Après la présentation d'un premier exemple simple (ici) qui montre la séparation entre la Vue et le Modèle dans les composants Swing et d'un deuxième exemple (ici) qui montre l'utilisation d'un Listener "logique" qui récupère les actions de l'utilisateur, cet article vise à montrer l'utilisation d'un Listener sur le modèle, c'est-à-dire, détecter qu'un changement a eu lieu dans le modèle de donnée.

Malgré qu'un tel écouteur peut être utilisé pour détecter des changements effectués par l'utilisateur, le plus intéressant est de l'utiliser pour détecter des changements appliqués par d'autres programmes ou bien d'autres threads.

Ainsi, pour cet article, deux classes seront nécessaires :
  • La fenêtre qui contiendra la liste et son modèle. Pour simplifier l'exemple, le modèle sera exposé via un simple getter.
  • Un "autre programme" qui simule une source externe du changement effectué sur le modèle de la liste.
L'écouteur sur un modèle de données ne cherche pas à détecter des actions de l'utilisateur. Mais, il se concentre sur les changements possibles des données. Ainsi, l'écouteur (Listener) ListDataListener propose trois méthodes :
  • intervalAdded
  • intervalRemoved
  • contentsChanged
Les deux premières permet de récupérer les événements simples, le troisième sert à récupérer des changements du contenu du modèle.


Les trois méthodes reçoivent le même objet événement : ListDataEvent


Si ce dernier ne cache pas les données changées, il garde les positions (début/fin) des intervalles affectées par le changement. Par la suite, il est possible de récupérer les données à partir du modèle lui-même.

Pour pouvoir utiliser directement ces fonctionnalités, il faut faire appel au moins à la classe AbstractListModel, d'ailleurs, c'est la valeur ajoutée principale de cette classe par rapport à l'interface de base ListModel. Autrement, tous les mécanismes internes doivent être implémentées (chose qui n'est pas vraiment difficile après tout).


Revenons à notre exemple. Il se compose de trois classes :
  • Une classe qui simule un programme externe qui change les données. Elle ajoute une donnée à partir d'une position aléatoire d'un tableau qui contient "quelques données".
  • Une fenêtre qui continent la liste et qui expose le modèle de la liste. Cette classe constitue aussi l'écouteur ListDtaListener. en recevant une nouvelle valeur, le texte d'une JLabel est mis à jour.
  • La classe qui contient le main crée une instance de chaque classe et les passe à deux thread séparés. Pour une meilleure visibilité, l'ajout des valeurs aléatoires s'effectue à des intervalles de 2 seconds. 
Ainsi, le code essentiel de la première classe :

class MonDeuxiemeProgramme {
 
 MaFrame frameAttachee;
 String[] quelquesDonnees = ...;

 public MonDeuxiemeProgramme(MaFrame frame){
  frameAttachee = frame;
 }
 
 public void ajouterElementAleatoire(){
  Random r = new Random();
  int valeur = r.nextInt(26);
  
  frameAttachee.getModele().addElement(quelquesDonnees[valeur]);
 }
 
}

Le code essentiel de la deuxième classe :


class MaFrame extends JFrame implements ListDataListener {

 JList liste;
 DefaultListModel modele;
 
 ...
 
 public MaFrame(){
  
  setTitle("Exemple JList");
  setSize(800, 400);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  modele = new DefaultListModel();
  modele.addListDataListener(this);
  liste = new JList(modele);
  
  creerInterface();
  
 }
 
 public void creerInterface(){
  ...
 }
 
 public void intervalAdded(ListDataEvent e){
  lblMessage.setText("Ajout à la position " + e.getIndex0() + " (" + modele.get(e.getIndex0()) + ")");
 }
 
 public DefaultListModel getModele(){
  return modele;
 }
 
 public void intervalRemoved(ListDataEvent e){}
 
 public void contentsChanged(ListDataEvent e){}

} 

Le code complet :


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.util.*;

public class ExempleEvenementModelJList {
 
 public static void main (String args[]) {
  
  MaFrame frame = new MaFrame();
  MonDeuxiemeProgramme autre = new MonDeuxiemeProgramme(frame);
  
  Thread t1 = new Thread(new Runnable(){
   
   public void run(){
    frame.setVisible(true);
   }
   
  });
  
  Thread t2 = new Thread(new Runnable(){
   
   public void run(){
    while(true){
     try{
      Thread.sleep(2000);
      autre.ajouterElementAleatoire();
     }catch(Exception e){}
    }
   }
   
  });
  
  t1.start();
  t2.start();
  
 }
}

class MaFrame extends JFrame implements ListDataListener {

 JList liste;
 JScrollPane scroll;
 DefaultListModel modele;
 
 JLabel lblMessage;
 
 JPanel pnlDroite;
 JPanel pnlGauche;
 
 public MaFrame(){
  
  setTitle("Exemple JList");
  setSize(800, 400);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  modele = new DefaultListModel();
  modele.addListDataListener(this);
  liste = new JList(modele);
  
  creerInterface();
  
 }
 
 public void creerInterface(){
  
  lblMessage = new JLabel("Pas d'élément pour le moment");
  lblMessage.setBounds(20, 20, 450, 30);
  
  pnlDroite = new JPanel();
  pnlDroite.setLayout(null);
  pnlDroite.add(lblMessage);
  pnlDroite.setPreferredSize(new Dimension(480, 360));
  
  scroll = new JScrollPane(liste);
  scroll.setBounds(0, 0, 250, 360);
  
  pnlGauche = new JPanel();
  pnlGauche.setLayout(null);
  pnlGauche.add(scroll);
  pnlGauche.setPreferredSize(new Dimension(280, 360));
  
  getContentPane().setLayout(new FlowLayout());
  getContentPane().add(pnlGauche);
  getContentPane().add(pnlDroite);
  
 }
 
 public void intervalAdded(ListDataEvent e){
  lblMessage.setText("Ajout à la position " + e.getIndex0() + " (" + modele.get(e.getIndex0()) + ")");
 }
 
 public DefaultListModel getModele(){
  return modele;
 }
 
 public void intervalRemoved(ListDataEvent e){}
 
 public void contentsChanged(ListDataEvent e){}

} 

class MonDeuxiemeProgramme {
 
 MaFrame frameAttachee;
 String[] quelquesDonnees = new String[]{"Alfa",
  "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf",
  "Hotel", "India", "Juliett", "Kilo", "Lima", "Mike",
  "November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra",
  "Tango", "Uniform", "Victor", "Whiskey", "X-ray", "Yankee",
  "Zulu"};
 
 public MonDeuxiemeProgramme(MaFrame frame){
  frameAttachee = frame;
 }
 
 public void ajouterElementAleatoire(){
  Random r = new Random();
  int valeur = r.nextInt(26);
  
  frameAttachee.getModele().addElement(quelquesDonnees[valeur]);
 }
 
}

Résultat après quelques seconds :