Costruzione di GUI con Java 1.1 AWT
Lettore di CD Audio in Java, parte II


di
Michael Hamilton
traduzione di
Luciano Serafini e
Paolo Maria Ruscitti

Nel precedente articolo è stato descritto un semplice lettore CD. Nel corso del presente, invece, si vedrà come estendere l'oggetto Drive in modo che possa eseguire la riproduzione programmata e casuale delle tracce di un CD.


Indice:


Nel mio precedente articolo ho descritto un semplice lettore CD che può essere modificato per creare un lettore GUI del tipo Jcd, un lettore freeware che ho reso disponibile sul Web al sito http://www.actrix.gen.nz/users/michael/giveaways.html

In questo articolo descriverò come estendere l'oggetto Drive cosicché possa eseguire la riproduzione programmata (riprodurre una lista di tracce) e la riproduzione casuale (riprodurre ogni traccia una volta in ordine casuale). La struttura del nuovo lettore è descritta dal diagramma delle classi nella figura sottostante. Piuttosto che iniziare con i componenti di basso livello, ho pensato che sarebbe più interessante iniziare con una descrizione dell'interfaccia GUI. Per ora assumiamo che ci sia una nuova versione SmartDrive di Drive che includa tutte le vecchie funzionalità, più il controllo dello stato di Drive e la possibilità di accettare e modificare una lista di tracce da riprodurre. Introdurrò l'uso delle nuove funzionalità col procedere della descrizione del CD Player.

Nel mio precedente articolo ho descritto una classe Java chiamata ``Drive'', che fornisce le seguenti funzionalià del lettore CD

Oggetto Drive:
	Informational methods: 
		currentTrack, currentIndex, numberOfTracks,
		currentAddress,
		cdEndAddress, trackAddress, trackLength

Control methods:
	play, stop, pause, resume, eject, setVolume

Da quando ho scritto il mio precedente articolo, è stato effettuato il porting di Java Development Kit 1.1 su Linux. Il codice in questo articolo userà le caratteristiche dell'AWT del JDK 1.1. L'articolo fu scritto in origine usando il JDK 1.0.1, ed il codice originale JDK 1.0.1 è incluso nel file tar disponibile al sito ftp di SSC Linux Journal.


La Classe Player


Il nuovo lettore CD che sto per descrivere è chiamato Player e viene lanciato dalla linea di comando digitando:

setenv SBPCD 1 # se avete un vecchio drive SoundBlaster
java Jcd.Player
All'avvio l'applicazione crea l'interfaccia GUI mostrata in Figura 1 e in Figura 2.

Il codice sorgente per la classe Player è presentato nel Listato 1. La classe Player ha un metodo statico main() alla linea 30, che è il punto da cui partirà l'esecuzione del programma. Alla linea 32 il metodo main() crea un'istanza della classe Player:

Player player = new Player(); 
Dalla linea 38 alla 43, il costruttore della classe Player setta l'oggetto SmartDrive che comunica con il lettore CD. Il nome del dispositivo da aprire (/dev/cdrom) e la locazione del modulo nativo (Jcd_Drive.so) è incorporato nel costruttore del Player. Il modulo nativo implementa il kernel dell'interfaccia al drive del CDROM. In una versione reale del sistema, questi parametri dovrebbero essere letti da un file di configurazione o dedotti interrogando l'ambiente operativo.

Come precedentemente affermato, SmartDrive è una versione estesa della classe di interfaccia hardware descritta nel mio primo articolo -- essa è stata migliorata per supportare la riproduzione programmata e per fornire informazioni sullo stato del lettore CD. Alla linea 56 il costruttore di Player avvia il monitor di SmartDrive. Il monitor inizierà a mandare eventi del lettore CD ad ogni oggetto che è stato registrato per riceverle.

Figura 1. Componenti GUI

