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 @ 40559

History | View | Annotate | Download (17.4 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.IOException;
27
import java.nio.ByteBuffer;
28
import java.nio.MappedByteBuffer;
29
import java.nio.channels.FileChannel;
30
import java.nio.charset.Charset;
31
import java.text.FieldPosition;
32
import java.text.NumberFormat;
33
import java.util.Calendar;
34
import java.util.Date;
35
import java.util.Iterator;
36
import java.util.Locale;
37

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

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

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

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

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

    
97
        init();
98
    }
99

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

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

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

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

    
151
        this.headDrity = true;
152
    }
153

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

    
160
            // put the 'not-deleted' marker
161
            buffer.put((byte) ' ');
162

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

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

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

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

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

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

    
235
        write();
236
    }
237

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

    
297
    }
298

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

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

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

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

    
398
        buffer = null;
399
        channel = null;
400
        formatter = null;
401
    }
402

    
403
    /** Utility for formatting Dbase fields. */
404
    public static class FieldFormatter {
405

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

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

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

    
424
            emtpyString = sb.toString();
425
        }
426

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

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

    
440
            buffer.setLength(size);
441
            return buffer.toString();
442
        }
443

    
444
        public String getFieldString(Date d) {
445

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

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

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

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

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

    
481
            buffer.setLength(8);
482
            return buffer.toString();
483
        }
484

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

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

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

    
507
    public void setCharset(Charset charset) {
508
        this.charset = charset;
509

    
510
    }
511

    
512
}