La rappresentazione dei dati è una tipica funzione di interfaccia, quindi, in ambiente Java, un'operazione svolta dalle pagine JSP. La forma che solitamente utilizziamo è quella degli elenchi, che hanno il compito di introdurre in maniera schematica i dati da mostrare all'utente.
Le tabelle, in questo particolare tipo di rappresentazione, sono lo strumento più adatto, in quanto sono state pensate proprio a questo scopo. In questo articolo ci occuperemo di realizzare un componente che automatizzi la rappresentazione dei dati che la nostra applicazione manipola.
L'idea è quella di creare uno strato di logica software che si occupi di gestire automaticamente la rappresentazione di tutte le entità (in forma di Javabeans) in modo da riutilizzare lo stesso componente lungo tutta l'applicazione con tutti gli oggetti contenitore possibili, senza ogni volta avere la preoccupazione di gestirne la rappresentazione specifica.
Ci occuperemo di un altro aspetto di rappresentazione, ossia quello dell'operazione di esportazione dei dati in un foglio di calcolo (excel). Attraverso l'utilizzo delle API Jakarta POI svilupperemo uno strato di logica che si occuperà di creare un file excel con la formattazione dei dati in forma tabellare, indipendentemente dal tipo di dati utilizzato.
Export dei dati
Esistono diversi progetti open source, dedicati all'integrazione tra Java e la suite di Office, per la creazione di documenti di lavoro. Noi utilizzeremo il progetto Jakarta POI e la parte relativa alla gestione dei file in formato excel.
L'idea che seguiremo sarà: utilizzare uno strumento capace di leggere a runtime le proprietà dei Javabean passati come riferimento, in modo da astrarre al livello più alto, e quindi scrivere le informazioni relative in forma strutturata. Per poter fare ciò diventa indispensabile fare uso della Reflection di Java. Attraverso di essa riusciamo a recuperare una lista di proprietà contenute in qualsiasi oggetto e effettuare la chiamata ai metodi accessori (get
e set
) attraverso la loro ispezione.
Utilizzando le API del progetto POI, quindi, effettueremo la creazione del documento excel e la scrittura dei dati recuperati tramite Reflection. L'idea è semplice e davvero potente, in quanto alla fine dello sviluppo sarete in grado di utilizzare il software creato con tutti i tipi di oggetti manipolati all'interno della vostra applicazione.
Immaginate quindi di avere un gestionale con entità come Utente, Ordine, Fattura, ecc potrete utilizzare la stessa classe per effettuare l'export della lista di dati in excel. Vediamo quindi la pratica:
Listato 1. Esporta un metodo pubblico per effettuare l'operazione di creazione del foglio di lavoro excel
package it.html.excel;
import org.apache.poi.hssf.usermodel.*;
import it.html.sm.bridge.reflection.ReflectionUtils;
import it.html.sm.javabeans.DataManager;
import java.io.*;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
//La classe esporta un metodo pubblico per effettuare l'operazione di creazione
//del foglio di lavoro excel, il cui scopo è quello di esportare gli elementi
//contenuti in lista.
public class ExcelExporter{
//Variabili di istanza: il percorso fisico del file e la lista di beans da rappresentare
private String nameFile;
private Collection list;
//La variabile che virtualizza il foglio di lavoro excel
private HSSFWorkbook wb;
public ExcelExporter(String path,String nomeFile,Collection list){
this.list = list;
//Il nome fisico del file
this.nameFile = path + File.separator + nomeFile;
}
..//
}
Creiamo una classe concreta che contenga il riferimento ad una Collection generica ed il nome del file excel che vogliamo creare. Inoltre manteniamo il riferimento a un oggetto di tipo HSSFWorkbook, che è l'oggetto padre da cui recupereremo tutte le funzioni per la formattazione del file excel.
I metodi pubblici esposti sono il metodo di export e il metodo per recuperare il file creato:
Listato 2. Effettua l'operazione di creazione del file excel
//effettua l'operazione di creazione del file excel, la creazione
//dell'intestazione e il popolamento del foglio di lavoro
public void export(String sheetName) throws IOException{
if (list.isEmpty())
throw new NoSuchElementException();
//Inizializziamo il foglio di lavoro
wb = new HSSFWorkbook();
//Creiamo un foglio associandogli il nome passato in riferimento
creaFoglio(sheetName);
//Apro il flusso in scrittura
java.io.FileOutputStream fileOut = new java.io.FileOutputStream(nameFile);
//Scrivo il file con il contenuto
wb.write(fileOut);
//e chiudo
fileOut.close();
}
La logica è molto semplice: effettuiamo un controllo per verificare la lista di oggetti, creiamo una nuova istanza di HSSFWorkbook, richiamiamo il metodo privato creaFoglio()
, passando il nome del foglio di lavoro, il cui compito è valorizzare il foglio di lavoro con i dati. Questo è il metodo più importante, quello che definisce la formattazione dei dati all'interno del foglio excel, dopo c'è la scrittura del file sul disco rigido (o su un qualunque stream di scrittura, quindi anche un flusso di output criptato, una socket, ...).
Listato 3. Restituisco il file dove abbiamo salvato i dati
public File getExcelFile(){
return new File(this.nameFile);
}
Il metodo getExcelFile()
si occupa di restituire l'istanza del file appena creato.
Listato 4. Creare un foglio di lavoro con il contenuto
private void creaFoglio(String nomeFoglio){
//Creiamo un foglio di lavoro sul documento padre (wb)
HSSFSheet foglioDiLavoro=wb.createSheet(nomeFoglio);
//Creazione dell'intestazione
creaIntestazione(foglioDiLavoro);
//Creazione del contenuto
int righe=creaContenuto(foglioDiLavoro);
//Creazione di una riga di statistiche
creaStatistiche(foglioDiLavoro,righe);
}
Il metodo creaFoglio()
ha il compito di definire il singolo foglio di lavoro all'interno della cartella di lavoro excel. Ogni cartella può avere più fogli, e attraverso la chiamata al metodo createSheet()
definiamo la creazione di un nuovo foglio di lavoro. Nel nostro caso ne creeremo uno solo e dividiamo i compiti per la creazione dell'intestazione, del contenuto e delle statistiche finali tre metodi distinti.
Listato 5. Inserisce gli elementi formattandoli
private void creaIntestazione(HSSFSheet foglioDiLavoro) {
//Formattiamo il testo come bold
HSSFCellStyle stile = wb.createCellStyle();
HSSFFont font = wb.createFont();
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
stile.setFont(font);
//Recuperiamo un'istanza dalla lista per
//leggere a runtime il nome delle variabili di istanza
String fields[] = ReflectionUtils.getStringFields(getFirstFromList());
//Valorizziamo l'intestazione con il nome della proprietà
//del bean, per ognuna delle proprietà presenti (sconosciute a tempo di compilazione)
//Ci posizioniamo sulla prima riga (numero 0)
HSSFRow row = foglioDiLavoro.createRow(0);
//Iteriamo sugli elementi dell'array
for(int i=0;i<fields.length;i++){
HSSFCell cell = row.createCell( (short) i);
cell.setCellStyle(stile);
cell.setCellValue(new HSSFRichTextString(fields[i]));
}
}
La logica di sviluppo è davvero semplice: per prima cosa creiamo uno stile grassetto in modo da far risaltare l'intestazione, e poi scriviamo nella prima riga (riga 0) il nome delle proprietà dei bean presenti nella collezione. Attraverso i metodi della classe ReflectionUtils, recuperiamo prima un array contenente il nome delle proprietà. Badate che il metodo getFirstFromList()
ha il compito di recuperare un'istanza dalla Collection, in modo da conoscere il tipo concreto e quindi le relative proprietà. Dopodichè effettuiamo un'iterazione sull'array e creiamo una cella attraverso le API (partendo sempre da 0) per ogni suo elemento.
Listato 6. Crea e si sposta nella struttura
private int creaContenuto(HSSFSheet foglioDiLavoro){
String fields[] = ReflectionUtils.getStringFields(getFirstFromList());
//Iteriamo sugli elementi
Iterator it=list.iterator();
//contiamo le righe (considerando anche l'intestazione)
int cont=1;
while(it.hasNext()){
Object elem = it.next();
//spostiamoci sulla riga
HSSFRow row = foglioDiLavoro.createRow(cont);
//Iteriamo sugli elementi dell'array
for(int i=0;i<fields.length;i++){
//Recuperiamo il valore del campio iesimo via Reflection
String value = ReflectionUtils.getProperty(fields[i], elem);
//e lo settiamo
HSSFCell cell = row.createCell( (short) i);
cell.setCellValue(new HSSFRichTextString(value));
}
cont++;
}
//Restituiamo il numero di elementi inseriti
return cont;
}
Il metodo che gestisce il contenuto è altresì semplice. Qui manterremo uno stile di default e ci preoccuperemo di iterare su ogni elemento della collezione. Quindi, su ogni elemento effettueremo un'iterazione per effettuare la lettura dei metodi getXYZ()
, sempre attraverso reflection. Infine scriviamo il contenuto del valore all'interno della cella. Alla fine restituiamo il numero di oggetti inseriti.
Listato 7. Inserisce il contenuto all'interno delle celle
//Aggiungiamo una semplice formula per il conteggio degli elementi inseriti, settandola dopo RIGHE righe
private void creaStatistiche(HSSFSheet foglioDiLavoro, int righe) {
//Formattiamo il testo come ROSSO
HSSFCellStyle stile = wb.createCellStyle();
HSSFFont font = wb.createFont();
font.setColor(HSSFFont.COLOR_RED);
stile.setFont(font);
//Aggiungiamo dopo due righe dalla fine
HSSFRow row = foglioDiLavoro.createRow(righe+2);
//Creiamo due colonne
HSSFCell cell = row.createCell( (short) 0);
cell.setCellStyle(stile);
HSSFCell cell2 = row.createCell( (short) 1);
cell2.setCellStyle(stile);
//un'etichetta
cell.setCellValue(new HSSFRichTextString("TOTALE:"));
//ed una formula
cell2.setCellFormula("COUNT(A2:A"+(righe)+")");
}
Il metodo creaStatistiche()
è stato creato per mostrarvi come sia possibile inserire delle formule all'interno del foglio di lavoro. In questo caso creeremo uno stile con font rosso e, dopo esserci posizionati sulle righe alla fine del documento, creeremo una cella contenente la formula "COUNT(A2:Ai)" in modo da contare il numero di righe inserite. È evidente che in questo caso già conosciamo il numero, ma qui potrete inserire tutte le formule più adatte alle vostre specifiche esigenze.
Per valutare il funzionamento del componente potete utilizzare il seguente main:
Listato 8. Testa il risultato
public static void main(String[]args) throws IOException{
System.out.println("UNIT TEST");
Collection aList = DataManager.getData();
//Creo la directory di test, se non esiste
File dir = new File("c:/poitest");
dir.mkdir();
ExcelExporter ee=new ExcelExporter("c:/poitest","test.xls",aList);
ee.export("Test Foglio di Stile");
System.out.println("Apri il file: "+ee.getExcelFile().getPath());
}
Dove la classe DataManager simula la creazione di una serie di oggetti di tipo User (un semplice bean con proprietà: id, nome, cognome).
Il risultato dell'esecuzione darà la creazione di un foglio di calcolo mostrato in immagine: