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 / DbaseFileWriter.java @ 40435

History | View | Annotate | Download (17.3 KB)

1
/* gvSIG. Geographic Information System of the Valencian Government
2
 *
3
 * Copyright (C) 2007-2008 Infrastructures and Transports Department
4
 * of the Valencian Government (CIT)
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 2
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
 */
22
package org.gvsig.fmap.dal.store.dbf.utils;
23

    
24
import java.io.IOException;
25
import java.nio.ByteBuffer;
26
import java.nio.MappedByteBuffer;
27
import java.nio.channels.FileChannel;
28
import java.nio.charset.Charset;
29
import java.text.FieldPosition;
30
import java.text.NumberFormat;
31
import java.util.Calendar;
32
import java.util.Date;
33
import java.util.Iterator;
34
import java.util.Locale;
35

    
36
import org.gvsig.fmap.dal.DataTypes;
37
import org.gvsig.fmap.dal.exception.CloseException;
38
import org.gvsig.fmap.dal.exception.InitializeException;
39
import org.gvsig.fmap.dal.exception.UnsupportedEncodingException;
40
import org.gvsig.fmap.dal.exception.WriteException;
41
import org.gvsig.fmap.dal.feature.Feature;
42
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
43
import org.gvsig.fmap.dal.feature.FeatureType;
44

    
45
/**
46
 * A DbaseFileReader is used to read a dbase III format file. The general use of
47
 * this class is: <CODE><PRE>
48
 * DbaseFileHeader header = ...
49
 * WritableFileChannel out = new FileOutputStream("thefile.dbf").getChannel();
50
 * DbaseFileWriter w = new DbaseFileWriter(header,out);
51
 * while ( moreRecords ) {
52
 *   w.write( getMyRecord() );
53
 * }
54
 * w.close();
55
 * </PRE></CODE> You must supply the <CODE>moreRecords</CODE> and
56
 * <CODE>getMyRecord()</CODE> logic...
57
 * 
58
 * @author Ian Schneider
59
 */
