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

History | View | Annotate | Download (24.1 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.BufferOverflowException;
28
import java.nio.ByteBuffer;
29
import java.nio.MappedByteBuffer;
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.Arrays;
35
import java.util.Calendar;
36
import java.util.Date;
37
import java.util.Iterator;
38
import java.util.Locale;
39
import org.apache.commons.lang3.StringUtils;
40

    
41
import org.gvsig.fmap.dal.DataTypes;
42
import org.gvsig.fmap.dal.exception.CloseException;
43
import org.gvsig.fmap.dal.exception.InitializeException;
44
import org.gvsig.fmap.dal.exception.UnsupportedEncodingException;
45
import org.gvsig.fmap.dal.exception.WriteException;
46
import org.gvsig.fmap.dal.feature.Feature;
47
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
48
import org.gvsig.fmap.dal.feature.FeatureType;
49

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

    
67
    private DbaseFileHeader header;
68
    private DbaseFileWriter.FieldFormatter formatter =
69
        new DbaseFileWriter.FieldFormatter();
70
    FileChannel channel;
71
    private ByteBuffer buffer;
72
    private boolean headDrity = false;
73
    private ByteBuffer blank;
74
    private int blankSize;
75
    
76
    //private Charset charset = Charset.forName("ISO-8859-1");
77
    private Charset charset;
78

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

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

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

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

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

    
154
        this.headDrity = true;
155
    }
156

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

    
163
            // put the 'not-deleted' marker
164
            buffer.put((byte) ' ');
165

    
166
            @SuppressWarnings("unchecked")
167
            Iterator<FeatureAttributeDescriptor> iterator =
168
                featureType.iterator();
169
            while (iterator.hasNext()) {
170
                FeatureAttributeDescriptor fad = iterator.next();
171
                
172
                if (fad.getName().length() > DbaseFile.MAX_FIELD_NAME_LENGTH) {
173
                    throw new FieldNameTooLongException(
174
                        "DBF file", fad.getName());
175
                }
176
                
177
                int type = fad.getType();
178
                if (type == DataTypes.GEOMETRY) {
179
                    continue;
180
                }
181
                encodeField(fad, feature);
182
            }
183
        } catch (Exception e) {
184
            throw new WriteException("DbaseFileWriter", e);
185
        }
186
    }
187
    
188
    private void moveToEOF() throws IOException {
189
        this.moveTo(this.header.getNumRecords());
190
    }
191

    
192
    private void moveTo(long numReg) throws IOException {
193
        // if (!(channel instanceof FileChannel)) {
194
        // throw new IOException(
195
        // "DbaseFileWriterNIO: channel is not a FileChannel. Cannot position properly");
196
        // }
197

    
198
        long newPos =
199
            header.getHeaderLength() + numReg * header.getRecordLength();
200
        if (this.channel.position() != newPos) {
201
            this.channel.position(newPos);
202
        }
203
    }
204

    
205
    /**
206
     * Write a single dbase record. Useful to update a dbf.
207
     * 
208
     * @param record
209
     *            The entries to write.
210
     * @throws WriteException
211
     * @throws UnsupportedEncodingException
212
     */
213
    public void update(Feature feature, long numReg) throws WriteException,
214
        UnsupportedEncodingException {
215
        this.fillBuffer(feature);
216

    
217
        try {
218
            this.moveTo(numReg);
219
        } catch (IOException e) {
220
            throw new WriteException("DbaseFileWriter", e);
221
        }
222

    
223
        write();
224
    }
225

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

    
283
    }
