Sei sulla pagina 1di 28

34

Creazione di un download
manager in Java

Se ci si collegati a Internet con una connessione telefonica, probabilmente si


sperimentata linterruzione di un download, con la necessit di ricominciare da
zero. Le disconnessioni della linea o i crash del computer possono interrompere
un download. Tuttavia, anche con una connessione ad alta velocit si possono
verificare interruzioni della trasmissione. Come minimo, il riavvio di un download pu far perdere tempo ed essere unesperienza frustrante.
Un fatto a volte sottovalutato che molti download interrotti si possono riprendere. Ci consente di riavviare il download dal punto in cui si interrotto
invece di iniziare di nuovo. Nel presente capitolo si sviluppa uno strumento dal
nome Download Manager che gestisce i download da Internet e semplicemente riprende i download interrotti. Consente inoltre di fermare e riprendere un
download e di gestire download multipli contemporaneamente.
Al centro dellutilit del Download Manager si trova la capacit di sfruttare lo
scaricamento di parti specifiche di un file. In uno scenario di download classico,
un intero file viene scaricato dallinizio alla fine. Se la trasmissione del file viene
interrotta per qualche motivo, si perde lavanzamento verso il completamento
del download. Il Download Manager, tuttavia, pu riprendere dal punto in cui
si verificata uninterruzione e scaricare solo la parte restante del file. Non tutti i
download vengono creati allo stesso modo e alcuni non possono semplicemente
essere ripresi. I dettagli sui file che non possono essere ripresi sono spiegati nel
prossimo paragrafo.
Non solo il Download Manager unutility utile, ma illustra anche in modo
eccellente la potenza e la concisione delle API incorporate di Java, in particolare
per linterfacciamento a Internet. Poich Internet stata una delle forze dietro la
2012, McGraw-Hill

40 Capitolo 34

creazione di Java, non sorprende che le capacit di rete di Java siano insuperabili.
Per esempio, il tentativo di creare il Download Manager in un altro linguaggio,
quale C++, comporterebbe molti pi problemi e lavoro.

34.1 I download da Internet


Per capire e apprezzare il Download Manager necessario capire come funzionano i download da Internet.
Nella loro forma pi semplice i download da Internet sono semplicemente
transazioni client/server. Il client, il browser, richiede di scaricare un file da un
server su Internet. Il server risponde inviando il file richiesto al browser. Affinch
i client possano comunicare con i server, devono avere un protocollo definito. I
protocolli pi utilizzati per scaricare file sono File Transfer Protocol (FTP, protocollo di trasferimento di file) e Hypertext Transfer Protocol (HTTP, protocollo di
trasferimento di ipertesti). Solitamente FTP associato allo scambio di file tra
computer, mentre HTTP associato specificatamente al trasferimento di pagine
web e dei relativi file (grafiche, suoni e cos via). Nel tempo, con la crescita di popolarit del World Wide Web, HTTP diventato il protocollo dominante per lo
scaricamento di file da Internet, ma FTP non completamente estinto.
Per motivi di spazio il Download Manager sviluppato nel presente capitolo
supporta solo download HTTP. Tuttavia laggiunta di supporto per FTP un
ottimo esercizio per lestensione del codice. I download HTTP ha due forme: riprendibile (HTTP 1.1) e non riprendibile (HTTP 1.0). La differenza tra le due
forme sta nel modo in cui possibile richiedere i file dai server. Con il vecchio
HTTP 1.0, un client pu richiedere solo che un server invii un file, mentre con
HTTP 1.1 un client pu richiedere che un server invii un file completo o solo una
parte specifica di un file. Si tratta della caratteristica su cui creato il Download
Manager.

34.2 Una panoramica sul Download Manager


Il Download Manager utilizza una semplice ma efficace interfaccia GUI realizzata con le librerie Swing di Java. La finestra del Download Manager mostrata
nella Figura 34.1. Lutilizzo di Swing fornisce allinterfaccia un aspetto moderno.
La GUI mantiene un elenco di download gestiti. Ogni download nellelenco indica lURL, le dimensioni del file in byte, lavanzamento come percentuale
verso il completamento e lo stato corrente. I download si possono trovare in uno
degli stati indicati di seguito: Downloading (in scaricamento), Paused (in pausa), Complete (completo), Error (errore) o Cancelled (annullato). La GUI dispone
inoltre di controlli per aggiungere download allelenco e per modificare lo stato
di ogni download nellelenco. Quando si seleziona un download nellelenco, secondo lo stato corrente pu essere messo fermato, ripreso, annullato o eliminato
dallelenco.
Il Download Manager suddiviso in classi per la separazione naturale dei
componenti funzionali: Download, DownloadsTableModel, ProgressRenderer e
DownloadManager. La classe DownloadManager responsabile dellinterfaccia
GUI e utilizza le classi DownloadsTableModel e ProgressRenderer per la visua 2012, McGraw-Hill

Creazione di un download manager in Java 41

lizzazione dellelenco di download correnti. La classe Download rappresenta un


download gestito ed responsabile dello scaricamento di un file. Nei paragrafi
di seguito si esamina in dettaglio ogni classe, evidenziandone il funzionamento e
spiegandone il rapporto con le altre.

Figura 34.1 Linterfaccia GUI del Download Manager.

