Statistics
| Revision:

root / branches / Mobile_Compatible_Hito_1 / libFMap / src-file / org / gvsig / data / datastores / vectorial / file / dbf / utils / DbaseFileHeader.java @ 21606

History | View | Annotate | Download (21.6 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.ByteOrder;
11
import java.util.Calendar;
12
import java.util.Date;
13

    
14
import org.gvsig.data.exception.UnsupportedTypeException;
15
import org.gvsig.data.exception.UnsupportedVersionException;
16
import org.gvsig.data.vectorial.IFeatureAttributeDescriptor;
17
import org.gvsig.data.vectorial.IFeatureType;
18
import org.gvsig.datasources.common.IBigByteBuffer;
19
import org.gvsig.datasources.common.IByteBuffer;
20
import org.gvsig.datasources.common.IRandomFileChannel;
21
import org.gvsig.datasources.impljme.Impl;
22

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

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

    
34
        private static final int MINIMUM_HEADER = 33;
35

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

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

    
42
    // Number of records in the datafile
43
    private int myNumRecords = 0;
44

    
45
    // Length of the header structure
46
    private int myHeaderLength;
47

    
48
    // Length of the records
49
    private int myRecordLength;
50

    
51
    // Number of fields in the record.
52
    private int myNumFields;
53

    
54
    // collection of header records.
55
    private DbaseFieldDescriptor[] myFieldDescriptions;
56

    
57
        private byte myLanguageID;
58

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

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

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

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

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

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

    
106
        // set the field name
107
        String tempFieldName = inFieldName;
108

    
109
        if (tempFieldName == null) {
110
            tempFieldName = "NoName";
111
        }
112

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

    
120
        tempFieldDescriptors[myFieldDescriptions.length].myFieldName = tempFieldName;
121

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
230
                    return retCol;
231
                }
232

    
233
                tempFieldDescriptors[j] = myFieldDescriptions[i];
234
                tempFieldDescriptors[j].myFieldDataAddress = tempLength;
235
                tempLength += tempFieldDescriptors[j].myFieldLength;
236

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

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

    
250
        return retCol;
251
    }
252

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
378
        in.order(ByteOrder.BIG_ENDIAN);
379

    
380
        // skip the reserved bytes in the header.
381
        // in.position(in.position() + 20);
382

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

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

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

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

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

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

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

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

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

    
414
            if (tempLength < 0) {
415
                tempLength = tempLength + 256;
416
            }
417

    
418
            myFieldDescriptions[i].myFieldLength = tempLength;
419

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

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

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

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

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

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

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

    
511
        // Field Data Address offset from the start of the record.
512
        int myFieldDataAddress;
513

    
514
        // Length of the data in bytes
515
        int myFieldLength;
516

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

    
521
        public byte getLanguageID() {
522
                return myLanguageID;
523
        }
524

    
525
        public static DbaseFileHeader createDbaseHeader(IFeatureType featureType) throws UnsupportedTypeException {
526
                DbaseFileHeader header = new DbaseFileHeader();
527

    
528
                for (int i=0;i<featureType.size();i++) {
529
                        IFeatureAttributeDescriptor descriptor= (IFeatureAttributeDescriptor)featureType.get(i);
530

    
531
                        String type = descriptor.getDataType();
532
                        String colName = descriptor.getName();
533

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

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

    
556

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

    
577
                // Desde el principio
578
                out.position(0);
579

    
580
                IByteBuffer buffer = Impl.allocateDirect(myHeaderLength);
581
                buffer.order(ByteOrder.LITTLE_ENDIAN);
582

    
583
                // write the output file type.
584
                buffer.put((byte) MAGIC);
585

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

    
593
                // write the number of records in the datafile.
594
                buffer.putInt(myNumRecords);
595

    
596
                // write the length of the header structure.
597
                buffer.putShort((short) myHeaderLength);
598

    
599
                // write the length of a record
600
                buffer.putShort((short) myRecordLength);
601

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

    
606
                // write all of the header records
607
                int tempOffset = 0;
608

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

    
620
                                // write the field type
621
                                buffer.put((byte) myFieldDescriptions[i].myFieldType);
622

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

    
628
                                // write the length of the field.
629
                                buffer.put((byte) myFieldDescriptions[i].myFieldLength);
630

    
631
                                // write the decimal count.
632
                                buffer.put((byte) myFieldDescriptions[i].myDecimalCount);
633

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

    
642
                buffer.position(0);
643

    
644
                int r = buffer.remaining();
645

    
646
                while ((r -= out.write(buffer)) > 0) {
647
                        ; // do nothing
648
                }
649
        }
650

    
651
}