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