MokaByte 57 - 9mbre 2001 
Java Beans
La programmazione per componenti in Java
II parte
di
Andrea Gini
La programmazione a componenti, introdotta il mese scorso, è una tecnica di sviluppo di cui è possibile cogliere la portata solamente toccando con mano i prodotti che ne abbracciano la filosofia. In questo e nei prossimi articoli verranno approfonditi i temi già introdotti il mese scorso, illustrando, in parallelo con l’esposizione teorica, alcuni esempi che aiutino a  fissare i concetti. Questo mese parleremo delle proprietà Bean

Guida all'Implementazione dei JavaBeans
Realizzare un componente Java Bean è un compito alla portata di qualunque programmatore Java che disponga di buone conoscenze di sviluppo Object Oriented. Nei paragrafi seguenti verranno descritte dettagliatamente le convenzioni di naming dettate dalla specifica, e verranno fornite le istruzioni su come scrivere le poche righe di codice necessarie ad implementare i meccanismi che caratterizzano i servizi Bean. Infine verranno presentati degli esempi, che permetteranno di impratichirsi con il processo di implementazione delle specifiche. 

Le Proprietà
Le Proprietà sono attributi che descrivono l'aspetto e il comportamento di un Bean, e che possono essere modificate durante tutto il ciclo di vita del Componente. Di base, le Proprietà sono attributi privati, ai quali si accede attraverso una coppia di metodi della forma: 

public <PropertyType> get<PropertyName>()
public void set<PropertyName>(<PropertyType> property)

La convenzione di aggiungere il prefisso get e set ai metodi che forniscono l'accesso ad una proprietà, permette ai Tool grafici di rilevare le proprietà Bean, determinarne le regole di accesso (Read Only o Read/Write), dedurne il tipo, visualizzare le proprietà su un apposito property Sheet e individuare l'Editor di Proprietà più adatto al caso.

Se ad esempio un Tool grafico scopre, grazie all'introspezione, la coppia di metodi:

  public Color getForegroundColor() { ... }
  public void setForegroundColor(Color c) { ... }

da questi conclude che esiste una proprietà chiamata "foregroundColor" (notare la prima lettera minuscola), accessibile sia in lettura che in scrittura, di tipo Color. A questo punto, il Tool può cercare un Editor di Proprietà per parametri di tipo Color (ad esempio una palette tipo ColorChooser), e mostrare la proprietà su un property sheet in modo che possa essere vista e manipolata dal programmatore.
 
 
 

Proprietà Indicizzate (Indexed Property)
Le proprietà indicizzate permettono di gestire collezioni di valori accessibili attraverso indice, in maniera simile a come si fa con un vettore. Lo schema di composizione dei metodi di accesso di una proprietà indicizzata è il seguente:

  public <PropertyType>[] get<PropertyName>();
  public void set<PropertyName>(<PropertyType>[] value);

per i metodi che permettono di manipolare l'intera collection, mentre per accedere ai singoli elementi, si deve predisporre una coppia di metodi del tipo:

  public <PropertyType> get<PropertyName>(int index);
  public void set<PropertyName>(int index, <PropertyType> value);
 
 
 

Proprietà Bound
Le proprietà semplici, così come sono state descritte nei paragrafi precedenti, seguono una convenzione radicata da tempo nella normale programmazione ad oggetti. Le proprietà bound, al contrario, sono caratteristiche dell'universo dei componenti, dove si verifica molto spesso la necessità di collegare il valore delle proprietà di un Componente a quelli di un'altro, in modo tale che si mantengano aggiornati. I metodi 'set' delle proprietà bound, inviano una notifica a tutti gli ascoltatori registrati ogni qualvolta viene alterato il valore della proprietà. Il meccanismo di ascolto-noltifica, simile a quello degli Eventi Swing ed AWT, segue il pattern Observer.


Figura 1 - Il meccansimo di notifica di eventi bound segue il pattern Observer

Le proprietà bound, a differenza degli Eventi Swing, utilizzano un unico tipo di evento, ChangeEvent, cosa che semplifica il processo di implementazione. La classe PropertyChangeSupport, presente all'interno del package java.bean, fornisce i metodi che gestiscono la lista degli ascoltatori, e quelli che producono l'invio degli eventi.

Un oggetto che voglia mettersi in ascolto di una proprietà, deve implementare l'interfaccia  PropertyChangeListener, e deve registrarsi presso la sorgente di eventi . L'oggetto PropertyChangeEvent incapsula le informazioni riguardo alla proprietà modificata, la sorgente ed il valore della proprietà.
 
 
 