Tornerò adesso su Player e descriverò una parte del codice usato nella GUI. Dalla linea 45 alla 54 del Listato 1. il costruttore di Player crea le componenti GUI viste in Figura 1: una barra dei menu; un'area di testo per le informazioni sulle tracce; e un'area di controllo con i pulsanti per controllare il lettore CD.


Il Form Panel


Il toolkit AWT GUI fornisce componenti del tipo etichette di testo, campi editabili, menu e pulsanti. Le componenti sono poste in contenitori per costruire finestre e labels. Il contenitore AWT di più alto livello è la classe Frame. Un Frame costruisce una finestra separata e a se stante. Come altri toolkit GUI, l'AWT fornisce al programmatore delle classi di sotto-contenitori che possono essere usati per controllare il posizionamento delle componenti suddividendo una finestra in aree più piccole. Il più grande sotto-contenitore di AWT è la classe Panel. L'AWT fornisce un ulteriore controllo sul posizionamento, permettendo al programmatore di configurare il layout di un Frame o di un Panel. Per esempio, l'AWT FlowLayout standard, posiziona le componenti da sinistra a destra e dall'alto al basso. Il layout di un Frame o di un Panel può essere assegnato da quelli forniti dall'AWT, o può essere scritto in proprio.

Nella linea 10 del Listato 1, la classe Player è dichiarata per estendere la classe Form. Il codice sorgente di Form è riportato nel Listato 2. Form è una classe che ho creato e che estende la normale classe superiore Frame. Player è una Form, una Form è un Frame, un Frame crea una finestra autonoma, così Player crea una finestra autonoma. La classe Form usa il manager AWT GridBagLayout. Il manager GridBagLayout è il manager di layout più flessibile in AWT. Esso ha una grande varietà di opzioni per lo spaziamento ed il posizionamento degli oggetti con un Frame o un Panel. La sua grande flessibilità lo rende difficile da gestire. Form semplifica la gestione con GridBagLayuot fornendo il metodo addCenter(). Dalla linea 23 alla 32 del Listato 2, il metodo addCenter() controlla il posizionamento, ponendo l'oggetto alla successiva riga disponibile e facendogli occupare l'intera riga:

c.gridx = 0;
...
c.gridwidth = GridBagConstraints.REMAINDER;
Esso setta gli spazi occupati al valore NONE. Setta gli spazi intorno alla componente ad 1. L'effetto finale è che la sotto-classe Form posizionerà gli oggetti dall'alto al basso, un oggetto per riga, ognuno occupante lo spazio necessario più un piccolo spazio circostante.


La Barra dei Menu


Ritorniamo alla classe Player. Nelle linee 46 e 47 di Listato 1, il costruttore di Player setta la barra dei menu delle finestre:

setMenuBar(new MenuBar());
getMenuBar().add(createFileMenu()); 
Il menu a discesa attuale viene creato dal metodo createFileMenu() nelle linee da 70 a 78 di Listato 1. Esso crea il file-menu del lettore e vi aggiunge le singole voci del menu. Le linee 76 e 77 di createFileMenu() gestiscono la manipolazione degli eventi per il menu:
fileProgramItem.addActionListener(this);
fileExitItem.addActionListener(this); 
Queste due linee settano l'oggetto Player ("this") per gestire gli ActionEvents dai file-menu fileProgramItem e fileExitItem. Questi eventi sono generati quando l'utente seleziona una voce dal menu. Allo scopo di poter gestire questi eventi, la classe Player deve implementare l'interfaccia ActionListener -- essa è dichiarata come tale alla linea 10 di Listato 1:
public class Player extends Form implements ActionListener
Cosa succede adesso? Player eredita da -- "extends" -- Form, ma cosa vuol dire "implements ActionListener"? Player può ereditare dati e definizioni di metodo solo da un singolo genitore -- Java non supporta eredità multiple -- un oggetto può "estendere" solo una singola classe genitore. Comunque per fornire alcune delle funzionalità dell'eredità multipla, Java fornisce il meccanismo "implements/interface". In altri linguaggi l'eredità multipla deve accordarsi con lo scopo da ottenere quando una classe eredita due o più implementazioni della stessa struttura di dati o di metodi da due diversi genitori. Ad esempio, diciamo che entrambi i genitori abbiano un metodo add(), quale di questi dovrebbe essere usato nella sottoclasse? Il meccanismo di Java per l'eredità multipla limitata, "l'interfaccia", non supporta l'eredità dell'implementazione. Ad eccezione delle costanti relative a tutta la classe, le definizioni dell'interfaccia devono essere completamente astratte. Una definizione di interfaccia, del tipo ActionListener, non può fornire un'implementazione di un qualunque metodo che essa dichiari. Qualunque classe che desideri "implementare" un'interfaccia, deve fornire il suo proprio codice per implementare tutti i metodi nell'interfaccia. Una classe può implementare un qualunque numero di interfacce -- una classe potrebbe implementare sia ActionListener, sia MouseListener e gestire entrambi i tipi di eventi. Non fornendo un'implementazione, le interfacce lasciano la risoluzione dei conflitti nelle mani del programmatore che la sta progettando.

