Una classe statica per
l’I/O
Il
nostro obiettivo è quello di scrivere lo scheletro di una classe
da usare un po’ come il prezzemolo, ogni volta che dobbiamo leggere o salvare
un file e non vogliamo perderci troppo nei dettagli. La classe sarà
statica per semplicità e perché non esiste alcun motivo di
farne più di un’istanza. Supponiamo di chiamarla “FileManager”:
avrà di base due metodi:
FileManager.openFile(String
fileName)
che
ritorna una String col contenuto del file, e
FileManager.saveFile(String
fileName, String fileString)
che
potrà ritornare, ad esempio, un valore booleano (sì, ho salvato,
oppure no, ho avuto qualche problema).
Chiaramente
la classe si dovrà preoccupare di gestire in qualche modo gli errori,
poiché è intrinseca in qualsiasi operazione di I/O la possibilità
che qualcosa vada storto (il file non esiste, il supporto non è
pronto, la memoria sul supporto è esaurita e via dicendo).
Anche
qui sceglieremo una strada semplice, lasciando al futuro e alla nostra
buona volontà l’implementazione di un sistema più evoluto
di gestione degli errori. In pratica registreremo in una variabile di stato
della classe la descrizione dell’errore, fornendo un metodo che ne ritorni
il valore.
L’idea
è quella di permettere al programma di continuare l’esecuzione (senza
sollevare eccezioni, quindi) anche in presenza di errori. Ad esempio, se
lo spazio su disco è esaurito, il metodo saveFile ritornerà
false. Se ciò dovesse accadere, l’utente della classe potrà
richiedere una descrizione dell’errore alla classe stessa attraverso un
metodo getErrorMsg(). Scegliendo quest’approccio, ahimé, rinunciamo
alla staticità della classe – una classe statica non può
infatti per definizione contenere variabili di stato. Poco male, il lettore
esperto potrà facilmente trasformarla in un Singleton (una classe
di cui esiste sempre e soltanto una sola istanza) avendo cura di assicurarsi
che chi chiama il metodo getErrorMsg() sia lo stesso che ha generato l’errore
(non è una cosa banale, ma è certamente fattibile).
Leggere un file
Per
semplicità, supporremo che il file che vogliamo leggere sia un file
di testo, e che il suo contenuto possa essere riversato in una stringa.
Utilizzeremo principalmente la classe della Java API 1.1 (quindi presente
nel JDK 1.1 e successivi)
java.io.FileReader
Questa
classe non contiene però alcun metodo. Questo non significa che
non sia utilizzabile. Le Java API sono costruite seguendo attentamente
i dettami della programmazione orientata agli oggetti, per cui capita molto
spesso di imbattersi in classi all’apparenza inutili. In realtà,
per usarle, occorre utilizzare i concetti di incapsulamento, ereditarietà
e polimorfismo. Nel caso in esame il problema è specificare al sistema
in che modo vogliamo leggere il file: carattere per carattere? Forse è
meglio specificare un buffer e leggere a quantità discrete di byte.
C’è esattamente una classe che fa al caso nostro: si chiama BufferedReader,
e scopriamo che tra i suoi costruttori ce n’è uno che prende un
argomento di tipo “Reader”, che guarda caso è la classe genitore
di “FileReader”. Ecco applicato il concetto di ereditarietà
e polimorfismo: a seconda dello specifico tipo di Reader che passerò
a BufferedReader potrò bufferizzare in memoria, su disco o quant’altro.
Questa classe contiene un metodo readLine che legge linea per linea un
file di testo.
Ecco
dunque come implementeremo il metodo openFile:
public
String openFile(String fileName)
{
String ilFile = new String();
try
{
BufferedReader in=
new BufferedReader(new FileReader(fileName));
String linea = new String();
while ((linea=in.readLine())!= null )
{
ilFile+=linea;
ilFile+=System.getProperty(“line.separator”);
}
in.close();
}
catch (IOException e)
{
ilFile=null;
errorMsg=”Oper error:”+e.getMessage();
}
return ilFile;
}
Se
il fileName specifica un path inesistente, o si verifica un errore di supporto
non pronto, il metodo ritorna semplicemente un valore nullo. Vedremo poi
come utilizzare questo valore per comunicare all’utente il tipo di errore
verificatosi.
Salvare un file
L’operazione
di salvataggio di un file è leggermente più complessa della
precedente, perché oltre che specificare un’operazione con un buffer,
vorremmo che il file venga formattato come un file di testo e non come
un semplice flusso di byte – siccome Java è indipendente dalla piattaforma
questa è un’informazione tutt’altro che irrilevante: sul sistema
in uso come si descrive un ritorno carrello? Un file di testo è
formattato con uno speciale carattere di controllo? Occorre sempre ricordare,
quando si programma in Java, di non specificare direttamente i formati,
che possono differire da piattaforma a piattaforma e, soprattutto, possono
cambiare in futuro.
La
classe che fa per noi è PrintWriter. Ancora una volta, utilizzando
il polimorfismo, specificheremo che intendiamo utilizzare un buffer attraverso
la classe BufferedOutputStream.
Ecco
quindi una possibile implementazione:
public
boolean saveFile(String fileName, String fileString)
{
try
{
PrintWriter out = new PrintWriter(
new
BufferedOutputStream(
new
FileOutputStream(fileName)));
}
catch
(FileNotFoundException e)
{
errorMsg="Save File Error: "+e.getMessage();
return false;
}
out.print(fileString);
out.close();
if
(out.checkError())
{
errorMsg=”Out
of memory.”;
return
false;
}
return
true;
}
Se
il fileName specifica un path inesistente, il metodo ritorna false. Poiché
PrintWriter non solleva eccezioni (proprio come la nostra FileManager!)
esiste il metodo checkError per verificare se tutto è andato nel
modo giusto.
Altre utilità
di I/O
Naturalmente
potremo estendere a piacimento la nostra classe con metodi che salvano
e leggono direttamente in byte, oppure che serializzano oggetti che utilizziamo.
Ma senza arrivare a un tale livello di complessità, esistono due
metodi che sicuramente vorremo aggiungere fin da adesso. Uno che salva
in modalità append e uno che cancelli un file. Per entrambi avremo
bisogno di una routine che controlla che un file esista. Cominciamo da
quest’ultima:
public
boolean existFile(String fileName)
{
boolean exist;
try
{
FileReader fr = new FileReader(fileName);
exist=true;
}
catch (FileNotFoundException fex)
{
exist=false;
}
return exist;
}
sfruttando
cioè il fatto che il costruttore di FileReader innalza un’eccezione
nel caso il file non esista. Per noi non è affatto un’eccezione,
ma talvolta può essere comodo utilizzare try/catch in questo modo.
Per
la prima funzionalità la soluzione è molto semplice, dal
momento che esiste un costruttore di BufferedOutputStream che prende come
argomento un booleano che specifica se il salvataggio deve avvenire in
modalità append. Siccome l’utente della nostra classe può
non saperlo, non sarà male aggiungere un metodo sovraccaricato (overloaded)
che accetta come parametri il nome del file, il contenuto e questo booleano.
Per semplificare quindi ulteriormente le cose aggiungeremo:
public
boolean appendToFile(String fileName, String fileString)
{
if
(this.existFile(fileName))
return this.saveFile(fileName, fileString, true);
else
errorMsg=”File
to append to does not exist.”;
return
false;
}
Per
la seconda, invece, sfrutteremo il metodo delete della classe File:
public
boolean deleteFile(String fileName)
{
boolean deleted = false;
if (this.existFile(fileName))
{
File fileToKill = new File(fileName);
if (fileToKill.delete())
deleted = true;
else
errorMsg="Can't delete "+fileName;
}
else
errorMsg="Can't delete: file does not exist.";
return deleted;
}
Usare la classe
FileManager
Possiamo
testare la classe in questo modo:
FileManager
fm = new FileManager();
if
(fm.saveFile("Prova.txt", "Prova"))
{
System.out.print(fm.openFile("Prova.txt"));
fm.deleteFile("Prova.txt");
}
else
System.out.print(fm.getErrorMsg());
Qui
sotto trovate il listato completo.
/**
*
FileManager.java
*
@author Alessio Saltarin 2001
*
@since 04/2001
*
@version 1.0
*
*
Utility per il salvataggio/caricamento di file di testo
*
*/
package
name.alessiosaltarin.utils;
import
java.io.*;
public
final class FileManager
{
/**
* Se qualcosa è andato storto, questo metodo dice perché
* @return messaggio d'errore
*/
public String getErrorMsg()
{
return errorMsg;
}
/**
* Il contenuto del file aperto
* @param Path completo al file
* @return rappresentazione dei contenuti del file
*/
public
String openFile(String fileName)
{
String ilFile = new String();
try
{
BufferedReader in=
new BufferedReader(new FileReader(fileName));
String linea = new String();
while ((linea=in.readLine())!= null )
ilFile+=linea+NEWLINE;
in.close();
}
catch (IOException e)
{
ilFile=null;
errorMsg="Open File Error: "+e.getMessage();
}
return ilFile;
}
/**
* Controlla se un file esiste
* @param fileName path completo al file
* @return true se il file esiste
*/
public
boolean existFile(String fileName)
{
boolean exist;
try
{
FileReader fr = new FileReader(fileName);
exist=true;
}
catch (FileNotFoundException fex)
{
exist=false;
}
return exist;
}
/**
* Salva il contenuto di fileString in un file
* @param fileName Path completo al file che si vuole salvare
* @param fileString Contenuto del file
* @return true se il file e' stato salvato correttamente
*/
public
boolean saveFile(String fileName, String fileString)
{
PrintWriter out;
try
{
out = new PrintWriter(
new BufferedOutputStream(new FileOutputStream(fileName)));
}
catch (FileNotFoundException e)
{
errorMsg="Save File Error: "+e.getMessage();
return false;
}
out.print(fileString);
out.close();
if (out.checkError())
{
errorMsg="Out of memory";
return false;
}
return true;
}
/**
* Salva il contenuto di fileString in un file
* @param fileName Path completo al file che si vuole salvare
* @param fileString Contenuto del file
* @param append se true, appende la stringa al file esistente
* @return true se il file e' stato salvato correttamente
*/
public
boolean saveFile(String fileName, String fileString, boolean append)
{
PrintWriter out;
try
{
out = new PrintWriter(
new BufferedOutputStream(new FileOutputStream(fileName)), append);
}
catch (FileNotFoundException e)
{
errorMsg="Save File Error: "+e.getMessage();
return false;
}
out.print(fileString);
out.close();
if (out.checkError())
{
errorMsg="Out of memory";
return false;
}
return true;
}
/**
* Salva il contenuto di fileString in un file
* @param fileName Path completo al file che si vuole salvare in modalità
append
* @param fileString Contenuto del file
* @return true se il file e' stato salvato correttamente
*/
public
boolean appendToFile(String fileName, String fileString)
{
if (this.existFile(fileName))
return this.saveFile(fileName, fileString, true);
else
errorMsg="File to append to does not exist";
return false;
}
/**
* Cancella un file
* @param fileName Path completo al file che si vuole cancellare
* @return true se il file e' stato salvato cancellato
*/
public boolean deleteFile(String fileName)
{
boolean deleted = false;
if (this.existFile(fileName))
{
File fileToKill = new File(fileName);
if (fileToKill.delete())
deleted = true;
else
errorMsg="Can't delete "+fileName;
}
else
errorMsg="Can't delete: file does not exist.";
return deleted;
}
/**
* Constructor
*/
public FileManager()
{
errorMsg="OK";
}
/**
* For test purposes, only
* @deprecated
*/
public static void main(String[] args)
{
FileManager fm = new FileManager();
if (fm.saveFile("Prova.txt", "Prova"))
{
System.out.print(fm.openFile("Prova.txt"));
fm.deleteFile("Prova.txt");
}
else
System.out.print(fm.getErrorMsg());
}
private String errorMsg;
private static final String NEWLINE = System.getProperty(“line.separator”);
}
|