La classe Download il cavallo da tiro del Download Manager. Lo scopo primario scaricare un file e salvarne i contenuti nel disco. Ogni volta che si aggiunge un nuovo download al Download Manager, viene creato un nuovo oggetto
Download per gestirlo.
Il Download Manager in grado di scaricare pi file contemporaneamente.
Per farlo necessario che ogni download sia eseguito in modo indipendente.
inoltre necessario che ogni singolo download gestisca il proprio stato in modo
che possa essere riflesso nella GUI. Ci si ottiene con la classe Download.
Il codice completo per Download mostrato di seguito. Si noti che estende
Observable e implementa Runnable. Ogni parte si esamina in dettaglio nei paragrafi successivi.
import java.io.*;
import java.net.*;
import java.util.*;
// Questa classe scarica un file da un URL.
class Download extends Observable implements Runnable {
// Dimensioni max del buffer di download.
private static final int MAX_BUFFER_SIZE = 1024;
// Questi sono i nomi degli stati.
2012, McGraw-Hill

PARTE IV

34.3 La classe Download

42 Capitolo 34

public static final String STATUSES[] = {Downloading,


Paused, Complete, Cancelled, Error};
// Questi sono i codici degli stati.
public static final int DOWNLOADING = 0;
public static final int PAUSED = 1;
public static final int COMPLETE = 2;
public static final int CANCELLED = 3;
public static final int ERROR = 4;
private URL url; // URL di download
private int size; // dimensioni del download in byte
private int downloaded; // numero di byte scaricati
private int status; // stato corrente del download
// Costruttore per Download.
public Download(URL url) {
this.url = url;
size = -1;
downloaded = 0;
status = DOWNLOADING;
// Inizia il download.
download();
}
// Otiene lURL del download.
public String getUrl() {
return url.toString();
}
// Otiene le dimensioni del download.
public int getSize() {
return size;
}
// Ottiene lavanzamento del download.
public float getProgress() {
return ((float) downloaded / size) * 100;
}
// Ottiene lo stato del download.
public int getStatus() {
return status;
}
// Ferma il download.
public void pause() {
status = PAUSED;
stateChanged();
}
2012, McGraw-Hill

Creazione di un download manager in Java 43

// Riprende il download.
public void resume() {
status = DOWNLOADING;
stateChanged();
download();
}
// Annulla il download.
public void cancel() {
status = CANCELLED;
stateChanged();
}
// Contrassegna il download come con errore.
private void error() {
status = ERROR;
stateChanged();
}
// Avvia o riprende il download.
private void download() {
Thread thread = new Thread(this);
thread.start();
}

PARTE IV

// Ottiene la parte di nome di file dellURL.


private String getFileName(URL url) {
String fileName = url.getFile();
return fileName.substring(fileName.lastIndexOf(/) + 1);
}
// Scarica il file.
public void run() {
RandomAccessFile file = null;
InputStream stream = null;
try {
// Apre connessione verso lURL.
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();
// Specifica la parte di file da scaricare.
connection.setRequestProperty(Range,
bytes= + downloaded + -);
// Si connette al server.
connection.connect();
// Verifica che il codice di risposta sia nellintervallo 200.
2012, McGraw-Hill

44 Capitolo 34

if (connection.getResponseCode() / 100 != 2) {
error();
}
// Controlla la validit della lunghezza dei contenuti.
int contentLength = connection.getContentLength();
if (contentLength < 1) {
error();
}
/* Imposta le dimensioni per il download se
non sono gi impostate. */
if (size == -1) {
size = contentLength;
stateChanged();
}
// Apre il file e ne cerca la fine.
file = new RandomAccessFile(getFileName(url), rw);
file.seek(downloaded);
stream = connection.getInputStream();
while (status == DOWNLOADING) {
/* Dimensiona il buffer secondo la quantit di
file restata da scaricare. */
byte buffer[];
if (size - downloaded > MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[size - downloaded];
}
// Legge dal server nel buffer.
int read = stream.read(buffer);
if (read == -1)
break;
// Scrive il buffer nel file.
file.write(buffer, 0, read);
downloaded += read;
stateChanged();
}
/* Cambia lo stato in completo se questo punto stato
raggiunto perch finito il download. */
if (status == DOWNLOADING) {
status = COMPLETE;
2012, McGraw-Hill

Creazione di un download manager in Java 45

stateChanged();
}
} catch (Exception e) {
error();
} finally {
// Chiude il file.
if (file != null) {
try {
file.close();
} catch (Exception e) {}
}
// Chiude la connessine al server.
if (stream != null) {
try {
stream.close();
} catch (Exception e) {}
}

Le variabili di Download
Download inizia dichiarando numerose variabili static final che specificano le
diverse costanti utilizzate dalla classe, quindi sono dichiarate quattro variabili di
istanza. La variabile url contiene lURL Internet per il file da scaricare, la variabile
size contiene le dimensioni del file da scaricare in byte; la variabile downloaded
contiene il numero di byte scaricati e la variabile status indica lo stato corrente
del download.

Il costruttore di Download
Al costruttore di Download si passa un riferimento allURL da scaricare sotto
forma di un oggetto URL, assegnato alla variabile di istanza url. Si impostano
quindi le variabili di istanza restanti sui relativi stati iniziali e si chiama il metodo download(). Si noti che size impostata su -1 a indicare che non sono ancora
presenti le dimensioni.

Il metodo download()
Il metodo download() crea un nuovo oggetto Thread, passando ad esso un riferimento allistanza di Download chiamante. Come detto in precedenza, necessario che ogni download sia eseguito in modo indipendente. Affinch la classe
2012, McGraw-Hill

PARTE IV

}
}
// Segnala agli osservatori che lo stato del download cambiato.
private void stateChanged() {
setChanged();
notifyObservers();
}
}

46 Capitolo 34

Download agisca da sola, deve essere eseguita nel proprio thread. Java dispone di un eccellente supporto incorporato per i thread e ne facilita lutilizzo. Per
utilizzare i thread, la classe Download implementa semplicemente linterfaccia
Runnable ridefinendo il metodo run(). Quando il metodo download() ha creato
una nuova istanza di Thread, passando al costruttore la classe Download Runnable, chiama il metodo start() del thread. La chiamata al metodo start() fa in modo
che sia eseguito il metodo run() dellistanza Runnable (della classe Download).