Come Implementare il supporto alle proprietà Bound
Per aggiungere ad un Bean il supporto alle proprietà bound, bisogna anzitutto importare il package java.beans.*, in modo da garantire l'accesso alle classi PropertyChangeSupport e PropertyChangeEvent. Quindi bisogna creare un oggetto PropertyChangeSupport, che ha il compito di mantenere la lista degli ascoltatori, e di fornire i metodi che gestiscono l'invio degli eventi:

private PropertyChangeSupport changes = 
        new PropertyChangeSupport(this);

A questo punto bisogna realizzare, nella propria classe, i metodi che permettono di gestire la lista degli ascoltatori. Tali metodi sono dei semplici metodi wrapper che fanno riferimento a metodi con la stessa firma, presenti nel PropertyChangeSupport: 

  public void addPropertyChangeListener(
   PropertyChangeListener l) {
    changes.addPropertyChangeListener(l);
  }
  public void removePropertyChangeListener(
                PropertyChangeListener l) {
    changes.removePropertyChangeListener(l);
  }

La presenza di dei metodi addPropertyChangeListener e removePropertyChangeListener permette ai Tool grafici di riconoscere un oggetto in grado di inviare proprietà Bound, e di mettere a disposizione un'opportuna voce nel menù di gestione degli eventi.

L'ultimo passaggio consiste nel modificare i metodi set relativi alle proprietà che si vuole rendere bound, per fare in modo che venga generato un PropertyChangeEvent ogni  volta che la proprietà viene reimpostata:

  public void setColor(Color newColor) {
    Color oldColor = color;
    color = newColor;
    changes.firePropertyChange("color",
        oldColor , newColor);
  }

Nel caso di proprietà read only, prive di metodo set, l'invio dell'evento dovrà avvenire all'interno del metodo che attua la modifica della proprietà. Un'aspetto interessante del meccanismo di invio di PropertyChangeEvent, è che essi trasportano sia il nuovo valore che quello vecchio. Questa scelta dispensa chi implementa un ascoltatore dal compito di mantenere una copia del valore, qualora questo fosse necessario, dal momento che l'evento viene propagato dopo la modifica della relativa proprietà. Il metodo fireChangeEvent() della classe PropertyChangeListener fornisce il servizio di Event dispatching:

firePropertyChange(String propertyName, Object oldValue,
                   Object newValue)

In pratica esso impacchetta i parametri in un oggetto PropertyChangeEvent, e chiama il metodo  propertyChange(PropertyChangeEvent p) su tutti gli ascoltatori registrati. I parametri vengono trattati come Object, e nel caso si debbano inviare proprietà espresse in termini di tipi primitivi, occorre incapsularle nell'opportuno wrapper (Integer per valori int, Double per valori double e così via). Per facilitare questo compito, la classe propertyChangeSupport prevede delle varianti di firePropertyChange per valori int e boolean.
 
 
 

Come Implementare il supporto alle proprietà Bound su sottoclassi di JComponent
La classe JComponent, superclasse di tutti i componenti Swing, dispone del supporto nativo alla gestione di proprietà Bound. Di base, essa fornisce li metodi addPropertyChangeListener e removePropertyChangeListener, oltre ad una collezione di metodi firePropertyChange adatta ad ogni tipo primitivo. In questo caso l'implementazione di una proprietà bound richiederà solo una modifica al metodo set preposto, in modo simile a come descritto nell'ultimo passaggio del precedente paragrafo, con la differenza che non è necessario ricorrere ad un oggetto propertyChangeSupport per inviare la proprietà:

  public void setColor(Color newColor) {
    Color oldColor = color;
    color = newColor;
    firePropertyChange("color",
        oldColor , newColor);
  }
 

Ascoltatori di Proprietà 
Se si desidera mettersi in ascolto di una proprietà, bisogna definire un'opportuno oggetto PropertyChangeListener, e registrarlo presso il Bean. Un PropertyChangeListener deve definire il metodo propertyChange(PropertyChangeEvent e), che viene chiamato quando avviene la modifica di una proprietà bound.

Un PropertyChangeListener viene notificato quando avviene la modifica di una qualunque proprietà bound: per questa ragione esso deve, come prima cosa, verificare, che la proprietà appena modificata sia quella alla quale si è interessati. Una simile verifica richiede una chiamata al metodo getPropertyName di PropertyChangeEvent,  che restituisce il nome della proprietà. Per convenzione, i nomi di proprietà vengono estratti dai nomi dichiarati nei metodi get e set, con la prima lettera minuscola. Il seguente frammento di codice presenta un tipico PropertyChangeListener, che ascolta la proprietà 'foregroundColor':

  public class Listener implements PropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent e) {
      if(e.getPropertyName().equals("foregroundColor"))
        System.out.println(e.getNewValue());
    }
  }
 
 

