001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/io/dbaseapi/DBaseFile.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.io.dbaseapi;
037
038 import java.io.ByteArrayOutputStream;
039 import java.io.File;
040 import java.io.IOException;
041 import java.io.RandomAccessFile;
042 import java.net.URI;
043 import java.util.ArrayList;
044 import java.util.HashMap;
045 import java.util.List;
046 import java.util.Map;
047
048 import org.deegree.datatypes.QualifiedName;
049 import org.deegree.datatypes.Types;
050 import org.deegree.framework.util.TimeTools;
051 import org.deegree.model.feature.Feature;
052 import org.deegree.model.feature.FeatureFactory;
053 import org.deegree.model.feature.FeatureProperty;
054 import org.deegree.model.feature.schema.FeatureType;
055 import org.deegree.model.feature.schema.GeometryPropertyType;
056 import org.deegree.model.feature.schema.PropertyType;
057 import org.deegree.model.spatialschema.ByteUtils;
058 import org.deegree.ogcbase.CommonNamespaces;
059
060 /**
061 * the datatypes of the dBase file and their representation as java types:
062 *
063 * dBase-type dBase-type-ID java-type
064 *
065 * character "C" String float "F" Float number "N" Double logical "L" String memo "M" String date "D" Date binary "B"
066 * ByteArrayOutputStream
067 *
068 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
069 * @author last edited by: $Author: aschmitz $
070 *
071 * @version $Revision: 19488 $, $Date: 2009-09-03 15:05:04 +0200 (Do, 03. Sep 2009) $
072 */
073 public class DBaseFile {
074
075 private static final URI DEEGREEAPP = CommonNamespaces.buildNSURI( "http://www.deegree.org/app" );
076
077 private static final String APP_PREFIX = "app";
078
079 private ArrayList<String> colHeader = new ArrayList<String>();
080
081 // representing the datasection of the dBase file
082 // only needed for writing a dBase file
083 private DBFDataSection dataSection = null;
084
085 // feature type of generated features
086 private FeatureType ft;
087
088 // keys: property types, values: column (in dbase file)
089 private Map<PropertyType, String> ftMapping = new HashMap<PropertyType, String>( 100 );
090
091 // Hashtable to contain info abouts in the table
092 private Map<String, dbfCol> column_info = new HashMap<String, dbfCol>();
093
094 // references to the dbase file
095 private RandomAccessFile rafDbf;
096
097 // represents the dBase file header
098 // only needed for writing the dBase file
099 private DBFHeader header = null;
100
101 // representing the name of the dBase file
102 // only needed for writing the dBase file
103 private String fname = null;
104
105 private String ftName = null;
106
107 // number of records in the table
108 private double file_numrecs;
109
110 // data start position, and length of the data
111 private int file_datalength;
112
113 // data start position, and length of the data
114 private int file_datap;
115
116 // flag which indicates if a dBase file should be
117 // read or writed.
118 // filemode = 0 : read only
119 // filemode = 1 : write only
120 private int filemode = 0;
121
122 // number of columns
123 private int num_fields;
124
125 // current record
126 private long record_number = 0;
127
128 // size of the cache used for reading data from the dbase table
129 private long cacheSize = 1000000;
130
131 // array containing the data of the cache
132 private byte[] dataArray = null;
133
134 // file position the caches starts
135 private long startIndex = 0;
136
137 /**
138 * constructor<BR>
139 * only for reading a dBase file<BR>
140 *
141 * @param url
142 * @throws IOException
143 */
144 public DBaseFile( String url ) throws IOException {
145 fname = url;
146
147 // creates rafDbf
148 File file = new File( url + ".dbf" );
149 if ( !file.exists() ) {
150 file = new File( url + ".DBF" );
151 }
152 rafDbf = new RandomAccessFile( file, "r" );
153
154 // dataArray = new byte[(int)rafDbf.length()];
155 if ( cacheSize > rafDbf.length() ) {
156 cacheSize = rafDbf.length();
157 }
158
159 dataArray = new byte[(int) cacheSize];
160 rafDbf.read( dataArray );
161 rafDbf.seek( 0 );
162
163 // initialize dbase file
164 initDBaseFile();
165
166 filemode = 0;
167 }
168
169 /**
170 * constructor<BR>
171 * only for writing a dBase file<BR>
172 *
173 * @param url
174 * @param fieldDesc
175 * @throws DBaseException
176 *
177 */
178 public DBaseFile( String url, FieldDescriptor[] fieldDesc ) throws DBaseException {
179 fname = url;
180
181 // create header
182 header = new DBFHeader( fieldDesc );
183
184 // create data section
185 dataSection = new DBFDataSection( fieldDesc );
186
187 filemode = 1;
188 }
189
190 /**
191 *
192 */
193 public void close() {
194 try {
195 if ( rafDbf != null ) {
196 // just true for reading access
197 rafDbf.close();
198 }
199 } catch ( Exception ex ) {
200 // should never happen
201 ex.printStackTrace();
202 }
203 }
204
205 /**
206 * method: initDBaseFile(); inits a DBF file. This is based on Pratap Pereira's Xbase.pm perl module
207 *
208 */
209 private void initDBaseFile()
210 throws IOException {
211 // position the record pointer at 0
212 rafDbf.seek( 0 );
213
214 /*
215 * // read the file type file_type = fixByte( rafDbf.readByte() ); // get the last update date file_update_year
216 * = fixByte( rafDbf.readByte() ); file_update_month = fixByte( rafDbf.readByte() ); file_update_day = fixByte(
217 * rafDbf.readByte() );
218 */
219
220 fixByte( rafDbf.readByte() );
221 fixByte( rafDbf.readByte() );
222 fixByte( rafDbf.readByte() );
223 fixByte( rafDbf.readByte() );
224
225 // a byte array to hold little-endian long data
226 byte[] b = new byte[4];
227
228 // read that baby in...
229 rafDbf.readFully( b );
230
231 // convert the byte array into a long (really a double)
232 file_numrecs = ByteUtils.readLEInt( b, 0 );
233
234 b = null;
235
236 // a byte array to hold little-endian short data
237 b = new byte[2];
238
239 // get the data position (where it starts in the file)
240 rafDbf.readFully( b );
241 file_datap = ByteUtils.readLEShort( b, 0 );
242
243 // find out the length of the data portion
244 rafDbf.readFully( b );
245 file_datalength = ByteUtils.readLEShort( b, 0 );
246
247 // calculate the number of fields
248 num_fields = ( file_datap - 33 ) / 32;
249
250 // read in the column data
251 int locn = 0; // offset of the current column
252
253 // process each field
254 for ( int i = 1; i <= num_fields; i++ ) {
255 // seek the position of the field definition data.
256 // This information appears after the first 32 byte
257 // table information, and lives in 32 byte chunks.
258 rafDbf.seek( ( ( i - 1 ) * 32 ) + 32 );
259
260 b = null;
261
262 // get the column name into a byte array
263 b = new byte[11];
264 rafDbf.readFully( b );
265
266 // convert the byte array to a String
267 String col_name = new String( b ).trim().toUpperCase();
268 while ( colHeader.contains( col_name ) ) {
269 col_name = col_name + "__" + i; // do it like shp2pgsql to avoid same-column names all over
270 }
271
272 // read in the column type
273 char[] c = new char[1];
274 c[0] = (char) rafDbf.readByte();
275
276 // String ftyp = new String( c );
277
278 // skip four bytes
279 rafDbf.skipBytes( 4 );
280
281 // get field length and precision
282 short flen = fixByte( rafDbf.readByte() );
283 short fdec = fixByte( rafDbf.readByte() );
284
285 // set the field position to the current
286 // value of locn
287 int fpos = locn;
288
289 // increment locn by the length of this field.
290 locn += flen;
291
292 // create a new dbfCol object and assign it the
293 // attributes of the current field
294 dbfCol column = new dbfCol( col_name );
295 column.type = new String( c );
296 column.size = flen;
297 column.position = fpos + 1;
298 column.prec = fdec;
299
300 // to be done: get the name of dbf-table via method in ShapeFile
301 column.table = "NOT";
302
303 column_info.put( col_name, column );
304 colHeader.add( col_name );
305 } // end for
306
307 ft = createCanonicalFeatureType();
308
309 } // end of initDBaseFile
310
311 /**
312 * Overrides the default feature type (which is generated from all columns in the dbase file) to allow customized
313 * naming and ordering of properties.
314 *
315 * @param ft
316 * @param ftMapping
317 */
318 public void setFeatureType( FeatureType ft, Map<PropertyType, String> ftMapping ) {
319 this.ft = ft;
320 this.ftMapping = ftMapping;
321 }
322
323 /**
324 * Creates a canonical {@link FeatureType} from all fields of the <code>DBaseFile</code>.
325 *
326 * @return feature type that contains all fields as property types
327 */
328 private FeatureType createCanonicalFeatureType() {
329 dbfCol column = null;
330
331 PropertyType[] ftp = new PropertyType[colHeader.size() + 1];
332
333 for ( int i = 0; i < colHeader.size(); i++ ) {
334 // retrieve the dbfCol object which corresponds // to this column.
335 column = column_info.get( colHeader.get( i ) );
336
337 QualifiedName name = new QualifiedName( APP_PREFIX, column.name, DEEGREEAPP );
338
339 if ( column.type.equalsIgnoreCase( "C" ) ) {
340 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.VARCHAR, true );
341 } else if ( column.type.equalsIgnoreCase( "F" ) || column.type.equalsIgnoreCase( "N" ) ) {
342 if ( column.prec == 0 ) {
343 if ( column.size < 10 ) {
344 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.INTEGER, true );
345 } else {
346 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.BIGINT, true );
347 }
348 } else {
349 if ( column.size < 8 ) {
350 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.FLOAT, true );
351 } else {
352 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.DOUBLE, true );
353 }
354 }
355 } else if ( column.type.equalsIgnoreCase( "M" ) ) {
356 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.VARCHAR, true );
357 } else if ( column.type.equalsIgnoreCase( "L" ) ) {
358 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.VARCHAR, true );
359 } else if ( column.type.equalsIgnoreCase( "D" ) ) {
360 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.VARCHAR, true );
361 } else if ( column.type.equalsIgnoreCase( "B" ) ) {
362 ftp[i] = FeatureFactory.createSimplePropertyType( name, Types.BLOB, true );
363 }
364
365 this.ftMapping.put( ftp[i], column.name );
366 }
367
368 int index = fname.lastIndexOf( "/" );
369 ftName = fname;
370 if ( index >= 0 ) {
371 ftName = fname.substring( index + 1 );
372 } else {
373 index = fname.lastIndexOf( "\\" );
374 if ( index >= 0 ) {
375 ftName = fname.substring( index + 1 );
376 }
377 }
378
379 QualifiedName featureTypeName = new QualifiedName( APP_PREFIX, ftName, DEEGREEAPP );
380
381 QualifiedName name = new QualifiedName( APP_PREFIX, "GEOM", DEEGREEAPP );
382 ftp[ftp.length - 1] = FeatureFactory.createGeometryPropertyType( name, Types.GEOMETRY_PROPERTY_NAME, 1, 1 );
383
384 return FeatureFactory.createFeatureType( featureTypeName, false, ftp );
385 }
386
387 /**
388 *
389 * @return number of records in the table
390 * @throws DBaseException
391 */
392 public int getRecordNum()
393 throws DBaseException {
394 if ( filemode == 1 ) {
395 throw new DBaseException( "class is initialized in write-only mode" );
396 }
397
398 return (int) file_numrecs;
399 }
400
401 /**
402 *
403 * Positions the record pointer at the top of the table.
404 *
405 * @throws DBaseException
406 */
407 public void goTop()
408 throws DBaseException {
409 if ( filemode == 1 ) {
410 throw new DBaseException( "class is initialized in write-only mode" );
411 }
412
413 record_number = 0;
414 }
415
416 /**
417 * Advance the record pointer to the next record.
418 *
419 * @return true if pointer has been increased
420 * @throws DBaseException
421 */
422 public boolean nextRecord()
423 throws DBaseException {
424 if ( filemode == 1 ) {
425 throw new DBaseException( "class is initialized in write-only mode" );
426 }
427
428 if ( record_number < file_numrecs ) {
429 record_number++;
430 return true;
431 }
432 return false;
433
434 }
435
436 /**
437 *
438 * @param col_name
439 * @return column's string value from the current row.
440 * @throws DBaseException
441 */
442 public String getColumn( String col_name )
443 throws DBaseException {
444 if ( filemode == 1 ) {
445 throw new DBaseException( "class is initialized in write-only mode" );
446 }
447
448 try {
449 // retrieve the dbfCol object which corresponds
450 // to this column.
451 // System.out.println( columnNames.get( col_name ) + "/" + col_name );
452 dbfCol column = column_info.get( col_name );
453
454 // seek the starting offset of the current record,
455 // as indicated by record_number
456 long pos = file_datap + ( ( record_number - 1 ) * file_datalength );
457
458 // read data from cache if the requested part of the dbase file is
459 // within it
460 if ( ( pos >= startIndex ) && ( ( pos + column.position + column.size ) < ( startIndex + cacheSize ) ) ) {
461 pos = pos - startIndex;
462 } else {
463 // actualize cache starting at the current cursor position
464 // if neccesary correct cursor position
465 rafDbf.seek( pos );
466 rafDbf.read( dataArray );
467 startIndex = pos;
468 pos = 0;
469 }
470 int ff = (int) ( pos + column.position );
471 return new String( dataArray, ff, column.size ).trim();
472 } catch ( Exception e ) {
473 e.printStackTrace();
474 return e.toString();
475 }
476 }
477
478 /**
479 * @return properties (column headers) of the dBase-file<BR>
480 * @throws DBaseException
481 */
482 public String[] getProperties()
483 throws DBaseException {
484 if ( filemode == 1 ) {
485 throw new DBaseException( "class is initialized in write-only mode" );
486 }
487
488 return colHeader.toArray( new String[colHeader.size()] );
489 }
490
491 /**
492 * @return datatype of each column of the database<BR>
493 * @throws DBaseException
494 */
495 public String[] getDataTypes()
496 throws DBaseException {
497 if ( filemode == 1 ) {
498 throw new DBaseException( "class is initialized in write-only mode" );
499 }
500
501 String[] datatypes = new String[colHeader.size()];
502 dbfCol column;
503
504 for ( int i = 0; i < colHeader.size(); i++ ) {
505 // retrieve the dbfCol object which corresponds
506 // to this column.
507 column = column_info.get( colHeader.get( i ) );
508
509 datatypes[i] = column.type.trim();
510 }
511
512 return datatypes;
513 }
514
515 /**
516 * @param container
517 * @param element
518 * @return true if the container sting array contains element<BR>
519 */
520 private boolean contains( String[] container, String element ) {
521 for ( int i = 0; i < container.length; i++ )
522
523 if ( container[i].equals( element ) ) {
524 return true;
525 }
526
527 return false;
528 }
529
530 /**
531 * @param field
532 * @return the size of a column
533 * @throws DBaseException
534 */
535 public int getDataLength( String field )
536 throws DBaseException {
537 dbfCol col = column_info.get( field );
538 if ( col == null )
539 throw new DBaseException( "Field " + field + " not found" );
540
541 return col.size;
542 }
543
544 /**
545 * @param fields
546 * @return the datatype of each column of the database specified by fields<BR>
547 * @throws DBaseException
548 */
549 public String[] getDataTypes( String[] fields )
550 throws DBaseException {
551 if ( filemode == 1 ) {
552 throw new DBaseException( "class is initialized in write-only mode" );
553 }
554
555 ArrayList<String> vec = new ArrayList<String>();
556 dbfCol column;
557
558 for ( int i = 0; i < colHeader.size(); i++ ) {
559 // check if the current (i'th) column (string) is
560 // within the array of specified columns
561 if ( contains( fields, colHeader.get( i ) ) ) {
562 // retrieve the dbfCol object which corresponds
563 // to this column.
564 column = column_info.get( colHeader.get( i ) );
565
566 vec.add( column.type.trim() );
567 }
568 }
569
570 return vec.toArray( new String[vec.size()] );
571 }
572
573 /**
574 * Returns a row of the dBase file as a {@link Feature} instance.
575 *
576 * @param rowNo
577 * @return a row of the dBase file as a Feature instance
578 * @throws DBaseException
579 */
580 public Feature getFRow( int rowNo )
581 throws DBaseException {
582
583 Map<String, Object> columnValues = getRow( rowNo );
584
585 PropertyType[] propTypes = this.ft.getProperties();
586 List<FeatureProperty> props = new ArrayList<FeatureProperty>();
587
588 for ( int i = 0; i < propTypes.length; i++ ) {
589 PropertyType pt = propTypes[i];
590 if ( pt instanceof GeometryPropertyType ) {
591 // insert dummy property for geometry
592 FeatureProperty prop = FeatureFactory.createFeatureProperty( pt.getName(), null );
593 props.add( prop );
594 } else {
595 String columnName = this.ftMapping.get( pt );
596 Object columnValue = columnValues.get( columnName );
597 if ( columnValue != null ) {
598 FeatureProperty prop = FeatureFactory.createFeatureProperty( pt.getName(), columnValue );
599 props.add( prop );
600 }
601 }
602 }
603 FeatureProperty[] fp = props.toArray( new FeatureProperty[props.size()] );
604 return FeatureFactory.createFeature( ftName + rowNo, ft, fp );
605 }
606
607 /**
608 *
609 * @param rowNo
610 * @return a row of the dbase file
611 * @throws DBaseException
612 */
613 private Map<String, Object> getRow( int rowNo )
614 throws DBaseException {
615
616 Map<String, Object> columnValues = new HashMap<String, Object>();
617
618 goTop();
619 record_number += rowNo;
620
621 for ( int i = 0; i < colHeader.size(); i++ ) {
622
623 // retrieve the dbfCol object which corresponds to this column.
624 dbfCol column = column_info.get( colHeader.get( i ) );
625
626 String value = getColumn( column.name );
627 Object columnValue = value;
628
629 if ( value != null ) {
630 // cast the value of the i'th column to corresponding datatype
631 if ( column.type.equalsIgnoreCase( "C" ) ) {
632 // nothing to do
633 } else if ( column.type.equalsIgnoreCase( "F" ) || column.type.equalsIgnoreCase( "N" ) ) {
634 try {
635 if ( column.prec == 0 ) {
636 if ( column.size < 10 ) {
637 columnValue = new Integer( value );
638 } else {
639 columnValue = new Long( value );
640 }
641 } else {
642 if ( column.size < 8 ) {
643 columnValue = new Float( value );
644 } else {
645 columnValue = new Double( value );
646 }
647 }
648 } catch ( Exception ex ) {
649 columnValue = new Double( "0" );
650 }
651 } else if ( column.type.equalsIgnoreCase( "M" ) ) {
652 // nothing to do
653 } else if ( column.type.equalsIgnoreCase( "L" ) ) {
654 // nothing to do
655 } else if ( column.type.equalsIgnoreCase( "D" ) ) {
656 if ( value.equals( "" ) ) {
657 columnValue = null;
658 } else {
659 String s = value.substring( 0, 4 ) + '-' + value.substring( 4, 6 ) + '-'
660 + value.substring( 6, 8 );
661 columnValue = TimeTools.createCalendar( s ).getTime();
662 }
663 } else if ( column.type.equalsIgnoreCase( "B" ) ) {
664 ByteArrayOutputStream os = new ByteArrayOutputStream( 10000 );
665 try {
666 os.write( value.getBytes() );
667 } catch ( IOException e ) {
668 e.printStackTrace();
669 }
670 columnValue = os;
671 }
672 } else {
673 columnValue = "";
674 }
675 columnValues.put( column.name, columnValue );
676 }
677
678 return columnValues;
679 }
680
681 /**
682 * bytes are signed; let's fix them...
683 *
684 * @param b
685 * @return unsigned byte as short
686 */
687 private static short fixByte( byte b ) {
688 if ( b < 0 ) {
689 return (short) ( b + 256 );
690 }
691
692 return b;
693 }
694
695 /**
696 * creates the dbase file and writes all data to it if the file specified by fname (s.o.) exists it will be deleted!
697 *
698 * @throws IOException
699 * @throws DBaseException
700 */
701 public void writeAllToFile()
702 throws IOException, DBaseException {
703 if ( filemode == 0 ) {
704 throw new DBaseException( "class is initialized in read-only mode" );
705 }
706
707 // if a file with the retrieved filename exists, delete it!
708 File file = new File( fname + ".dbf" );
709
710 if ( file.exists() ) {
711 file.delete();
712 }
713
714 // create a new file
715 RandomAccessFile rdbf = new RandomAccessFile( fname + ".dbf", "rw" );
716
717 try {
718 byte[] b = header.getHeader();
719 int nRecords = dataSection.getNoOfRecords();
720 // write number of records
721 ByteUtils.writeLEInt( b, 4, nRecords );
722 // write header to the file
723 rdbf.write( b );
724 b = dataSection.getDataSection();
725 // write datasection to the file
726 rdbf.write( b );
727 } catch ( IOException e ) {
728 throw e;
729 } finally {
730 rdbf.close();
731 }
732 }
733
734 /**
735 * writes a data record to byte array representing the data section of the dBase file. The method gets the data type
736 * of each field in recData from fieldDesc wich has been set at the constructor.
737 *
738 * @param recData
739 * @throws DBaseException
740 */
741 public void setRecord( ArrayList recData )
742 throws DBaseException {
743 if ( filemode == 0 ) {
744 throw new DBaseException( "class is initialized in read-only mode" );
745 }
746
747 dataSection.setRecord( recData );
748 }
749
750 /**
751 * writes a data record to byte array representing the data section of the dBase file. The method gets the data type
752 * of each field in recData from fieldDesc wich has been set at the constructor. index specifies the location of the
753 * retrieved record in the datasection. if an invalid index is used an exception will be thrown
754 *
755 * @param index
756 * @param recData
757 * @throws DBaseException
758 */
759 public void setRecord( int index, ArrayList recData )
760 throws DBaseException {
761 if ( filemode == 0 ) {
762 throw new DBaseException( "class is initialized in read-only mode" );
763 }
764
765 dataSection.setRecord( index, recData );
766 }
767
768 /**
769 * @return the feature type of the generated features
770 */
771 public FeatureType getFeatureType() {
772 return ft;
773 }
774
775 } // end of class DBaseFile
776
777 /**
778 *
779 *
780 * @version $Revision: 19488 $
781 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
782 */
783 class tsColumn {
784 public String name = null; // the column's name
785
786 public String table = null; // the table which "owns" the column
787
788 public String type = null; // the column's type
789
790 public int prec = 0; // the column's precision
791
792 public int size = 0; // the column's size
793
794 /**
795 *
796 * Constructs a tsColumn object.
797 *
798 * @param s
799 * the column name
800 */
801 tsColumn( String s ) {
802 name = s;
803 }
804 } // end of class tsColumn
805
806 /**
807 *
808 *
809 * @version $Revision: 19488 $
810 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
811 */
812 class dbfCol extends tsColumn {
813 int position = 0;
814
815 /**
816 * Creates a new dbfCol object.
817 *
818 * @param c
819 */
820 public dbfCol( String c ) {
821 super( c );
822 }
823 }