Il metodo run()
Quando viene eseguito il metodo run(), inizia il download effettivo. A causa delle dimensioni e dellimportanza di tale metodo, viene esaminato in dettaglio. Il
metodo run() inizia con le righe mostrate di seguito:
RandomAccessFile file = null;
InputStream stream = null;
try {
// Apre connessione verso lURL.
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();

run() imposta le variabili per il flusso di rete da cui vengono letti i contenuti
del download e imposta il file in cui si scrivono i contenuti del download. Quindi
apre una connessione verso lURL del download chiamando url.openConnection(). Poich noto che il Download Manager supporta solo download HTTP, la
connessione viene convertita nel tipo HttpURLConnection. La conversione della
connessione in HttpURLConnection consente di sfruttare le funzioni di connessione specifiche per HTTP quali il metodo getResponseCode(). Si noti che la chiamata a url.openConnection() non crea una connessione verso il server dellURL;
crea semplicemente una nuova istanza URLConnection associata allURL che in
seguito verr utilizzato per la connessione al server.
Dopo la creazione di HttpURLConnection, si imposta la propriet di richiesta
della connessione chiamando connection.setRequestProperty(), come mostrato
di seguito:
// Specifica la parte di file da scaricare.
connection.setRequestProperty(Range,
bytes= + downloaded + -);

Limpostazione delle propriet di richiesta consente di inviare informazioni extra sulla richiesta al server da cui proviene il download. In questo caso si
imposta la propriet Range. molto importante, poich la propriet Range
specifica lintervallo di byte richiesti per il download dal server. Normalmente
vengono scaricati tutti i byte del file in una sola volta. Tuttavia, se un download
stato interrotto o fermato, si devono recuperare solo i byte restanti del download. Limpostazione della propriet Range la base del funzionamento del
Download Manager.
2012, McGraw-Hill

Creazione di un download manager in Java 47

La propriet Range specificata come di seguito:


byte iniziale-byte finale

Per esempio, 0 - 12345. Tuttavia il byte finale dellintervallo opzionale. Se


il byte finale assente, lintervallo termina alla fine del file. Il metodo run() non
specifica mai il byte finale perch i download devono essere eseguiti fino allo
scaricamento dellintero intervallo, se non vengono messi in pausa o interrotti.
Le righe successive sono mostrate di seguito:

Viene chiamato il metodo connection.connect() per effettuare la connessione al server di download. Quindi si controlla il codice di risposta restituito dal
server. Il protocollo HTTP ha un elenco di codici di risposta che indicano la risposta di un server a una richiesta. I codici di risposta HTTP sono organizzati
in intervalli numerici di cento in cento e lintervallo 200 indica il successo. Si
controlla che codice di risposta del server sia nellintervallo 200 chiamando connection.getResponseCode() e dividendolo per 100. Se il valore della divisione
2, la connessione stata effettuata con successo.
Quindi run() ottiene la lunghezza del contenuto chiamando connection.getContentLength(). La lunghezza del contenuto rappresenta il numero di byte nel
rile richiesto. Se la lunghezza del contenuto minore di 1, viene chiamato il metodo error(), che aggiorna lo stato del download su ERROR, quindi chiama stateChanged(). Il metodo stateChanged() descritto in dettaglio pi avanti.
Dopo avere ottenuto la lunghezza del contenuto, il codice di seguito controlla
se gi stato assegnato alla variabile size:
/* Imposta le dimensioni per il download se
non sono gi impostate. */
if (size == -1) {
size = contentLength;
stateChanged();
}

2012, McGraw-Hill

PARTE IV

// Si connette al server.
connection.connect();
// Verifica che il codice di risposta sia nellintervallo
200.
if (connection.getResponseCode() / 100 != 2) {
error();
}
// Controlla la validit della lunghezza dei contenuti.
int contentLength = connection.getContentLength();
if (contentLength < 1) {
error();
}

48 Capitolo 34

Come si pu osservare, invece di assegnare la lunghezza del contenuto alla variabile size in modo incondizionato, viene assegnata solo se non ha gi un valore.
Il motivo che la lunghezza del contenuto riflette il numero di byte che il server
invier. Se si specifica un elemento diverso da un inizio di intervallo basato su
0, la lunghezza del contenuto rappresenta solo una parte delle dimensioni del
file. La variabile size deve essere impostata sulle dimensioni complete del file di
download.
Le righe di codice successive mostrate di seguito creano un nuovo RandomAccessFile utilizzando la parte di nome di file dellURL del download recuperato
con una chiamata al metodo getFileName():
// Apre il file e ne cerca la fine.
file = new RandomAccessFile(getFileName(url), rw);
file.seek(downloaded);

Il RandomAccessFile viene aperto in modalit rw, che specifica che possibile leggere il file e scrivere in esso. Quando il file aperto, run() cerca la fine
del file chiamando il metodo file.seek(), passando la variabile downloaded. Ci
indica al file di posizionarsi sul numero di byte scaricati, in altre parole alla fine.
necessario posizionare il file alla fine nel caso in cui un download sia stato ripreso. Se si riprende un download, i byte scaricati vengono aggiunti alla fine del
file e non sovrascrivono i byte scaricati in precedenza. Dopo la preparazione del
file di output, si ottiene un gestore del flusso di rete verso il server aperto chiamando connection.getInputStream(), come mostrato di seguito:
stream = connection.getInputStream();
Inizia quindi il centro dellazione con un ciclo while:
while (status == DOWNLOADING) {
/* Dimensiona il buffer secondo la quantit di
file restata da scaricare. */
byte buffer[];
if (size - downloaded > MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[size - downloaded];
}
// Legge dal server nel buffer.
int read = stream.read(buffer);
if (read == -1)
break;
// Scrive il buffer nel file.
file.write(buffer, 0, read);
downloaded += read;
stateChanged();
}
2012, McGraw-Hill

Creazione di un download manager in Java 49

Il ciclo viene impostato per essere eseguito fino a quando la variabile status
diversa da DOWNLOADING. Nel ciclo si crea un array di buffer byte per contenere i byte da scaricare. Il buffer dimensionato secondo la quantit di download
restante per il completamento. Se si deve scaricare una quantit di byte maggiore
di MAX_BUFFER_SIZE, per dimensionare il buffer si utilizza MAX_BUFFER_
SIZE, altrimenti il buffer viene dimensionato sul numero di byte ancora da scaricare. Quando il buffer stato dimensionato correttamente, ha luogo il download
con una chiamata a stream.read(). La chiamata legge byte dal server e li inserisce
nel buffer, restituendo il conteggio dei byte effettivamente letti. Se il numero di
byte letti uguale a -1, il download completo e si esce dal ciclo, altrimenti il
download non terminato e i byte letti vengono scritti su disco con una chiamata
a file.write(). Quindi si aggiorna la variabile downloaded, in modo da riflettere
il numero di byte scaricati. Infine, allinterno del ciclo, si chiama il metodo stateChanged(). Ulteriori dettagli pi avanti.
Dopo luscita dal ciclo, il codice di seguito controlla perch si usciti dal ciclo:
/* Cambia lo stato in completo se questo punto stato
raggiunto perch finito il download. */
if (status == DOWNLOADING) {
status = COMPLETE;
stateChanged();
}

PARTE IV

Se lo stato del download ancora DOWNLOADING, significa che si usciti


dal ciclo perch il download stato completato, altrimenti si usciti dal ciclo perch cambiato lo stato del download in un valore diverso da DOWNLOADING.
Il metodo run() finisce con i blocchi catch e finally mostrati di seguito:
} catch (Exception e) {
error();
} finally {
// Chiude il file.
if (file != null) {
try {
file.close();
} catch (Exception e) {}
}
// Chiude la connessine al server.
if (stream != null) {
try {
stream.close();
} catch (Exception e) {}
}
}

