Statistics
| Revision:

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

History | View | Annotate | Download (15.3 KB)

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