Statistics
| Revision:

root / trunk / libraries / libFMap / src / com / iver / cit / gvsig / fmap / drivers / dbf / DbaseFileHeader.java @ 3217

History | View | Annotate | Download (17 KB)

1
/*
2
 * Created on 16-feb-2004
3
 *
4
 * To change the template for this generated file go to
5
 * Window>Preferences>Java>Code Generation>Code and Comments
6
 */
7
package com.iver.cit.gvsig.fmap.drivers.dbf;
8

    
9
import java.io.IOException;
10

    
11
import java.nio.ByteBuffer;
12
import java.nio.ByteOrder;
13

    
14
import java.util.Calendar;
15
import java.util.Date;
16

    
17
import com.iver.utiles.bigfile.BigByteBuffer;
18

    
19

    
20
/**
21
 * Class to represent the header of a Dbase III file. Creation date: (5/15/2001
22
 * 5:15:30 PM)
23
 */
24
public class DbaseFileHeader {
25
    // Constant for the size of a record
26
    private int FILE_DESCRIPTOR_SIZE = 32;
27

    
28
    // type of the file, must be 03h
29
    private int myFileType = 0x03;
30

    
31
    // Date the file was last updated.
32
    private Date myUpdateDate = new Date();
33

    
34
    // Number of records in the datafile
35
    private int myNumRecords = 0;
36

    
37
    // Length of the header structure
38
    private int myHeaderLength;
39

    
40
    // Length of the records
41
    private int myRecordLength;
42

    
43
    // Number of fields in the record.
44
    private int myNumFields;
45

    
46
    // collection of header records.
47
    private DbaseFieldDescriptor[] myFieldDescriptions;
48

    
49
    /**
50
     * DbaseFileHreader constructor comment.
51
     */
52
    public DbaseFileHeader() {
53
        super();
54
    }
55

    
56
    /**
57
     * Add a column to this DbaseFileHeader. The type is one of (C N L or D)
58
     * character, number, logical(true/false), or date. The Field length is
59
     * the total length in bytes reserved for this column. The decimal count
60
     * only applies to numbers(N), and floating point values (F), and refers
61
     * to the number of characters to reserve after the decimal point.
62
     *
63
     * @param inFieldName DOCUMENT ME!
64
     * @param inFieldType DOCUMENT ME!
65
     * @param inFieldLength DOCUMENT ME!
66
     * @param inDecimalCount DOCUMENT ME!
67
     *
68
     * @throws Exception DOCUMENT ME!
69
     */
70
    public void addColumn(String inFieldName, char inFieldType,
71
        int inFieldLength, int inDecimalCount) throws Exception {
72
        if (inFieldLength <= 0) {
73
            inFieldLength = 1;
74
        }
75

    
76
        if (myFieldDescriptions == null) {
77
            myFieldDescriptions = new DbaseFieldDescriptor[0];
78
        }
79

    
80
        int tempLength = 1; // the length is used for the offset, and there is a * for deleted as the first byte
81
        DbaseFieldDescriptor[] tempFieldDescriptors = new DbaseFieldDescriptor[myFieldDescriptions.length +
82
            1];
83

    
84
        for (int i = 0; i < myFieldDescriptions.length; i++) {
85
            myFieldDescriptions[i].myFieldDataAddress = tempLength;
86
            tempLength = tempLength + myFieldDescriptions[i].myFieldLength;
87
            tempFieldDescriptors[i] = myFieldDescriptions[i];
88
        }
89

    
90
        tempFieldDescriptors[myFieldDescriptions.length] = new DbaseFieldDescriptor();
91
        tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = inFieldLength;
92
        tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = inDecimalCount;
93
        tempFieldDescriptors[myFieldDescriptions.length].myFieldDataAddress = tempLength;
94

    
95
        // set the field name
96
        String tempFieldName = inFieldName;
97

    
98
        if (tempFieldName == null) {
99
            tempFieldName = "NoName";
100
        }
101

    
102
        if (tempFieldName.length() > 11) {
103
            tempFieldName = tempFieldName.substring(0, 11);
104
            warn("FieldName " + inFieldName +
105
                " is longer than 11 characters, truncating to " +
106
                tempFieldName);
107
        }
108

    
109
        tempFieldDescriptors[myFieldDescriptions.length].myFieldName = tempFieldName;
110

    
111
        // the field type
112
        if ((inFieldType == 'C') || (inFieldType == 'c')) {
113
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'C';
114

    
115
            if (inFieldLength > 254) {
116
                warn("Field Length for " + inFieldName + " set to " +
117
                    inFieldLength +
118
                    " Which is longer than 254, not consistent with dbase III");
119
            }
120
        } else if ((inFieldType == 'S') || (inFieldType == 's')) {
121
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'C';
122
            warn("Field type for " + inFieldName +
123
                " set to S which is flat out wrong people!, I am setting this to C, in the hopes you meant character.");
124

    
125
            if (inFieldLength > 254) {
126
                warn("Field Length for " + inFieldName + " set to " +
127
                    inFieldLength +
128
                    " Which is longer than 254, not consistent with dbase III");
129
            }
130

    
131
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 8;
132
        } else if ((inFieldType == 'D') || (inFieldType == 'd')) {
133
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'D';
134

    
135
            if (inFieldLength != 8) {
136
                warn("Field Length for " + inFieldName + " set to " +
137
                    inFieldLength + " Setting to 8 digets YYYYMMDD");
138
            }
139

    
140
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 8;
141
        } else if ((inFieldType == 'F') || (inFieldType == 'f')) {
142
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'F';
143

    
144
            if (inFieldLength > 20) {
145
                warn("Field Length for " + inFieldName + " set to " +
146
                    inFieldLength +
147
                    " Preserving length, but should be set to Max of 20 not valid for dbase IV, and UP specification, not present in dbaseIII.");
148
            }
149
        } else if ((inFieldType == 'N') || (inFieldType == 'n')) {
150
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'N';
151

    
152
            if (inFieldLength > 18) {
153
                warn("Field Length for " + inFieldName + " set to " +
154
                    inFieldLength +
155
                    " Preserving length, but should be set to Max of 18 for dbase III specification.");
156
            }
157

    
158
            if (inDecimalCount < 0) {
159
                warn("Field Decimal Position for " + inFieldName + " set to " +
160
                    inDecimalCount +
161
                    " Setting to 0 no decimal data will be saved.");
162
                tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = 0;
163
            }
164

    
165
            if (inDecimalCount > (inFieldLength - 1)) {
166
                warn("Field Decimal Position for " + inFieldName + " set to " +
167
                    inDecimalCount + " Setting to " + (inFieldLength - 1) +
168
                    " no non decimal data will be saved.");
169
                tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = inFieldLength -
170
                    1;
171
            }
172
        } else if ((inFieldType == 'L') || (inFieldType == 'l')) {
173
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'L';
174

    
175
            if (inFieldLength != 1) {
176
                warn("Field Length for " + inFieldName + " set to " +
177
                    inFieldLength +
178
                    " Setting to length of 1 for logical fields.");
179
            }
180

    
181
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 1;
182
        } else {
183
            throw new Exception("Undefined field type " + inFieldType +
184
                " For column " + inFieldName);
185
        }
186

    
187
        // the length of a record
188
        tempLength = tempLength +
189
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength;
190

    
191
        // set the new fields.
192
        myFieldDescriptions = tempFieldDescriptors;
193
        myHeaderLength = 33 + (32 * myFieldDescriptions.length);
194
        myNumFields = myFieldDescriptions.length;
195
        myRecordLength = tempLength;
196
    }
197

    
198
    /**
199
     * Remove a column from this DbaseFileHeader.
200
     *
201
     * @param inFieldName DOCUMENT ME!
202
     *
203
     * @return index of the removed column, -1 if no found
204
     */
205
    public int removeColumn(String inFieldName) {
206
        int retCol = -1;
207
        int tempLength = 1;
208
        DbaseFieldDescriptor[] tempFieldDescriptors = new DbaseFieldDescriptor[myFieldDescriptions.length -
209
            1];
210

    
211
        for (int i = 0, j = 0; i < myFieldDescriptions.length; i++) {
212
            if (!inFieldName.equalsIgnoreCase(
213
                        myFieldDescriptions[i].myFieldName.trim())) {
214
                // if this is the last field and we still haven't found the
215
                // named field
216
                if ((i == j) && (i == (myFieldDescriptions.length - 1))) {
217
                    System.err.println("Could not find a field named '" +
218
                        inFieldName + "' for removal");
219

    
220
                    return retCol;
221
                }
222

    
223
                tempFieldDescriptors[j] = myFieldDescriptions[i];
224
                tempFieldDescriptors[j].myFieldDataAddress = tempLength;
225
                tempLength += tempFieldDescriptors[j].myFieldLength;
226

    
227
                // only increment j on non-matching fields
228
                j++;
229
            } else {
230
                retCol = i;
231
            }
232
        }
233

    
234
        // set the new fields.
235
        myFieldDescriptions = tempFieldDescriptors;
236
        myHeaderLength = 33 + (32 * myFieldDescriptions.length);
237
        myNumFields = myFieldDescriptions.length;
238
        myRecordLength = tempLength;
239

    
240
        return retCol;
241
    }
242

    
243
    /**
244
     * DOCUMENT ME!
245
     *
246
     * @param inWarn DOCUMENT ME!
247
     */
248
    private void warn(String inWarn) {
249
        //TODO Descomentar esto cuando tenga la clase warning support
250
        //            warnings.warn(inWarn);
251
    }
252

    
253
    /**
254
     * Return the Field Descriptor for the given field.
255
     *
256
     * @param inIndex DOCUMENT ME!
257
     *
258
     * @return DOCUMENT ME!
259
     */
260
    public DbaseFieldDescriptor getFieldDescription(int inIndex) {
261
        return myFieldDescriptions[inIndex];
262
    }
263

    
264
    // Retrieve the length of the field at the given index
265
    public int getFieldLength(int inIndex) {
266
        return myFieldDescriptions[inIndex].myFieldLength;
267
    }
268

    
269
    // Retrieve the location of the decimal point within the field.
270
    public int getFieldDecimalCount(int inIndex) {
271
        return myFieldDescriptions[inIndex].myDecimalCount;
272
    }
273

    
274
    // Retrieve the Name of the field at the given index
275
    public String getFieldName(int inIndex) {
276
        return myFieldDescriptions[inIndex].myFieldName;
277
    }
278

    
279
    // Retrieve the type of field at the given index
280
    public char getFieldType(int inIndex) {
281
        return myFieldDescriptions[inIndex].myFieldType;
282
    }
283

    
284
    /**
285
     * Return the date this file was last updated.
286
     *
287
     * @return DOCUMENT ME!
288
     */
289
    public Date getLastUpdateDate() {
290
        return myUpdateDate;
291
    }
292

    
293
    /**
294
     * Return the number of fields in the records.
295
     *
296
     * @return DOCUMENT ME!
297
     */
298
    public int getNumFields() {
299
        return myNumFields;
300
    }
301

    
302
    /**
303
     * Return the number of records in the file
304
     *
305
     * @return DOCUMENT ME!
306
     */
307
    public int getNumRecords() {
308
        return myNumRecords;
309
    }
310

    
311
    /**
312
     * Return the length of the records in bytes.
313
     *
314
     * @return DOCUMENT ME!
315
     */
316
    public int getRecordLength() {
317
        return myRecordLength;
318
    }
319

    
320
    /**
321
     * Return the length of the header
322
     *
323
     * @return DOCUMENT ME!
324
     */
325
    public int getHeaderLength() {
326
        return myHeaderLength;
327
    }
328

    
329
    /**
330
     * Read the header data from the DBF file.
331
     *
332
     * @param in DOCUMENT ME!
333
     *
334
     * @throws IOException DOCUMENT ME!
335
     */
336
    public void readHeader(BigByteBuffer in) throws IOException {
337
        // type of file.
338
        myFileType = in.get();
339

    
340
        if (myFileType != 0x03) {
341
            throw new IOException("Unsupported DBF file Type " +
342
                Integer.toHexString(myFileType));
343
        }
344

    
345
        // parse the update date information.
346
        int tempUpdateYear = (int) in.get();
347
        int tempUpdateMonth = (int) in.get();
348
        int tempUpdateDay = (int) in.get();
349
        tempUpdateYear = tempUpdateYear + 1900;
350

    
351
        Calendar c = Calendar.getInstance();
352
        c.set(Calendar.YEAR, tempUpdateYear);
353
        c.set(Calendar.MONTH, tempUpdateMonth - 1);
354
        c.set(Calendar.DATE, tempUpdateDay);
355
        myUpdateDate = c.getTime();
356

    
357
        // read the number of records.
358
        in.order(ByteOrder.LITTLE_ENDIAN);
359
        myNumRecords = in.getInt();
360

    
361
        // read the length of the header structure.
362
        myHeaderLength = in.getShort();
363

    
364
        // read the length of a record
365
        myRecordLength = in.getShort();
366

    
367
        in.order(ByteOrder.BIG_ENDIAN);
368

    
369
        // skip the reserved bytes in the header.
370
        in.position(in.position() + 20);
371

    
372
        // calculate the number of Fields in the header
373
        myNumFields = (myHeaderLength - FILE_DESCRIPTOR_SIZE - 1) / FILE_DESCRIPTOR_SIZE;
374

    
375
        // read all of the header records
376
        myFieldDescriptions = new DbaseFieldDescriptor[myNumFields];
377
        int fieldOffset = 0;
378

    
379
        for (int i = 0; i < myNumFields; i++) {
380
            myFieldDescriptions[i] = new DbaseFieldDescriptor();
381

    
382
            // read the field name
383
            byte[] buffer = new byte[11];
384
            in.get(buffer);
385
            myFieldDescriptions[i].myFieldName = new String(buffer);
386

    
387
            // read the field type
388
            myFieldDescriptions[i].myFieldType = (char) in.get();
389

    
390
            // read the field data address, offset from the start of the record.
391
            myFieldDescriptions[i].myFieldDataAddress = in.getInt();
392

    
393
            // read the field length in bytes
394
            int tempLength = (int) in.get();
395

    
396
            if (tempLength < 0) {
397
                tempLength = tempLength + 256;
398
            }
399

    
400
            myFieldDescriptions[i].myFieldLength = tempLength;
401

    
402
            // read the field decimal count in bytes
403
            myFieldDescriptions[i].myDecimalCount = (int) in.get();
404

    
405
            // NUEVO: Calculamos los offsets aqu? para no
406
            // tener que recalcular cada vez que nos piden
407
            // algo.            
408
            myFieldDescriptions[i].myFieldDataAddress = fieldOffset;
409
            fieldOffset += tempLength;
410
            // Fin NUEVO
411
            // read the reserved bytes.
412
            in.position(in.position() + 14);
413
        }
414

    
415
        // Last byte is a marker for the end of the field definitions.
416
        in.get();
417
    }
418

    
419
    /**
420
     * Set the number of records in the file
421
     *
422
     * @param inNumRecords DOCUMENT ME!
423
     */
424
    protected void setNumRecords(int inNumRecords) {
425
        myNumRecords = inNumRecords;
426
    }
427

    
428
    /*
429
     * Write the header data to the DBF file.
430
     *
431
     * @param out DOCUMENT ME!
432
     *
433
     * @throws Exception DOCUMENT ME!
434
     *
435
           public void writeHeader(LEDataOutputStream out) throws Exception {
436
               // write the output file type.
437
               out.writeByte(myFileType);
438
               // write the date stuff
439
               Calendar c = Calendar.getInstance();
440
               c.setTime(new Date());
441
               out.writeByte(c.get(Calendar.YEAR) - 1900);
442
               out.writeByte(c.get(Calendar.MONTH) + 1);
443
               out.writeByte(c.get(Calendar.DAY_OF_MONTH));
444
               // write the number of records in the datafile.
445
               out.writeInt(myNumRecords);
446
               // write the length of the header structure.
447
               out.writeShort(myHeaderLength);
448
               // write the length of a record
449
               out.writeShort(myRecordLength);
450
               // write the reserved bytes in the header
451
               for (int i = 0; i < 20; i++)
452
                   out.writeByte(0);
453
               // write all of the header records
454
               int tempOffset = 0;
455
               for (int i = 0; i < myFieldDescriptions.length; i++) {
456
                   // write the field name
457
                   for (int j = 0; j < 11; j++) {
458
                       if (myFieldDescriptions[i].myFieldName.length() > j) {
459
                           out.writeByte((int) myFieldDescriptions[i].myFieldName.charAt(
460
                                   j));
461
                       } else {
462
                           out.writeByte(0);
463
                       }
464
                   }
465
                   // write the field type
466
                   out.writeByte(myFieldDescriptions[i].myFieldType);
467
                   // write the field data address, offset from the start of the record.
468
                   out.writeInt(tempOffset);
469
                   tempOffset += myFieldDescriptions[i].myFieldLength;
470
                   // write the length of the field.
471
                   out.writeByte(myFieldDescriptions[i].myFieldLength);
472
                   // write the decimal count.
473
                   out.writeByte(myFieldDescriptions[i].myDecimalCount);
474
                   // write the reserved bytes.
475
                   for (int j = 0; j < 14; j++)
476
                       out.writeByte(0);
477
               }
478
               // write the end of the field definitions marker
479
               out.writeByte(0x0D);
480
           }
481
     */
482

    
483
    /**
484
     * Class for holding the information assicated with a record.
485
     */
486
    class DbaseFieldDescriptor {
487
        // Field Name
488
        String myFieldName;
489

    
490
        // Field Type (C N L D F or M)
491
        char myFieldType;
492

    
493
        // Field Data Address offset from the start of the record.
494
        int myFieldDataAddress;
495

    
496
        // Length of the data in bytes
497
        int myFieldLength;
498

    
499
        // Field decimal count in Binary, indicating where the decimal is
500
        int myDecimalCount;
501
    }
502
}