Statistics
| Revision:

svn-gvsig-desktop / tags / v1_0_1_RELEASE / libraries / libFMap / src / com / iver / cit / gvsig / fmap / drivers / shp / DbaseFileWriterNIO.java @ 9531

History | View | Annotate | Download (11.8 KB)

1
/*
2
 *    Geotools - OpenSource mapping toolkit
3
 *    (C) 2002, Centre for Computational Geography
4
 *
5
 *    This library is free software; you can redistribute it and/or
6
 *    modify it under the terms of the GNU Lesser General Public
7
 *    License as published by the Free Software Foundation;
8
 *    version 2.1 of the License.
9
 *
10
 *    This library is distributed in the hope that it will be useful,
11
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 *    Lesser General Public License for more details.
14
 *
15
 *    You should have received a copy of the GNU Lesser General Public
16
 *    License along with this library; if not, write to the Free Software
17
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
 *
19
 *    This file is based on an origional contained in the GISToolkit project:
20
 *    http://gistoolkit.sourceforge.net/
21
 *
22
 */
23
/* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
24
 *
25
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
26
 *
27
 * This program is free software; you can redistribute it and/or
28
 * modify it under the terms of the GNU General Public License
29
 * as published by the Free Software Foundation; either version 2
30
 * of the License, or (at your option) any later version.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35
 * GNU General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU General Public License
38
 * along with this program; if not, write to the Free Software
39
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
40
 *
41
 * For more information, contact:
42
 *
43
 *  Generalitat Valenciana
44
 *   Conselleria d'Infraestructures i Transport
45
 *   Av. Blasco Ib??ez, 50
46
 *   46010 VALENCIA
47
 *   SPAIN
48
 *
49
 *      +34 963862235
50
 *   gvsig@gva.es
51
 *      www.gvsig.gva.es
52
 *
53
 *    or
54
 *
55
 *   IVER T.I. S.A
56
 *   Salamanca 50
57
 *   46005 Valencia
58
 *   Spain
59
 *
60
 *   +34 963163400
61
 *   dac@iver.es
62
 */
63
package com.iver.cit.gvsig.fmap.drivers.shp;
64

    
65
import java.io.IOException;
66
import java.nio.ByteBuffer;
67
import java.nio.MappedByteBuffer;
68
import java.nio.channels.FileChannel;
69
import java.nio.channels.WritableByteChannel;
70
import java.nio.charset.Charset;
71
import java.text.FieldPosition;
72
import java.text.NumberFormat;
73
import java.util.Calendar;
74
import java.util.Date;
75
import java.util.Locale;
76

    
77
import com.hardcode.gdbms.engine.values.BooleanValue;
78
import com.hardcode.gdbms.engine.values.DateValue;
79
import com.hardcode.gdbms.engine.values.NullValue;
80
import com.hardcode.gdbms.engine.values.NumericValue;
81
import com.hardcode.gdbms.engine.values.StringValue;
82

    
83

    
84
/** A DbaseFileReader is used to read a dbase III format file.
85
 * The general use of this class is:
86
 * <CODE><PRE>
87
 * DbaseFileHeader header = ...
88
 * WritableFileChannel out = new FileOutputStream("thefile.dbf").getChannel();
89
 * DbaseFileWriter w = new DbaseFileWriter(header,out);
90
 * while ( moreRecords ) {
91
 *   w.write( getMyRecord() );
92
 * }
93
 * w.close();
94
 * </PRE></CODE>
95
 * You must supply the <CODE>moreRecords</CODE> and <CODE>getMyRecord()</CODE>
96
 * logic...
97
 * @author Ian Schneider
98
 */