284

    
285
    private void encodeField(FeatureAttributeDescriptor attr, Feature feature) throws java.io.UnsupportedEncodingException, UnsupportedEncodingException {
286
        int type = attr.getType();
287
        int dbfFieldIndex = this.header.getFieldIndex(attr.getName());
288
        final int fieldLen = header.getFieldLength(dbfFieldIndex);
289
        String fieldString = "";
290
        
291
        if( DataTypes.BOOLEAN == type ) {
292
            boolean b = feature.getBoolean(attr.getIndex());
293
            if( b ) {
294
                safeEncode("T", 1, true);
295
            } else {
296
                safeEncode("F", 1, true);
297
            }
298
        
299
        } else if( DataTypes.BYTE == type ) {
300
            fieldString = String.valueOf(feature.getByte(attr.getIndex()));
301
            safeEncode(fieldString, 8, false);
302
        
303
        } else if( DataTypes.DATE == type ) {
304
            Date date = feature.getDate(attr.getIndex());
305
            fieldString = formatter.getFieldString(date);
306
            safeEncode(fieldString, 8, false);
307
        
308
        } else if( DataTypes.DOUBLE == type ) {
309
            double d = feature.getDouble(attr.getIndex());
310
            fieldString  = formatter.getFieldString(
311
                fieldLen, header.getFieldDecimalCount(dbfFieldIndex), d
312
            );
313
            safeEncode(fieldString, fieldLen, false);
314
        
315
        } else if( DataTypes.FLOAT == type ) {
316
            float f = feature.getFloat(attr.getIndex());
317
            fieldString = formatter.getFieldString(
318
                fieldLen, header.getFieldDecimalCount(dbfFieldIndex), f
319
            );
320
            safeEncode(fieldString, fieldLen, false);
321
        
322
        } else if( DataTypes.INT == type ) {
323
            int integer = feature.getInt(attr.getIndex());
324
            fieldString = formatter.getFieldString(
325
                fieldLen, header.getFieldDecimalCount(dbfFieldIndex), integer
326
            );
327
            safeEncode(fieldString, fieldLen, false);
328
        
329
        } else if( DataTypes.LONG == type ) {
330
            long l = feature.getLong(attr.getIndex());
331
            fieldString = formatter.getFieldString(
332
                fieldLen, header.getFieldDecimalCount(dbfFieldIndex),l
333
            );
334
            safeEncode(fieldString, fieldLen, false);
335
        
336
        } else if( DataTypes.STRING == type ) {
337
            String s = feature.getString(attr.getIndex());
338
            safeEncode(StringUtils.defaultIfEmpty(s, ""), fieldLen, true);
339
        
340
        } else {
341
            // Si no conocemos el tipo intentamos guardarlo como un string
342
            String s = feature.getString(attr.getIndex());
343
            safeEncode(StringUtils.defaultIfEmpty(s, ""), fieldLen, true);
344

    
345
        }
346

    
347
    }
348

    
349
    /**
350
     * Returns a safely padded (and potentially truncated) string 
351
     * 
352
     * This may truncate some record, but it is required to ensure
353
     * that the field limit is not overflowed when using 
354
     * variable-length charsets such as UTF-8.
355
     * @throws UnsupportedEncodingException 
356
     */