Le interfacce Listener come ActionListener, MouseListener ed altre sono state introdotte recentemente nel JDK 1.1. Il nuovo JDK 1.1 AWT Event model usa il meccanismo dell'interfaccia Java per fornire un meccanismo di gestione degli eventi, più flessibile rispetto alla precedente versione del JDK. Ci sono interfacce separate per i differenti tipi di eventi, come quelli del mouse e della tastiera. Più oggetti possono essere registrati per gli stessi eventi e tutti loro li riceveranno.

Allo scopo di implementare l'interfaccia ActionListener, la classe Player deve avere un metodo actionPerformed() -- il metodo è definito nelle linee da 59 a 68 --. Mediante una chiamata al metodo actionPerformed() verranno passati gli eventi di menu a Player. actionPerformed() controlla quale componente sia la sorgente dell'evento ed invoca un'appropriato frammento di codice: alle linee da 62 a 65 il metodo actionPerformed() di Player controlla se la sorgente dell'evento sia il fileProgramItem -- se è così e non è mostrato alcun programma esistente, ne viene creato uno nuovo --. Alla linea 66 se la sorgente era fileExitItem, il programma viene terminato.


Adattatori AWT


In alcuni casi l'interfaccia necessaria a gestire un evento AWT è abbastanza complessa. Per risparmiare al programmatore il lavoro di dover definire completamente un'interfaccia degli eventi AWT, l'AWT include le classi Adapter predefinite che forniscono le implementazioni di default per le interfacce più complesse. Ad esempio l'interfaccia MouseListener ha una corrispondente classe MouseAdapter che fornisce un'implementazione di default. Queste classi Adapter preconfezionate dell'AWT possono essere sottoclassate per sostituirsi selettivamente ai metodi esistenti.

La classe Player fa uso di una classe Adapter per gestire le richieste di chiusura provenienti dal manager della finestra. Le richieste di chiusura sono, in genere, il risultato di un doppio click dell'utente sul pulsante di chiusura posto sulla barra del titolo della finestra. Alla linea 52 di Listato 1, Player registra un WindowListener:

addWindowListener(new DoClose());
L'interfaccia WindowListener ha diversi metodi ed io voglio sostituire solo uno di essi -- il metodo windowClosing() --. Sfortunatamente la classe Player non può ereditare dalla classe di default WindowAdapter poiché Player già eredita dalla classe Form. La soluzione che ho applicato in questo caso è quella di usare un'altra delle nuove caratteristiche del JDK 1.1. JDK 1.1 aggiunge le Inner Classes (Classi Interne) al linguaggio Java -- ciò significa che posso dichiarare una classe all'interno di un'altra classe -- :
public class Player extends Form implements ActionListener {
...
addWindowListener(new DoClose());
...
private class DoClose extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
...
}
Player usa addWindowListener() per registrare un nuova istanza della sua propria Classe Interna DoClose. Poiché DoClose è una Classe Interna di Player, essa ha accesso ai dati e ai metodi di Player e può essere, quindi, più strettamente integrata in Player di una classe dichiarata separatamente. In altri linguaggi è abbastanza comune risolvere situazioni come questa passando dei puntatori ai metodi, alle funzioni o ai frammenti di codice -- ma in Java possono essere passati solo gli oggetti, così le Classi Interne vengono fornite come una soluzione.


