Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2005-2006.

Lezione 13:

PERSISTENZA

Oggetti persistenti = Oggetti che sono capaci a scriversi su file e rileggersi da file. Servono per "ricordare" lo stato di un oggetto tra una esecuzione e l'altra del programma.

Classi persistenti implementano l'interfaccia Serializable, che non ha metodi, ma occorre dichiarare che una classe implementa Serializable per poterla scrivere e rileggere attraverso i metodi di ObjectOutputStream e ObjectInputStream.

ObjectOutputStream

Classe le cui istanze rappresentano file e sono in grado di scrivere oggetti appartenenti a classi che implementano l'interfaccia Serializable.

Costruzione di un'istanza di ObjectOutputStream, per es. sia "pippo.txt" il nome del file, e scrittura di un oggetto sul file:

 FileOutputStream fos = new FileOutputStream("pippo.txt");
 ObjectOutputStream oos = new ObjectOutputStream(fos);
 oos.writeObject(...oggetto da scrivere...);
 oos.close();
 fos.close();
Nota: tutte queste operazioni possono sollevare eccezioni di classe IOException.

ObjectInputStream

Classe le cui istanze rappresentano file e sono in grado di leggere oggetti appartenenti a classi che implementano l'interfaccia Serializable.

Costruzione di un'istanza di ObjectInputStream, per es. sia "pippo.txt" il nome del file, e lettura di un oggetto dal file:

 FileInStream fis = new FileInputStream("pippo.txt");
 ObjectInputStream ois = new ObjectInputStream(fis);
 ...oggetto da leggere... = (...classe dell'oggetto...)ois.readObject();
 ois.close();
 fis.close();
Note:
L'oggetto viene restituito dalla funzione read come oggetto di classe generica Object, bisogna convertirlo forzatamente alla sua classe effettiva (fare un cast).
Tutte queste operazioni possono sollevare eccezioni di classe IOException.

Esempio: Magazzino

Il magazzino tiene traccia degli articoli presenti. Ogni articolo ha un codice numerico che lo identifica all'interno del magazzino. Ha inoltre una una stringa di descrizione. Ogni articolo e' presente in magazzino con un certo numero di pezzi.

Il magazzino puo' fare le seguenti operazioni:

Classi usate

  1. Una classe tiene le informazioni di un articolo, tranne il suo codice (tiene cioe' descrizione e numero di pezzi presenti)
  2. L'associazione tra il codice e le altre informazioni dell'articolo e' tenuta per mezzo di un array di oggetti della classe articolo suddetta: i codici sono numeri interi a partire da zero, usati come indici dell'array, ogni articolo e' memorizzato alla posizione corrispondente al suo codice
  3. Una classe rappresenta il magazzino
  4. Una classe realizza poi l'interfaccia grafica che e' un applet

Classe Articolo

File Articolo.java.

Implementa l'interfaccia Serializable, quindi i suoi oggetti possono essere scritti e riletti da file.

Ha due attributi: descrizione (stringa) e numero di pezzi (intero).

Ha i seguenti metodi pubblici:

Array degli articoli

All'interno del magazzino (la prossima classe che vedremo) gli articoli sono tenuti in un array. I codici degli articoli sono numeri interi a partire da zero. Ogni articolo e' memorizzato nella posizione dell'array corrispondente al suo codice. L'array ha un certo numero di posti (capacita' del magazzino), e in generale non tutti sono occupati. Quelli non occupati contengono l'oggetto nullo (null).

Quando registro un nuovo articolo, cerco una posizione vuota nell'array e lo memorizzo in quella posizione, che diventa il codice dell'articolo.

Quando depenno un articolo, lo calcello dall'array mettendo al suo posto l'oggetto nullo. La posizione che occupava nell'array rimane libera. Di conseguenza, lo stesso codice potra' poi essere usato per un nuovo articolo che sara' registrato in futuro.

Classe Magazzino

File Magazzino.java.

Implementa l'interfaccia Serializable, quindi i suoi oggetti possono essere scritti e riletti da file.

Attributi (privati):

Costruttori:

Metodi che leggono la situazione del magazzino:

Metodi che modificano la situazione del magazzino:

Classe GestioneMagazzino

File GestioneMagazzino.java. Interfaccia grafica per Magazzino. Puo' funzionare sia come applet (e' sottoclasse di Applet) che come applicazione (ha un main che mette l'applet in un frame).

Ha tra i suoi attributi un oggetto magazzino di classe Magazzino.

Come applicazione, puo' essere eseguita senza parametri (in questo caso crea un nuovo magazzino vuoto) oppure con parametro il nome di un file da cui leggere il magazzino.
Tra gli attributi ha anche il nome fileName del file contenente il magazzino.

L'applet contiene quattro bottoni:

Ogni bottone fa apparire una finestra di dialogo.
Un'altra finestra di dialogo appare per segnalare errori (le varie eccezioni che possono sorgere).

Gli altri attributi sono i quattro bottoni (di classe Button) e le cinque finestre di dialogo (di sotto-classi di Dialog appositamente definite, ved. dopo).

Costruttori

I costruttori non creano l'interfaccia grafica, questo viene fatto invece nel metodo "init" (ereditato dalla super-classe Applet e qui re-implementato).

Il metodo init costruisce le 5 finestre di dialogo ed associa a ciascuna i listener previsti a seconda della classe di appartenenza. Costruisce i 4 bottoni e li aggiunge all'applet.
Lo vedremo piu' in dettaglio dopo.

Classi per le finestre di dialogo

Le varie finestre di dialogo sono realizzate con classi a parte.

Consideriamo gli elementi che queste finestre devono presentare:

Considerando il contenuto delle finestre nell'ordine in cui sono state descritte, notiamo che ognuna aggiunge elementi alla precedente, quindi sembra ragionevole organizzare le rispettive classi un una gerarchia cosi' fatta:

Classe Dialog1

Definisce l'organizzazione comune a tutte le finestre di dialogo. Usata direttamente per mostrare i messaggi di errore. Usata anche indirettamente come superclasse di Dialog2.

La costruzione del contenuto della finestra e' fatta nel metodo "costruisci" che viene poi chiamato nel costruttore prima di eseguire "pack".

La finestra e' strutturata con BorderLayout. La posizione sud e' occupata da un pannello contenente finora solo un bottone (Ok). La posizione centrale inizialmente contiene nulla ma puo' essere riempita da un pannello chiamando la funzione "addCenter".

La finestra per errori riempie la parte centrale con un pannello contenente un'etichetta col messaggio. Questo pannello viene costruito e aggiunto al volo subito prima di mostrare la finestra (ved. metodi prepareErrorDialog della classe GestioneMagazzino).

Le sotto-classi aggiungeranno altri bottoni alla parte in basso e riempiranno la parte centrale con pannelli costruiti diversamente.

La funzione "addOkListener" permette di associare una callback al bottone "Ok". La funzione "clear" qui non fa nulla, ma sara' re-implementata nelle sotto-classi.

Classe Dialog2

Sotto-classe di Dialog1. Usata direttamente per inserire un nuovo articolo. Usata anche indirettamente come superclasse di Dialog3.

Rispetto alla classe Dialog1, aggiunge al pannello in posizione sud altri due bottoni (Clear e Dismiss). Inoltre riempie la posizione centrale con un pannello contenente un'etichetta affiancata a un campo di testo.

Tale pannello centrale e' organizzato a griglia (1 riga e 2 colonne) e predisposto in modo tale che possa essere espanso con nuove righe nelle sotto-classi. Infatti il numero di righe non e' fisso ma e' dato dal risultato della funzione "righe" che sara' ridefinita nelle sotto-classi.
Anche la stringa da mostrare nell'etichetta esplicativa non e' fissa ma e' data dal risultato della funzione "dicitura" che sara' ridefinita nelle sotto-classi.

La funzione "clear", ereditata da Dialog1, e' re-implementata per cancellare il campo di testo.

Le funzioni "addClearListener" e "addDismissListener" permettono di associare callback ai bottoni "Clear" e "Dismiss". Inoltre la funzione "addInfoListener" permette di associare una callback al campo di testo, che scatta quando l'utente batte "return". La funzione "scritto1" restituisce il testo scritto nel campo.

Classe Dialog3

Sotto-classe di Dialog2. Usata direttamente per scaricare un articolo. Usata anche indirettamente come superclasse di Dialog4.

Rispetto alla classe Dialog2, l'etichetta esplicativa a fianco del campo di testo cambia (avendo re-implementato la funzione "dicitura") in quanto il campo di testo e' destinato a contenere il codice di un articolo e non piu' la descrizione.

Il pannello in centro ha due righe in piu' (essendo stata re-implementata la funzione "righe"), e contiene due etichette destinate a mostrare descrizione e numero di pezzi dell'articolo corrispondente al codice inserito, ciascuna con accanto la sua etichetta esplicativa.

La funzione "clear" e' re-implementata per pulire anche le due nuove etichette (quelle per descrizione e numero pezzi). La nuova funzione "info" scrive due stringe assegnate nelle due etichette sopra citate.

ClasseDialog4

Sotto-classe di Dialog3. Usata per le finestre di movimento entrata e uscita.

Rispetto alla classe Dialog3, il pannello al centro ha una riga in piu' (re-implementata la funzione "righe") con un altro campo di testo affiancato dalla relativa etichetta.
Nel nuovo campo di testo l'utente scrive il numero di pezzi da far entrare / uscire.

La funzione "clear" e' re-implementata per azzerare anche questo nuovo campo di testo.
La nuova funzione "scritto2" restituisce la stringa presente nel nuovo campo di testo.

Gestione degli eventi in GestioneMagazzino

Una serie di classi interne a GestioneMagazzino implementa gli event listener che definiscono i comportamenti per le varie operazioni.

Comportamenti presenti in tutte le finestre di dialogo

Ciascuna di queste classi prevede come argomento del costruttore la finestra di dialogo su cui agire, e la memorizza come suo attributo (usato poi nella funzione actionPerformed).
HideListener pulisce chiamando la funzione "clear" prima di nasconderlo.

Comportamenti corrispondenti alle operazioni specifiche

Lo schema generale del comportamento e' questo:

  1. controlla che i parametri richiesti per l'operazione (contenuti nei campi di testo - uno o due - della finestra di dialogo corrispondente) non siano vuoti
  2. se sono vuoti solleva eccezione
  3. esegue l'operazione chiamando sul magazzino l'opportuna funzione, a seconda dei casi: nuovoArticolo (dopo averlo costruito), entraArticolo, esceArticolo, scaricaArticolo
  4. pulisce e nasconde la finestra di dialogo
Abbiamo detto che possono sorgere eccezioni. In caso di eccezione:
  1. chiama la funzione "prepareErrorDialog" per preparare la finestra di dialogo degli errori (tale funzione crea un pannello con il messaggio da mostrare e lo mette al centro della finestra di dialogo degli errori, usando "addCenter" della classe Dialog1)
  2. mostra la finestra di dialogo degli errori

La funzione init di GestioneMagazzino

Sostituisce costruttore e main nel caso in cui il programma gira come applet.

Costruisce tutte le finestre di dialogo (modali), costruisce i bottoni dell'applet e stabilisce le callback dei bottoni. Vediamo piu' in dettaglio...

Crea finestra di dialogo per gli errori (errorD) di classe Dialog1. Stabilisce comportamento per il bottone "Ok" (nascondera' la finestra, action listener di classe HideListener costruito passando la finestra da nascondere - qui errorD - come argomento).

Crea finestra di dialogo per nuovo articolo (nuovoD) di classe Dialog2. Stabilisce comportamento per i bottoni "Ok" (aggiunge l'articolo, action listener di classe EseguiNuovo), "Clear" (action listener di classe ClearListener) e "Dismiss" (action listener di classe HideListener). Gli ultimi due listener costruiti passando la finestra su cui agire - qui nuovoD - come argomento).

Crea finestra di dialogo per movimento entrata (entraD) di classe Dialog4. Stabilisce comportamento per i bottoni "Ok" (fa entrare pezzi, action listener di classe EseguiEntrata), "Clear" e "Dismiss" (come sopra), e per la battitura di "return" nel campo di testo (scrive nelle etichette le informazioni dell'articolo di codice dato, action listener di classe InfoListener).

Crea finestra di dialogo per movimento uscita (esciD) di classe Dialog4. Stabilisce comportamento per i bottoni "Ok" (fa uscire pezzi, action listener di classe EseguiUscita), "Clear" e "Dismiss", e battitura di "return" nel campo di testo (come sopra).

Crea finestra di dialogo per scarica articolo (scaricaD) di classe Dialog3. Stabilisce comportamento per i bottoni "Ok" (depenna articolo, action listener di classe EseguiScarica), "Clear" e "Dismiss", e battitura di "return" nel campo di testo (come sopra).

Poiche' una finestra di dialogo deve dipendere da un frame, la funzione getFrame cerca, risalendo nella gerarchia di contenimento delle finestre, il frame che contiene l'applet (un frame che lo contiene esiste anche quando gira nella pagina web...).

Stabilisce che l'applet e' gestito con FlowLayout, crea e aggiunge i quattro bottoni e ne stabilisce il comportamento: mostreranno le relative finestre di dialogo per "nuovo articolo", "movimento entrata", "movimento uscita" e "scarica articolo" (action listener di classe ShowListener costruito passando in argomento la finestra di dialogo da mostrare).

Il main di GestioneMagazzino

Se il programma funziona come applicazione, entrano in gioco i costruttori (che abbiamo visto prima) e il main.

Il metodo main crea un frame dove mettere l'applet, aggiunge la gestione della chiusura del frame, mostra il frame.
Se da command-line e' dato un nome di file, l'applet viene creato col costruttore nella forma che legge il magazzino da file.
Alla chiusura del frame, se il magazzino era stato letto da file lo riscrive sullo stesso file, altrimenti lo scrive sul file dal nome convenzionale "magazzino.txt".