Se durante il processo di scaricamento viene lanciata uneccezione, il blocco


catch rileva leccezione e chiama il metodo error(). Il blocco finally garantisce che
2012, McGraw-Hill

50 Capitolo 34

se le connessioni file e stream sono state aperte, vengano chiuse, che sia stata lanciata uneccezione o no. Come esercizio, si pu provare a modificare il codice in
modo da utilizzare la nuova dichiarazione try con risorse per gestire tali risorse.

Il metodo stateChanged()
Affinch il Download Manager visualizzi informazioni aggiornate sui download che gestisce, deve sapere quando cambiano le informazioni su un download.
Per gestire tale situazione si utilizza lo schema di design Observer. Lo schema
Observer analogo a una mailing list di annunci in cui le persone si registrano
per ricevere annunci. Ogni volta che esiste un nuovo annuncio, tutte le persone
nellelenco ricevono un messaggio con lannuncio. Nel caso dello schema Observer esiste una classe osservata presso cui le classi osservatrici possono registrarsi
per ricevere notifiche di modifiche.
La classe Download utilizza lo schema Observer estendendo la classe di utility
incorporata Observable di Java. Lestensione della classe Observable consente
alle classi che implementano linterfaccia Observer di Java di registrarsi presso
la classe Download per ricevere notifiche di modifiche. Ogni volta che la classe
Download deve notificare un cambiamento agli Observer registrati, viene chiamato il metodo stateChanged(). Il metodo stateChanged() chiama prima il metodo setChanged() della classe Observable per contrassegnare la classe come modificata. Quindi il metodo stateChanged() chiama il metodo notifyObservers()
di Observable, che trasmette la notifica di cambiamento agli Observer registrati.

Metodi di azione e accessori


La classe Download dispone di numerosi metodi di azione e accessori per controllare un download e ottenere dati da esso. Ogni metodo di azione pause(),
resume() e cancel() fa semplicemente ci che implica il nome: ferma, riprende o
annulla il download, rispettivamente. Analogamente, il metodo error() contrassegna il download come con un errore. I metodi accessori getUrl(), getSize(),
getProgress() e getStatus() restituiscono i rispettivi valori correnti.

34.4 La classe ProgressRenderer


La classe ProgressRenderer una piccola classe di utility utilizzata per visualizzare lavanzamento corrente di un download elencato nellistanza JTable
Downloads della GUI. Normalmente unistanza JTable visualizza i dati di ogni
cella come testo. Tuttavia, spesso particolarmente utile visualizzare i dati di
una cella in modo diverso dal testo. Nel caso del Download Manager si desidera
visualizzare ogni cella della colonna Progress della tabella come una barra di
avanzamento. La classe ProgressRenderer mostrata di seguito lo rende possibile.
Si noti che estende JProgressBar e implementa TableCellRenderer.
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
// Questa classe visualizza una JProgressBar in una cella in tabella.
2012, McGraw-Hill

Creazione di un download manager in Java 51

class ProgressRenderer extends JProgressBar


implements TableCellRenderer
{
// Costruttore per ProgressRenderer.
public ProgressRenderer(int min, int max) {
super(min, max);
}

La classe ProgressRenderer sfrutta il fatto che la classe JTable di Swing ha un


sistema di rendering in grado di accettare plug-in per la visualizzazione di
celle di tabella. Per inserire tale sistema di rendering, la classe ProgressRenderer
deve implementare linterfaccia TableCellRenderer di Swing. Quindi necessario registrare unistanza di ProgressRenderer presso unistanza di JTable; in tal
modo indica allistanza JTable quali celle deve visualizzare con il plug-in.
Limplementazione dellinterfaccia TableCellRenderer richiede che la classe
ridefinisca il metodo getTableCellRendererComponent(), chiamato ogni volta
che unistanza JTable deve visualizzare una cella per cui la classe stata registrata. Al metodo si passano numerose variabili, ma in questo caso si utilizza solo
la variabile value, che contiene i dati per la cella da visualizzare e viene passata
al metodo setValue() di JProgressBar. Il metodo getTableCellRendererComponent() termina restituendo un riferimento alla relativa classe. Funziona perch
la classe ProgressRenderer una sottoclasse di JProgressBar, discendente della
classe AWT Component.

34.6 La classe DownloadsTableModel


La classe DownloadsTableModel contiene lelenco di download del Download
Manager ed lorigine di dati che supporta listanza di JTable Downloads della
GUI. La classe DownloadsTableModel mostrata di seguito. Si noti che estende
AbstractTableModel e implementa linterfaccia Observer.
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
2012, McGraw-Hill

PARTE IV

/* Restituisce questa JProgressBar come visualizzatore


per la cella di tabella data. */
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
// Imposta il valore di completamento in percentuale di
JProgressBar.
setValue((int) ((Float) value).floatValue());
return this;
}
}

52 Capitolo 34

// Questa classe gestisce i dati della tabella di download.