Il Main Button Controls


Rivolgiamoci, adesso, alla parte restante dell'interfaccia GUI di Player: le classi Controls e Display. Descriverò prima la classe Controls poiché è la più semplice. Controls è un pannello di pulsanti che voi potete vedere in fondo alla Figura 1. Il Listato 3 mostra il codice sorgente per la classe Controls. Poiché esso è destinato ad essere un sotto pannello di Player, eredita da Panel:

class Controls extends Panel implements ActionListener
Anche la classe Controls ha bisogno di eseguire azioni quando vengono premuti dei pulsanti, cosicché anch'essa implementa l'interfaccia ActionListener.

Le linee da 17 a 22 di Listato 3 dichiarano il set di pulsanti. La dichiarazione dei pulsanti specifica anche come inizializzarli quando viene creato un oggetto Controls. Nelle linee da 26 a 32 il costruttore di Controls aggiunge ognuno dei pulsanti a Panel. Il semplice manager GridLayout, da non confondere con il più complesso manager GridBagLayout, viene usato per controllare il posizionamento delle componenti all'interno di Panel. GridLayout posiziona le componenti da sinistra a destra e dall'alto in basso, in celle di uguale dimensione nella grid specificata:

setLayout(new GridLayout(1, 6, 2, 2)); 
In questo caso la grid è di 1 riga per 6 colonne -- una colonna per ogni pulsante. Gli ultimi due argomenti specificano una spaziatura orizzontale e verticale tra le celle della grid, pari a 2.

Il metodo Controls add(), alle linee da 31 a 34, sostituisce il metodo add() ereditato dalla super-classe Panel. Il comportamento di add() è stato modificato per settare l'oggetto Controls come l'ActionListener per ogni pulsante:

private void add(Button b) {
b.addActionListener(this); 
super.add(b); // Now call super class add() method.
}
Il metodo actionPerformed() di Controls, linee da 36 a 49, risponde ad eventi di pressione di pulsanti invocando i corrispondenti metodi CdPlayer.


Il Display dello Stato del CD Player


La parte finale dell'interfaccia mostrata in Figura 1 è il pannello Display, il cui codice sorgente può essere visto in Listato 4. Il pannello Display consiste di tre campi di testo per visualizzare la traccia del CD, l'indice ed il tempo rimanente -- trackField, indexField, e timeField --. Essi vengono dichiarati nelle linee da 13 a 15 di Listato 4 e saranno inizializzati come nuovi textField quando viene creato un oggetto Display (il che succede una sola volta in questa applicazione).

Il metodo costruttore di Display() è alla linea 20 di Listato 4. Le linee da 22 a 29 inizializzano le componenti di base coinvolte nel display. Il manager FlowLayout è assegnato al Panel, il che vuol dire che le chiamate al metodo add() posizioneranno i tre campi di testo in una riga da sinistra a destra con un po' di spazio di separazione in alto ed in basso. Alle linee 23 e 24 ho reso i campi indexField e timeField a sola lettura, così l'utente non può alterare i loro valori:

 indexField.setEditable(false);
 timeField.setEditable(false); 
Il campo trackField viene lasciato editabile cosicché l'utente può scegliere il numero della traccia da cui far partire la riproduzione. Allo scopo di gestire gli eventi del mouse e della tastiera nel campo trackField, dobbiamo registrare una coppia di Listeners di eventi:
trackField.addFocusListener(new TrackFocusLost());
trackField.addKeyListener(new TrackKeyPress()); 
Entrambi i Listeners sono abbastanza complessi, così piuttosto che scrivere le nostre proprie implementazioni complete, due classi Adapter di default sono sottoclassate alle linee da 53 a 74, per portare a termine il lavoro -- come spiegherò in seguito --.