357
    private void safeEncode(String in, int limit, boolean rightPadding) throws UnsupportedEncodingException {
358
            try {
359
                    byte[] encodedString = in.getBytes(this.charset);
360
                    if (encodedString.length>limit) {
361
                            // too long, truncating
362
                            /*
363
                             * The block code bellow is equivalent to this simple code
364
                             * fragment:
365

366
                    if (rightPadding) {
367
                            in = in.substring(0, in.length()-1);
368
                            encodedString = in.getBytes(charset);
369
                    }
370
                    else {
371
                            in.substring(1, in.length());
372
                            encodedString = in.getBytes(charset);
373
                    }
374

375
                    However, the implemented algorithm has a much better performance
376
                    for the average and worst cases (when the input string has a lot
377
                    of multibyte characters), while keeping a good performance
378
                    for the best case (when all the characters in the input string
379
                    can be represented as single bytes using the selected charset).
380

381
                    The general strategy is to compute the deviation from the
382
                    required maximum number of bytes (limit) and the actual number
383
                    of bytes of the encoded String.
384

385
                    Then, we use this deviation to estimate the amount of characters
386
                    to truncate, based on the average factor of bytes per char in the
387
                    input string.
388

389
                    We truncate the string using this approach until the deviation
390
                    gets stable.
391

392
                    Finally, as we should be close enough to the right truncation position,
393
                    we increment/decrement the truncated string by only 1 character, to
394
                    ensure we truncate in the exact position. 
395
                             */
396
                            String str = in;
397
                            int estimatedDiff, deviation;
398
                            int deviationPrev;
399
                            double ratio;
400
                            byte[] encodedChar;
401
                            int truncatePos = 0;
402
                            deviation = encodedString.length - limit;
403
                            deviationPrev = deviation - 1;
404
                            while(Math.abs(deviation)>Math.abs(deviationPrev) && str.length()>0) {
405
                                    ratio = ((double)encodedString.length) / ((double)str.length());
406
                                    // apply the estimated diff, ensuring it is at least >= 1.0 in absolute value
407
                                    estimatedDiff = Math.max((int)(((double)deviation)/ratio), (int)(Math.signum(deviation)*1));
408
                                    // too long, truncating
409
                                    if (rightPadding) {
410
                                            truncatePos = Math.max(str.length()-estimatedDiff, 0);
411
                                            str = in.substring(0, truncatePos);
412
                                    }
413
                                    else {
414
                                            truncatePos = Math.max(truncatePos + estimatedDiff, 0);
415
                                            str = in.substring(truncatePos);                                  
416
                                    }
417
                                    encodedString = str.getBytes(charset);
418
                                    deviationPrev = deviation;
419
                                    deviation = encodedString.length - limit;
420
                            }
421
                            // now we are close enough, get the exact position for truncating
422
                            while (encodedString.length>limit) {
423
                                    // too long, truncating
424
                                    //                                      System.out.println("truncating");
425
                                    if (rightPadding) {
426
                                            str = in.substring(0, str.length()-1);
427
                                    }
428
                                    else {
429
                                            truncatePos = truncatePos + 1;
430
                                            str = in.substring(truncatePos);
431
                                    }
432
                                    encodedString = str.getBytes(charset);
433
                            }
434
                            while (encodedString.length<limit && str.length()<in.length()) {
435
                                    // Extend if necessary:
436
                                    // 1 - Get the length in bytes of the next char
437
                                    // 2 - Add the char to the substring if we are still within the limits 
438
                                    //                                      System.out.println("extending");
439
                                    if (rightPadding) {
440
                                            encodedChar = in.substring(str.length(), str.length()+1).getBytes(charset);
441
                                    }
442
                                    else {
443
                                            encodedChar = in.substring(truncatePos-1, truncatePos).getBytes(charset);
444
                                            //                                              System.out.println(encodedChar);
445
                                            //                                              System.out.println(encodedChar.length);
446
                                            //                                              System.out.println(testStrings[i].substring(truncatePos-1, truncatePos));
447
                                    }
448
                                    //                                      System.out.println(testStrings[i].substring(in.length(), in.length()+1));
449
                                    if ((encodedString.length + encodedChar.length)>limit) {
450
                                            // one more char would overflow the limit
451
                                            break;
452
                                    }
453
                                    // too short, extending
454
                                    if (rightPadding) {
455
                                            str = in.substring(0, str.length()+1);
456
                                    }
457
                                    else {
458
                                            truncatePos = truncatePos - 1;
459
                                            str = in.substring(truncatePos);
460
                                    }
461
                                    encodedString = str.getBytes(charset);
462
                            }
463
                    }
464
                    if (rightPadding) {
465
                            buffer.put(encodedString);
466
                    }
467
                    if (encodedString.length<limit) {
468
                            // too short, padding
469
                            int i = encodedString.length;
470
                            while (i<limit) {
471
                                    blank.position(0);
472
                                    buffer.put(blank);
473
                                    i=i+blankSize;
474
                            }
475
                            if (i>limit) {
476
                                    // Might happen for instance if charset is UTF16 and the
477
                                    // limit of characters in the field is an odd number
478
                                    throw new UnsupportedEncodingException(new Exception("Impossible to encode this DBF using the selected charset"));
479
                            }
480
                    }
481
                    if (!rightPadding) {
482
                            buffer.put(encodedString);
483
                    }
484
            }
485
                catch(BufferOverflowException exc) {
486
                        // Might happen for instance if charset is UTF16 and the
487
                        // limit of characters in the field is an odd number
488
                        throw new UnsupportedEncodingException(exc);
489
                }
490
    }
491
    
492
    /**
493
     * Returns a safely padded (and potentially truncated) string 
494
     * 
495
     * This may truncate some record, but it is required to ensure
496
     * that the field limit is not overflowed when using 
497
     * variable-length charsets such as UTF-8.
498
     * 
499
     * This implementation is not used but it is kept here for reference.
500
     * It is fully equivalent to the {@link #safeEncode(String, int, boolean)}
501
     * method and easier to understand, but this implementation is much
502
     * slower for any multibyte charset (such as UTF-8).
503
     *  
504
     * @throws UnsupportedEncodingException 
505
     */
506
    private void safeEncodeSlow(String in, int limit, boolean rightPadding) throws UnsupportedEncodingException {
507
            try {
508
                    byte[] encodedString = in.getBytes(this.charset);
509
                    while (encodedString.length>limit) {
510
                            // too long, truncating
511
                            if (rightPadding) {
512
                                    in = in.substring(0, in.length()-1);
513
                                    encodedString = in.getBytes(charset);
514
                            }
515
                            else {
516
                                    in.substring(1, in.length());
517
                                    encodedString = in.getBytes(charset);
518
                            }
519
                    }
520
                    if (rightPadding) {
521
                            buffer.put(encodedString);
522
                    }
523
                    if (encodedString.length<limit) {
524
                            // too short, padding
525
                            int i = encodedString.length;
526
                            while (i<limit) {
527
                                    blank.position(0);
528
                                    buffer.put(blank);
529
                                    i=i+blankSize;
530
                            }
531
                            if (i>limit) {
532
                                    throw new UnsupportedEncodingException(new Exception("Impossible to encode this DBF using the selected charset"));
533
                            }
534
                    }
535
                    if (!rightPadding) {
536
                            buffer.put(encodedString);
537
                    }
538
            }
539
            catch(BufferOverflowException exc) {
540
                    // Might happen for instance if charset is UTF16 and the
541
                    // limit of characters in the field is an odd number
542
                    throw new UnsupportedEncodingException(exc);
543
            }
544
    }