class DownloadsTableModel extends AbstractTableModel
implements Observer
{
// Questi sono i nomi delle colonne della tabella.
private static final String[] columnNames = {URL, Size,
Progress, Status};
// Queste sono le classi per i valori di ogni colonna.
private static final Class[] columnClasses = {String.class,
String.class, JProgressBar.class, String.class};
// Lelenco di download della tabella.
private ArrayList<Download> downloadList = new
ArrayList<Download>();
// Aggiunge un nuovo download alla tabella.
public void addDownload(Download download) {
// Si registra per essere avvisto quando cambia il download.
download.addObserver(this);
downloadList.add(download);
// Invia alla tabella notifica di inserimento di riga.
fireTableRowsInserted(getRowCount() - 1, getRowCount()1);
}
// Ottiene un download per la riga specificata.
public Download getDownload(int row) {
return downloadList.get(row);
}
// Elimina un download dallelenco.
public void clearDownload(int row) {
downloadList.remove(row);
// Invia alla tabella notifica di eliminazione di riga.
fireTableRowsDeleted(row, row);
}
// Ottiene conteggio di colonne della tabella.
public int getColumnCount() {
return columnNames.length;
}
// Ottiene il nome di una colonna.
public String getColumnName(int col) {
2012, McGraw-Hill

Creazione di un download manager in Java 53

return columnNames[col];
}
// Ottiene la classe di una colonna.
public Class<?> getColumnClass(int col) {
return columnClasses[col];
}
// Ottiene conteggio di righe della tabella.
public int getRowCount() {
return downloadList.size();
}

/* Update viene chiamato quando un Download informa i suoi


osservatori di modifiche */
public void update(Observable o, Object arg) {
int index = downloadList.indexOf(o);
// Invia alla tabella notifica di aggiornamento di riga.
fireTableRowsUpdated(index, index);
}
}

In sostanza la classe DownloadsTableModel una classe di utility utilizzata


dallistanza JTable Downloads per gestire i dati nella tabella. Quando si inizializza listanza JTable, si passa unistanza DownloadsTableModel. JTable chiama
quindi numerosi metodi sullistanza DownloadsTableModel per la compilazione della tabella. Il metodo getColumnCount() viene chiamato per recuperare il
numero di colonne nella tabella. Analogamente, getRowCount() utilizzato per
recuperare il numero di righe nella tabella. Il metodo getColumnName() restituisce il nome di una colonna dato lID. Il metodo getDownload() prende un ID
2012, McGraw-Hill

PARTE IV

// Ottiene valore per una combinazione specifica di riga e


di colonna.
public Object getValueAt(int row, int col) {
Download download = downloadList.get(row);
switch (col) {
case 0: // URL
return download.getUrl();
case 1: // Dimensioni
int size = download.getSize();
return (size == -1) ? : Integer.toString(size);
case 2: // Avanzamento
return new Float(download.getProgress());
case 3: // Stato
return Download.STATUSES[download.getStatus()];
}
return ;
}

54 Capitolo 34

di riga e restituisce loggetto Download associato dallelenco. Gli altri metodi


della classe DownloadsTableModel, pi complessi, sono descritti in dettaglio nei
paragrafi successivi.

Il metodo addDownload()
Il metodo addDownload(), mostrato di seguito, aggiunge un nuovo oggetto
Download allelenco di download gestiti, quindi aggiunge una riga nella tabella:
// Aggiunge un nuovo download alla tabella.
public void addDownload(Download download) {
// Si registra per essere avvisto quando cambia il download.
download.addObserver(this);
downloadList.add(download);
// Invia alla tabella notifica di inserimento di riga.
fireTableRowsInserted(getRowCount() - 1, getRowCount() - 1);
}

Il metodo si registra presso il nuovo Download come Observer interessato a


ricevere notifiche di cambiamenti. Quindi il Download viene aggiunto allelenco
interno di download gestiti. Infine viene inviata una notifica di evento di inserimento di riga nella tabella per indicare alla tabella che stata aggiunta una nuova
riga.

Il metodo clearDownload()
Il metodo clearDownload(), mostrato di seguito, elimina un Download dallelenco di download gestiti:
// Elimina un download dallelenco.
public void clearDownload(int row) {
downloadList.remove(row);
// Invia alla tabella notifica di eliminazione di riga.
fireTableRowsDeleted(row, row);
}

Dopo avere eliminato il Download dallelenco interno, viene inviata una notifica di eliminazione di riga di tabella per indicare alla tabella che stata eliminata
una riga.

Il metodo getColumnClass()
Il metodo getColumnClass(), mostrato di seguito, restituisce il tipo di classe per
i dati visualizzati nella colonna specificata:
// Ottiene la classe di una colonna.
public Class<?> getColumnClass(int col) {
return columnClasses[col];
}
2012, McGraw-Hill

Creazione di un download manager in Java 55

Tutte le colonne sono visualizzate come testo (vale a dire oggetti String) ad eccezione della colonna Progress, visualizzata come barra di avanzamento (un oggetto di tipo JProgressBar).

Il metodo getValueAt()
Il metodo getValueAt(), mostrato di seguito, viene chiamato per ottenere il valore corrente da visualizzare per ogni cella della tabella:
// Ottiene valore per una combinazione specifica di riga e di
colonna.
public Object getValueAt(int row, int col) {
Download download = downloadList.get(row);
switch (col) {
case 0: // URL
return download.getUrl();
case 1: // Dimensioni
int size = download.getSize();
return (size == -1) ? : Integer.toString(size);
case 2: // Avanzamento
return new Float(download.getProgress());
case 3: // Stato
return Download.STATUSES[download.getStatus()];
}
return ;
}

Il metodo update()
Il metodo update() mostrato di seguito. Adempie al contratto con linterfaccia
Observer consentendo alla classe DownloadsTableModel di ricevere notifiche
dagli oggetti Download quando cambiano.
/* Update viene chiamato quando un Download informa i suoi
osservatori di modifiche */
public void update(Observable o, Object arg) {
int index = downloadList.indexOf(o);
// Invia alla tabella notifica di aggiornamento di riga.
fireTableRowsUpdated(index, index);
}