Un esempio di Bean con proprietà Bound
Un JavaBean rappresenta un mattone di un programma. Ogni componente è un'unità di utilizzo abbastanza grossa da incorporare una funzionalità evoluta, ma piccola rispetto ad un programma fatto e finito. Il concetto del riuso può essere presente a diversi livelli del progetto: il seguente Bean fornisce un esempio di elevata versatilità

Il Bean PhotoAlbum è un pannello grafico al cui interno vengono caricate delle immagini. Il metodo showNext() permette di passare da un'immagine all'altra, in modo ciclico. Il numero ed il tipo di immagini viene determinato al momento dell'avvio: durante la fase di costruzione viene letto il file comment.txt, presente nella directory images, che contiene una riga di commento per ogni immagine presente nella cartella. Le immagini devono essere nominate in modo progressivo (img0.jpg, img1.jpg, img2.jpg....) e devono essere presenti in numero uguale alle righe del file comment.txt. Questa scelta progettuale consente di introdurre il riuso ad un livello abbastanza alto: qualunque utente, anche con scarse conoscenze del linguaggio, può personalizzare il componente, inserendo le sue foto preferite, senza la necessità di alterare il codice sorgente.

Il Bean PhotoAlbum ha tre proprietà:

  • imageNumber, che restituisce il numero di immagini contenute nell'album. Essendo una quantità immutabile, tale proprietà è stata implementata come proprietà semplice.
  • imageIndex: restituisce l'indice dell'immagine attualmente visualizzata. Al cambio di immagine viene inviato un PropertyChangeEvent
  • imageComment: restituisce una stringa di commento all'immagine. Anche in questo caso, al cambio di immagine viene generato un PropertyChangeEvent
Il Bean viene definito come sottoclasse di JPanel: per questo motivo non vengono dichiarati i metodi addPropertyChangeListener e removePropertyChangeListener, già presenti nella superclasse. L'invio delle proprietà verrà messo in atto grazie al metodo firePropertyChange di JComponent.

package com.mokabyte.mokabook.javaBeans.photoAlbum;

import java.awt.*;
import java.beans.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;

public class PhotoAlbum extends JPanel {

  private Vector comments = new Vector();
  private int imageIndex;

  public PhotoAlbum() {
    super();
    setLayout(new BorderLayout());
    setupComments();
    imageIndex=0;
    showNext();
  }
  private void setupComments() {
    try { 
      URL indexUrl = 
        getClass().getResource("images/"+"comments.txt");
      InputStream in = indexUrl.openStream();
      BufferedReader lineReader =
        new BufferedReader(new InputStreamReader(in));
      String line;
      while((line = lineReader.readLine())!=null) 
        comments.add(line);
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
  public int getImageNumber() {
    return comments.size();
  }
  public int getImageIndex() {
    return imageIndex;
  }
  public String getImageComment() {
    return (String)comments.elementAt(imageIndex);
  }
  public void showNext() {
    int oldImageIndex = imageIndex;
    imageIndex = ((imageIndex+1)%comments.size());
    String imageName = "img"+Integer.toString(imageIndex)+".jpg";
    showImage(getClass().getResource("images/"+imageName));
    String oldImageComment =
       (String)comments.elementAt(oldImageIndex);
    String currentImageComment =
       (String)comments.elementAt(imageIndex);
    firePropertyChange("imageComment",oldImageComment,
        currentImageComment);
    firePropertyChange("imageIndex",oldImageIndex,imageIndex);
  }
  private void showImage(URL imageUrl) {
    ImageIcon img = new ImageIcon(imageUrl);
    JLabel picture = new JLabel(img);
    JScrollPane pictureScrollPane = new JScrollPane(picture);
    removeAll();
    add(BorderLayout.CENTER,pictureScrollPane); 
    validate(); 
  }
}

Possiamo testare il Bean come fosse una normale classe Java, utilizzando queste semplici righe di codice:

package com.mokabyte.mokabook.javaBeans.photoAlbum;

import com.mokabyte.mokabook.javaBeans.photoAlbum.*;
import java.beans.*;
import javax.swing.*;

public class PhotoAlbumTest {
public static void main(String argv[]) {
  JFrame f = new JFrame("Photo Album");
  PhotoAlbum p = new PhotoAlbum();
  f.getContentPane().add(p);
  p.addPropertyChangeListener(new PropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent e) {
      System.out.println(e.getPropertyName()+": "+e.getNewValue());
    }
  });
  f.setSize(500,400);
  f.setVisible(true);

  while(true)
    for(int i=0;i<7;i++) {
      p.showNext();
      try {Thread.sleep(1000);}catch(Exception e) {}
    }
}
}