60
public class DbaseFileWriter {
61

    
62
    private DbaseFileHeader header;
63
    private DbaseFileWriter.FieldFormatter formatter =
64
        new DbaseFileWriter.FieldFormatter();
65
    FileChannel channel;
66
    private ByteBuffer buffer;
67
    // private final Number NULL_NUMBER = new Integer(0);
68
    private final String NULL_STRING = "";
69
    private final String NULL_DATE = "        ";
70
    private boolean headDrity = false;
71

    
72
    // TODO: READ HEADER AND STABLIST THE RIGHT CHARSET
73
    private Charset charset = Charset.forName("ISO-8859-1");
74

    
75
    /**
76
     * Create a DbaseFileWriter using the specified header and writing to the
77
     * given channel.
78
     * 
79
     * @param header
80
     *            The DbaseFileHeader to write.
81
     * @param out
82
     *            The Channel to write to.
83
     * 
84
     * 
85
     * @throws InitializeWriterException
86
     * @throws IOException
87
     *             If errors occur while initializing.
88
     */
89
    public DbaseFileWriter(DbaseFileHeader header, FileChannel out,
90
        boolean isNew) throws InitializeException {
91
        this.header = header;
92
        this.channel = out;
93
        this.headDrity = isNew;
94

    
95
        init();
96
    }
97

    
98
    private void init() throws InitializeException {
99
        try {
100
            if (this.channel.size() < this.header.getHeaderLength()) {
101
                this.writeHeader();
102
            }
103
            buffer = ByteBuffer.allocateDirect(header.getRecordLength());
104
        } catch (Exception e) {
105
            throw new InitializeException("DBF Writer", e);
106
        }
107
    }
108

    
109
    private void write() throws WriteException {
110
        buffer.position(0);
111
        int r = buffer.remaining();
112
        try {
113
            while ((r -= channel.write(buffer)) > 0) {
114
                ; // do nothing
115
            }
116
        } catch (IOException e) {
117
            throw new WriteException("DBF Writer", e);
118
        }
119
    }
120

    
121
    private void writeHeader() throws WriteException {
122
        try {
123
            channel.position(0);
124
            header.writeHeader(channel);
125
        } catch (IOException e) {
126
            throw new WriteException("DBF Writer", e);
127
        }
128
    }
129

    
130
    /**
131
     * Write a single dbase record.
132
     * 
133
     * @param record
134
     *            The entries to write.
135
     * @throws UnsupportedEncodingException
136
     * @throws WriteException
137
     */
138
    public void append(Feature feature) throws WriteException,
139
        UnsupportedEncodingException {
140
        this.fillBuffer(feature);
141
        try {
142
            this.moveToEOF();
143
        } catch (IOException e) {
144
            throw new WriteException("DbaseFileWriter", e);
145
        }
146
        this.header.setNumRecords(this.header.getNumRecords() + 1);
147
        write();
148

    
149
        this.headDrity = true;
150
    }
151

    
152
    private void fillBuffer(Feature feature)
153
        throws UnsupportedEncodingException, WriteException {
154
        FeatureType featureType = feature.getType();
155
        try {
156
            buffer.position(0);
157

    
158
            // put the 'not-deleted' marker
159
            buffer.put((byte) ' ');
160

    
161
            @SuppressWarnings("unchecked")
162
            Iterator<FeatureAttributeDescriptor> iterator =
163
                featureType.iterator();
164
            while (iterator.hasNext()) {
165
                FeatureAttributeDescriptor fad = iterator.next();
166
                
167
                if (fad.getName().length() > DbaseFile.MAX_FIELD_NAME_LENGTH) {
168
                    throw new FieldNameTooLongException(
169
                        "DBF file", fad.getName());
170
                }
171
                
172
                int type = fad.getType();
173
                if (type == DataTypes.GEOMETRY) {
174
                    continue;
175
                }
176
                String fieldString = fieldString(fad, feature);
177
                if (fieldString == null) {
178
                    if (type == DataTypes.STRING) {
179
                        fieldString = NULL_STRING;
180
                    } else
181
                        if (type == DataTypes.DATE) {
182
                            fieldString = NULL_DATE;
183
                        } else {
184
                            fieldString = "0";
185
                        }
186
                }
187
                try {
188
                    buffer.put(fieldString.getBytes(charset.name()));
189
                } catch (java.io.UnsupportedEncodingException e) {
190
                    throw new UnsupportedEncodingException(e);
191
                }
192
            }
193
        } catch (Exception e) {
194
            throw new WriteException("DbaseFileWriter", e);
195
        }
196
    }
197

    
198
    private void moveToEOF() throws IOException {
199
        this.moveTo(this.header.getNumRecords());
200
    }
201

    
202
    private void moveTo(long numReg) throws IOException {
203
        // if (!(channel instanceof FileChannel)) {
204
        // throw new IOException(
205
        // "DbaseFileWriterNIO: channel is not a FileChannel. Cannot position properly");
206
        // }
207

    
208
        long newPos =
209
            header.getHeaderLength() + numReg * header.getRecordLength();
210
        if (this.channel.position() != newPos) {
211
            this.channel.position(newPos);
212
        }
213
    }
214

    
215
    /**
216
     * Write a single dbase record. Useful to update a dbf.
217
     * 
218
     * @param record
219
     *            The entries to write.
220
     * @throws WriteException
221
     * @throws UnsupportedEncodingException
222
     */
223
    public void update(Feature feature, long numReg) throws WriteException,
224
        UnsupportedEncodingException {
225
        this.fillBuffer(feature);
226

    
227
        try {
228
            this.moveTo(numReg);
229
        } catch (IOException e) {
230
            throw new WriteException("DbaseFileWriter", e);
231
        }
232

    
233
        write();
234
    }
235

    
236
    private String fieldString(FeatureAttributeDescriptor attr, Feature feature) {
237
        int type = attr.getType();
238
        int dbfFieldIndex = this.header.getFieldIndex(attr.getName());
239
        final int fieldLen = header.getFieldLength(dbfFieldIndex);
240
        String fieldString = "";
241
        if (DataTypes.BOOLEAN == type) {
242
            boolean b = feature.getBoolean(attr.getIndex());
243
            if (b) {
244
                fieldString = "T";
245
            } else {
246
                fieldString = "F";
247
            }
248
        } else
249
            if (DataTypes.BYTE == type) {
250
                fieldString = String.valueOf(feature.getByte(attr.getIndex()));
251
            } else
252
                if (DataTypes.DATE == type) {
253
                    Date date = feature.getDate(attr.getIndex());
254
                    fieldString = formatter.getFieldString(date);
255
                } else
256
                    if (DataTypes.DOUBLE == type) {
257
                        double d = feature.getDouble(attr.getIndex());
258
                        fieldString =
259
                            formatter.getFieldString(fieldLen,
260
                                header.getFieldDecimalCount(dbfFieldIndex), d);
261
                    } else
262
                        if (DataTypes.FLOAT == type) {
263
                            float f = feature.getFloat(attr.getIndex());
264
                            fieldString =
265
                                formatter.getFieldString(fieldLen,
266
                                    header.getFieldDecimalCount(dbfFieldIndex),
267
                                    f);
268
                        } else
269
                            if (DataTypes.INT == type) {
270
                                int integer = feature.getInt(attr.getIndex());
271
                                fieldString =
272
                                    formatter.getFieldString(fieldLen, header
273
                                        .getFieldDecimalCount(dbfFieldIndex),
274
                                        integer);
275
                            } else
276
                                if (DataTypes.LONG == type) {
277
                                    long l = feature.getLong(attr.getIndex());
278
                                    fieldString =
279
                                        formatter
280
                                            .getFieldString(
281
                                                fieldLen,
282
                                                header
283
                                                    .getFieldDecimalCount(dbfFieldIndex),
284
                                                l);
285
                                } else
286
                                    if (DataTypes.STRING == type) {
287
                                        String s =
288
                                            feature.getString(attr.getIndex());
289
                                        fieldString =
290
                                            formatter.getFieldString(fieldLen,
291
                                                s);
292
                                    }
293
        return fieldString;
294

    
295
    }
296

    
297
    // private String fieldString(Object obj,final int col) {
298
    // String o;
299
    // final int fieldLen = header.getFieldLength(col);
300
    // switch (header.getFieldType(col)) {
301
    // case 'C':
302
    // case 'c':
303
    // o = formatter.getFieldString(
304
    // fieldLen,
305
    // (obj instanceof NullValue)? NULL_STRING : ((StringValue) obj).getValue()
306
    // );
307
    // break;
308
    // case 'L':
309
    // case 'l':
310
    // o = (obj instanceof NullValue) ? "F" : ((BooleanValue)obj).getValue() ==
311
    // true ? "T" : "F";
312
    // break;
313
    // case 'M':
314
    // case 'G':
315
    // o = formatter.getFieldString(
316
    // fieldLen,
317
    // (obj instanceof NullValue) ? NULL_STRING : ((StringValue) obj).getValue()
318
    // );
319
    // break;
320
    // /* case 'N':
321
    // case 'n':
322
    // // int?
323
    // if (header.getFieldDecimalCount(col) == 0) {
324
    //
325
    // o = formatter.getFieldString(
326
    // fieldLen, 0, (Number) (obj == null ? NULL_NUMBER :
327
    // Double.valueOf(obj.toString()))
328
    // );
329
    // break;
330
    //
331
    // }
332
    // */
333
    // case 'N':
334
    // case 'n':
335
    // case 'F':
336
    // case 'f':
337
    // Number number = null;
338
    // if(obj instanceof NullValue){
339
    // number = NULL_NUMBER;
340
    // }else{
341
    // NumericValue gVal = (NumericValue) obj;
342
    // number = new Double(gVal.doubleValue());
343
    // }
344
    // o = formatter.getFieldString(fieldLen,
345
    // header.getFieldDecimalCount(col),
346
    // number);
347
    // break;
348
    // case 'D':
349
    // case 'd':
350
    // if (obj instanceof NullValue)
351
    // o = NULL_DATE;
352
    // else
353
    // o = formatter.getFieldString(((DateValue)obj).getValue());
354
    // break;
355
    // default:
356
    // throw new RuntimeException("Unknown type " + header.getFieldType(col));
357
    // }
358
    //
359
    // return o;
360
    // }
361

    
362
    /**
363
     * Release resources associated with this writer. <B>Highly recommended</B>
364
     * 
365
     * @throws CloseException
366
     * @throws IOException
367
     *             If errors occur.
368
     */
369
    public void close() throws CloseException {
370
        // IANS - GEOT 193, bogus 0x00 written. According to dbf spec, optional
371
        // eof 0x1a marker is, well, optional. Since the original code wrote a
372
        // 0x00 (which is wrong anyway) lets just do away with this :)
373
        // - produced dbf works in OpenOffice and ArcExplorer java, so it must
374
        // be okay.
375
        // buffer.position(0);
376
        // buffer.put((byte) 0).position(0).limit(1);
377
        // write();
378

    
379
        if (headDrity) {
380
            try {
381
                this.writeHeader();
382
            } catch (WriteException e) {
383
                throw new CloseException("DbaseFileWriter", e);
384
            }
385
        }
386

    
387
        try {
388
            channel.close();
389
        } catch (IOException e) {
390
            throw new CloseException("DBF Writer", e);
391
        }
392
        if (buffer instanceof MappedByteBuffer) {
393
            // NIOUtilities.clean(buffer);
394
        }
395

    
396
        buffer = null;
397
        channel = null;
398
        formatter = null;
399
    }
400

    
401
    /** Utility for formatting Dbase fields. */
402
    public static class FieldFormatter {
403

    
404
        private StringBuffer buffer = new StringBuffer(255);
405
        private NumberFormat numFormat = NumberFormat
406
            .getNumberInstance(Locale.US);
407
        private Calendar calendar = Calendar.getInstance(Locale.US);
408
        private String emtpyString;
409
        private static final int MAXCHARS = 255;
410

    
411
        public FieldFormatter() {
412
            // Avoid grouping on number format
413
            numFormat.setGroupingUsed(false);
414

    
415
            // build a 255 white spaces string
416
            StringBuffer sb = new StringBuffer(MAXCHARS);
417
            sb.setLength(MAXCHARS);
418
            for (int i = 0; i < MAXCHARS; i++) {
419
                sb.setCharAt(i, ' ');
420
            }
421

    
422
            emtpyString = sb.toString();
423
        }
424

    
425
        public String getFieldString(int size, String s) {
426
            buffer.replace(0, size, emtpyString);
427
            buffer.setLength(size);
428

    
429
            if (s != null) {
430
                buffer.replace(0, size, s);
431
                if (s.length() <= size) {
432
                    for (int i = s.length(); i < size; i++) {
433
                        buffer.append(' ');
434
                    }
435
                }
436
            }
437

    
438
            buffer.setLength(size);
439
            return buffer.toString();
440
        }
441

    
442
        public String getFieldString(Date d) {
443

    
444
            if (d != null) {
445
                buffer.delete(0, buffer.length());
446

    
447
                calendar.setTime(d);
448
                int year = calendar.get(Calendar.YEAR);
449
                int month = calendar.get(Calendar.MONTH) + 1; // returns 0 based
450
                                                              // month?
451
                int day = calendar.get(Calendar.DAY_OF_MONTH);
452

    
453
                if (year < 1000) {
454
                    if (year >= 100) {
455
                        buffer.append("0");
456
                    } else
457
                        if (year >= 10) {
458
                            buffer.append("00");
459
                        } else {
460
                            buffer.append("000");
461
                        }
462
                }
463
                buffer.append(year);
464

    
465
                if (month < 10) {
466
                    buffer.append("0");
467
                }
468
                buffer.append(month);
469

    
470
                if (day < 10) {
471
                    buffer.append("0");
472
                }
473
                buffer.append(day);
474
            } else {
475
                buffer.setLength(8);
476
                buffer.replace(0, 8, emtpyString);
477
            }
478

    
479
            buffer.setLength(8);
480
            return buffer.toString();
481
        }
482

    
483
        public String getFieldString(int size, int decimalPlaces, double n) {
484
            buffer.delete(0, buffer.length());
485

    
486
            // if (n != null) {
487
            numFormat.setMaximumFractionDigits(decimalPlaces);
488
            numFormat.setMinimumFractionDigits(decimalPlaces);
489
            numFormat.format(n, buffer, new FieldPosition(
490
                NumberFormat.INTEGER_FIELD));
491
            // }
492

    
493
            int diff = size - buffer.length();
494
            if (diff >= 0) {
495
                while (diff-- > 0) {
496
                    buffer.insert(0, ' ');
497
                }
498
            } else {
499
                buffer.setLength(size);
500
            }
501
            return buffer.toString();
502
        }
503
    }
504

    
505
    public void setCharset(Charset charset) {
506
        this.charset = charset;
507

    
508
    }
509

    
510
}