Al metodo si passa un riferimento al Download cambiato, sotto forma di oggetto Observable. Quindi si cerca un indice verso tale download nellelenco dei
download e si utilizza tale indice per inviare una notifica di evento di aggiornamento di riga di tabella, che indica alla tabella che la riga data stata modificata.
La tabella visualizza la riga con lindice dato, riflettendo i nuovi valori.
2012, McGraw-Hill

PARTE IV

Il metodo prima cerca il Download corrispondente alla riga specificata, quindi


si utilizza la colonna specificata per determinare quale valore delle propriet di
Download restituire.

56 Capitolo 34

34.6 La classe DownloadManager


Gettate le basi con la spiegazione di tutte le classi helper del Download Manager,
possibile esaminare la classe DownloadManager, responsabile della creazione e dellesecuzione della GUI del Download Manager. La classe dispone di un
metodo main(), il primo dichiarato durante lesecuzione. Il metodo main() crea
una nuova istanza della classe DownloadManager e chiama il relativo metodo
show(), che la visualizza.
La classe DownloadManager mostrata di seguito. Si noti che estende JFrame
e implementa Observer. I paragrafi di seguito la esaminano in dettaglio.
import
import
import
import
import
import

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

// Il Download Manager.
public class DownloadManager extends JFrame
implements Observer
{
// Aggiunge i campi di testo per i download.
private JTextField addTextField;
// Il modello di dati della tabella di download.
private DownloadsTableModel tableModel;
// Tabella che elenca i download.
private JTable table;
// Questi sono i pulsanti per la gestione del download selezionato.
private JButton pauseButton, resumeButton;
private JButton cancelButton, clearButton;
// Download selezionato.
private Download selectedDownload;
// Flag per sapere se la selezione di tabella deve essere
annullata.
private boolean clearing;
// Costruttore per Download Manager.
public DownloadManager()
{
// Imposta titolo dellapplicazione.
setTitle(Download Manager);
2012, McGraw-Hill

Creazione di un download manager in Java 57

// Imposta dimensioni della finestra.


setSize(640, 480);
// Gestisce eventi di chiusura della finestra.
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
actionExit();
}
});

// Imposta pannello di aggiunta.


JPanel addPanel = new JPanel();
addTextField = new JTextField(30);
addPanel.add(addTextField);
JButton addButton = new JButton(Add Download);
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionAdd();
}
});
addPanel.add(addButton);
// Imposta tabella di download.
tableModel = new DownloadsTableModel();
table = new JTable(tableModel);
table.getSelectionModel().addListSelectionListener(new
ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
tableSelectionChanged();
}
});
// Consente di selezionare una sola riga per volta.
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
2012, McGraw-Hill

PARTE IV

// Imposta il menu di file.


JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu(File);
fileMenu.setMnemonic(KeyEvent.VK_F);
JMenuItem fileExitMenuItem = new JMenuItem(Exit,
KeyEvent.VK_X);
fileExitMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionExit();
}
});
fileMenu.add(fileExitMenuItem);
menuBar.add(fileMenu);
setJMenuBar(menuBar);

58 Capitolo 34

// Imposta ProgressBar come visualizzatore per colonna di


avanzamento.
ProgressRenderer renderer = new ProgressRenderer(0, 100);
renderer.setStringPainted(true); // mostra testo avanzamento
table.setDefaultRenderer(JProgressBar.class, renderer);
// Imposta altezza riga di tabella sufficiente per contenere JProgressBar.
table.setRowHeight(
(int) renderer.getPreferredSize().getHeight());
// Imposta pannello di download.
JPanel downloadsPanel = new JPanel();
downloadsPanel.setBorder(
BorderFactory.createTitledBorder(Downloads));
downloadsPanel.setLayout(new BorderLayout());
downloadsPanel.add(new JScrollPane(table),
BorderLayout.CENTER);
// Imposta pannello di pulsanti.
JPanel buttonsPanel = new JPanel();
pauseButton = new JButton(Pause);
pauseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionPause();
}
});
pauseButton.setEnabled(false);
buttonsPanel.add(pauseButton);
resumeButton = new JButton(Resume);
resumeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionResume();
}
});
resumeButton.setEnabled(false);
buttonsPanel.add(resumeButton);
cancelButton = new JButton(Cancel);
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionCancel();
}
});
cancelButton.setEnabled(false);
buttonsPanel.add(cancelButton);
clearButton = new JButton(Clear);
clearButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionClear();
}

2012, McGraw-Hill

Creazione di un download manager in Java 59

});
clearButton.setEnabled(false);
buttonsPanel.add(clearButton);
// Aggiunge pannelli da visualizzare.
getContentPane().setLayout(new BorderLayout());
getContentPane().add(addPanel, BorderLayout.NORTH);
getContentPane().add(downloadsPanel, BorderLayout.CENTER);
getContentPane().add(buttonsPanel, BorderLayout.SOUTH);
}
// Esce dal programma.
private void actionExit() {
System.exit(0);
}

// Verifica URL di download.


