Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.dal / org.gvsig.fmap.dal.file / org.gvsig.fmap.dal.file.dbf / src / main / java / org / gvsig / fmap / dal / store / dbf / utils / DbaseFile.java @ 44001

History | View | Annotate | Download (14.2 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.fmap.dal.store.dbf.utils;
25

    
26
import java.io.File;
27
import java.io.IOException;
28
import java.io.RandomAccessFile;
29
import java.nio.ByteBuffer;
30
import java.nio.channels.FileChannel;
31
import java.nio.charset.Charset;
32
import java.text.FieldPosition;
33
import java.text.NumberFormat;
34
import java.util.Calendar;
35
import java.util.Date;
36
import java.util.Locale;
37

    
38
import org.gvsig.fmap.dal.exception.CloseException;
39
import org.gvsig.fmap.dal.exception.FileNotFoundException;
40
import org.gvsig.fmap.dal.exception.UnsupportedEncodingException;
41
import org.gvsig.fmap.dal.exception.UnsupportedVersionException;
42
import org.gvsig.fmap.dal.exception.WriteException;
43
import org.gvsig.fmap.dal.feature.EditableFeatureType;
44
import org.gvsig.utils.bigfile.BigByteBuffer2;
45

    
46
import org.exolab.castor.util.List;
47
import org.slf4j.Logger;
48
import org.slf4j.LoggerFactory;
49

    
50

    
51
/**
52
 * Class to read and write data to a dbase III format file. Creation date:
53
 * (5/15/2001 5:15:13 PM)
54
 */
55
public class DbaseFile {
56

    
57
    private static final Logger logger = LoggerFactory.getLogger(DbaseFile.class);
58

    
59
    public static final int MAX_FIELD_NAME_LENGTH = 10;
60

    
61
        // Header information for the DBase File
62
        private DbaseFileHeader myHeader;
63

    
64
        private File file;
65

    
66
        private RandomAccessFile raf;
67

    
68
        private FileChannel channel;
69

    
70
        private BigByteBuffer2 buffer;
71

    
72
        private FileChannel.MapMode mode;
73

    
74
        private FieldFormatter formatter = new FieldFormatter();
75

    
76
        private long posActual = -1;
77

    
78
        private long recordOffset;
79

    
80
        private ByteBuffer cachedRecord = null;
81

    
82
        private byte[] bytesCachedRecord = null;
83

    
84
        private final Number NULL_NUMBER = new Integer(0);
85

    
86
        private final String NULL_STRING = "";
87

    
88
        private final String NULL_DATE = "        ";
89

    
90
        private Charset chars = null;
91
        private Charset charsOriginal;
92

    
93
        private boolean isOpen = false;
94

    
95
    private boolean allowDuplicatedFieldNames;
96

    
97

    
98
        /** Utility for formatting Dbase fields. */
99
        public static class FieldFormatter {
100
                private StringBuffer buffer = new StringBuffer(255);
101

    
102
                private NumberFormat numFormat = NumberFormat
103
                                .getNumberInstance(Locale.US);
104

    
105
                private Calendar calendar = Calendar.getInstance(Locale.US);
106

    
107
                private String emtpyString;
108

    
109
                private static final int MAXCHARS = 255;
110

    
111
                public FieldFormatter() {
112
                        // Avoid grouping on number format
113
                        numFormat.setGroupingUsed(false);
114

    
115
                        // build a 255 white spaces string
116
                        StringBuffer sb = new StringBuffer(MAXCHARS);
117
                        sb.setLength(MAXCHARS);
118
                        for (int i = 0; i < MAXCHARS; i++) {
119
                                sb.setCharAt(i, ' ');
120
                        }
121

    
122
                        emtpyString = sb.toString();
123
                }
124

    
125
                public String getFieldString(int size, String s) {
126
                        buffer.replace(0, size, emtpyString);
127
                        buffer.setLength(size);
128

    
129
                        if (s != null) {
130
                                buffer.replace(0, size, s);
131
                                if (s.length() <= size) {
132
                                        for (int i = s.length(); i < size; i++) {
133
                                                buffer.append(' ');
134
                                        }
135
                                }
136
                        }
137

    
138
                        buffer.setLength(size);
139
                        return buffer.toString();
140
                }
141

    
142
                public String getFieldString(Date d) {
143

    
144
                        if (d != null) {
145
                                buffer.delete(0, buffer.length());
146

    
147
                                calendar.setTime(d);
148
                                int year = calendar.get(Calendar.YEAR);
149
                                int month = calendar.get(Calendar.MONTH) + 1; // returns 0
150
                                                                                                                                // based month?
151
                                int day = calendar.get(Calendar.DAY_OF_MONTH);
152

    
153
                                if (year < 1000) {
154
                                        if (year >= 100) {
155
                                                buffer.append("0");
156
                                        } else if (year >= 10) {
157
                                                buffer.append("00");
158
                                        } else {
159
                                                buffer.append("000");
160
                                        }
161
                                }
162
                                buffer.append(year);
163

    
164
                                if (month < 10) {
165
                                        buffer.append("0");
166
                                }
167
                                buffer.append(month);
168

    
169
                                if (day < 10) {
170
                                        buffer.append("0");
171
                                }
172
                                buffer.append(day);
173
                        } else {
174
                                buffer.setLength(8);
175
                                buffer.replace(0, 8, emtpyString);
176
                        }
177

    
178
                        buffer.setLength(8);
179
                        return buffer.toString();
180
                }
181

    
182
                public String getFieldString(int size, int decimalPlaces, Number n) {
183
                        buffer.delete(0, buffer.length());
184

    
185
                        if (n != null) {
186
                                numFormat.setMaximumFractionDigits(decimalPlaces);
187
                                numFormat.setMinimumFractionDigits(decimalPlaces);
188
                                numFormat.format(n, buffer, new FieldPosition(
189
                                                NumberFormat.INTEGER_FIELD));
190
                        }
191

    
192
                        int diff = size - buffer.length();
193
                        if (diff >= 0) {
194
                                while (diff-- > 0) {
195
                                        buffer.insert(0, ' ');
196
                                }
197
                        } else {
198
                                buffer.setLength(size);
199
                        }
200
                        return buffer.toString();
201
                }
202
        }
203

    
204
    public DbaseFile(File afile) {
205
        this(afile, null);
206
    }
207

    
208
    public DbaseFile(File afile, Charset chars) {
209
        this(afile, chars, false);
210
    }
211

    
212
    public DbaseFile(File afile, Charset chars, boolean allowDuplicatedFieldNames) {
213
        this.file = afile;
214
        this.chars = chars;
215
        this.allowDuplicatedFieldNames = allowDuplicatedFieldNames;
216
    }
217

    
218
    /**
219
     * @deprecated Use {@link #getCodePageInt()} instead
220
     */
221
    @Deprecated
222
        public byte getCodePage() {
223
                return (byte) myHeader.getLanguageID();
224
        }
225
        
226
        public int getCodePageInt() {
227
                return myHeader.getLanguageID();
228
        }
229
        
230
        /**
231
         * Returns the charset used to read/write this dbf. Maybe different
232
         * from the declared in the file if we have forced a different one
233
         * 
234
         * @return
235
         */
236
        public String getCharsetName() { 
237
                return chars.name();
238
                //return myHeader.getCharsetName();
239
        }
240

    
241
        /**
242
         * Returns the charset declared on the dbf file (or the
243
         * default one if none is declared)
244
         * 
245
         * @return
246
         */
247
        public String getOriginalCharsetName() {
248
                return myHeader.getOriginalCharset();
249
        }
250

    
251
        // Retrieve number of records in the DbaseFile
252
        public int getRecordCount() {
253
                return myHeader.getNumRecords();
254
        }
255

    
256
        /**
257
         * DOCUMENT ME!
258
         *
259
         * @return DOCUMENT ME!
260
         */
261
        public int getFieldCount() {
262
                return myHeader.getNumFields();
263
        }
264

    
265
        /**
266
         * DOCUMENT ME!
267
         *
268
         * @param rowIndex
269
         *            DOCUMENT ME!
270
         * @param fieldId
271
         *            DOCUMENT ME!
272
         *
273
         * @return DOCUMENT ME!
274
         */
275
        public boolean getBooleanFieldValue(long rowIndex, int fieldId) {
276
                long recordOffset = (myHeader.getRecordLength() * rowIndex)
277
                                + myHeader.getHeaderLength() + 1;
278

    
279
                // Se calcula el offset del campo
280
                int fieldOffset = 0;
281

    
282
                for (int i = 0; i < (fieldId - 1); i++) {
283
                        fieldOffset += myHeader.getFieldLength(i);
284
                }
285

    
286
                buffer.position(recordOffset + fieldOffset);
287

    
288
                char bool = (char) buffer.get();
289

    
290
                return ((bool == 't') || (bool == 'T') || (bool == 'Y') || (bool == 'y'));
291
        }
292

    
293
        /**
294
         * DOCUMENT ME!
295
         *
296
         * @param rowIndex
297
         *            DOCUMENT ME!
298
         * @param fieldId
299
         *            DOCUMENT ME!
300
         *
301
         * @return DOCUMENT ME!
302
         * @throws UnsupportedEncodingException
303
         */
304
        public String getStringFieldValue(long rowIndex, int fieldId)
305
                        throws UnsupportedEncodingException {
306
                int fieldOffset = myHeader.getFieldDescription(fieldId).myFieldDataAddress;
307
                byte[] data = new byte[myHeader.getFieldLength(fieldId)];
308
                if (rowIndex != posActual) {
309
                        recordOffset = ( myHeader.getRecordLength() * rowIndex)
310
                                        + myHeader.getHeaderLength() + 1;
311

    
312
                        /*
313
                         * System.err.println("getStringFieldValue: rowIndex = " +
314
                         * rowIndex); System.err.println("recordOffset = " + recordOffset + "
315
                         * fieldOffset=" + fieldOffset);
316
                         */
317

    
318
                        buffer.position(recordOffset);
319
                        buffer.get(bytesCachedRecord);
320
                        cachedRecord = ByteBuffer.wrap(bytesCachedRecord);
321
                        posActual = rowIndex;
322

    
323
                }
324
                cachedRecord.position(fieldOffset);
325
                cachedRecord.get(data);
326

    
327
                return new String(data, chars);
328
        }
329

    
330
        public void setFieldValue(long rowIndex, int fieldId, Object obj) throws UnsupportedEncodingException, WriteException {
331
                try{
332
                        int fieldOffset = myHeader.getFieldDescription(fieldId).myFieldDataAddress;
333
                        String str = fieldString(obj, fieldId);
334
                        byte[] data = new byte[myHeader.getFieldLength(fieldId)];
335
                        recordOffset = (myHeader.getRecordLength() * rowIndex)
336
                                        + myHeader.getHeaderLength() + 1;
337

    
338
                        ByteBuffer aux = ByteBuffer.wrap(data);
339
                        aux.put(str.getBytes(chars));
340
//                        raf.seek(recordOffset + fieldOffset);
341
//                        raf.writeBytes(str);
342
                        aux.flip();
343
//                        int numBytesWritten = channel.write(aux, recordOffset + fieldOffset);
344
                        channel.write(aux, recordOffset + fieldOffset);
345
                        //channel.force(true);
346
                }catch (java.io.UnsupportedEncodingException e) {
347
                        throw new UnsupportedEncodingException(e);
348
                }catch (IOException e) {
349
                        throw new WriteException("DBF",e);
350
                }
351

    
352
        }
353

    
354

    
355
        /**
356
         * Retrieve the name of the given column.
357
         *
358
         * @param inIndex
359
         *            DOCUMENT ME!
360
         *
361
         * @return DOCUMENT ME!
362
         */
363
        public String getFieldName(int inIndex) {
364
                return myHeader.getFieldName(inIndex).trim();
365
        }
366

    
367
        /**
368
         * Retrieve the type of the given column.
369
         *
370
         * @param inIndex
371
         *            DOCUMENT ME!
372
         *
373
         * @return DOCUMENT ME!
374
         */
375
        public char getFieldType(int inIndex) {
376
                return myHeader.getFieldType(inIndex);
377
        }
378

    
379
        /**
380
         * Retrieve the length of the given column.
381
         *
382
         * @param inIndex
383
         *            DOCUMENT ME!
384
         *
385
         * @return DOCUMENT ME!
386
         */
387
        public int getFieldLength(int inIndex) {
388
                return myHeader.getFieldLength(inIndex);
389
        }
390

    
391
        /*
392
         * Retrieve the value of the given column as string.
393
         *
394
         * @param idField DOCUMENT ME! @param idRecord DOCUMENT ME!
395
         *
396
         * @return DOCUMENT ME!
397
         *
398
         * public Object getFieldValue(int idField, long idRecord) throws
399
         * IOException { Object[] tmpReg = getRecord(idRecord); return
400
         * tmpReg[idField]; }
401
         */
402
        /*
403
         * DOCUMENT ME!
404
         *
405
         * @param idField DOCUMENT ME! @param idRecord DOCUMENT ME!
406
         *
407
         * @return DOCUMENT ME!
408
         *
409
         * public double getFieldValueAsDouble(int idField, int idRecord) throws
410
         * IOException { Object[] tmpReg = getRecord(idRecord); return (double)
411
         * Double.parseDouble(tmpReg[idField].toString()); }
412
         */
413

    
414
        /**
415
         * Retrieve the location of the decimal point.
416
         *
417
         * @param inIndex
418
         *            DOCUMENT ME!
419
         *
420
         * @return DOCUMENT ME!
421
         */
422
        public int getFieldDecimalLength(int inIndex) {
423
                return myHeader.getFieldDecimalCount(inIndex);
424
        }
425

    
426
        /**
427
         * read the DBF file into memory.
428
         *
429
         * @param file
430
         *            DOCUMENT ME!
431
         * @throws FileNotFoundException
432
         * @throws UnsupportedVersionException
433
         * @throws IOException
434
         *
435
         * @throws IOException
436
         *             DOCUMENT ME!
437
         */
438
        public void open() throws FileNotFoundException,
439
                        UnsupportedVersionException, IOException {
440

    
441
                if (!file.exists()) {
442
                        throw new FileNotFoundException(file);
443
                }
444
//                if (file.canWrite()) {
445
//                        try {
446
//                                raf = new RandomAccessFile(file, "rw");
447
//                                mode = FileChannel.MapMode.READ_WRITE;
448
//                        } catch (java.io.FileNotFoundException e) {
449
//                                raf = new RandomAccessFile(file, "r");
450
//                                mode = FileChannel.MapMode.READ_ONLY;
451
//                        }
452
//                } else {
453
                        raf = new RandomAccessFile(file, "r");
454
                        mode = FileChannel.MapMode.READ_ONLY;
455
//                }
456
                channel = raf.getChannel();
457

    
458
                // buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
459
                // channel.size());
460
                buffer = new BigByteBuffer2(channel, mode);
461

    
462
                // create the header to contain the header information.
463
                myHeader = new DbaseFileHeader();
464
                if (chars == null) {
465
                        myHeader.readHeader(buffer, null, allowDuplicatedFieldNames);
466
                } else {
467
                        myHeader.readHeader(buffer, chars.name(), allowDuplicatedFieldNames);
468
                }
469
                if (myHeader.getLanguageID()==0x00) {
470
                        // read from the .cpg file if ldid is 0x00
471
                        DbaseCodepage cpReader = new DbaseCodepage(file);
472
                        String charsetName = cpReader.read();
473
                        if (charsetName == null) {
474
                                charsetName = "ISO-8859-1"; // for compatibility with old gvSIG files
475
                        }
476
                        charsOriginal = Charset.forName(myHeader.mappingEncoding(charsetName));
477
                }
478
                else {
479
                        charsOriginal = Charset.forName(myHeader.mappingEncoding(myHeader.getOriginalCharset()));                        
480
                }
481
                if (chars == null) {
482
                        chars = charsOriginal;
483
                }
484
                bytesCachedRecord = new byte[myHeader.getRecordLength()];
485
                this.isOpen = true;
486
        }
487

    
488
        /**
489
         * Removes all data from the dataset
490
         * @throws CloseException
491
         *
492
         * @throws IOException
493
         *             DOCUMENT ME!
494
         */
495
        public void close() throws CloseException {
496
                logger.debug("Closing dbf file '"+this.file.getAbsolutePath()+"'");
497

    
498
                try{
499
                raf.close();
500
                channel.close();
501
                buffer = null;
502
                posActual=-1;
503
                myHeader = null;
504
                }catch (Exception e) {
505
                        throw new CloseException("DBF",e);
506
                }
507
                this.isOpen = false;
508
        }
509

    
510
        public FileChannel getWriteChannel() {
511
                return channel;
512
        }
513

    
514
        private String fieldString(Object obj, final int col) {
515
                String o;
516
                final int fieldLen = myHeader.getFieldLength(col);
517
                switch (myHeader.getFieldType(col)) {
518
                case 'C':
519
                case 'c':
520
                        o = formatter.getFieldString(fieldLen, (obj == null) ? NULL_STRING
521
                                        : ((String) obj));
522
                        break;
523
                case 'L':
524
                case 'l':
525
                        o = (obj == null) ? "F"
526
                                        : ((Boolean) obj).booleanValue() == true ? "T" : "F";
527
                        break;
528
                case 'M':
529
                case 'G':
530
                        o = formatter.getFieldString(fieldLen, (obj == null) ? NULL_STRING
531
                                        : ((String) obj));
532
                        break;
533
                case 'N':
534
                case 'n':
535
                case 'F':
536
                case 'f':
537
                        Number number = null;
538
                        if (obj == null) {
539
                                number = NULL_NUMBER;
540
                        } else {
541
                                Number gVal = (Number) obj;
542
                                number = new Double(gVal.doubleValue());
543
                        }
544
                        o = formatter.getFieldString(fieldLen, myHeader
545
                                        .getFieldDecimalCount(col), number);
546
                        break;
547
                case 'D':
548
                case 'd':
549
                        if (obj == null) {
550
                                o = NULL_DATE;
551
                        } else {
552
                                o = formatter.getFieldString(((Date) obj));
553
                        }
554
                        break;
555
                default:
556
                        throw new RuntimeException("Unknown type "
557
                                        + myHeader.getFieldType(col));
558
                }
559

    
560
                return o;
561
        }
562

    
563
        public boolean isOpen() {
564
                return this.isOpen;
565
        }
566

    
567
        public int getFieldIndex(String name) {
568
                return myHeader.getFieldIndex(name);
569
        }
570

    
571
        public Charset getCurrenCharset() {
572
                return chars;
573
        }
574

    
575
        public Charset getOriginalCharset() {
576
                return charsOriginal;
577
        }
578

    
579
        public void setCharset(Charset chars) {
580
                this.chars = chars;
581
        }
582

    
583
        public boolean isWritable(){
584
            return this.file.canWrite();
585
        }
586

    
587
}