Statistics
| Revision:

root / trunk / libraries / libFMap / src / com / iver / cit / gvsig / fmap / drivers / shp / DbaseFileReaderNIO.java @ 10627

History | View | Annotate | Download (15.4 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

    
66
import java.io.IOException;
67
import java.nio.ByteBuffer;
68
import java.nio.ByteOrder;
69
import java.nio.CharBuffer;
70
import java.nio.MappedByteBuffer;
71
import java.nio.channels.FileChannel;
72
import java.nio.channels.ReadableByteChannel;
73
import java.nio.charset.Charset;
74
import java.nio.charset.CharsetDecoder;
75
import java.util.Calendar;
76

    
77
import org.geotools.resources.NIOUtilities;
78
import org.geotools.resources.NumberParser;
79

    
80

    
81
/** A DbaseFileReader is used to read a dbase III format file.
82
 * <br>
83
 * The general use of this class is:
84
 * <CODE><PRE>
85
 * FileChannel in = new FileInputStream("thefile.dbf").getChannel();
86
 * DbaseFileReader r = new DbaseFileReader( in )
87
 * Object[] fields = new Object[r.getHeader().getNumFields()];
88
 * while (r.hasNext()) {
89
 *    r.readEntry(fields);
90
 *    // do stuff
91
 * }
92
 * r.close();
93
 * </PRE></CODE>
94
 * For consumers who wish to be a bit more selective with their reading of rows,
95
 * the Row object has been added. The semantics are the same as using the
96
 * readEntry method, but remember that the Row object is always the same. The
97
 * values are parsed as they are read, so it pays to copy them out (as each call
98
 * to Row.read() will result in an expensive String parse).
99
 * <br><b>EACH CALL TO readEntry OR readRow ADVANCES THE FILE!</b><br>
100
 * An example of using the Row method of reading:
101
 * <CODE><PRE>
102
 * FileChannel in = new FileInputStream("thefile.dbf").getChannel();
103
 * DbaseFileReader r = new DbaseFileReader( in )
104
 * int fields = r.getHeader().getNumFields();
105
 * while (r.hasNext()) {
106
 *   DbaseFileReader.Row row = r.readRow();
107
 *   for (int i = 0; i < fields; i++) {
108
 *     // do stuff
109
 *     Foo.bar( row.read(i) );
110
 *   }
111
 * }
112
 * r.close();
113
 * </PRE></CODE>
114
 *
115
 * @author Ian Schneider
116
 */
117
public class DbaseFileReaderNIO {
118
  
119
  public final class Row {
120
    public Object read(int column) throws IOException {
121
      int offset = getOffset(column);
122
      return readObject(offset, column);
123
    }
124
  }
125
  
126
  DbaseFileHeaderNIO header;
127
  ByteBuffer buffer;
128
  ReadableByteChannel channel;
129
  CharBuffer charBuffer;
130
  CharsetDecoder decoder;
131
  char[] fieldTypes;
132
  int[] fieldLengths;
133
  int cnt = 1;
134
  Row row;
135
  NumberParser numberParser = new NumberParser();
136
  
137
  /** Creates a new instance of DBaseFileReader
138
   * @param channel The readable channel to use.
139
   * @throws IOException If an error occurs while initializing.
140
   */
141
  public DbaseFileReaderNIO(ReadableByteChannel channel) throws IOException {
142
    this.channel = channel;
143
    
144
    header = new DbaseFileHeaderNIO();
145
    ///header.readHeader(channel);
146
    
147
    init();
148
  }
149
  
150
  private int fill(ByteBuffer buffer,ReadableByteChannel channel) throws IOException {
151
    int r = buffer.remaining();
152
    // channel reads return -1 when EOF or other error
153
    // because they a non-blocking reads, 0 is a valid return value!!
154
    while (buffer.remaining() > 0 && r != -1) {
155
      r = channel.read(buffer);
156
    }
157
    if (r == -1) {
158
      buffer.limit(buffer.position());
159
    }
160
    return r;
161
  }
162
  
163
  private void bufferCheck() throws IOException {
164
    // remaining is less than record length
165
    // compact the remaining data and read again
166
    if (!buffer.isReadOnly() && buffer.remaining() < header.getRecordLength()) {
167
      buffer.compact();
168
      fill(buffer,channel);
169
      buffer.position(0);
170
    }
171
  }
172
  
173
  private int getOffset(int column) {
174
    int offset = 0;
175
    for (int i = 0, ii = column; i < ii; i++) {
176
      offset += fieldLengths[i];
177
    }
178
    return offset;
179
  }
180
  
181
  private void init() throws IOException {
182
    // create the ByteBuffer
183
    // if we have a FileChannel, lets map it
184
    if (channel instanceof FileChannel) {
185
      FileChannel fc = (FileChannel) channel;
186
      buffer = fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size());
187
      buffer.position((int) fc.position());
188
    } else {
189
      // Some other type of channel
190
      // start with a 8K buffer, should be more than adequate
191
      int size = 8 * 1024;
192
      // if for some reason its not, resize it
193
      size = header.getRecordLength() > size ? header.getRecordLength() : size;
194
      buffer = ByteBuffer.allocateDirect(size);
195
      // fill it and reset
196
      fill(buffer,channel);
197
      buffer.flip();
198
    }
199
    
200
    // The entire file is in little endian
201
    buffer.order(ByteOrder.LITTLE_ENDIAN);
202
    
203
    // Set up some buffers and lookups for efficiency
204
    fieldTypes = new char[header.getNumFields()];
205
    fieldLengths = new int[header.getNumFields()];
206
    for (int i = 0, ii = header.getNumFields(); i < ii; i++) {
207
      fieldTypes[i] = header.getFieldType(i);
208
      fieldLengths[i] = header.getFieldLength(i);
209
    }
210
    
211
    charBuffer = CharBuffer.allocate(header.getRecordLength() - 1);
212
    Charset chars = Charset.forName("ISO-8859-1");
213
    decoder = chars.newDecoder();
214
    
215
    row = new Row();
216
  }
217
  
218
  
219
  
220
  /** Get the header from this file. The header is read upon instantiation.
221
   * @return The header associated with this file or null if an error occurred.
222
   */
223
  public DbaseFileHeaderNIO getHeader() {
224
    return header;
225
  }
226
  
227
  /** Clean up all resources associated with this reader.<B>Highly recomended.</B>
228
   * @throws IOException If an error occurs.
229
   */
230
  public void close() throws IOException {
231
    if (channel.isOpen()) {
232
      channel.close();
233
    }
234
    if (buffer instanceof MappedByteBuffer) {
235
      NIOUtilities.clean(buffer);
236
    }
237
    buffer = null;
238
    channel = null;
239
    charBuffer = null;
240
    decoder = null;
241
    header = null;
242
    row = null;
243
  }
244
  
245
  /** Query the reader as to whether there is another record.
246
   * @return True if more records exist, false otherwise.
247
   */
248
  public boolean hasNext() {
249
    return cnt < header.getNumRecords() + 1;
250
  }
251
  
252
  /** Get the next record (entry). Will return a new array of values.
253
   * @throws IOException If an error occurs.
254
   * @return A new array of values.
255
   */
256
  public Object[] readEntry() throws IOException {
257
    return readEntry(new Object[header.getNumFields()]);
258
  }
259
  
260
  public Row readRow() throws IOException {
261
    read();
262
    return row;
263
  }
264
  
265
  /** Skip the next record.
266
   * @throws IOException If an error occurs.
267
   */
268
  public void skip() throws IOException {
269
    boolean foundRecord = false;
270
    while (!foundRecord) {
271
      
272
      bufferCheck();
273
      
274
      // read the deleted flag
275
      char tempDeleted = (char) buffer.get();
276
      
277
      // skip the next bytes
278
      buffer.position(buffer.position() + header.getRecordLength() - 1); //the 1 is for the deleted flag just read.
279
      
280
      // add the row if it is not deleted.
281
      if (tempDeleted != '*') {
282
        foundRecord = true;
283
      }
284
    }
285
  }
286
  
287
  /** Copy the next record into the array starting at offset.
288
   * @param entry Th array  to copy into.
289
   * @param offset The offset to start at
290
   * @throws IOException If an error occurs.
291
   * @return The same array passed in.
292
   */
293
  public Object[] readEntry(Object[] entry,final int offset) throws IOException {
294
    if (entry.length - offset < header.getNumFields()) {
295
      throw new ArrayIndexOutOfBoundsException();
296
    }
297
    
298
    read();
299
    
300
    // retrieve the record length
301
    final int numFields = header.getNumFields();
302
    
303
    int fieldOffset = 0;
304
    for (int j=0; j < numFields; j++){
305
      entry[j + offset] = readObject(fieldOffset,j);
306
      fieldOffset += fieldLengths[j];
307
    }
308
    
309
    return entry;
310
  }
311
  
312
  /**
313
   * Transfer, by bytes, the next record to the writer.
314
   */
315
  public void transferTo(DbaseFileWriterNIO writer) throws IOException {
316
      bufferCheck();
317
      buffer.limit(buffer.position() + header.getRecordLength());
318
      writer.channel.write(buffer);
319
      buffer.limit(buffer.capacity());
320
      
321
      cnt++;
322
  }
323
  
324
  private void read() throws IOException {
325
    boolean foundRecord = false;
326
    while (!foundRecord) {
327
      
328
      bufferCheck();
329
      
330
      // read the deleted flag
331
      char deleted = (char) buffer.get();
332
      if (deleted == '*') {
333
          continue;
334
      }
335
      
336
      charBuffer.position(0);
337
      buffer.limit(buffer.position() + header.getRecordLength() - 1);
338
      decoder.decode(buffer,charBuffer,true);
339
      buffer.limit(buffer.capacity());
340
      charBuffer.flip();
341
      
342
      foundRecord = true;
343
    }
344
    
345
    cnt++;
346
  }
347
  
348
  /** Copy the next entry into the array.
349
   * @param entry The array to copy into.
350
   * @throws IOException If an error occurs.
351
   * @return The same array passed in.
352
   */
353
  public Object[] readEntry(Object[] entry) throws IOException {
354
    return readEntry(entry,0);
355
  }
356
  
357
  private Object readObject(final int fieldOffset,final int fieldNum) throws IOException {
358
    final char type = fieldTypes[fieldNum];
359
    final int fieldLen = fieldLengths[fieldNum];
360
    Object object = null;
361
    
362
    //System.out.println( charBuffer.subSequence(fieldOffset,fieldOffset + fieldLen));
363
    
364
    if(fieldLen > 0) {
365
      
366
      switch (type){
367
        // (L)logical (T,t,F,f,Y,y,N,n)
368
        case 'l':
369
        case 'L':
370
          switch (charBuffer.charAt(fieldOffset)) {
371
            
372
            case 't': case 'T': case 'Y': case 'y':
373
              object = Boolean.TRUE;
374
              break;
375
            case 'f': case 'F': case 'N': case 'n':
376
              object = Boolean.FALSE;
377
              break;
378
            default:
379
              
380
              throw new IOException("Unknown logical value : '" + charBuffer.charAt(fieldOffset) + "'");
381
          }
382
          break;
383
          // (C)character (String)
384
        case 'c':
385
        case 'C':
386
          // oh, this seems like a lot of work to parse strings...but,
387
          // For some reason if zero characters ( (int) char == 0 ) are allowed
388
          // in these strings, they do not compare correctly later on down the
389
          // line....
390
          int start = fieldOffset;
391
          int end = fieldOffset + fieldLen - 1;
392
          // trim off whitespace and 'zero' chars
393
          while (start < end) {
394
            char c = charBuffer.get(start);
395
            if (c== 0 || Character.isWhitespace(c)) {
396
              start++;
397
            }
398
            else break;
399
          }
400
          while (end > start) {
401
            char c = charBuffer.get(end);
402
            if (c == 0 || Character.isWhitespace(c)) {
403
              end--;
404
            }
405
            else break;
406
          }
407
          // set up the new indexes for start and end
408
          charBuffer.position(start).limit(end + 1);
409
          String s = charBuffer.toString();
410
          // this resets the limit...
411
          charBuffer.clear();
412
          object = s;
413
          break;
414
          // (D)date (Date)
415
        case 'd':
416
        case 'D':
417
            try{
418
              String tempString = charBuffer.subSequence(fieldOffset,fieldOffset + 4).toString();
419
              int tempYear = Integer.parseInt(tempString);
420
              tempString = charBuffer.subSequence(fieldOffset + 4,fieldOffset + 6).toString();
421
              int tempMonth = Integer.parseInt(tempString) - 1;
422
              tempString = charBuffer.subSequence(fieldOffset + 6,fieldOffset + 8).toString();
423
              int tempDay = Integer.parseInt(tempString);
424
              Calendar cal = Calendar.getInstance();
425
              cal.clear();
426
              cal.set(cal.YEAR,tempYear);
427
              cal.set(cal.MONTH, tempMonth);
428
              cal.set(cal.DAY_OF_MONTH, tempDay);
429
              object = cal.getTime();
430
            }
431
            catch(NumberFormatException nfe){
432
                //todo: use progresslistener, this isn't a grave error.
433
            }
434
          break;
435
          
436
          // (F)floating (Double)
437
        case 'n':
438
        case 'N':
439
          try {
440
            if (header.getFieldDecimalCount(fieldNum) == 0) {
441
              object = new Integer(numberParser.parseInt(charBuffer, fieldOffset, fieldOffset + fieldLen - 1));
442
              break;
443
            }
444
            // else will fall through to the floating point number
445
          } catch (NumberFormatException e) {
446
              
447
            // todo: use progresslistener, this isn't a grave error.
448

    
449
            // don't do this!!! the Double parse will be attemted as we fall
450
            // through, so no need to create a new Object. -IanS
451
            //object = new Integer(0);
452
              
453
            // Lets try parsing a long instead...
454
            try {
455
                object = new Long(numberParser.parseLong(charBuffer,fieldOffset,fieldOffset + fieldLen - 1));
456
                break;
457
            } catch (NumberFormatException e2) {
458
                
459
            }
460
          }
461
          
462
        case 'f':
463
        case 'F': // floating point number
464
          try {
465
            
466
            
467
            object = new Double(numberParser.parseDouble(charBuffer,fieldOffset, fieldOffset + fieldLen - 1));
468
          } catch (NumberFormatException e) {
469
              // todo: use progresslistener, this isn't a grave error, though it
470
              // does indicate something is wrong
471
           
472
              // okay, now whatever we got was truly undigestable. Lets go with
473
              // a zero Double.
474
              object = new Double(0.0);
475
          }
476
          break;
477
        default:
478
          throw new IOException("Invalid field type : " + type);
479
      }
480
      
481
    } 
482
    return object;
483
  }
484
  
485
  public static void main(String[] args) throws Exception {
486
  /*  FileChannel channel = new FileInputStream(args[0]).getChannel();
487
    DbaseFileReaderNIO reader = new DbaseFileReaderNIO(channel);
488
    System.out.println(reader.getHeader());
489
    int r = 0;
490
    while (reader.hasNext()) {
491
      System.out.println(++r + "," + java.util.Arrays.asList(reader.readEntry()));
492
    }
493
    */
494
  }
495
  
496
}