private URL verifyUrl(String url) {
// Consente solo URL HTTP.
if (!url.toLowerCase().startsWith(http://))
return null;
// Verifica formato dellURL.
URL verifiedUrl = null;
try {
verifiedUrl = new URL(url);
} catch (Exception e) {
return null;
}
// Verifica che lURL specifichi un file.
if (verifiedUrl.getFile().length() < 2)
return null;
return verifiedUrl;
}
2012, McGraw-Hill

PARTE IV

// Aggiunge un nuovo download.


private void actionAdd() {
URL verifiedUrl = verifyUrl(addTextField.getText());
if (verifiedUrl != null) {
tableModel.addDownload(new Download(verifiedUrl));
addTextField.setText(); // ripristina campo di testo
di aggiunta
} else {
JOptionPane.showMessageDialog(this,
Invalid Download URL, Error,
JOptionPane.ERROR_MESSAGE);
}
}

60 Capitolo 34

// Chiamato quando cambia selezione di riga di tabella.


private void tableSelectionChanged() {
/* Annulla registrazione per ricezione notifiche
dallultimo download selezionato. */
if (selectedDownload != null)
selectedDownload.deleteObserver(DownloadManager.this);
/* Se non si trova nellannullamento di un download,
imposta download selezionato e si registra per
ricevere notifiche da esso. */
if (!clearing && table.getSelectedRow() > -1) {
selectedDownload =
tableModel.getDownload(table.getSelectedRow());
selectedDownload.addObserver(DownloadManager.this);
updateButtons();
}
}
// Ferma il download selezionato.
private void actionPause() {
selectedDownload.pause();
updateButtons();
}
// Riprende il download selezionato.
private void actionResume() {
selectedDownload.resume();
updateButtons();
}
// Annulla il download selezionato.
private void actionCancel() {
selectedDownload.cancel();
updateButtons();
}
// Svuota il download selezionato.
private void actionClear() {
clearing = true;
tableModel.clearDownload(table.getSelectedRow());
clearing = false;
selectedDownload = null;
updateButtons();
}
/* Aggiorna lo stato di ogni pulsante in base allo
stato del download selezionato. */
private void updateButtons() {
if (selectedDownload != null) {
int status = selectedDownload.getStatus();
2012, McGraw-Hill

switch (status) {
case Download.DOWNLOADING:
pauseButton.setEnabled(true);
resumeButton.setEnabled(false);
cancelButton.setEnabled(true);
clearButton.setEnabled(false);
break;
case Download.PAUSED:
pauseButton.setEnabled(false);
resumeButton.setEnabled(true);
cancelButton.setEnabled(true);
clearButton.setEnabled(false);
break;
case Download.ERROR:
pauseButton.setEnabled(false);
resumeButton.setEnabled(true);
cancelButton.setEnabled(false);
clearButton.setEnabled(true);
break;
default: // COMPLETE o CANCELLED
pauseButton.setEnabled(false);
resumeButton.setEnabled(false);
cancelButton.setEnabled(false);
clearButton.setEnabled(true);
}
} else {
// Nella tabella non selezionato alcun download.
pauseButton.setEnabled(false);
resumeButton.setEnabled(false);
cancelButton.setEnabled(false);
clearButton.setEnabled(false);
}
}
/* Update viene chiamato quando un Download informa i suoi
osservatori di modifiche. */
public void update(Observable o, Object arg) {
// Aggiorna pulsanti se il download cambiato.
if (selectedDownload != null && selectedDownload.
equals(o))
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateButtons();
}
});
}
// Esegue il Download Manager.
public static void main(String[] args) {
2012, McGraw-Hill

PARTE IV

Creazione di un download manager in Java 61

62 Capitolo 34

SwingUtilities.invokeLater(new Runnable() {
public void run() {
DownloadManager manager = new DownloadManager();
manager.setVisible(true);
}
});
}
}

Le variabili di DownloadManager
DownloadManager inizia dichiarando numerose variabili di istanza, molte delle
quali contengono riferimenti ai controlli della GUI. La variabile selectedDownload contiene un riferimento alloggetto Download rappresentato dalla riga selezionata nella tabella. Infine la variabile di istanza clearing una flag boolean che
indica se un download in fase di eliminazione dalla tabella Downloads.

Il costruttore di DownloadManager
Quando si crea unistanza di DownloadManager, tutti i controlli della GUI vengono inizializzati allinterno del costruttore. Il costruttore contiene molto codice,
in gran parte semplice. La descrizione di seguito fornisce una panoramica.
In primo luogo si imposta il titolo della finestra con una chiamata a setTitle().
Quindi la chiamata a setSize() definisce laltezza e la larghezza della finestra in
pixel. In seguito si aggiunge un rilevatore di finestra chiamando addWindowListener(), passando un oggetto WindowAdapter che ridefinisce il gestore di eventi windowClosing(). Il gestore chiama il metodo actionExit() quando viene chiusa la finestra dellapplicazione. Si aggiunge alla finestra dellapplicazione una
barra del menu con un menu File. Quindi si imposta il pannello add, con
il campo di testo e il pulsante. Si aggiunge un ActionListener al pulsante Add
Download in modo che il metodo actionAdd() sia chiamato ogni volta che si fa
clic sul pulsante.
In seguito viene costruita la tabella dei download. Si aggiunge alla tabella un
ListSelectionListener, in modo che ogni volta che si seleziona una riga nella tabella sia chiamato il metodo tableSelectionChanged(). Viene inoltre aggiornata la
modalit di selezione della tabella in ListSelectionModel.SINGLE_SELECTION,
in modo che sia possibile selezionare una sola riga per volta. La limitazione della
selezione di righe a una sola riga semplifica la logica per determinare i pulsanti
da attivare nella GUI quando viene selezionata una riga nella tabella di download. Quindi si istanzia un oggetto della classe ProgressRenderer e si registra nella
tabella per gestire la colonna Progress. Laltezza della riga di tabella si aggiorna
secondo laltezza di ProgressRenderer chiamando table.setRowHeight(). Dopo
la creazione e la regolazione della tabella, questa viene inserita in un JScrollPane
affinch si possa scorrere in essa e viene aggiunta a un pannello.
Infine si crea il riquadro dei pulsanti, contenente i pulsanti Pause, Resume,
Cancel e Clear. Ciascun pulsante aggiunge un ActionListener che chiama il rispettivo metodo di azione quando si fa clic su di esso. Dopo la creazione del
riquadro dei pulsanti, tutti i riquadri creati vengono aggiunti alla finestra.
2012, McGraw-Hill

Creazione di un download manager in Java 63