Alla linea 34 registriamo Display() come Observer del cdPlayer:

cdPlayer.monitor.addObserver(this); 
Lo stato del lettore CD viene attualmente controllato da un oggetto Monitor -- l'oggetto cdPlayer.monitor --. Il monitor gira nel suo propro thread (un thread può essere pensato come un sottoprocesso leggero che ha un'accesso condiviso ai dati del programma principale). Il bisogno di monitorare eventi e il notificarli ad altri oggetti è un problema comune nella programmazione. Java fornisce la Classe Observable, e la sua compagna Observer Interface, come una base standard per l'indirizzamento di questo tipo di problema. L'oggetto monitor è una sottoclasse della classe Observable. La classe Observable fornisce il codice necessario a gestire le relazioni Observer/Observable. La classe Monitor verrà descritta più avanti con maggiori dettagli. Display è dichiarato per implementare Observer, il che significa che deve definire un metodo update(). Il metodo update() verrà chiamato quando si verifica un evento Observable. Il metodo update() del Display è definito alle linee da 37 a 51, gli viene passato l'oggetto Observable che ha causato l'evento ed un altro ulteriore argomento (che non è usato in questa applicazione).

Normalmente il campo trackField viene aggiornato una volta al secondo quando il monitor invia un aggiornamento dello stato al suo Observer. L'utente può anche alterare il valore nel campo trackField inserendo una nuova track, il che forzerà il lettore a saltare immediatamente alla nuova track. Per prevenire che l'aggiornamento periodico prevalga sull'input dell'utente, il metodo update() è attento a non aggiornare il campo trackField prima che la traccia sia effettivamente cambiata:

if (prevText.compareTo(newTrackText) != 0) {
trackField.setText(newTrackText);
prevText = newTrackText;
}
Le linee da 53 a 75 del Listato 4 definiscono due classi interne per gestire gli input dell'utente immessi in trackField. TrackFocusLost ripristina il numero di traccia corretto quando l'utente trasferisce il fuoco all'esterno del campo trackField. La classe interna TrackKeyPress controlla ogni tasto premuto in trackField per il tasto enter. Se viene premuto il tasto enter viene fatto un tentativo di analizzare il testo digitato come un valore intero, se questo ha successo il lettore CD viene istruito a riprodurre immediatamente la traccia inserita.


Lo SmartDrive ed il Monitor Thread


Prima di andare avanti nel descrivere come scrivere una GUI per la riproduzione programmata e casuale, dobbiamo veramente capire qualcosa in più della nuova classe SmartDrive che estende la classe Drive del mio primo articolo. SmartDrive.java può essere vista nel Listato 5. SmartDrive è una sottoclasse della classe originaria Drive. Essa, principalmente, aggiunge nuovi metodi per fornire la riproduzione di una lista di tracce.

Allo scopo di memorizzare la lista di riproduzione delle tracce, viene definita una nuova classe chiamata TrackList alle linee da 175 a 243. TrackList è una sottoclasse della classe Vector del JDK. Una Vector è un'implementazione di JDK di una struttura tipo lista. Una Vector può memorizzare solamente oggetti Java. Mi piacerebbe memorizzare i numeri di traccia che sono tipi interi, ma i tipi interi non sono degli oggetti di Java, bensì dei tipi di dati primitivi e questi non sono oggetti di prima classe. Per aggirare questo problema JDK fornisce una classe wrapper per ogni tipo di dato primitivo. In questo caso devo usare la classe wrapper Integer per contenere ogni numero di traccia. Ogni volta che una traccia viene aggiunta a TrackList, il codice memorizza un oggetto Integer corrispondente:

addTrack(int t) { addElement(new Integer(t)); }
TrackList fornisce metodi per testare lo stato della lista, per avanzare lungo la lista e per resettarla. Poiché Vector memorizza oggetti generici, TrackList deve anche fare alcune modifiche. Ad esempio elementAt() ritorna un Oggetto che deve esere modificato in un Integer prima che io possa usarlo:
Integer elem = (Integer) (elementAt(position)); // Cast Object to Integer
I metodi all'interno di TrackList sono stati dichiarati come sincronizzati. Questo previene che i thread multipli cerchino di accedere simultaneamente allo stesso oggetto TrackList. Ad esempio, non vogliamo che la GUI cerchi di cancellare la lista delle tracce nello stesso momento in cui cdPlayer cerca di avanzare alla traccia successiva della lista. Dichiarando i metodi come sincronizzati, noi ci assicuriamo che le richieste vengano manipolate una alla volta -- le chiamate in attesa si bloccheranno finché l'oggetto non è disponibile --.

Per implementare la riproduzione programmata, la classe SmartDrive include un'istanza di TrackList, chiamata tracksToPlay. I metodi SmartDrive del tipo next() e prev(), alle linee da 53 a 83, riproducono le tracce sia nella sequenza numerica normale, sia nell'ordine ritornato dai metodi tracksToPlay, nextTrack(), prevTrack().

Ogni volta che il lettore arriva alla fine di una traccia, SmartDrive deve riferire a tracksToPlay e deve emettere una nuova chiamata play() per riprodurre la prossima traccia nel programma. Allo scopo di far questo, imposta una istanza all'oggetto Monitor menzionato precedentemente.

Il codice per la classe Monitor è riportato in Listato 6. Come già detto, l'oggetto Monitor, è una sottoclasse di una classe Observable, una classe JDK che fornisce parte del codice necessario a gestire le relazioni Observer/Observable. La classe Monitor gira in un thread separato che interroga il lettore CD e controlla il suo stato ogni secondo.

Il monitor viene lanciato chiamando il metodo start() del monitor -- in questo caso la chiamata a start() è fatta nel metodo main() in Listato 1. Il metodo start() del Monitor, alle linee da 57 a 65 di Listato 6, crea un nuovo thread e lo lancia:

if (updateThread == null) {
System.out.println("Starting thread");
updateThread = new Thread(this); 
updateThread.start();
}
Il costruttore del Thread si aspetta che gli venga passato un oggetto che implementa l'interfaccia Runnable, in questo caso il monitor è il suo stesso Runnable, così esso passa se stesso ("this"). Per implementare l'interfaccia Runnable, Monitor deve definire un metodo run(). Il metodo run() fornisce il codice che verrà eseguito in un nuovo thread. Quando il metodo updateThread.start() è chiamato, verrà creato il thread per la nuova esecuzione. Il nuovo thread chiamerà, allora, il metodo run() del monitor. Il metodo run() entra in un ciclo infinito collezionando stati dal cdPlayer, controllandoli e quindi si ferma per un secondo. L'aggiornamento è eseguito in un comando sincronizzato:
synchronized (cdPlayer) {
updateCdInfo(); 
setChanged();  // Force notifyObservers() to do its thing.
notifyObservers(); 
}    
Il comando sincronizzato otterrà un blocco nel cdPlayer prima di aggiornare le proprie informazioni e di notificarle ad ogni observer. Questo assicura che tutti gli Observer abbiano lo stesso quadro coerente. Il monitor usa il metodo setChanged(), ereditato da Observable, per indicare che l'Observer ha bisogno di essere informato. Esso chiama, allora, il metodo notifyObservers(), anch'esso ereditato da Observers, che passa l'aggiornamento a tutti gli Observers che sono stati precedentemente registrati con il Monitor.