Figura 2  -  Un programma di prova per il Bean PhotoAlbum







Creazione di un file jar
Prima di procedere alla consegna del Bean entro un file jar, bisogna anzitutto compilare le classi PhotoAlbum.java e PhotoAlbumTest.java, che devono trovarsi nella cartella com\mokabyte\mokabook\javaBeans\

javac com\mokabyte\mokabook\javaBeans\photoAlbum\*.java

A questo punto bisogna creare, ricorrendo ad un semplice editor di testo tipo Notepad, un file photoAlbumManifest.tmp con il seguente contenuto:

Main-Class: com.mokabyte.mokabook.javaBeans.photoAlbum.PhotoAlbumTest

Name: com/mokabyte/mokabook/javaBeans/photoAlbum/PhotoAlbum.class
Java-Bean: True

Le prime due righe, opzionali, segnalano la presenza di una classe dotata di metodo main: in questo modo il file jar potrà essere avviato digitando:

java PhotoAlbum.jar

o semplicemente con un doppio click sull'icona del file, che diventa in questo modo un eseguibile interpiattaforma.


Figura 3 - Un file Jar opportunamente confezionato si comporta come un 
file eseguibile multipiattaforma, attivabile con un doppio click del mouse

Le ultime due righe del file manifest specificano che la classe PhotoAlbum.class è un JavaBean. Se l'archivio contiene più di un bean, è necessario elencarli tutti.

Per generare l'archivio photoAlbum.jar, bisogna digitare la riga di comando:

jar cfm photoAlbum.jar photoAlbumManifest.tmp com\mokabyte\mokabook\javaBeans\photoAlbum\*.class com\mokabyte\mokabook\javaBeans\photoAlbum\images\*.*

Il file così generato contiene tutte le classi e le immagini necessarie a dar vita al Bean PhotoAlbum. Tale file potrà essere utilizzato facilmente all'interno di Tool grafici o di pagine web, racchiuso dentro un'applet. 

NOTA: le istruzioni fornite sono valide per la piattaforma Windows. Su piattaforma Unix, le eventuali occorrenze del simbolo "\", che funge da path separator su piattaforme Windows, andranno sostituite col simbolo "/". Le convenzioni adottate all'interno del file manifest valgono invece su entrambe le piattaforme.
 
 

Integrazione con altri Beans
Nonostante il Bean PhotoAlbum fornisca un servizio abbastanza evoluto, non è ancora classificabile come applicazione. Esso, opportunamente integrato con altri Beans, può comunque dar vita a numerosi programmi; vediamo qualche esempio:
 

  • Collegato ad un CalendarBean, PhotoAlbum può dar vita ad un simpatico calendario elettronico.
  • Collegando un bottone Bean al metodo showNext() possiamo creare un album interattivo, impacchettarlo su un'applet e pubblicarlo su Internet
  • Impacchettando il Bean PhotoAlbum con foto natalizie, e collegandolo con un Bean Carillon, possiamo ottenere un biglietto di auguri elettronico.



Figura 4 - Combinando, all'interno del Bean Box, il Bean PhotoAlbum con un 
pulsante Bean, otteniamo una piccola applicazione

A questi esempi se ne possono facilmente aggiungere altri; altri ancora diventano possibili aggiungendo al Bean nuovi metodi, come previousImage() e setImageAt(int i); un compito ormai alla portata del lettore, che fornisce un ottimo pretesto per esercitarsi.
 
 
 

Conclusioni
Questo mese abbiamo parlato delle proprietà Bean, un meccanismo di comunicazioni tra componenti molto valido e potente. Grazie alle proprietà bound è possibile fare in modo che il cambiamento di una proprietà su un Bean provochi una reazione su un altro, mantenendo intatta la consistenza. Nel prossimo articolo parleremo di eventi Bean, un meccanismo generico per la diffusione di eventi.
 
 

Allegati
Gli esempi completi si possono scaricare qui


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati 
E' vietata la riproduzione anche parziale 
Per comunicazioni inviare una mail a info@mokabyte.it