MokaByte
Numero 29 - Aprile 1999
|
||||
|
DBF in Java |
|||
|
di
Matteo Baccan |
Come gestire archivi dati nel formato DB3, uno dei più famosi e diffusi nel passato |
||
|
||||
Vediamo come sia possibile creare una classe di interfaccia verso file DBF, contenente una serie di funzionalità con la stessa sintassi dei comandi CLIPPER. Il tutto senza usare l’interfaccia JDBC, ma tramite un’interfaccia diretta e più performante. |
Il problema
nell’usare i driver ODBC per accedere ai file DBP, è che tali driver
hanno infatti una logica di funzionamento SQL. Non essendo il DBF un tipo
di dato nato per essere trattato in modo SQL, il modo migliore per prenderne
i dati è quello di accedere direttamente alla sua struttura, creando
una classe specifica.
Lo scopo di questo articolo è quello di illustrare come sia possibile creare una semplice classe JAVA in grado di scorrere un DBF, prenderne i campi, ed eventualmente aggiornarne il contenuto, il tutto senza usare un driver JDBC. Un’altra funzionalità della classe che andremo a costruire sarà poi quella di avere una sintassi stile CLIPPER/XBASE, per favorire un eventuale passaggio di sorgenti da programmi Clipper o da qualsiasi dialetto xbase, verso JAVA. Esistono ancora molte applicazioni, scritte in questi linguaggi, che vengono tuttora sviluppate e sopportate. Rivederle in linguaggio JAVA solitamente porta ad una riscrittura totale del codice. Avendo però una classe in grado di emulare quantomeno la sintassi di accesso ai dati, questo passaggio risulterà sicuramente molto più semplice. Definizione
della struttura
Prima di partire
con la stesura del codice della nostra classe andremo ad analizzare il
formato DBF, per capirne il corretto funzionamento e come sia possibile
intervenire per la creazione di algoritmi generici di accesso ai dati.
Il primo byte che incontriamo è quello che ci permette di capire se si tratta di un DBF o di un altro tipo di file. Se infatti contiene il valore 03, nella maggior parte dei casi è un DBF. Esistono delle eccezioni a questa regola: il valore 83 è quella più comune, e indica che l’archivio è accompagnato da un file DBT, contenente i dati dei campi testuali. Subito dopo questo valore ci sono tre byte che contengono anno, mese e giorno di ultima modifica. Da qui possiamo facilmente capire che, dato che il campo che contiene il secolo è di un solo byte, è stato usato uno stratagemma per memorizzare in modo corretto il valore dell’anno di ultima modifica. Lo stratagemma è dato dal fatto che, al valore contenuto, deve essere sommato 1900, per ottenerne il valore corretto. Nel nostro caso il valore, in notazione esadecimale, è 62, che corrisponde ad un 98 decimale. Tale valore, sommato a 1900, riporta esattamente 1998. A questo punto potrebbe nascere un piccolo dubbio: "Ma cosa succede se supero il valore 255?". I progettisti della struttura DBF avevano pensato a questo fatto, ma in maniera molto marginale (supponendo che questa struttura dati venisse abbandonata prima di quella data). Per verificare cosa potrebbe accadere ad una vostra applicazione con archivi DBF nel 1900 + 256, cioè nel 2156, provate a impostare questa data sul vostro PC e ad utilizzarla, apportando delle modifiche ai dati. Nella migliore delle ipotesi il programma andrà semplicemente ad impostare il valore 0 nel secondo byte, ma vi potrebbero anche essere dei casi in cui si vada a sporcare il byte precedente, ed in questo caso l’applicazione non sarebbe più in grado di riaprire l’archivio! Subito dopo le informazioni a riguardo della data di modifica, vi sono quattro byte che indicano il numero di record dell’archivio, nel nostro caso 0B cioè 11. Questa informazione è molto importante. Infatti è utilizzata da alcuni programmi, come dbase, per sapere in che punto dell’archivio ci si deve posizionare per aggiungere un record o fino a dove arrivare in caso di eliminazione dei record cancellati. Chiaramente se l’informazione riporta un numero di record scorretto, si potrebbero avere delle perdite di dati. I due byte successivi indicano la grandezza dell’header. Questo vuol dire che, essendo 65536 il valore massimo che 2 byte possono contenere ed essendoci uno scarto di 32, dato dai primi byte dell’archivio, il numero massimo di campi che un DBF può contenere è 2047. Questa informazione è importante per capire quale sarà la migliore struttura dati da utilizzare per gestire le caratteristiche dei campi. I due byte successivi sono invece quelli che indicano quanto la grandezza del record contenuto nel DBF. Nel nostro caso questo valore è E4 00, cioè 224. Da qui in avanti, fino al byte 32, i vari programmi che hanno gestito il formato DBF hanno dato una loro libera interpretazione. Ecco perché sarebbe buona norma non fare affidamento ad informazioni presenti fuori da questi primi byte. Successivamente a questa prima parte di header, iniziano una serie di blocchi, grandi a loro volta 32 byte. Tali blocchi contengono le informazioni riguardanti i singoli campi, il loro nome, la loro tipologia e grandezza. Anche in questo caso vi sono interpretazioni diverse sull’utilizzo dei byte. Tutte le implementazioni sono però concordi nell’utilizzare i primi 11 caratteri per il nome, terminante con 0. Questo vuol dire che se vi fossero i primi tre caratteri pieni, il quarto col valore 0 e il quinto e successivi a loro volta riempiti con dei valori validi, il nome del campo sarebbe da interrompere alla posizione quattro. Dopo il nome vi è l’informazione sul tipo di campo. I valori standard, presenti in questo byte sono: C carattere, D data, N numerico, L logico, M memo. A questo punto vi sono altri 2 byte che indicano la lunghezza del campo. Finiti i blocchi dei campi, iniziano ad essere elencati tutti i record, preceduti da un carattere che, se ‘*’, indica che il record in questione è da considerarsi cancellato. A questo punto sappiamo tutto ciò che server per poter utilizzare la struttura di questi archivi e possiamo finalmente iniziare a gestire dei file DBF. La classe La classe che
creeremo si chiamerà xbase, in quanto avrà lo scopo di poter
leggere e scorrere tutte le informazioni contenute all’interno di un qualsiasi
archivio xbase, cioè DBF.
I passi successivi saranno quelli di leggere il tipo di archivio e i tre byte di informazione della data di ultima modifica, tramite il metodo readUnsignedByte dell’oggetto dbf. Questi dati ci serviranno, per fare un controllo di validità del DBF e gestire un eventuale errore di apertura. Successivamente andremo a leggere il numero di record, la posizione di inizio dei dati e la grandezza dei singoli record. L’unico problema che avremo nel fare questo, sarà il fatto che, i valori contenuti in questi byte, non sono allineati secondo l’ordine discendente INTEL, ma con un ordine ascendente. Pertanto non è possibile utilizzare i metodi readInt() e readUnsignedShort() dell’oggetto dbf, ma saremo costretti a costruire due metodi che leggano nel corretto ordine questi byte. Vediamo pertanto come dovremo leggere l’informazione sul numero di record: e come invece dovremo leggere l’indirizzo di partenza dei dati e la lunghezza del singolo record: Se per curiosità volete vedere come JAVA gestisce readInt() e readUnsignedShort() vi consiglio di andare a leggere i sorgenti della classe RandomAccessFile. Noterete che il numero di byte letti è lo stesso, ma il calcolo del valore di ritorno è esattamente il contrario. Per questa ragione non è possibile usare il metodo standard di lettura dati. Il passo successivo sarà ora quello di andare alla posizione 32 per leggere i blocchi contenenti i dati dei singoli campi. Per effettuare questa operazione utilizzeremo una classe contenitore delle informazioni riguardanti i singoli campi e creeremo un’array di oggetti di tale classe. Tale array dovrà così essere inizializzato in base al numero di campi scritto nel header. Vediamo ora la struttura di tale classe: I dati di questa classe saranno tutti valorizzati dalla lettura del blocco dati del singolo campo. L’unica informazione che però non è presente in tale blocco è il displacement. Tale informazione sarà infatti l’unica calcolata dinamicamente e ci servirà per sapere la posizione del campo all’interno del record (per diminuire il numero di calcoli da effettuare, una volta richiesto un particolare campo). A questo punto il metodo use è pronto listato 1 (il sorgente del metodo use della classe xbase): public void use( String fn, String rw ) throws IOException { // Apertura file dbf = new RandomAccessFile(fn,rw); // Informazioni dell'header dbfType=dbf.readUnsignedByte(); dbfLastUpdateYear =(byte)dbf.read(); dbfLastUpdateMonth=(byte)dbf.read(); dbfLastUpdateDay =(byte)dbf.read(); dbfNumberRecs =readBackwardsInt(); dbfDataPosition =readBackwardsUnsignedShort(); dbfDataLength =readBackwardsUnsignedShort(); dbfNumberFields =(dbfDataPosition-33)/32; dbf.seek(32); // Struttura campi byte fieldNameBuffer[] = new byte[11]; int locn=0; //Il primo campo e' il campo di deleted. Non ? in struttura, ma //esiste e se contiene '*' il record e' cancellato, se ' ' il record //e' calido dbfFields = new fieldHeader[dbfNumberFields+1]; dbfFields[0] = new fieldHeader(); dbfFields[0].fieldName="@DELETED@"; dbfFields[0].dataType='C'; dbfFields[0].displacement=0; locn+= (dbfFields[0].length=1); dbfFields[0].decimal=0; // Ciclo di lettura dei campi for (int i=1; i<=dbfNumberFields; i++) { dbfFields[i] = new fieldHeader(); // Nome dbf.read(fieldNameBuffer); dbfFields[i].fieldName= new String(fieldNameBuffer); int posZero = dbfFields[i].fieldName.indexOf( 0 ); dbfFields[i].fieldName = dbfFields[i].fieldName.substring(0,posZero); // Tipo dbfFields[i].dataType=(char)dbf.read(); // Lunghezza dbf.skipBytes(4); dbfFields[i].displacement=locn; locn+=(dbfFields[i].length=dbf.read()); // Decimali if( dbfFields[i].dataType=='N' ) dbfFields[i].decimal=(byte)dbf.read(); else { dbfFields[i].decimal = 0; int len = (byte)dbf.read(); dbfFields[i].length += len*256; } dbf.skipBytes(14); } } La fase successiva è ora la gestione del movimento fra i record del DBF. Per implementare i metodi che si occuperanno di tale operazione creeremo una variabile in grado di passare dal valore 1 al record valore massimo contenuto nell’archivio. Tale informazione è scritta nell’intestazione del DBF, ed è pertanto facilmente reperibile. Vediamo ora in dettaglio tali metodi: La variabile curRec è utilizzata per contenere il numero del record corrente, mentre la variabile dbfNumerRecs è quella precedentemente impostata, dal metodo use.public void gotop() { Un’organizzazione di questo tipo ha il vantaggio di avere una rapidissima velocità di spostamento all’interno dei dati, superiore anche a quella di alcuni prodotti specifici di accesso a DBF (infatti, in tal modo non vi sono accessi a disco in questa fase, ma tutte le operazioni sono effettuate in memoria). Il passo successivo è quello di dare la possibilità alla nostra classe di accedere alle informazioni contenute nei campi dei singoli record. Per fare questo useremo le informazioni di record corrente e le informazioni contenute nell’array dei campi del DBF. Prima di questo dovremo però spostare il puntatore dell’oggetto dbf al primo byte del record da leggere. Per sapere la posizione esatta alla quale muoverci basterà moltiplicare il numero di record al quale desideriamo andare, meno uno, per la lunghezza del record. A tutto questo andrà poi sommata la posizione di partenza dei dati. Sintetizzando la posizione di inizio del record è data da: A questa posizione andrà poi sommata la posizione del campo che desideriamo controllare, cioè il suo displacement. Pertanto se volessimo prendere il terzo campo del dbf al record 33 dovremo semplicemente fare il seguente calcolo per saperne l’esatta posizione: Ora il puntatore al file DBF si troverà esattamente sul carattere di partenza del campo che ci interesserà controllare. Pertanto se il campo fosse di tipo carattere di 10 byte basterà effettuare questa semplice lettura per ottenerne il valore: A questo punto la nostra classe DBF contiene tutto ciò che ci server per poter aprire un DBF, spostarsi attraverso i record e leggerne i valori dei campi. Su questa classe potremo poi costruire tutti i metodi utili alla navigazione all’interno dei record, come ad esempio il metodo di lunghezza header o il metodo per leggere la data di ultima modifica. Le uniche cose che non possiamo ancora fare sono le operazioni di ricerca record, seek, all’interno del DBF. Infatti per poter effettuare delle operazioni di questo genere è necessario utilizzare gli indici del file, che sono elementi esterni al file DBF, non descritti nell’intestazione del DBF stresso. Per chi volesse comunque approfondire questo aspetto ho inserito nella documentazione della classe anche un documento che descrive la modalità con la quale è costituito un indice NTX. Conclusioni Creare delle
classi che supportino i DBF con la sintassi Clipper è un’operazione
molto facile, se ci si limita a leggerne di dati. Infatti se si volesse
andare più in dettaglio, introducendo la gestione degli indici e
la gestione degli accessi concorrenti, il lavoro da effettuare sarebbe
molto più lungo e complicato.
Bibliografia
[1] http://java.sun.com/products/jdk/1.2/
Sito di riferimento per il JDK 1.2 usato per l’interfaccia DBF
|
Matteo Baccan è uno specialista di progettazione e sviluppo in C++. È coautore di dBsee 4 e dBsee++, ed autore di dBsee400. Attualmente si occupa dello studio di tecniche avanzate di programmazione in ambito Internet/Intranet tramite Lotus Notes, presso ISA Italian Software Agency, e può essere contattato tramite e-mail all’indirizzo baccan@isanet.it |
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|