La maggior parte del codice della classe Monitor, nelle linee da 72 a 127, implementa il metodo updateCDInfo() che raccoglie informazioni dall'oggetto Drive/SmartDrive. Esso cattura le informazioni del CD per evitare di appesantire il kernel di Linux con continue richieste di informazioni sempre uguali, come ad esempio la lunghezza delle tracce nel CD corrente. L'interfaccia Drive è stata trattata estensivamente nel mio primo articolo, così non entreremo qui un'altra volta nei dettagli delle chiamate al cdPlayer.

Sebbene il Monitor fornisca i mezzi principali per il trasporto delle informazioni sullo stato, il metodo update() di SmartDrives, alle linee da 118 a 148 di Listato 5, deve passare ad un modo più preciso per gestire la transizione da una traccia alla successiva. L'aggiornamento normale eseguito una volta al secondo dal monitor non è sufficiente a controllare il passaggio preciso tra le tracce, cosicché il metodo update() esegue un proprio controllo più frequente quando si avvicina la fine della traccia:

if (monitor.currentAddress >= tend - 210) { // Near end of
track?
// Poll frequently so we don't miss the event.
while (currentAddress() 
&& monitor.status == Drive.STATUS_PLAY
&& currentAddress() != 0) {
try { Thread.sleep(100); } // Sleep 100 msec's.
catch (InterruptedException e) { }
}
Questo assicura che l'ascoltatore non senta piccoli suoni provenienti dalla traccia successiva.


La Finestra Program


Figura 2. Finestra di Program

Adesso possiamo rivolgerci alla componente finale GUI -- la classe Program che crea la finestra Program --. La finestra Program può essere vista in Figura 2. Il codice sorgente per la classe Program può essere visto in Listato 7. La classe Program visualizza i suoi sub-panel usando lo stesso oggetto Form descritto in precedenza. Il costruttore Program, alle linee da 40 a 87, assembla tre sub-panel:

+ programListing - a text field;
+ trackPanel - a grid of track buttons;
+ and buttonPanel - a row of control buttons.
La classe Program non è, in verità, più complessa del resto della Gui, ad eccezione del fatto che essa presenta un uso abbastanza estensivo delle classi Inner e Anonymous introdotte di recente in Java 1.1. La maggior parte della descrizione che segue si concentrerà su queste due nuove caratteristiche del linguaggio.

Le linee da 53 a 70 impostano il buttonPanel e le azioni da eseguire quando ogni pulsante di controllo viene premuto. Il costruttore Program() usa il metodo proprio di Program addButton() per aggiungere i pulsanti di controllo al buttonPanel. addButton() si aspetta di ricevere il panel, il pulsante e un oggetto per gestire l'azione associata alla pressione di un pulsante:

void addButton(Panel panel, Button button, DoAction action)
{
panel.add(button);
button.addActionListener(action);
}
Il parametro action (azione), è dichiarato provenire dalla classe DoAction. DoAction è dichiarata in cima alla classe Program come una classe Inner -- una classe contenuta all'interno della classe Program --:
class Program extends Form implements Observer {
private abstract class DoAction implements ActionListener {
public void actionPerformed(ActionEvent event) {
this.invoke();
}
abstract void invoke();
}
La classe DoAction è astratta poiché essa non ha implementazione per il metodo invoke(). L'implementazione di invoke() è fornita individualmente per ogni pulsante da nuove sottoclassi di DoAction. Queste nuove sottoclassi sono create all'interno del costruttore Program() nelle linee da 54 a 78 -- ogni chiamate addButton() crea una nuova sottoclasse DoAction:
addButton(buttonPanel,
 editButton,
 new DoAction() { void invoke() { setEditMode(); } });
In ognuna di queste chiamate ad addButton, l'ultimo parametro è una sottoclasse Anonymous di DoAction. Come le classi Inner, le classi Anonymous furono aggiunte a Java per fornire ai programmatori i mezzi per l'implementazione di oggetti come ActionListeners senza che il programmatore dovesse creare miliardi di mini classi autonome. Il new, normalmente usato per creare un nuovo oggetto, è usato qui per creare una nuova classe:
new DoAction() { void invoke() { setEditMode(); } }
Questo codice crea una nuova sottoclasse anonima, cioè senza nome, di DoAction. Il corpo della classe, tra le parentesi graffe, fornisce un'implementazione del metodo invoke() specifico all'editButton. Nello stesso modo vengono create altre 6 classi anonime per gestire ogni pulsante di controllo.

Un'altra classe anonima viene usata per gestire la richiesta di chiusura della finestra Program nelle linee da 71 a 79. Piuttosto che dichiarare completamente una singola classe per gestire la richiesta, ho usato una sottoclasse anonima della classe AWT WindowAdapter:

new WindowAdapter() {
public void windowClosing(WindowEvent e) {
dismiss(); // call dismiss(0) for the outer class.
}
}
Di nuovo ``new'' crea una nuova classe. In questo caso viene creata una sotto-classe di WindowAdapter con un override per il metodo windowClosing().

Poiché queste classi anonime sono classi interne di Program, hanno accesso ai suoi dati ed ai suoi metodi. Ad esempio, windowClosing() nel precedente esempio chiama dismiss()-- dismiss() è un metodo della classe Program --.

L'uso suggerito delle classi Anonymous è valido solo per piccoli frammenti di codice. I frammenti di codice più grandi sono espressi in modo più ordinato come classi interne nominate.

Il metodo updateTrackPanel(), alle linee da 171 a 194, contiene un uso "delicato" delle classi interne. Qui esse sono usate per trasferire informazioni aggiuntive. Quando la finestra Program viene creata, il metodo updateTrackPanel() viene chiamato per impostare i pulsanti numero di traccia. Il metodo viene chiamato anche ogni volta che il numero di pulsanti deve essere alterato per un cambiamento nel CD.

Quando l'utente preme il pulsante numero di traccia, il gestore degli eventi dei pulsanti, deve conoscere quale pulsante è stato premuto. updateTrackPanel() permette questo sottoclassando la classe DoAction:

for (int i = prev_n; i 
class TrackAction extends DoAction {
int track; 
public TrackAction(int i) { track = i; }
void invoke() { pickTrack(track); } 
} 
addButton(trackPanel,  
 new Button(Integer.toString(i + 1)),
 new TrackAction(i + 1)); 
} 
Ogni oggetto TrackAction è inizializzato con il numero di traccia a cui è associato.

Il resto del codice nella classe Program si riferisce alla meccanica del modo "riproduzione programmata". Questo include il passaggio del modo della track panel tra add (una traccia), del (una traccia), e play (riproduci immediatamente una traccia). La nuova caratteristica di Java introdotta da questo codice è l'uso della classe JDK Random per selezionare le tracce a caso nelle linee da 152 a 169.


Sommario


Questo conclude la mia descrizione sul funzionamento del CdPlayer. Ho iniziato questo esercizio per scoprire quante più cose possibili su Java, sia in termini di compilatori e tools, sia in termini di linguaggio e di librerie JDK. Ho scoperto che il linguaggio, le librerie ed i compilatori sono abbastanza stabili, per me, da scrivere programmi non banali, il codice completo di Jcd consiste in 3600 linee di Java e 450 linee di C. I compilatori funzionano ma sono lenti. Le librerie JDK sono abbastanza funzionali ed offrono più possibilità standard di quelle fornite originariamente con C od il C++. L'integrazione col C viene portata a termine facilmente. Xemacs mi ha fornito un ambiente di editing di Java veramente buono.


Risorse


Vedere il precedente articolo in Linux Gazette n. 28 per le referenze dettagliate.

Tar file contiene tutti i listati di questo articolo.

La mia home page contiene maggiori informazioni sul Jcd.

Jcd è disponibile su Sunsite.

La Linux Java page--un buon punto di partenza.


Per l'articolo originale: © 1998 Michael Hamilton
Pubblicato sul n. 29- Giugno 1998 di Linux Gazette
Per l'edizione italiana: © 1998 LGEI