545

    
546

    
547
    /**
548
     * Release resources associated with this writer. <B>Highly recommended</B>
549
     * 
550
     * @throws CloseException
551
     * @throws IOException
552
     *             If errors occur.
553
     */
554
    public void close() throws CloseException {
555
        // IANS - GEOT 193, bogus 0x00 written. According to dbf spec, optional
556
        // eof 0x1a marker is, well, optional. Since the original code wrote a
557
        // 0x00 (which is wrong anyway) lets just do away with this :)
558
        // - produced dbf works in OpenOffice and ArcExplorer java, so it must
559
        // be okay.
560
        // buffer.position(0);
561
        // buffer.put((byte) 0).position(0).limit(1);
562
        // write();
563

    
564
        if (headDrity) {
565
            try {
566
                this.writeHeader();
567
            } catch (WriteException e) {
568
                throw new CloseException("DbaseFileWriter", e);
569
            }
570
        }
571

    
572
        try {
573
            channel.close();
574
        } catch (IOException e) {
575
            throw new CloseException("DBF Writer", e);
576
        }
577
        if (buffer instanceof MappedByteBuffer) {
578
            // NIOUtilities.clean(buffer);
579
        }
580

    
581
        buffer = null;
582
        channel = null;
583
        formatter = null;
584
    }
585

    
586
    /** Utility for formatting Dbase fields. */
587
    public static class FieldFormatter {
588

    
589
        private StringBuffer buffer = new StringBuffer(255);
590
        private NumberFormat numFormat = NumberFormat
591
            .getNumberInstance(Locale.US);
592
        private Calendar calendar = Calendar.getInstance(Locale.US);
593
        private String emtpyString;
594
        private static final int MAXCHARS = 255;
595

    
596
        public FieldFormatter() {
597
            // Avoid grouping on number format
598
            numFormat.setGroupingUsed(false);
599

    
600
            // build a 255 white spaces string
601
            StringBuffer sb = new StringBuffer(MAXCHARS);
602
            sb.setLength(MAXCHARS);
603
            for (int i = 0; i < MAXCHARS; i++) {
604
                sb.setCharAt(i, ' ');
605
            }
606

    
607
            emtpyString = sb.toString();
608
        }
609

    
610
        public String getFieldString(int size, String s) {
611
            buffer.replace(0, size, emtpyString);
612
            buffer.setLength(size);
613

    
614
            if (s != null) {
615
                buffer.replace(0, size, s);
616
                if (s.length() <= size) {
617
                    for (int i = s.length(); i < size; i++) {
618
                        buffer.append(' ');
619
                    }
620
                }
621
            }
622

    
623
            buffer.setLength(size);
624
            return buffer.toString();
625
        }
626

    
627
        public String getFieldString(Date d) {
628

    
629
            if (d != null) {
630
                buffer.delete(0, buffer.length());
631

    
632
                calendar.setTime(d);
633
                int year = calendar.get(Calendar.YEAR);
634
                int month = calendar.get(Calendar.MONTH) + 1; // returns 0 based
635
                                                              // month?
636
                int day = calendar.get(Calendar.DAY_OF_MONTH);
637

    
638
                if (year < 1000) {
639
                    if (year >= 100) {
640
                        buffer.append("0");
641
                    } else
642
                        if (year >= 10) {
643
                            buffer.append("00");
644
                        } else {
645
                            buffer.append("000");
646
                        }
647
                }
648
                buffer.append(year);
649

    
650
                if (month < 10) {
651
                    buffer.append("0");
652
                }
653
                buffer.append(month);
654

    
655
                if (day < 10) {
656
                    buffer.append("0");
657
                }
658
                buffer.append(day);
659
            } else {
660
                buffer.setLength(8);
661
                buffer.replace(0, 8, emtpyString);
662
            }
663

    
664
            buffer.setLength(8);
665
            return buffer.toString();
666
        }
667

    
668
        public String getFieldString(int size, int decimalPlaces, double n) {
669
            buffer.delete(0, buffer.length());
670

    
671
            numFormat.setMaximumFractionDigits(decimalPlaces);
672
            numFormat.setMinimumFractionDigits(decimalPlaces);
673
            numFormat.format(n, buffer, new FieldPosition(
674
                NumberFormat.INTEGER_FIELD));
675
            return buffer.toString();
676
        }
677
    }
678

    
679
    public void setCharset(Charset charset) {
680
        this.charset = charset;
681
            blank = charset.encode(" ");
682
            blankSize = blank.limit();
683
    }
684

    
685
}