Il metodo verifyUrl()
Il metodo verifyUrl() viene chiamato dal metodo actionAdd() ogni volta che si
aggiunge un download al Download Manager. Il metodo verifyUrl() mostrato
di seguito:
// Verifica URL di download.
private URL verifyUrl(String url) {
// Consente solo URL HTTP.
if (!url.toLowerCase().startsWith(http://))
return null;
// Verifica formato dellURL.
URL verifiedUrl = null;
try {
verifiedUrl = new URL(url);
} catch (Exception e) {
return null;
}
// Verifica che lURL specifichi un file.
if (verifiedUrl.getFile().length() < 2)
return null;
return verifiedUrl;

Il metodo verifica che lURL immesso sia un URL HTTP, poich supportato
solo HTTP. Quindi si utilizza lURL verificato per costruire una nuova istanza
della classe URL. Se lURL malformato, il costruttore della classe URL lancia
uneccezione. Infine il metodo verifica che nellURL sia specificato un file.

Il metodo tableSelectionChanged()
Il metodo tableSelectionChanged(), mostrato di seguito, viene chiamato ogni
volta che si seleziona una riga nella tabella dei download:
// Chiamato quando cambia selezione di riga di tabella.
private void tableSelectionChanged() {
/* Annulla registrazione per ricezione notifiche
dallultimo download selezionato. */
if (selectedDownload != null)
selectedDownload.deleteObserver(DownloadManager.this);
/* Se non si trova nellannullamento di un download,
imposta download selezionato e si registra per
ricevere notifiche da esso. */
if (!clearing && table.getSelectedRow() > -1) {
selectedDownload =
tableModel.getDownload(table.getSelectedRow());
selectedDownload.addObserver(DownloadManager.this);
2012, McGraw-Hill

PARTE IV

64 Capitolo 34

updateButtons();
}
}

Il metodo inizia verificando se gi selezionata una riga controllando se la


variabile selectedDownload null. Se la variabile non null, DownloadManager
si elimina come osservatore del download in modo da non ricevere pi notifiche
di cambiamenti. Quindi si controlla la flag clearing. Se la tabella non vuota e la
flag clearing false, prima la variabile selectedDownload viene aggiornata con
il Download corrispondente alla riga selezionata, quindi il DownloadManager
viene registrato come Observer per il Download selezionato. Infine si chiama
updateButtons() per aggiornare gli stati dei pulsanti in base allo stato del Download selezionato.

Il metodo updateButtons()
Il metodo updateButtons() aggiorna lo stato di tutti i pulsanti del riquadro in
base allo stato del download selezionato. Il metodo updateButtons() mostrato
di seguito.
/* Aggiorna lo stato di ogni pulsante in base allo
stato del download selezionato. */
private void updateButtons() {
if (selectedDownload != null) {
int status = selectedDownload.getStatus();
switch (status) {
case Download.DOWNLOADING:
pauseButton.setEnabled(true);
resumeButton.setEnabled(false);
cancelButton.setEnabled(true);
clearButton.setEnabled(false);
break;
case Download.PAUSED:
pauseButton.setEnabled(false);
resumeButton.setEnabled(true);
cancelButton.setEnabled(true);
clearButton.setEnabled(false);
break;
case Download.ERROR:
pauseButton.setEnabled(false);
resumeButton.setEnabled(true);
cancelButton.setEnabled(false);
clearButton.setEnabled(true);
break;
default: // COMPLETE o CANCELLED
pauseButton.setEnabled(false);
resumeButton.setEnabled(false);
cancelButton.setEnabled(false);
clearButton.setEnabled(true);
}
2012, McGraw-Hill

Creazione di un download manager in Java 65

} else {
// Nella tabella non selezionato alcun download.
pauseButton.setEnabled(false);
resumeButton.setEnabled(false);
cancelButton.setEnabled(false);
clearButton.setEnabled(false);
}
}

Se nella tabella dei download non selezionato alcun download, tutti i pulsanti vengono disattivati. Se selezionato un download, lo stato di ogni pulsante
viene impostato in base al fatto che loggetto Download abbia uno stato DOWNLOADING, PAUSED, ERROR, COMPLETE o CANCELLED.

Gestione degli eventi di azione


Ogni controllo della GUI del Download Manager registra un ActionListener che
chiama il rispettivo metodo di azione. Gli ActionListener vengono attivati ogni
volta che si verifica un evento di azione su un controllo della GUI. Per esempio,
quando si fa clic su un pulsante, viene generato un ActionEvent e vengono avvisati tutti gli ActionListener registrati del pulsante. Si pu osservare unanalogia
tra il funzionamento degli ActionListener e lo schema Observer descritto in precedenza, perch rappresentano lo stesso schema con due schemi di nomi diversi.

34.7 Compilazione ed esecuzione del Download


Manager
javac DownloadManager.java DownloadsTableModel.java
ProgressRenderer.java
Download.java

Eseguire DownloadManager come mostrato di seguito:


javaw DownloadManager

Il Download Manager facile da utilizzare: immettere lURL di un file da scaricare nel campo di testo nella parte superiore dello schermo. Per esempio, per
scaricare un file dal nome 0072229713_code.zip dal sito web di McGraw-Hill immettere
http://www.mhprofessional.com/downloads/proucts/
0072229713/0072229713_code.zip

Si tratta del file contenente il codice per il libro dellautore The Art of Java,
scritto insieme a James Holmes.
2012, McGraw-Hill

PARTE IV

Compilare DownloadManager come mostrato di seguito:

66 Capitolo 34

Dopo avere aggiunto un download al Download Manager, si pu gestire selezionandolo nella tabella. Dopo la selezione possibile fermare, annullare, riprendere ed eliminare un download. La Figura 34.2 mostra il Download Manager in
funzione.

34.8 Miglioramento del Download Manager


Il Download Manager descritto completamente funzionante, con la capacit
di fermare e riprendere download e di scaricare pi file contemporaneamente,
ma possibile aggiungere molti miglioramenti. Alcune idee: supporto per server
proxy, supporto per FTP e HTTPS e supporto per drag-and-drop. Un miglioramento particolarmente interessante una funzione di programmazione che
consenta di programmare un download a unora specifica, per esempio di notte
quando le risorse del sistema sono completamente disponibili.
Si noti che le tecniche illustrate nel presente capitolo non sono limitate allo
scaricamento di file nel senso classico: il codice ha molti altri utilizzi pratici. Per
esempio, molti software distribuiti su Internet sono costituiti da due parti: la prima una piccola applicazione compatta che si pu scaricare velocemente, che
contiene un mini download manager per lo scaricamento della seconda parte,
in generale di dimensioni molto maggiori. Il concetto utile, in particolare nella
misura in cui aumentano le dimensioni dellapplicazione, che solitamente aumentano la possibilit di interruzioni del download. Si pu provare ad adattare
il Download Manager a questo scopo.

Figura 34.2 Il Download Manager in funzione.

2012, McGraw-Hill