Statistics
| Revision:

svn-gvsig-desktop / trunk / libraries / libDataSourceBaseDrivers / src / org / gvsig / data / datastores / vectorial / file / dbf / utils / DbaseFileHeader.java @ 20414

History | View | Annotate | Download (22.2 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 org.gvsig.data.datastores.vectorial.file.dbf.utils;
8

    
9
import java.io.IOException;
10
import java.nio.ByteBuffer;
11
import java.nio.ByteOrder;
12
import java.nio.channels.FileChannel;
13
import java.util.Calendar;
14
import java.util.Date;
15
import java.util.Iterator;
16

    
17
import org.gvsig.data.exception.UnsupportedTypeException;
18
import org.gvsig.data.exception.UnsupportedVersionException;
19
import org.gvsig.data.vectorial.IFeatureAttributeDescriptor;
20
import org.gvsig.data.vectorial.IFeatureType;
21

    
22
import com.iver.utiles.bigfile.BigByteBuffer2;
23

    
24

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

    
33
        // type of the file, must be 03h
34
        private static final byte MAGIC = 0x03;
35

    
36
        private static final int MINIMUM_HEADER = 33;
37

    
38
    // type of the file, must be 03h
39
    private int myFileType = 0x03;
40

    
41
    // Date the file was last updated.
42
    private Date myUpdateDate = new Date();
43

    
44
    // Number of records in the datafile
45
    private int myNumRecords = 0;
46

    
47
    // Length of the header structure
48
    private int myHeaderLength;
49

    
50
    // Length of the records
51
    private int myRecordLength;
52

    
53
    // Number of fields in the record.
54
    private int myNumFields;
55

    
56
    // collection of header records.
57
    private DbaseFieldDescriptor[] myFieldDescriptions;
58

    
59
        private byte myLanguageID;
60

    
61
    /**
62
     * DbaseFileHreader constructor comment.
63
     */
64
    public DbaseFileHeader() {
65
        super();
66
    }
67

    
68
    /**
69
     * Add a column to this DbaseFileHeader. The type is one of (C N L or D)
70
     * character, number, logical(true/false), or date. The Field length is
71
     * the total length in bytes reserved for this column. The decimal count
72
     * only applies to numbers(N), and floating point values (F), and refers
73
     * to the number of characters to reserve after the decimal point.
74
     *
75
     * @param inFieldName DOCUMENT ME!
76
     * @param inFieldType DOCUMENT ME!
77
     * @param inFieldLength DOCUMENT ME!
78
     * @param inDecimalCount DOCUMENT ME!
79
     * @throws BadFieldDriverException
80
     *
81
     * @throws Exception DOCUMENT ME!
82
     */
83
    public void addColumn(String inFieldName, char inFieldType,
84
        int inFieldLength, int inDecimalCount) throws UnsupportedTypeException {
85
        if (inFieldLength <= 0) {
86
            inFieldLength = 1;
87
        }
88

    
89
        if (myFieldDescriptions == null) {
90
            myFieldDescriptions = new DbaseFieldDescriptor[0];
91
        }
92

    
93
        int tempLength = 1; // the length is used for the offset, and there is a * for deleted as the first byte
94
        DbaseFieldDescriptor[] tempFieldDescriptors = new DbaseFieldDescriptor[myFieldDescriptions.length +
95
            1];
96

    
97
        for (int i = 0; i < myFieldDescriptions.length; i++) {
98
            myFieldDescriptions[i].myFieldDataAddress = tempLength;
99
            tempLength = tempLength + myFieldDescriptions[i].myFieldLength;
100
            tempFieldDescriptors[i] = myFieldDescriptions[i];
101
        }
102

    
103
        tempFieldDescriptors[myFieldDescriptions.length] = new DbaseFieldDescriptor();
104
        tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = inFieldLength;
105
        tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = inDecimalCount;
106
        tempFieldDescriptors[myFieldDescriptions.length].myFieldDataAddress = tempLength;
107

    
108
        // set the field name
109
        String tempFieldName = inFieldName;
110

    
111
        if (tempFieldName == null) {
112
            tempFieldName = "NoName";
113
        }
114

    
115
        if (tempFieldName.length() > 11) {
116
            tempFieldName = tempFieldName.substring(0, 11);
117
            warn("FieldName " + inFieldName +
118
                " is longer than 11 characters, truncating to " +
119
                tempFieldName);
120
        }
121

    
122
        tempFieldDescriptors[myFieldDescriptions.length].myFieldName = tempFieldName;
123

    
124
        // the field type
125
        if ((inFieldType == 'C') || (inFieldType == 'c')) {
126
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'C';
127

    
128
            if (inFieldLength > 254) {
129
                warn("Field Length for " + inFieldName + " set to " +
130
                    inFieldLength +
131
                    " Which is longer than 254, not consistent with dbase III");
132
            }
133
        } else if ((inFieldType == 'S') || (inFieldType == 's')) {
134
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'C';
135
            warn("Field type for " + inFieldName +
136
                " set to S which is flat out wrong people!, I am setting this to C, in the hopes you meant character.");
137

    
138
            if (inFieldLength > 254) {
139
                warn("Field Length for " + inFieldName + " set to " +
140
                    inFieldLength +
141
                    " Which is longer than 254, not consistent with dbase III");
142
            }
143

    
144
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 8;
145
        } else if ((inFieldType == 'D') || (inFieldType == 'd')) {
146
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'D';
147

    
148
            if (inFieldLength != 8) {
149
                warn("Field Length for " + inFieldName + " set to " +
150
                    inFieldLength + " Setting to 8 digets YYYYMMDD");
151
            }
152

    
153
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 8;
154
        } else if ((inFieldType == 'F') || (inFieldType == 'f')) {
155
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'F';
156

    
157
            if (inFieldLength > 20) {
158
                warn("Field Length for " + inFieldName + " set to " +
159
                    inFieldLength +
160
                    " Preserving length, but should be set to Max of 20 not valid for dbase IV, and UP specification, not present in dbaseIII.");
161
            }
162
        } else if ((inFieldType == 'N') || (inFieldType == 'n')) {
163
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'N';
164

    
165
            if (inFieldLength > 18) {
166
                warn("Field Length for " + inFieldName + " set to " +
167
                    inFieldLength +
168
                    " Preserving length, but should be set to Max of 18 for dbase III specification.");
169
            }
170

    
171
            if (inDecimalCount < 0) {
172
                warn("Field Decimal Position for " + inFieldName + " set to " +
173
                    inDecimalCount +
174
                    " Setting to 0 no decimal data will be saved.");
175
                tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = 0;
176
            }
177

    
178
            if (inDecimalCount > (inFieldLength - 1)) {
179
                warn("Field Decimal Position for " + inFieldName + " set to " +
180
                    inDecimalCount + " Setting to " + (inFieldLength - 1) +
181
                    " no non decimal data will be saved.");
182
                tempFieldDescriptors[myFieldDescriptions.length].myDecimalCount = inFieldLength -
183
                    1;
184
            }
185
        } else if ((inFieldType == 'L') || (inFieldType == 'l')) {
186
            tempFieldDescriptors[myFieldDescriptions.length].myFieldType = 'L';
187

    
188
            if (inFieldLength != 1) {
189
                warn("Field Length for " + inFieldName + " set to " +
190
                    inFieldLength +
191
                    " Setting to length of 1 for logical fields.");
192
            }
193

    
194
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength = 1;
195
        } else {
196
            throw new UnsupportedTypeException("DBF", ""+inFieldType, tempFieldName);
197
        }
198

    
199
        // the length of a record
200
        tempLength = tempLength +
201
            tempFieldDescriptors[myFieldDescriptions.length].myFieldLength;
202

    
203
        // set the new fields.
204
        myFieldDescriptions = tempFieldDescriptors;
205
        myHeaderLength = 33 + (32 * myFieldDescriptions.length);
206
        myNumFields = myFieldDescriptions.length;
207
        myRecordLength = tempLength;
208
    }
209

    
210
    /**
211
     * Remove a column from this DbaseFileHeader.
212
     *
213
     * @param inFieldName DOCUMENT ME!
214
     *
215
     * @return index of the removed column, -1 if no found
216
     */
217
    public int removeColumn(String inFieldName) {
218
        int retCol = -1;
219
        int tempLength = 1;
220
        DbaseFieldDescriptor[] tempFieldDescriptors = new DbaseFieldDescriptor[myFieldDescriptions.length -
221
            1];
222

    
223
        for (int i = 0, j = 0; i < myFieldDescriptions.length; i++) {
224
            if (!inFieldName.equalsIgnoreCase(
225
                        myFieldDescriptions[i].myFieldName.trim())) {
226
                // if this is the last field and we still haven't found the
227
                // named field
228
                if ((i == j) && (i == (myFieldDescriptions.length - 1))) {
229
                    System.err.println("Could not find a field named '" +
230
                        inFieldName + "' for removal");
231

    
232
                    return retCol;
233
                }
234

    
235
                tempFieldDescriptors[j] = myFieldDescriptions[i];
236
                tempFieldDescriptors[j].myFieldDataAddress = tempLength;
237
                tempLength += tempFieldDescriptors[j].myFieldLength;
238

    
239
                // only increment j on non-matching fields
240
                j++;
241
            } else {
242
                retCol = i;
243
            }
244
        }
245

    
246
        // set the new fields.
247
        myFieldDescriptions = tempFieldDescriptors;
248
        myHeaderLength = 33 + (32 * myFieldDescriptions.length);
249
        myNumFields = myFieldDescriptions.length;
250
        myRecordLength = tempLength;
251

    
252
        return retCol;
253
    }
254

    
255
    /**
256
     * DOCUMENT ME!
257
     *
258
     * @param inWarn DOCUMENT ME!
259
     */
260
    private void warn(String inWarn) {
261
        //TODO Descomentar esto cuando tenga la clase warning support
262
        //            warnings.warn(inWarn);
263
    }
264

    
265
    /**
266
     * Return the Field Descriptor for the given field.
267
     *
268
     * @param inIndex DOCUMENT ME!
269
     *
270
     * @return DOCUMENT ME!
271
     */
272
    public DbaseFieldDescriptor getFieldDescription(int inIndex) {
273
        return myFieldDescriptions[inIndex];
274
    }
275

    
276
    // Retrieve the length of the field at the given index
277
    public int getFieldLength(int inIndex) {
278
        return myFieldDescriptions[inIndex].myFieldLength;
279
    }
280

    
281
    // Retrieve the location of the decimal point within the field.
282
    public int getFieldDecimalCount(int inIndex) {
283
        return myFieldDescriptions[inIndex].myDecimalCount;
284
    }
285

    
286
    // Retrieve the Name of the field at the given index
287
    public String getFieldName(int inIndex) {
288
        return myFieldDescriptions[inIndex].myFieldName;
289
    }
290

    
291
    // Retrieve the type of field at the given index
292
    public char getFieldType(int inIndex) {
293
        return myFieldDescriptions[inIndex].myFieldType;
294
    }
295

    
296
    /**
297
     * Return the date this file was last updated.
298
     *
299
     * @return DOCUMENT ME!
300
     */
301
    public Date getLastUpdateDate() {
302
        return myUpdateDate;
303
    }
304

    
305
     /**
306
     * Return the number of fields in the records.
307
     *
308
     * @return DOCUMENT ME!
309
     */
310
    public int getNumFields() {
311
        return myNumFields;
312
    }
313

    
314
    /**
315
     * Return the number of records in the file
316
     *
317
     * @return DOCUMENT ME!
318
     */
319
    public int getNumRecords() {
320
        return myNumRecords;
321
    }
322

    
323
    /**
324
     * Return the length of the records in bytes.
325
     *
326
     * @return DOCUMENT ME!
327
     */
328
    public int getRecordLength() {
329
        return myRecordLength;
330
    }
331

    
332
    /**
333
     * Return the length of the header
334
     *
335
     * @return DOCUMENT ME!
336
     */
337
    public int getHeaderLength() {
338
        return myHeaderLength;
339
    }
340

    
341
    /**
342
     * Read the header data from the DBF file.
343
     *
344
     * @param in DOCUMENT ME!
345
     * @throws UnsupportedVersionException
346
     *
347
     * @throws IOException DOCUMENT ME!
348
     */
349
    public void readHeader(BigByteBuffer2 in) throws UnsupportedVersionException {
350
        // type of file.
351
        myFileType = in.get();
352

    
353
        if (myFileType != 0x03) {
354
            throw new UnsupportedVersionException("Unsupported DBF file Type " +
355
                Integer.toHexString(myFileType),new Exception());
356
        }
357

    
358
        // parse the update date information.
359
        int tempUpdateYear = (int) in.get();
360
        int tempUpdateMonth = (int) in.get();
361
        int tempUpdateDay = (int) in.get();
362
        tempUpdateYear = tempUpdateYear + 1900;
363

    
364
        Calendar c = Calendar.getInstance();
365
        c.set(Calendar.YEAR, tempUpdateYear);
366
        c.set(Calendar.MONTH, tempUpdateMonth - 1);
367
        c.set(Calendar.DATE, tempUpdateDay);
368
        myUpdateDate = c.getTime();
369

    
370
        // read the number of records.
371
        in.order(ByteOrder.LITTLE_ENDIAN);
372
        myNumRecords = in.getInt();
373

    
374
        // read the length of the header structure.
375
        myHeaderLength = in.getShort();
376

    
377
        // read the length of a record
378
        myRecordLength = in.getShort(); //posicon 0h
379

    
380
        in.order(ByteOrder.BIG_ENDIAN);
381

    
382
        // skip the reserved bytes in the header.
383
        // in.position(in.position() + 20);
384

    
385
        // Leemos el byte de language
386
        in.position(29);
387
        myLanguageID = in.get();
388

    
389
        // Posicionamos para empezar a leer los campos.
390
        in.position(32);
391

    
392
        // calculate the number of Fields in the header
393
        myNumFields = (myHeaderLength - FILE_DESCRIPTOR_SIZE - 1) / FILE_DESCRIPTOR_SIZE;
394

    
395
        // read all of the header records
396
        myFieldDescriptions = new DbaseFieldDescriptor[myNumFields];
397
        int fieldOffset = 0;
398

    
399
        for (int i = 0; i < myNumFields; i++) {
400
            myFieldDescriptions[i] = new DbaseFieldDescriptor();
401

    
402
            // read the field name
403
            byte[] buffer = new byte[11];
404
            in.get(buffer);
405
            myFieldDescriptions[i].myFieldName = new String(buffer);
406

    
407
            // read the field type
408
            myFieldDescriptions[i].myFieldType = (char) in.get();
409

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

    
413
            // read the field length in bytes
414
            int tempLength = (int) in.get();
415

    
416
            if (tempLength < 0) {
417
                tempLength = tempLength + 256;
418
            }
419

    
420
            myFieldDescriptions[i].myFieldLength = tempLength;
421

    
422
            // read the field decimal count in bytes
423
            myFieldDescriptions[i].myDecimalCount = (int) in.get();
424

    
425
            // NUEVO: Calculamos los offsets aqu? para no
426
            // tener que recalcular cada vez que nos piden
427
            // algo.
428
            myFieldDescriptions[i].myFieldDataAddress = fieldOffset;
429
            fieldOffset += tempLength;
430
            // Fin NUEVO
431
            // read the reserved bytes.
432
            in.position(in.position() + 14);
433
        }
434

    
435
        // Last byte is a marker for the end of the field definitions.
436
        in.get();
437
    }
438

    
439
    /**
440
     * Set the number of records in the file
441
     *
442
     * @param inNumRecords DOCUMENT ME!
443
     */
444
    public void setNumRecords(int inNumRecords) {
445
        myNumRecords = inNumRecords;
446
    }
447

    
448
    /*
449
     * Write the header data to the DBF file.
450
     *
451
     * @param out DOCUMENT ME!
452
     *
453
     * @throws Exception DOCUMENT ME!
454
     *
455
           public void writeHeader(LEDataOutputStream out) throws Exception {
456
               // write the output file type.
457
               out.writeByte(myFileType);
458
               // write the date stuff
459
               Calendar c = Calendar.getInstance();
460
               c.setTime(new Date());
461
               out.writeByte(c.get(Calendar.YEAR) - 1900);
462
               out.writeByte(c.get(Calendar.MONTH) + 1);
463
               out.writeByte(c.get(Calendar.DAY_OF_MONTH));
464
               // write the number of records in the datafile.
465
               out.writeInt(myNumRecords);
466
               // write the length of the header structure.
467
               out.writeShort(myHeaderLength);
468
               // write the length of a record
469
               out.writeShort(myRecordLength);
470
               // write the reserved bytes in the header
471
               for (int i = 0; i < 20; i++)
472
                   out.writeByte(0);
473
               // write all of the header records
474
               int tempOffset = 0;
475
               for (int i = 0; i < myFieldDescriptions.length; i++) {
476
                   // write the field name
477
                   for (int j = 0; j < 11; j++) {
478
                       if (myFieldDescriptions[i].myFieldName.length() > j) {
479
                           out.writeByte((int) myFieldDescriptions[i].myFieldName.charAt(
480
                                   j));
481
                       } else {
482
                           out.writeByte(0);
483
                       }
484
                   }
485
                   // write the field type
486
                   out.writeByte(myFieldDescriptions[i].myFieldType);
487
                   // write the field data address, offset from the start of the record.
488
                   out.writeInt(tempOffset);
489
                   tempOffset += myFieldDescriptions[i].myFieldLength;
490
                   // write the length of the field.
491
                   out.writeByte(myFieldDescriptions[i].myFieldLength);
492
                   // write the decimal count.
493
                   out.writeByte(myFieldDescriptions[i].myDecimalCount);
494
                   // write the reserved bytes.
495
                   for (int j = 0; j < 14; j++)
496
                       out.writeByte(0);
497
               }
498
               // write the end of the field definitions marker
499
               out.writeByte(0x0D);
500
           }
501
     */
502

    
503
    /**
504
     * Class for holding the information assicated with a record.
505
     */
506
    class DbaseFieldDescriptor {
507
        // Field Name
508
        String myFieldName;
509

    
510
        // Field Type (C N L D F or M)
511
        char myFieldType;
512

    
513
        // Field Data Address offset from the start of the record.
514
        int myFieldDataAddress;
515

    
516
        // Length of the data in bytes
517
        int myFieldLength;
518

    
519
        // Field decimal count in Binary, indicating where the decimal is
520
        int myDecimalCount;
521
    }
522

    
523
        public byte getLanguageID() {
524
                return myLanguageID;
525
        }
526

    
527
        public static DbaseFileHeader createDbaseHeader(IFeatureType featureType) throws UnsupportedTypeException {
528
                DbaseFileHeader header = new DbaseFileHeader();
529
                Iterator iterator=featureType.iterator();
530
                while (iterator.hasNext()) {
531
                        IFeatureAttributeDescriptor descriptor = (IFeatureAttributeDescriptor) iterator.next();
532

    
533

    
534
                        String type = descriptor.getDataType();
535
                        String colName = descriptor.getName();
536

    
537
                        int fieldLen = descriptor.getSize(); // TODO aqu? el
538
                        // tama?o no es
539
                        // correcto hay que
540
                        // calcularlo, ahora
541
                        // mismo est? puesto
542
                        // a pi??n.
543
                        int decimales = descriptor.getPrecision();
544

    
545
                        if (IFeatureAttributeDescriptor.TYPE_DOUBLE == type ||
546
                                        IFeatureAttributeDescriptor.TYPE_FLOAT == type ||
547
                                        IFeatureAttributeDescriptor.TYPE_INT == type ||
548
                                        IFeatureAttributeDescriptor.TYPE_LONG == type ){
549
                                header.addColumn(colName, 'N', Math.min(fieldLen, 18),
550
                                                decimales);
551
                        }else if (IFeatureAttributeDescriptor.TYPE_DATE == type ){
552
                                header.addColumn(colName, 'D', fieldLen, 0);
553
                        }else if (IFeatureAttributeDescriptor.TYPE_BOOLEAN == type ){
554
                                header.addColumn(colName, 'L', 1, 0);
555
                        }else if (IFeatureAttributeDescriptor.TYPE_STRING == type ){
556
                                header.addColumn(colName, 'C', Math.min(254, fieldLen), 0);
557
                        }
558

    
559

    
560
                }
561
                return header;
562
        }
563
        /**
564
         * Write the header data to the DBF file.
565
         *
566
         * @param out
567
         *            A channel to write to. If you have an OutputStream you can
568
         *            obtain the correct channel by using
569
         *            java.nio.Channels.newChannel(OutputStream out).
570
         *
571
         * @throws IOException
572
         *             If errors occur.
573
         */
574
        public void writeHeader(FileChannel out) throws IOException {
575
                // take care of the annoying case where no records have been added...
576
                if (myHeaderLength == -1) {
577
                        myHeaderLength = MINIMUM_HEADER;
578
                }
579

    
580
                // Desde el principio
581
                out.position(0);
582

    
583
                ByteBuffer buffer = ByteBuffer.allocateDirect(myHeaderLength);
584
                buffer.order(ByteOrder.LITTLE_ENDIAN);
585

    
586
                // write the output file type.
587
                buffer.put((byte) MAGIC);
588

    
589
                // write the date stuff
590
                Calendar c = Calendar.getInstance();
591
                c.setTime(new Date());
592
                buffer.put((byte) (c.get(Calendar.YEAR) % 100));
593
                buffer.put((byte) (c.get(Calendar.MONTH) + 1));
594
                buffer.put((byte) (c.get(Calendar.DAY_OF_MONTH)));
595

    
596
                // write the number of records in the datafile.
597
                buffer.putInt(myNumRecords);
598

    
599
                // write the length of the header structure.
600
                buffer.putShort((short) myHeaderLength);
601

    
602
                // write the length of a record
603
                buffer.putShort((short) myRecordLength);
604

    
605
                // // write the reserved bytes in the header
606
                // for (int i=0; i<20; i++) out.writeByteLE(0);
607
                buffer.position(buffer.position() + 20);
608

    
609
                // write all of the header records
610
                int tempOffset = 0;
611

    
612
                if (myFieldDescriptions != null) {
613
                        for (int i = 0; i < myFieldDescriptions.length; i++) {
614
                                // write the field name
615
                                for (int j = 0; j < 11; j++) {
616
                                        if (myFieldDescriptions[i].myFieldName.length() > j) {
617
                                                buffer.put((byte) myFieldDescriptions[i].myFieldName.charAt(j));
618
                                        } else {
619
                                                buffer.put((byte) 0);
620
                                        }
621
                                }
622

    
623
                                // write the field type
624
                                buffer.put((byte) myFieldDescriptions[i].myFieldType);
625

    
626
                                // // write the field data address, offset from the start of the
627
                                // record.
628
                                buffer.putInt(tempOffset);
629
                                tempOffset += myFieldDescriptions[i].myFieldLength;
630

    
631
                                // write the length of the field.
632
                                buffer.put((byte) myFieldDescriptions[i].myFieldLength);
633

    
634
                                // write the decimal count.
635
                                buffer.put((byte) myFieldDescriptions[i].myDecimalCount);
636

    
637
                                // write the reserved bytes.
638
                                // for (in j=0; jj<14; j++) out.writeByteLE(0);
639
                                buffer.position(buffer.position() + 14);
640
                        }
641
                }
642
                // write the end of the field definitions marker
643
                buffer.put((byte) 0x0D);
644

    
645
                buffer.position(0);
646

    
647
                int r = buffer.remaining();
648

    
649
                while ((r -= out.write(buffer)) > 0) {
650
                        ; // do nothing
651
                }
652
        }
653

    
654
}