99
public class DbaseFileWriterNIO  {
100
  
101
  private DbaseFileHeaderNIO header;
102
  private DbaseFileWriterNIO.FieldFormatter formatter = new DbaseFileWriterNIO.FieldFormatter();
103
  WritableByteChannel channel;
104
  private ByteBuffer buffer;
105
  private final Number NULL_NUMBER = new Integer(0);
106
  private final String NULL_STRING = "";
107
  private final String NULL_DATE = "        ";
108
  
109
  // TODO: READ HEADER AND STABLIST THE RIGHT CHARSET
110
  private Charset charset = Charset.forName("ISO-8859-1");
111
  
112
  /** Create a DbaseFileWriter using the specified header and writing to the given
113
   * channel.
114
   * @param header The DbaseFileHeader to write.
115
   * @param out The Channel to write to.
116
   * @throws IOException If errors occur while initializing.
117
   */
118
  public DbaseFileWriterNIO(DbaseFileHeaderNIO header,FileChannel out) throws IOException {
119
    header.writeHeader(out);
120
    this.header = header;
121
    this.channel = out;
122
    
123
    init();
124
  }
125
  
126
  private void init() throws IOException {
127
    buffer = ByteBuffer.allocateDirect(header.getRecordLength());
128
  }
129
  
130
  private void write() throws IOException {
131
    buffer.position(0);
132
    int r = buffer.remaining();
133
    while ( (r -= channel.write(buffer)) > 0) {
134
      ; // do nothing
135
    }
136
  }
137
  
138
  
139
  /** Write a single dbase record.
140
   * @param record The entries to write.
141
   * @throws IOException If IO error occurs.
142
   * @throws DbaseFileException If the entry doesn't comply to the header.
143
   */
144
  public void write(Object[] record) throws IOException{
145
  /*  
146
    if (record.length != header.getNumFields()) {
147
      throw new DbaseFileException("Wrong number of fields " + record.length +
148
      " expected " +  header.getNumFields());
149
    }
150
    */
151
    buffer.position(0);
152
    
153
    // put the 'not-deleted' marker
154
    buffer.put( (byte) ' ');
155
    
156
    for (int i = 0; i < header.getNumFields(); i++) {
157
      String fieldString = fieldString(record[i], i);
158
//      if ( header.getFieldLength(i) != fieldString.getBytes().length) {
159
//          System.out.println(i + " : " + header.getFieldName(i));
160
//      }
161
      
162
      
163
      buffer.put(fieldString.getBytes(charset.name()));
164
    
165
    }
166
    
167
    
168
    write();
169
  }
170
  
171
  /** Write a single dbase record. Useful to update a dbf.
172
   * @param record The entries to write.
173
   * @throws IOException If IO error occurs.
174
   * @throws DbaseFileException If the entry doesn't comply to the header.
175
   */
176
  public void writeRecord(Object[] record, int numReg) throws IOException{
177
   
178
        if (!(channel instanceof FileChannel)) {
179
              throw new IOException("DbaseFileWriterNIO: channel is not a FileChannel. Cannot position properly");
180
        }
181
          
182
    if (record.length != header.getNumFields()) {
183
      throw new IOException("Wrong number of fields " + record.length +
184
      " expected " +  header.getNumFields());
185
    }
186
    
187
    FileChannel fileChannel = (FileChannel) channel;
188
    long newPos = header.getHeaderLength() + numReg*header.getRecordLength();
189
    
190
    buffer.position(0);
191
    
192
    // put the 'not-deleted' marker
193
    buffer.put( (byte) ' ');
194
    
195
    for (int i = 0; i < header.getNumFields(); i++) {
196
      String fieldString = fieldString(record[i], i);
197
//      if ( header.getFieldLength(i) != fieldString.getBytes().length) {
198
//          System.out.println(i + " : " + header.getFieldName(i));
199
//      }
200
      
201
      
202
      buffer.put(fieldString.getBytes(charset.name()));
203
    
204
    }
205
    
206
    fileChannel.write(buffer, newPos);
207
  }
208

    
209
  
210
  private String fieldString(Object obj,final int col) {
211
    String o;
212
    final int fieldLen = header.getFieldLength(col);
213
    switch (header.getFieldType(col)) {
214
      case 'C':
215
      case 'c':
216
        o = formatter.getFieldString(
217
          fieldLen, 
218
          (obj instanceof NullValue)? NULL_STRING : ((StringValue) obj).getValue()
219
        );
220
        break;
221
      case 'L':
222
      case 'l':
223
        o = (obj instanceof NullValue) ? "F" : ((BooleanValue)obj).getValue() == true ? "T" : "F";
224
        break;
225
      case 'M':
226
      case 'G':
227
        o = formatter.getFieldString(
228
          fieldLen, 
229
          (obj instanceof NullValue) ? NULL_STRING : ((StringValue) obj).getValue()
230
        );
231
        break;
232
     /* case 'N':
233
      case 'n':
234
        // int?
235
        if (header.getFieldDecimalCount(col) == 0) {
236
            
237
          o = formatter.getFieldString(
238
            fieldLen, 0, (Number) (obj == null ? NULL_NUMBER : Double.valueOf(obj.toString()))
239
          );
240
          break;
241
       
242
         }
243
      */
244
      case 'N':
245
      case 'n':
246
      case 'F':
247
      case 'f':
248
            Number number = null;
249
            if(obj instanceof NullValue){
250
                    number = NULL_NUMBER;
251
            }else{
252
                    NumericValue gVal = (NumericValue) obj;
253
                    number = new Double(gVal.doubleValue());
254
            }
255
        o = formatter.getFieldString(fieldLen,
256
                        header.getFieldDecimalCount(col),
257
                                number);
258
        break;
259
      case 'D':
260
      case 'd':
261
          if (obj instanceof NullValue)
262
              o = NULL_DATE;
263
          else
264
              o = formatter.getFieldString(((DateValue)obj).getValue());
265
        break;
266
      default:
267
        throw new RuntimeException("Unknown type " + header.getFieldType(col));
268
    }
269

    
270
    return o;
271
  }
272
  
273
  /** Release resources associated with this writer.
274
   * <B>Highly recommended</B>
275
   * @throws IOException If errors occur.
276
   */
277
  public void close() throws IOException {
278
      // IANS - GEOT 193, bogus 0x00 written. According to dbf spec, optional
279
      // eof 0x1a marker is, well, optional. Since the original code wrote a
280
      // 0x00 (which is wrong anyway) lets just do away with this :)
281
      // - produced dbf works in OpenOffice and ArcExplorer java, so it must
282
      // be okay. 
283
//    buffer.position(0);
284
//    buffer.put((byte) 0).position(0).limit(1);
285
//    write();
286
    channel.close();
287
    if (buffer instanceof MappedByteBuffer) {
288
     // NIOUtilities.clean(buffer);
289
    }
290
    
291
    buffer = null;
292
    channel = null;
293
    formatter = null;
294
  }
295
  
296
  
297
  /** Utility for formatting Dbase fields. */
298
  public static class FieldFormatter {
299
    private StringBuffer buffer = new StringBuffer(255);
300
    private NumberFormat numFormat = NumberFormat.getNumberInstance(Locale.US);
301
    private Calendar calendar = Calendar.getInstance(Locale.US);
302
    private String emtpyString;
303
    private static final int MAXCHARS = 255;
304
    
305
    public FieldFormatter() {
306
      // Avoid grouping on number format
307
      numFormat.setGroupingUsed(false);
308
      
309
      // build a 255 white spaces string
310
      StringBuffer sb = new StringBuffer(MAXCHARS);
311
      sb.setLength(MAXCHARS);
312
      for(int i = 0; i < MAXCHARS; i++) {
313
        sb.setCharAt(i, ' ');
314
      }
315
      
316
      emtpyString = sb.toString();
317
    }
318
    
319
    public String getFieldString(int size, String s) {
320
      buffer.replace(0, size, emtpyString);
321
      buffer.setLength(size);
322
      
323
      if(s != null) {
324
        buffer.replace(0, size, s);
325
        if(s.length() <= size) {
326
          for(int i = s.length(); i < size; i++) {
327
            buffer.append(' ');
328
          }
329
        }
330
      }
331
      
332
      buffer.setLength(size);
333
      return buffer.toString();
334
    }
335
    
336
    public String getFieldString(Date d) {
337
      
338
      if(d != null) {
339
        buffer.delete(0, buffer.length());
340
        
341
        calendar.setTime(d);
342
        int year = calendar.get(Calendar.YEAR);
343
        int month = calendar.get(Calendar.MONTH) + 1;  // returns 0 based month?
344
        int day = calendar.get(Calendar.DAY_OF_MONTH);
345
        
346
        if(year < 1000) {
347
          if(year >= 100) {
348
            buffer.append("0");
349
          } else if(year >= 10) {
350
            buffer.append("00");
351
          } else {
352
            buffer.append("000");
353
          }
354
        }
355
        buffer.append(year);
356
        
357
        if(month < 10) {
358
          buffer.append("0");
359
        }
360
        buffer.append(month);
361
        
362
        if(day < 10) {
363
          buffer.append("0");
364
        }
365
        buffer.append(day);
366
      } else {
367
        buffer.setLength(8);
368
        buffer.replace(0, 8, emtpyString);
369
      }
370
      
371
      buffer.setLength(8);
372
      return buffer.toString();
373
    }
374
    
375
    public String getFieldString(int size, int decimalPlaces, Number n) {
376
      buffer.delete(0, buffer.length());
377
      
378
      if (n != null) {
379
        numFormat.setMaximumFractionDigits(decimalPlaces);
380
        numFormat.setMinimumFractionDigits(decimalPlaces);
381
        numFormat.format(n, buffer, new FieldPosition(NumberFormat.INTEGER_FIELD));
382
      }
383
      
384
      int diff = size - buffer.length();
385
      if (diff >= 0) {
386
          while (diff-- > 0) {
387
              buffer.insert(0, ' ');
388
          }
389
      } else {
390
          buffer.setLength(size);
391
      }
392
      return buffer.toString();
393
    }
394
  }
395

    
396

    
397
public void setCharset(Charset charset) {
398
        this.charset = charset;
399
        
400
}
401
  
402
  
403
}