001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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.FileOutputStream;
041    import java.io.IOException;
042    import java.io.RandomAccessFile;
043    import java.net.URI;
044    import java.util.ArrayList;
045    import java.util.HashMap;
046    import java.util.List;
047    import java.util.Map;
048    
049    import org.deegree.datatypes.QualifiedName;
050    import org.deegree.datatypes.Types;
051    import org.deegree.framework.util.TimeTools;
052    import org.deegree.model.feature.Feature;
053    import org.deegree.model.feature.FeatureFactory;
054    import org.deegree.model.feature.FeatureProperty;
055    import org.deegree.model.feature.schema.FeatureType;
056    import org.deegree.model.feature.schema.GeometryPropertyType;
057    import org.deegree.model.feature.schema.PropertyType;
058    import org.deegree.model.spatialschema.ByteUtils;
059    import org.deegree.ogcbase.CommonNamespaces;
060    
061    /**
062     * the datatypes of the dBase file and their representation as java types:
063     * 
064     * dBase-type dBase-type-ID java-type
065     * 
066     * character "C" String float "F" Float number "N" Double logical "L" String memo "M" String date "D" Date binary "B"
067     * ByteArrayOutputStream
068     * 
069     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
070     * @author last edited by: $Author: apoth $
071     * 
072     * @version $Revision: 24227 $, $Date: 2010-05-07 11:57:54 +0200 (Fr, 07 Mai 2010) $
073     */
074    public class DBaseFile {
075    
076        private static final URI DEEGREEAPP = CommonNamespaces.buildNSURI( "http://www.deegree.org/app" );
077    
078        private static final String APP_PREFIX = "app";
079    
080        private ArrayList<String> colHeader = new ArrayList<String>();
081    
082        // representing the datasection of the dBase file
083        // only needed for writing a dBase file
084        private DBFDataSection dataSection = null;
085    
086        // feature type of generated features
087        private FeatureType ft;
088    
089        // keys: property types, values: column (in dbase file)
090        private Map<PropertyType, String> ftMapping = new HashMap<PropertyType, String>( 100 );
091    
092        // Hashtable to contain info abouts in the table
093        private Map<String, dbfCol> column_info = new HashMap<String, dbfCol>();
094    
095        // references to the dbase file
096        private RandomAccessFile rafDbf;
097    
098        // represents the dBase file header
099        // only needed for writing the dBase file
100        private DBFHeader header = null;
101    
102        // representing the name of the dBase file
103        // only needed for writing the dBase file
104        private String fname = null;
105    
106        private String ftName = null;
107    
108        // number of records in the table
109        private double file_numrecs;
110    
111        // data start position, and length of the data
112        private int file_datalength;
113    
114        // data start position, and length of the data
115        private int file_datap;
116    
117        // flag which indicates if a dBase file should be
118        // read or writed.
119        // filemode = 0 : read only
120        // filemode = 1 : write only
121        private int filemode = 0;
122    
123        // number of columns
124        private int num_fields;
125    
126        // current record
127        private long record_number = 0;
128    
129        // size of the cache used for reading data from the dbase table
130        private long cacheSize = 1000000;
131    
132        // array containing the data of the cache
133        private byte[] dataArray = null;
134    
135        // file position the caches starts
136        private long startIndex = 0;
137        
138        private List<FeatureProperty> props = new ArrayList<FeatureProperty>(50);
139    
140        /**
141         * constructor<BR>
142         * only for reading a dBase file<BR>
143         * 
144         * @param url
145         * @throws IOException
146         */
147        public DBaseFile( String url ) throws IOException {
148            fname = url;
149    
150            // creates rafDbf
151            File file = new File( url + ".dbf" );
152            if ( !file.exists() ) {
153                file = new File( url + ".DBF" );
154            }
155            rafDbf = new RandomAccessFile( file, "r" );
156    
157            // dataArray = new byte[(int)rafDbf.length()];
158            if ( cacheSize > rafDbf.length() ) {
159                cacheSize = rafDbf.length();
160            }
161    
162            dataArray = new byte[(int) cacheSize];
163            rafDbf.read( dataArray );
164            rafDbf.seek( 0 );
165    
166            // initialize dbase file
167            initDBaseFile();
168    
169            filemode = 0;
170        }
171    
172        /**
173         * constructor<BR>
174         * only for writing a dBase file<BR>
175         * 
176         * @param url
177         * @param fieldDesc
178         * @throws DBaseException
179         * 
180         */
181        public DBaseFile( String url, FieldDescriptor[] fieldDesc ) throws DBaseException {
182            fname = url;
183    
184            // create header
185            header = new DBFHeader( fieldDesc );
186    
187            // create data section
188            dataSection = new DBFDataSection( fieldDesc );
189    
190            filemode = 1;
191        }
192    
193        /**
194         *
195         */
196        public void close() {
197            try {
198                if ( rafDbf != null ) {
199                    // just true for reading access
200                    rafDbf.close();
201                }
202            } catch ( Exception ex ) {
203                // should never happen
204                ex.printStackTrace();
205            }
206        }
207    
208        /**
209         * method: initDBaseFile(); inits a DBF file. This is based on Pratap Pereira's Xbase.pm perl module
210         * 
211         */
212        private void initDBaseFile()
213                                throws IOException {
214            // position the record pointer at 0
215            rafDbf.seek( 0 );
216    
217            /*
218             * // read the file type file_type = fixByte( rafDbf.readByte() ); // get the last update date file_update_year
219             * = fixByte( rafDbf.readByte() ); file_update_month = fixByte( rafDbf.readByte() ); file_update_day = fixByte(
220             * rafDbf.readByte() );
221             */
222    
223            fixByte( rafDbf.readByte() );
224            fixByte( rafDbf.readByte() );
225            fixByte( rafDbf.readByte() );
226            fixByte( rafDbf.readByte() );
227    
228            // a byte array to hold little-endian long data
229            byte[] b = new byte[4];
230    
231            // read that baby in...
232            rafDbf.readFully( b );
233    
234            // convert the byte array into a long (really a double)
235            file_numrecs = ByteUtils.readLEInt( b, 0 );
236    
237            b = null;
238    
239            // a byte array to hold little-endian short data
240            b = new byte[2];
241    
242            // get the data position (where it starts in the file)
243            rafDbf.readFully( b );
244            file_datap = ByteUtils.readLEShort( b, 0 );
245    
246            // find out the length of the data portion
247            rafDbf.readFully( b );
248            file_datalength = ByteUtils.readLEShort( b, 0 );
249    
250            // calculate the number of fields
251            num_fields = ( file_datap - 33 ) / 32;
252    
253            // read in the column data
254            int locn = 0; // offset of the current column
255    
256            // process each field
257            for ( int i = 1; i <= num_fields; i++ ) {
258                // seek the position of the field definition data.
259                // This information appears after the first 32 byte
260                // table information, and lives in 32 byte chunks.
261                rafDbf.seek( ( ( i - 1 ) * 32 ) + 32 );
262    
263                b = null;
264    
265                // get the column name into a byte array
266                b = new byte[11];
267                rafDbf.readFully( b );
268    
269                // convert the byte array to a String
270                String col_name = new String( b ).trim().toUpperCase();
271                while ( colHeader.contains( col_name ) ) {
272                    col_name = col_name + "__" + i; // do it like shp2pgsql to avoid same-column names all over
273                }
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 allow customized
316         * 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         * @throws DBaseException
394         */
395        public int getRecordNum()
396                                throws DBaseException {
397            if ( filemode == 1 ) {
398                throw new DBaseException( "class is initialized in write-only mode" );
399            }
400    
401            return (int) file_numrecs;
402        }
403    
404        /**
405         * 
406         * Positions the record pointer at the top of the table.
407         * 
408         * @throws DBaseException
409         */
410        public void goTop()
411                                throws DBaseException {
412            if ( filemode == 1 ) {
413                throw new DBaseException( "class is initialized in write-only mode" );
414            }
415    
416            record_number = 0;
417        }
418    
419        /**
420         * Advance the record pointer to the next record.
421         * 
422         * @return true if pointer has been increased
423         * @throws DBaseException
424         */
425        public boolean nextRecord()
426                                throws DBaseException {
427            if ( filemode == 1 ) {
428                throw new DBaseException( "class is initialized in write-only mode" );
429            }
430    
431            if ( record_number < file_numrecs ) {
432                record_number++;
433                return true;
434            }
435            return false;
436    
437        }
438    
439        /**
440         * 
441         * @param col_name
442         * @return column's string value from the current row.
443         * @throws DBaseException
444         */
445        public String getColumn( String col_name )
446                                throws DBaseException {
447            if ( filemode == 1 ) {
448                throw new DBaseException( "class is initialized in write-only mode" );
449            }
450    
451            try {
452                // retrieve the dbfCol object which corresponds
453                // to this column.
454                // System.out.println( columnNames.get( col_name ) + "/" + col_name );
455                dbfCol column = column_info.get( col_name );
456    
457                // seek the starting offset of the current record,
458                // as indicated by record_number
459                long pos = file_datap + ( ( record_number - 1 ) * file_datalength );
460    
461                // read data from cache if the requested part of the dbase file is
462                // within it
463                if ( ( pos >= startIndex ) && ( ( pos + column.position + column.size ) < ( startIndex + cacheSize ) ) ) {
464                    pos = pos - startIndex;
465                } else {
466                    // actualize cache starting at the current cursor position
467                    // if neccesary correct cursor position
468                    rafDbf.seek( pos );
469                    rafDbf.read( dataArray );
470                    startIndex = pos;
471                    pos = 0;
472                }
473                int ff = (int) ( pos + column.position );
474                return new String( dataArray, ff, column.size ).trim();
475            } catch ( Exception e ) {
476                e.printStackTrace();
477                return e.toString();
478            }
479        }
480    
481        /**
482         * @return properties (column headers) of the dBase-file<BR>
483         * @throws DBaseException
484         */
485        public String[] getProperties()
486                                throws DBaseException {
487            if ( filemode == 1 ) {
488                throw new DBaseException( "class is initialized in write-only mode" );
489            }
490    
491            return colHeader.toArray( new String[colHeader.size()] );
492        }
493    
494        /**
495         * @return datatype of each column of the database<BR>
496         * @throws DBaseException
497         */
498        public String[] getDataTypes()
499                                throws DBaseException {
500            if ( filemode == 1 ) {
501                throw new DBaseException( "class is initialized in write-only mode" );
502            }
503    
504            String[] datatypes = new String[colHeader.size()];
505            dbfCol column;
506    
507            for ( int i = 0; i < colHeader.size(); i++ ) {
508                // retrieve the dbfCol object which corresponds
509                // to this column.
510                column = column_info.get( colHeader.get( i ) );
511    
512                datatypes[i] = column.type.trim();
513            }
514    
515            return datatypes;
516        }
517    
518        /**
519         * @param container
520         * @param element
521         * @return true if the container sting array contains element<BR>
522         */
523        private boolean contains( String[] container, String element ) {
524            for ( int i = 0; i < container.length; i++ )
525    
526                if ( container[i].equals( element ) ) {
527                    return true;
528                }
529    
530            return false;
531        }
532    
533        /**
534         * @param field
535         * @return the size of a column
536         * @throws DBaseException
537         */
538        public int getDataLength( String field )
539                                throws DBaseException {
540            dbfCol col = column_info.get( field );
541            if ( col == null )
542                throw new DBaseException( "Field " + field + " not found" );
543    
544            return col.size;
545        }
546    
547        /**
548         * @param fields
549         * @return the datatype of each column of the database specified by fields<BR>
550         * @throws DBaseException
551         */
552        public String[] getDataTypes( String[] fields )
553                                throws DBaseException {
554            if ( filemode == 1 ) {
555                throw new DBaseException( "class is initialized in write-only mode" );
556            }
557    
558            ArrayList<String> vec = new ArrayList<String>();
559            dbfCol column;
560    
561            for ( int i = 0; i < colHeader.size(); i++ ) {
562                // check if the current (i'th) column (string) is
563                // within the array of specified columns
564                if ( contains( fields, colHeader.get( i ) ) ) {
565                    // retrieve the dbfCol object which corresponds
566                    // to this column.
567                    column = column_info.get( colHeader.get( i ) );
568    
569                    vec.add( column.type.trim() );
570                }
571            }
572    
573            return vec.toArray( new String[vec.size()] );
574        }
575    
576        /**
577         * Returns a row of the dBase file as a {@link Feature} instance.
578         * 
579         * @param rowNo
580         * @return a row of the dBase file as a Feature instance
581         * @throws DBaseException
582         */
583        public Feature getFRow( int rowNo )
584                                throws DBaseException {
585    
586            Map<String, Object> columnValues = getRow( rowNo );
587    
588            PropertyType[] propTypes = this.ft.getProperties();
589            
590            props.clear();
591            for ( int i = 0; i < propTypes.length; i++ ) {
592                PropertyType pt = propTypes[i];
593                if ( pt instanceof GeometryPropertyType ) {
594                    // insert dummy property for geometry
595                    FeatureProperty prop = FeatureFactory.createFeatureProperty( pt.getName(), null );
596                    props.add( prop );
597                } else {
598                    String columnName = this.ftMapping.get( pt );
599                    Object columnValue = columnValues.get( columnName );
600                    if ( columnValue != null ) {
601                        FeatureProperty prop = FeatureFactory.createFeatureProperty( pt.getName(), columnValue );
602                        props.add( prop );
603                    }
604                }
605            }
606            FeatureProperty[] fp = props.toArray( new FeatureProperty[props.size()] );
607            return FeatureFactory.createFeature( ftName + rowNo, ft, fp );
608        }
609    
610        /**
611         * 
612         * @param rowNo
613         * @return a row of the dbase file
614         * @throws DBaseException
615         */
616        private Map<String, Object> getRow( int rowNo )
617                                throws DBaseException {
618    
619            Map<String, Object> columnValues = new HashMap<String, Object>();
620    
621            goTop();
622            record_number += rowNo;
623    
624            for ( int i = 0; i < colHeader.size(); i++ ) {
625    
626                // retrieve the dbfCol object which corresponds to this column.
627                dbfCol column = column_info.get( colHeader.get( i ) );
628    
629                String value = getColumn( column.name );
630                Object columnValue = value;
631    
632                if ( value != null ) {
633                    // cast the value of the i'th column to corresponding datatype
634                    if ( column.type.equalsIgnoreCase( "C" ) ) {
635                        // nothing to do
636                    } else if ( column.type.equalsIgnoreCase( "F" ) || column.type.equalsIgnoreCase( "N" ) ) {
637                        try {
638                            if ( column.prec == 0 ) {
639                                if ( column.size < 10 ) {
640                                    columnValue = new Integer( value );
641                                } else {
642                                    columnValue = new Long( value );
643                                }
644                            } else {
645                                if ( column.size < 8 ) {
646                                    columnValue = new Float( value );
647                                } else {
648                                    columnValue = new Double( value );
649                                }
650                            }
651                        } catch ( Exception ex ) {
652                            columnValue = new Double( "0" );
653                        }
654                    } else if ( column.type.equalsIgnoreCase( "M" ) ) {
655                        // nothing to do
656                    } else if ( column.type.equalsIgnoreCase( "L" ) ) {
657                        // nothing to do
658                    } else if ( column.type.equalsIgnoreCase( "D" ) ) {
659                        if ( value.equals( "" ) ) {
660                            columnValue = null;
661                        } else {
662                            String s = value.substring( 0, 4 ) + '-' + value.substring( 4, 6 ) + '-'
663                                       + value.substring( 6, 8 );
664                            columnValue = TimeTools.createCalendar( s ).getTime();
665                        }
666                    } else if ( column.type.equalsIgnoreCase( "B" ) ) {
667                        ByteArrayOutputStream os = new ByteArrayOutputStream( 10000 );
668                        try {
669                            os.write( value.getBytes() );
670                        } catch ( IOException e ) {
671                            e.printStackTrace();
672                        }
673                        columnValue = os;
674                    }
675                } else {
676                    columnValue = "";
677                }
678                columnValues.put( column.name, columnValue );
679            }
680    
681            return columnValues;
682        }
683    
684        /**
685         * bytes are signed; let's fix them...
686         * 
687         * @param b
688         * @return unsigned byte as short
689         */
690        private static short fixByte( byte b ) {
691            if ( b < 0 ) {
692                return (short) ( b + 256 );
693            }
694    
695            return b;
696        }
697    
698        /**
699         * creates the dbase file and writes all data to it if the file specified by fname (s.o.) exists it will be deleted!
700         * 
701         * @throws IOException
702         * @throws DBaseException
703         */
704        public void writeAllToFile()
705                                throws IOException, DBaseException {
706            if ( filemode == 0 ) {
707                throw new DBaseException( "class is initialized in read-only mode" );
708            }
709    
710            // if a file with the retrieved filename exists, delete it!
711            File file = new File( fname + ".dbf" );
712    
713            if ( file.exists() ) {
714                file.delete();
715            }
716    
717            // create a new file
718            // RandomAccessFile rdbf = new RandomAccessFile( fname + ".dbf", "rw" );
719            FileOutputStream fos = new FileOutputStream( fname + ".dbf" );
720            try {
721                byte[] b = header.getHeader();
722                int nRecords = dataSection.getNoOfRecords();
723                // write number of records
724                ByteUtils.writeLEInt( b, 4, nRecords );
725                // write header to the file
726                // rdbf.write( b );
727                fos.write( b );
728                // b = dataSection.getDataSection(fos);
729                dataSection.getDataSection( fos );
730                // write datasection to the file
731                // rdbf.write( b );
732            } catch ( IOException e ) {
733                throw e;
734            } finally {
735                // rdbf.close();
736                fos.close();
737            }
738        }
739    
740        /**
741         * writes a data record to byte array representing the data section of the dBase file. The method gets the data type
742         * of each field in recData from fieldDesc wich has been set at the constructor.
743         * 
744         * @param recData
745         * @throws DBaseException
746         */
747        public void setRecord( List<?> recData )
748                                throws DBaseException {
749            if ( filemode == 0 ) {
750                throw new DBaseException( "class is initialized in read-only mode" );
751            }
752    
753            dataSection.setRecord( recData );
754        }
755    
756        /**
757         * writes a data record to byte array representing the data section of the dBase file. The method gets the data type
758         * of each field in recData from fieldDesc wich has been set at the constructor. index specifies the location of the
759         * retrieved record in the datasection. if an invalid index is used an exception will be thrown
760         * 
761         * @param index
762         * @param recData
763         * @throws DBaseException
764         */
765        public void setRecord( int index, List<?> recData )
766                                throws DBaseException {
767            if ( filemode == 0 ) {
768                throw new DBaseException( "class is initialized in read-only mode" );
769            }
770    
771            dataSection.setRecord( index, recData );
772        }
773    
774        /**
775         * @return the feature type of the generated features
776         */
777        public FeatureType getFeatureType() {
778            return ft;
779        }
780    
781    } // end of class DBaseFile
782    
783    /**
784     * 
785     * 
786     * @version $Revision: 24227 $
787     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
788     */
789    class tsColumn {
790        public String name = null; // the column's name
791    
792        public String table = null; // the table which "owns" the column
793    
794        public String type = null; // the column's type
795    
796        public int prec = 0; // the column's precision
797    
798        public int size = 0; // the column's size
799    
800        /**
801         * 
802         * Constructs a tsColumn object.
803         * 
804         * @param s
805         *            the column name
806         */
807        tsColumn( String s ) {
808            name = s;
809        }
810    } // end of class tsColumn
811    
812    /**
813     * 
814     * 
815     * @version $Revision: 24227 $
816     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
817     */
818    class dbfCol extends tsColumn {
819        int position = 0;
820    
821        /**
822         * Creates a new dbfCol object.
823         * 
824         * @param c
825         */
826        public dbfCol( String c ) {
827            super( c );
828        }
829    }