    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/io/mapinfoapi/MapInfoDataSource.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree.
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006       http://www.geographie.uni-bonn.de/deegree/
    007     and
    008       lat/lon GmbH
    009       http://lat-lon.de/
    010
    011     Additional copyright notes:
    012     This class is based on the MapInfoDataSource class from the GeoTools project.
    013     Geotools - OpenSource mapping toolkit
    014     (C) 2002, Centre for Computational Geography
    015
    016     This library is free software; you can redistribute it and/or modify it under
    017     the terms of the GNU Lesser General Public License as published by the Free
    018     Software Foundation; either version 2.1 of the License, or (at your option)
    019     any later version.
    020     This library is distributed in the hope that it will be useful, but WITHOUT
    021     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    022     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    023     details.
    024     You should have received a copy of the GNU Lesser General Public License
    025     along with this library; if not, write to the Free Software Foundation, Inc.,
    026     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    027
    028     Contact information:
    029
    030     lat/lon GmbH
    031     Aennchenstr. 19, 53177 Bonn
    032     Germany
    033
    034     Department of Geography, University of Bonn
    035     Prof. Dr. Klaus Greve
    036     Postfach 1147, 53001 Bonn
    037     Germany
    038
    039     e-mail: info@deegree.org
    040    ----------------------------------------------------------------------------*/
    041    package org.deegree.io.mapinfoapi;
    042
    043    import java.io.BufferedReader;
    044    import java.io.File;
    045    import java.io.FileNotFoundException;
    046    import java.io.FileReader;
    047    import java.io.IOException;
    048    import java.net.URL;
    049    import java.util.ArrayList;
    050    import java.util.StringTokenizer;
    051
    052    import org.deegree.datatypes.QualifiedName;
    053    import org.deegree.datatypes.Types;
    054    import org.deegree.framework.log.ILogger;
    055    import org.deegree.framework.log.LoggerFactory;
    056    import org.deegree.framework.util.CharsetUtils;
    057    import org.deegree.framework.util.StringTools;
    058    import org.deegree.model.feature.Feature;
    059    import org.deegree.model.feature.FeatureCollection;
    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.PropertyType;
    064    import org.deegree.model.filterencoding.Filter;
    065    import org.deegree.model.spatialschema.JTSAdapter;
    066    import org.deegree.ogcwebservices.wfs.operation.Query;
    067
    068    import com.vividsolutions.jts.geom.Coordinate;
    069    import com.vividsolutions.jts.geom.Geometry;
    070    import com.vividsolutions.jts.geom.GeometryFactory;
    071    import com.vividsolutions.jts.geom.Polygon;
    072    import com.vividsolutions.jts.geom.TopologyException;
    073
    074    /**
    075     * TODO add documentation here
    076     *
    077     *
    078     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
    079     * @author last edited by: $Author: mschneider $
    080     *
    081     * @version $Revision: 18191 $, $Date: 2009-06-18 17:26:44 +0200 (Do, 18. Jun 2009) $
    082     */
    083    public class MapInfoDataSource {
    084
    085        private static ILogger LOG = LoggerFactory.getLogger( MapInfoDataSource.class );
    086
    087        public static final String TYPE_NONE = "none";
    088
    089        public static final String TYPE_POINT = "point";
    090
    091        public static final String TYPE_LINE = "line";
    092
    093        public static final String TYPE_PLINE = "pline";
    094
    095        public static final String TYPE_REGION = "region";
    096
    097        public static final String TYPE_ARC = "arc";
    098
    099        public static final String TYPE_TEXT = "text";
    100
    101        public static final String TYPE_RECT = "rectangle";
    102
    103        public static final String TYPE_ROUNDRECT = "rounded rectangle";
    104
    105        public static final String TYPE_ELLIPSE = "ellipse";
    106
    107        public static final String CLAUSE_SYMBOL = "SYMBOL";
    108
    109        public static final String CLAUSE_PEN = "PEN";
    110
    111        public static final String CLAUSE_SMOOTH = "SMOOTH";
    112
    113        public static final String CLAUSE_CENTER = "CENTER";
    114
    115        public static final String CLAUSE_BRUSH = "BRUSH";
    116
    117        public static final String CLAUSE_VERSION = "Version";
    118
    119        public static final String CLAUSE_CHARSET = "Charset";
    120
    121        public static final String CLAUSE_DELIMETER = "DELIMITER";
    122
    123        public static final String CLAUSE_UNIQUE = "UNIQUE";
    124
    125        public static final String CLAUSE_INDEX = "INDEX";
    126
    127        public static final String CLAUSE_COLUMNS = "COLUMNS";
    128
    129        private ArrayList<String> hColumnsNames;
    130
    131        private ArrayList<String> hColumnsTypes;
    132
    133        private ArrayList<String> hIndex;
    134
    135        private ArrayList<String> hUnique;
    136
    137        private FeatureType lineType = null;
    138
    139        private FeatureType pointType = null;
    140
    141        private FeatureType polygonType = null;
    142
    143        // Factory to use to build Geometries
    144        private GeometryFactory geomFactory;
    145
    146        private String filename;
    147
    148        private String hCharset;
    149
    150        private String hDelimeter = "\t";
    151
    152        // Header information
    153        private String hVersion;
    154
    155        // CoordsSys not supported
    156        // Transform not supported
    157        // Global variables (for the initial read)
    158        private String line; // The current Line of the MIF file.
    159
    160        /**
    161         * Creates a new MapInfoDataSource object.
    162         *
    163         * @param url
    164         *
    165         * @throws java.net.MalformedURLException
    166         */
    167        public MapInfoDataSource( URL url ) throws java.net.MalformedURLException {
    168            try {
    169                filename = java.net.URLDecoder.decode( url.getFile(), CharsetUtils.getSystemCharset() );
    170            } catch ( Exception e ) {
    171                throw new java.net.MalformedURLException( e.toString() );
    172            }
    173            geomFactory = new GeometryFactory();
    174        }
    175
    176        /**
    177         * Reads the MIF and MID files and returns a ArrayList of the Features they contain
    178         *
    179         * @return a ArrayList of features?
    180         *
    181         * @throws Exception
    182         *             if file doesn't exist or is not readable etc
    183         */
    184        protected ArrayList readMifMid()
    185                                throws Exception {
    186            if ( filename == null ) {
    187                throw new Exception( "Invalid filename passed to readMifMid" );
    188            }
    189
    190            String mifFile = setExtension( filename, "MIF" );
    191            String midFile = setExtension( filename, "MID" );
    192
    193            // Read files
    194            try {
    195                File mif = new File( mifFile );
    196
    197                if ( !mif.exists() ) {
    198                    mifFile = setExtension( filename, "mif" );
    199                    mif = new File( mifFile );
    200
    201                    if ( !mif.exists() ) {
    202                        mifFile = setExtension( filename.toLowerCase(), "mif" );
    203                        mif = new File( mifFile );
    204
    205                        if ( !mif.exists() ) {
    206                            mifFile = setExtension( filename.toUpperCase(), "MIF" );
    207                            mif = new File( mifFile );
    208                        } // and at that I'm out of guesses
    209                    }
    210                }
    211
    212                File mid = new File( midFile );
    213
    214                if ( !mid.exists() ) {
    215                    midFile = setExtension( filename, "mid" );
    216                    mid = new File( midFile );
    217
    218                    if ( !mid.exists() ) {
    219                        midFile = setExtension( filename.toLowerCase(), "mid" );
    220                        mid = new File( midFile );
    221
    222                        if ( !mid.exists() ) {
    223                            midFile = setExtension( filename.toUpperCase(), "MID" );
    224                            mid = new File( midFile );
    225                        } // and at that I'm out of guesses
    226                    }
    227                }
    228
    229                ArrayList features = readMifMid( new BufferedReader( new FileReader( mif ) ),
    230                                                 new BufferedReader( new FileReader( mid ) ) );
    231
    232                return features;
    233            } catch ( FileNotFoundException fnfexp ) {
    234                throw new Exception( "FileNotFoundException trying to read mif file : ", fnfexp );
    235            }
    236        }
    237
    238        /**
    239         * @param filename
    240         * @param ext
    241         *
    242         * @return
    243         */
    244        private String setExtension( String filename, String ext ) {
    245            if ( ext.indexOf( "." ) == -1 ) {
    246                ext = "." + ext;
    247            }
    248
    249            if ( filename.lastIndexOf( "." ) == -1 ) {
    250                return filename + ext;
    251            }
    252
    253            return filename.substring( 0, filename.lastIndexOf( "." ) ) + ext;
    254        }
    255
    256        /**
    257         * This private method constructs the factories used to create the Feature, and Geometries as
    258         * they are read It takes it's setup values from the value of the COLUMNS clause in the MIF file
    259         *
    260         * @throws Exception
    261         */
    262        private void setUpFactories()
    263                                throws Exception {
    264            // Go through each column name, and set up an attribute for each one
    265            ArrayList<PropertyType> colAttribs = new ArrayList<PropertyType>( hColumnsNames.size() );
    266
    267            // Add attributes for each column
    268            // Iterator it = hColumns.keySet().iterator();
    269            for ( int i = 0; i < hColumnsNames.size(); i++ ) {
    270                String type = hColumnsTypes.get( i ).toLowerCase();
    271                int typeClass = -999;
    272
    273                if ( type.equals( "float" ) || type.startsWith( "decimal" ) ) {
    274                    typeClass = Types.NUMERIC;
    275                    hColumnsTypes.set( i, "Double" );
    276                } else if ( type.startsWith( "char" ) ) {
    277                    typeClass = Types.VARCHAR;
    278                    hColumnsTypes.set( i, "String" );
    279                } else if ( type.equals( "integer" ) || type.equals( "smallint" ) ) {
    280                    typeClass = Types.INTEGER;
    281                    hColumnsTypes.set( i, "Integer" );
    282                } else {
    283                    typeClass = Types.VARCHAR;
    284                    hColumnsTypes.set( i, "String" );
    285                }
    286
    287                PropertyType ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( hColumnsNames.get( i ) ),
    288                                                                            typeClass, true );
    289                colAttribs.add( ftp );
    290            }
    291
    292            PropertyType ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, true );
    293
    294            // Add default Geometry attribute type
    295            colAttribs.add( 0, ftp );
    296
    297            // create point feature Type & factory
    298            try {
    299                PropertyType[] ftps = colAttribs.toArray( new PropertyType[colAttribs.size()] );
    300                pointType = FeatureFactory.createFeatureType( filename.toString() + "_point", false, ftps );
    301            } catch ( Exception schexp ) {
    302                throw new Exception( "SchemaException setting up point factory : ", schexp );
    303            }
    304
    305            // Set up Line factory
    306            // Add default attribute type
    307            ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, true );
    308            colAttribs.set( 0, ftp );
    309
    310            // create line feature Type & factory
    311            try {
    312                PropertyType[] ftps = colAttribs.toArray( new PropertyType[colAttribs.size()] );
    313                lineType = FeatureFactory.createFeatureType( filename.toString() + "_line", false, ftps );
    314            } catch ( Exception schexp ) {
    315                throw new Exception( "SchemaException setting up line factory : ", schexp );
    316            }
    317
    318            // Set up Polygon factory
    319            // Add default attribute type
    320            ftp = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, true );
    321            colAttribs.set( 0, ftp );
    322
    323            // create polygon feature Type & factory
    324            try {
    325                PropertyType[] ftps = colAttribs.toArray( new PropertyType[colAttribs.size()] );
    326                polygonType = FeatureFactory.createFeatureType( filename.toString() + "_poly", false, ftps );
    327            } catch ( Exception schexp ) {
    328                throw new Exception( "SchemaException setting up polygon factory : ", schexp );
    329            }
    330        }
    331
    332        /**
    333         * Reads an entire MID/MIF file. (Two files, actually, separately opened)
    334         *
    335         * @param mifReader
    336         *            An opened BufferedReader to the MIF file.
    337         * @param midReader
    338         *            An opened BufferedReader to the MID file.
    339         *
    340         * @return
    341         * @throws Exception
    342         */
    343        private ArrayList readMifMid( BufferedReader mifReader, BufferedReader midReader )
    344                                throws Exception {
    345            // Read the MIF header
    346            readMifHeader( mifReader );
    347
    348            // Set up factories
    349            setUpFactories();
    350
    351            ArrayList<Feature> features = new ArrayList<Feature>( 100 );
    352
    353            // Start by reading first line
    354            try {
    355                line = readMifLine( mifReader );
    356            } catch ( IOException ioexp ) {
    357                throw new Exception( "No data at start of file", ioexp );
    358            }
    359
    360            Feature feature;
    361
    362            // Read each object in the MIF file
    363            while ( ( feature = readObject( mifReader, midReader ) ) != null ) {
    364                // Figure out which type of feature it is
    365                // Add to relevent ArrayList
    366                features.add( feature );
    367            }
    368
    369            return features;
    370        }
    371
    372        /**
    373         * Reads the header from the given MIF file stream
    374         *
    375         * @param mifReader
    376         *
    377         * @throws Exception
    378         */
    379        private void readMifHeader( BufferedReader mifReader )
    380                                throws Exception {
    381            try {
    382                while ( ( readMifLine( mifReader ) != null ) && !line.trim().equalsIgnoreCase( "DATA" ) ) {
    383                    if ( clause( line ).equalsIgnoreCase( CLAUSE_VERSION ) ) {
    384                        // Read Version clause
    385                        hVersion = line.trim().substring( line.trim().indexOf( ' ' ) ).trim();
    386                        LOG.logDebug( "version [" + hVersion + "]" );
    387                    }
    388
    389                    if ( clause( line ).equalsIgnoreCase( CLAUSE_CHARSET ) ) {
    390                        // Read Charset clause
    391                        // hCharset = line.replace('\"','
    392                        // ').trim().substring(line.trim().indexOf(' ')).trim();
    393                        hCharset = remainder( line ).replace( '"', ' ' ).trim();
    394                        LOG.logDebug( "Charset [" + hCharset + "]" );
    395                    }
    396
    397                    if ( clause( line ).equalsIgnoreCase( CLAUSE_DELIMETER ) ) {
    398                        // Read Delimeter clause
    399                        hDelimeter = line.replace( '\"', ' ' ).trim().substring( line.trim().indexOf( ' ' ) ).trim();
    400                        LOG.logDebug( "delimiter [" + hDelimeter + "]" );
    401                    }
    402
    403                    if ( clause( line ).equalsIgnoreCase( CLAUSE_UNIQUE ) ) {
    404                        // Read Unique clause
    405                        StringTokenizer st = new StringTokenizer( line.trim().substring( line.trim().indexOf( ' ' ) ), "," );
    406                        hUnique = new ArrayList<String>();
    407                        LOG.logDebug( "Unique cols " );
    408
    409                        while ( st.hasMoreTokens() ) {
    410                            String uniq = st.nextToken();
    411                            LOG.logDebug( "\t" + uniq );
    412                            hUnique.add( uniq );
    413                        }
    414                    }
    415
    416                    if ( clause( line ).equalsIgnoreCase( CLAUSE_INDEX ) ) {
    417                        // Read Index clause
    418                        StringTokenizer st = new StringTokenizer( line.trim().substring( line.trim().indexOf( ' ' ) ), "," );
    419                        hIndex = new ArrayList<String>( st.countTokens() );
    420                        LOG.logDebug( "Indexes" );
    421
    422                        while ( st.hasMoreTokens() ) {
    423                            String index = st.nextToken();
    424                            LOG.logDebug( "\t" + index );
    425                            hIndex.add( index );
    426                        }
    427                    }
    428
    429                    if ( clause( line ).equalsIgnoreCase( CLAUSE_COLUMNS ) ) {
    430                        // Read Columns clause
    431                        int cols = 0;
    432
    433                        try {
    434                            cols = Integer.parseInt( remainder( line ) );
    435                        } catch ( NumberFormatException nfexp ) {
    436                            LOG.logError( "bad number of colums ", nfexp );
    437                        }
    438
    439                        // Read each of the columns
    440                        hColumnsNames = new ArrayList<String>( cols );
    441                        hColumnsTypes = new ArrayList<String>( cols );
    442
    443                        for ( int i = 0; i < cols; i++ ) {
    444                            line = readMifLine( mifReader );
    445
    446                            // StringTokenizer st = new
    447                            // StringTokenizer(line.trim().substring(line.trim().indexOf('
    448                            // ')), " ");
    449                            String name = clause( line );
    450                            String value = remainder( line );
    451
    452                            LOG.logDebug( "column name " + name + " value " + value );
    453
    454                            hColumnsNames.add( name );
    455                            hColumnsTypes.add( value );
    456                        }
    457                    }
    458                }
    459            } catch ( IOException ioexp ) {
    460                throw new Exception( "IOException reading MIF header : " + ioexp.getMessage() );
    461            }
    462        }
    463
    464        /**
    465         * A 'Clause' is stored as a single string at the start of a line. This rips the clause name out
    466         * of the given line.
    467         *
    468         * @param line
    469         *
    470         * @return
    471         */
    472        private String clause( String line ) {
    473            return clause( line, ' ' );
    474        }
    475
    476        /**
    477         * @param line
    478         * @param delimiter
    479         *
    480         * @return
    481         */
    482        private String clause( String line, char delimiter ) {
    483            line = line.trim();
    484
    485            int index = line.indexOf( delimiter );
    486
    487            if ( index == -1 ) {
    488                return line;
    489            }
    490            return line.substring( 0, index ).trim();
    491        }
    492
    493        /**
    494         * returns the last word of the string
    495         *
    496         * @param line
    497         *
    498         * @return the last word of the string
    499         */
    500        private String remainder( String line ) {
    501            return remainder( line, ' ' );
    502        }
    503
    504        /**
    505         * @param line
    506         * @param delimiter
    507         *
    508         * @return
    509         */
    510        private String remainder( String line, char delimiter ) {
    511            line = line.trim();
    512
    513            int index = line.lastIndexOf( delimiter );
    514
    515            if ( index == -1 ) {
    516                return "";
    517            }
    518            return line.substring( index ).trim();
    519        }
    520
    521        /**
    522         * Reads the next line in the reader, ignoring lines which are nothing but whitespace. Sets the
    523         * global 'line' variable to the currently read line
    524         *
    525         * @param reader
    526         *
    527         * @return
    528         * @throws IOException
    529         * @throws Exception
    530         */
    531        private String readMifLine( BufferedReader reader )
    532                                throws IOException, Exception {
    533            do {
    534                line = reader.readLine();
    535
    536                if ( line == null ) {
    537                    return null;
    538                }
    539
    540                if ( isShadingClause( line ) ) {
    541                    LOG.logDebug( "going to process shading" );
    542
    543                    // processShading(line);
    544                    line = " ";
    545                }
    546            } while ( line.trim().length() == 0 );
    547
    548            line = line.trim();
    549
    550            // LOG.logDebug("returning line " + line);
    551            return line;
    552        }
    553
    554        /**
    555         * Reads a single MIF Object (Point, Line, Region, etc.) as a Feature
    556         *
    557         * @param mifReader
    558         * @param midReader
    559         *
    560         * @return
    561         * @throws Exception
    562         */
    563        private Feature readObject( BufferedReader mifReader, BufferedReader midReader )
    564                                throws Exception {
    565            Feature feature = null;
    566
    567            // LOG.logDebug("line = " + line);
    568            // examine The current line
    569            if ( line == null ) {
    570                return null;
    571            }
    572
    573            int index = line.indexOf( ' ' );
    574
    575            if ( index == -1 ) {
    576                index = line.length();
    577            }
    578
    579            if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_POINT ) ) {
    580                // Read point data
    581                feature = readPointObject( mifReader, midReader );
    582            } else if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_LINE ) ) {
    583                // Read line data
    584                feature = readLineObject( mifReader, midReader );
    585            } else if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_PLINE ) ) {
    586                // Read pline data
    587                feature = readPLineObject( mifReader, midReader );
    588            } else if ( line.substring( 0, index ).equalsIgnoreCase( TYPE_REGION ) ) {
    589                // Read region data
    590                feature = readRegionObject( mifReader, midReader );
    591            } else {
    592                LOG.logDebug( line + " unknown object in mif reader" );
    593            }
    594
    595            return feature;
    596        }
    597
    598        /**
    599         * Reads Point information from the MIF stream
    600         *
    601         * @param mifReader
    602         * @param midReader
    603         *
    604         * @return
    605         * @throws Exception
    606         */
    607        private Feature readPointObject( BufferedReader mifReader, BufferedReader midReader )
    608                                throws Exception {
    609            Feature feature = null;
    610
    611            StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ), "," );
    612
    613            try {
    614                double x = Double.parseDouble( st.nextToken() );
    615                double y = Double.parseDouble( st.nextToken() );
    616
    617                // Construct Geomtry
    618                Geometry pointGeom = geomFactory.createPoint( new Coordinate( x, y ) );
    619
    620                // Read next line
    621                readMifLine( mifReader );
    622
    623                // Hashtable shading = readShading(mifReader);
    624                // Shading is not included, as null feature attributes are not
    625                // supported yet
    626                ArrayList<Object> midValues = readMid( midReader );
    627
    628                // midValues.putAll(shading);
    629                // Create Feature
    630                feature = buildFeature( pointType, pointGeom, midValues );
    631
    632                LOG.logDebug( "Built point feature : " + x + " " + y );
    633            } catch ( NumberFormatException nfexp ) {
    634                throw new Exception( "Exception reading Point data from MIF file : ", nfexp );
    635            } catch ( IOException ioexp ) {
    636                throw new Exception( "IOException reading point data : ", ioexp );
    637            }
    638
    639            return feature;
    640        }
    641
    642        /**
    643         * Reads Line information from the MIF stream
    644         *
    645         * @param mifReader
    646         * @param midReader
    647         *
    648         * @return
    649         * @throws Exception
    650         */
    651        private Feature readLineObject( BufferedReader mifReader, BufferedReader midReader )
    652                                throws Exception {
    653            Feature feature = null;
    654
    655            StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ), "," );
    656
    657            try {
    658                double x1 = Double.parseDouble( st.nextToken() );
    659                double y1 = Double.parseDouble( st.nextToken() );
    660                double x2 = Double.parseDouble( st.nextToken() );
    661                double y2 = Double.parseDouble( st.nextToken() );
    662
    663                // Construct Geomtry
    664                Coordinate[] cPoints = new Coordinate[2];
    665                cPoints[0] = new Coordinate( x1, y1 );
    666                cPoints[1] = new Coordinate( x2, y2 );
    667
    668                Geometry lineGeom = geomFactory.createLineString( cPoints );
    669
    670                // Read next line
    671                readMifLine( mifReader );
    672
    673                // Hashtable shading = readShading(mifReader);
    674                // Shading is not included, as null feature attributes are not
    675                // supported yet
    676                ArrayList<Object> midValues = readMid( midReader );
    677
    678                // midValues.putAll(shading);
    679                // Create Feature
    680                feature = buildFeature( lineType, lineGeom, midValues );
    681
    682                LOG.logDebug( "Built line feature : " + x1 + " " + y1 + " - " + x2 + " " + y2 );
    683            } catch ( NumberFormatException nfexp ) {
    684                throw new Exception( "Exception reading Point data from MIF file : " + nfexp.getMessage() );
    685            } catch ( IOException ioexp ) {
    686                throw new Exception( "IOException reading point data : " + ioexp.getMessage() );
    687            }
    688
    689            return feature;
    690        }
    691
    692        /**
    693         * Reads Multi-Line (PLine) information from the MIF stream
    694         *
    695         * @param mifReader
    696         * @param midReader
    697         *
    698         * @return
    699         * @throws Exception
    700         */
    701        private Feature readPLineObject( BufferedReader mifReader, BufferedReader midReader )
    702                                throws Exception {
    703            Feature feature = null;
    704
    705            StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ) );
    706
    707            try {
    708                int numsections = 1;
    709
    710                if ( st.hasMoreTokens() && st.nextToken().trim().equalsIgnoreCase( "MULTIPLE" ) ) {
    711                    numsections = Integer.parseInt( st.nextToken() );
    712                }
    713
    714                // A ArrayList of coordinates
    715                ArrayList<Coordinate> coords = new ArrayList<Coordinate>( numsections );
    716
    717                // Read each polygon
    718                for ( int i = 0; i < numsections; i++ ) {
    719                    // Read line (number of points
    720                    int numpoints = Integer.parseInt( readMifLine( mifReader ) );
    721
    722                    // Read each point
    723                    for ( int p = 0; p < numpoints; p++ ) {
    724                        StringTokenizer pst = new StringTokenizer( readMifLine( mifReader ) );
    725                        double x = Double.parseDouble( pst.nextToken() );
    726                        double y = Double.parseDouble( pst.nextToken() );
    727                        coords.add( new Coordinate( x, y ) );
    728                    }
    729                }
    730
    731                Geometry plineGeom = geomFactory.createLineString( coords.toArray( new Coordinate[coords.size()] ) );
    732
    733                // Read next line
    734                readMifLine( mifReader );
    735
    736                // Hashtable shading = readShading(mifReader);
    737                // Shading is not included, as null feature attributes are not
    738                // supported yet
    739                ArrayList<Object> midValues = readMid( midReader );
    740
    741                // midValues.putAll(shading);
    742                // Create Feature
    743                feature = buildFeature( lineType, plineGeom, midValues );
    744
    745                LOG.logDebug( "Read polyline (" + coords.size() + ")" );
    746            } catch ( NumberFormatException nfexp ) {
    747                throw new Exception( "Exception reading Point data from MIF file : " + nfexp.getMessage() );
    748            } catch ( IOException ioexp ) {
    749                throw new Exception( "IOException reading point data : " + ioexp.getMessage() );
    750            }
    751
    752            return feature;
    753        }
    754
    755        /**
    756         * Reads Region (Polygon) information from the MIF stream
    757         *
    758         * @param mifReader
    759         * @param midReader
    760         *
    761         * @return
    762         * @throws Exception
    763         */
    764        private Feature readRegionObject( BufferedReader mifReader, BufferedReader midReader )
    765                                throws Exception {
    766            Feature feature = null;
    767
    768            StringTokenizer st = new StringTokenizer( line.substring( line.indexOf( " " ) ) );
    769
    770            try {
    771                int numpolygons = Integer.parseInt( st.nextToken() );
    772
    773                // A ArrayList of polygons
    774                ArrayList<Polygon> polys = new ArrayList<Polygon>( numpolygons );
    775
    776                // Read each polygon
    777                for ( int i = 0; i < numpolygons; i++ ) {
    778                    // Read number of points
    779                    int numpoints = Integer.parseInt( readMifLine( mifReader ) );
    780                    ArrayList<Coordinate> coords = new ArrayList<Coordinate>( numpoints );
    781
    782                    // Read each point
    783                    for ( int p = 0; p < numpoints; p++ ) {
    784                        StringTokenizer pst = new StringTokenizer( readMifLine( mifReader ) );
    785                        double x = Double.parseDouble( pst.nextToken() );
    786                        double y = Double.parseDouble( pst.nextToken() );
    787                        coords.add( new Coordinate( x, y ) );
    788                    }
    789
    790                    // Create polygon from points
    791                    coords.add( new Coordinate( ( coords.get( 0 ) ).x, ( coords.get( 0 ) ).y ) );
    792
    793                    try {
    794                        Polygon pol = geomFactory.createPolygon(
    795                                                                 geomFactory.createLinearRing( coords.toArray( new Coordinate[coords.size()] ) ),
    796                                                                 null );
    797
    798                        // Add to ArrayList
    799                        polys.add( pol );
    800                    } catch ( TopologyException topexp ) {
    801                        throw new Exception( "TopologyException reading Region polygon : ", topexp );
    802                    }
    803                }
    804
    805                Geometry polyGeom = geomFactory.createMultiPolygon( polys.toArray( new Polygon[polys.size()] ) );
    806
    807                // Read next line
    808                readMifLine( mifReader );
    809
    810                // Hashtable shading = readShading(mifReader);
    811                // Shading is not included, as null feature attributes are not
    812                // supported yet
    813                ArrayList<Object> midValues = readMid( midReader );
    814
    815                // midValues.putAll(shading);
    816                // Create Feature
    817                feature = buildFeature( polygonType, polyGeom, midValues );
    818
    819                LOG.logDebug( "Read Region (" + polys.size() + ")" );
    820            } catch ( NumberFormatException nfexp ) {
    821                throw new Exception( "Exception reading Point data from MIF file : ", nfexp );
    822            } catch ( IOException ioexp ) {
    823                throw new Exception( "IOException reading point data : ", ioexp );
    824            }
    825
    826            return feature;
    827        }
    828
    829        /**
    830         * Builds a complete Feature object using the given FeatureType, with the Geometry geom, and the
    831         * given attributes.
    832         *
    833         * @param featureType
    834         *            The FeatureType to use to constuct the Feature
    835         * @param geom
    836         *            The Geometry to use as the default Geometry
    837         * @param attribs
    838         *            The attibutes to use as the Feature's attributes (Attributes must be set up in the
    839         *            FeatureType)
    840         *
    841         * @return A fully-formed Feature
    842         *
    843         * @throws Exception
    844         */
    845        private Feature buildFeature( FeatureType featureType, Geometry geom, ArrayList<Object> attribs )
    846                                throws Exception {
    847            int numAttribs = featureType.getProperties().length;
    848
    849            // add geometry to the attributes
    850            attribs.add( 0, JTSAdapter.wrap( geom ) );
    851
    852            if ( numAttribs != attribs.size() ) {
    853                throw new Exception( "wrong number of attributes passed to buildFeature.\n" + "expected " + numAttribs
    854                                     + " got " + attribs.size() );
    855            }
    856
    857            // Create Feature
    858            FeatureProperty[] fp = new FeatureProperty[attribs.size()];
    859            PropertyType[] ftp = featureType.getProperties();
    860            for ( int i = 0; i < fp.length; i++ ) {
    861                fp[i] = FeatureFactory.createFeatureProperty( ftp[i].getName(), attribs.get( i ) );
    862            }
    863            try {
    864                return FeatureFactory.createFeature( "id", featureType, fp );
    865            } catch ( Exception ifexp ) {
    866                throw new Exception( "Exception creating feature : ", ifexp );
    867            }
    868        }
    869
    870        /**
    871         * Reads a single line of the given MID file stream, and returns a hashtable of the data in it,
    872         * keyed byt he keys in the hColumns hash
    873         *
    874         * @param midReader
    875         *
    876         * @return
    877         * @throws Exception
    878         */
    879        private ArrayList<Object> readMid( BufferedReader midReader )
    880                                throws Exception {
    881            ArrayList<Object> midValues = new ArrayList<Object>();
    882
    883            if ( midReader == null ) {
    884                return midValues;
    885            }
    886
    887            // The delimeter is a single delimiting character
    888            String midLine = "";
    889
    890            try {
    891                midLine = midReader.readLine();
    892                LOG.logDebug( "Read MID " + midLine );
    893            } catch ( IOException ioexp ) {
    894                throw new Exception( "IOException reading MID file" );
    895            }
    896
    897            // read MID tokens
    898            int col = 0;
    899            StringTokenizer quotes = new StringTokenizer( midLine, "\"" );
    900
    901            while ( quotes.hasMoreTokens() ) {
    902                StringTokenizer delimeters = new StringTokenizer( quotes.nextToken(), hDelimeter + "\0" );
    903
    904                // Read each delimited value into the ArrayList
    905                while ( delimeters.hasMoreTokens() ) {
    906                    String token = delimeters.nextToken();
    907                    String type = hColumnsTypes.get( col++ );
    908                    addAttribute( type, token, midValues );
    909                }
    910
    911                // Store the whole of the next bit (it's a quoted string)
    912                if ( quotes.hasMoreTokens() ) {
    913                    String token = quotes.nextToken();
    914                    String type = hColumnsTypes.get( col++ );
    915                    addAttribute( type, token, midValues );
    916                    LOG.logDebug( "adding " + token );
    917                }
    918            }
    919
    920            return midValues;
    921        }
    922
    923        /**
    924         * @param type
    925         * @param token
    926         * @param midValues
    927         */
    928        private void addAttribute( String type, String token, ArrayList<Object> midValues ) {
    929            if ( type.equals( "String" ) ) {
    930                midValues.add( token );
    931            } else if ( type.equals( "Double" ) ) {
    932                try {
    933                    token = StringTools.validateString( token, "," );
    934                    midValues.add( new Double( token ) );
    935                } catch ( NumberFormatException nfe ) {
    936                    LOG.logError( "Bad double " + token, nfe );
    937                    midValues.add( new Double( 0.0 ) );
    938                }
    939            } else if ( type.equals( "Integer" ) ) {
    940                try {
    941                    // token = StringExtend.validateString( token, "," );
    942                    midValues.add( new Integer( token ) );
    943                } catch ( NumberFormatException nfe ) {
    944                    LOG.logError( "Bad integer " + token, nfe );
    945                    midValues.add( new Integer( 0 ) );
    946                }
    947            } else {
    948                LOG.logDebug( "Unknown type " + type );
    949            }
    950        }
    951
    952        /**
    953         * Reads the shading information at the end of Object data
    954         *
    955         * @param line
    956         *
    957         * @throws Exception
    958         */
    959
    960        // private void processShading(String line) throws Exception {
    961        // int color;
    962        // int r;
    963        // int g;
    964        // int b;
    965        //
    966        // if (line == null) {
    967        // return;
    968        // }
    969        //
    970        // String shadeType = line.toLowerCase();
    971        // String name = clause(shadeType, '(');
    972        // String settings = remainder(shadeType, '(');
    973        // StringTokenizer st = new StringTokenizer(settings, "(),");
    974        // String[] values = new String[st.countTokens()];
    975        //
    976        // for (int i = 0; st.hasMoreTokens(); i++) {
    977        // values[i] = st.nextToken();
    978        // }
    979        //
    980        // if (name.equals("pen")) {
    981        // try {
    982        //
    983        // stroke.setWidth(filterFactory.createLiteralExpression(new
    984        // Integer(values[0])));
    985        //
    986        // int pattern = Integer.parseInt(values[1]);
    987        //
    988        // stroke.setDashArray(MifStyles.getPenPattern(new Integer(pattern)));
    989        // color = Integer.parseInt(values[2]);
    990        //
    991        // String rgb = Integer.toHexString(color);
    992        //
    993        // stroke.setColor(filterFactory.createLiteralExpression(rgb));
    994        // } catch (Exception nfe) {
    995        // throw new Exception("Error setting up pen", nfe);
    996        // }
    997        //
    998        // return;
    999        // } else if (name.equals("brush")) {
    1000        // LOG.logDebug("setting new brush " + settings);
    1001        //
    1002        // int pattern = Integer.parseInt(values[0]);
    1003        // LOG.logDebug("pattern = " + pattern);
    1004        //
    1005        // Graphic dg = styleFactory.getDefaultGraphic();
    1006        // dg.addExternalGraphic(MifStyles.getBrushPattern(new Integer(pattern)));
    1007        // stroke.setGraphicFill(dg);
    1008        // color = Integer.parseInt(values[1]);
    1009        //
    1010        // String rgb = Integer.toHexString(color);
    1011        // LOG.logDebug("color " + color + " -> " + rgb);
    1012        // fill.setColor(filterFactory.createLiteralExpression(rgb)); // foreground
    1013        //
    1014        // if (values.length == 3) { // optional parameter
    1015        // color = Integer.parseInt(values[2]);
    1016        // rgb = Integer.toHexString(color);
    1017        // LOG.logDebug("color " + color + " -> " + rgb);
    1018        //
    1019        // fill.setBackgroundColor(filterFactory.createLiteralExpression(rgb)); //
    1020        // background
    1021        // } else {
    1022        // fill.setBackgroundColor((Expression) null);
    1023        // }
    1024        // } else if (name.equals("center")) {
    1025        // LOG.logDebug("setting center " + settings);
    1026        // } else if (name.equals("smooth")) {
    1027        // LOG.logDebug("setting smooth on");
    1028        // } else if (name.equals("symbol")) {
    1029        // LOG.logDebug("setting symbol " + settings);
    1030        //
    1031        // Mark symbol = null;
    1032        // ExternalGraphic eg = null;
    1033        //
    1034        // if (values.length == 3) { // version 3.0
    1035        //
    1036        // //symbol = symbols.get(new Integer(symNumb));
    1037        // } else if (values.length == 6) {}
    1038        // else if (values.length == 4) { // custom bitmap
    1039        // eg = styleFactory.createExternalGraphic("CustSymb/" +
    1040        // values[0],"image/unknown"); // hack!
    1041        //
    1042        // } else {
    1043        // LOGGER.info("unexpected symbol style " + name + settings);
    1044        // }
    1045        // } else if (name.equals("font")) {
    1046        // LOG.logDebug("setting font " + settings);
    1047        // } else {
    1048        // LOG.logDebug("unknown styling directive " + name + settings);
    1049        // }
    1050        //
    1051        // return;
    1052        // }
    1053        /**
    1054         * Test whether the given line contains a known shading clause keyword (PEN, STYLE, etc.)
    1055         *
    1056         * @param line
    1057         *
    1058         * @return
    1059         */
    1060        private boolean isShadingClause( String line ) {
    1061            line = line.toUpperCase();
    1062
    1063            boolean ret = ( ( line.indexOf( CLAUSE_PEN ) != -1 ) || ( line.indexOf( CLAUSE_SYMBOL ) != -1 )
    1064                            || ( line.indexOf( CLAUSE_SMOOTH ) != -1 ) || ( line.indexOf( CLAUSE_CENTER ) != -1 ) || line.indexOf( CLAUSE_BRUSH ) != -1 );
    1065
    1066            return ret;
    1067        }
    1068
    1069        /**
    1070         * Loads features from the datasource into the passed collection, based on the passed filter.
    1071         * Note that all data sources must support this method at a minimum.
    1072         *
    1073         * @param collection
    1074         *            The collection to put the features into.
    1075         * @param query
    1076         *            contains info about request of which features to retrieve.
    1077         *
    1078         * @throws Exception
    1079         *             For all data source errors.
    1080         */
    1081        public void getFeatures( FeatureCollection collection, Query query )
    1082                                throws Exception {
    1083            Filter filter = null;
    1084
    1085            if ( query != null ) {
    1086                filter = query.getFilter();
    1087            }
    1088
    1089            ArrayList features = readMifMid();
    1090
    1091            for ( int i = 0; i < features.size(); i++ ) {
    1092                if ( ( filter == null ) || filter.evaluate( (Feature) features.get( i ) ) ) {
    1093                    collection.add( (Feature) features.get( i ) );
    1094                }
    1095            }
    1096        }
    1097
    1098        /**
    1099         * Retrieves the featureType that features extracted from this datasource will be created with.
    1100         * TODO: implement this method.
    1101         *
    1102         * @return <code>null</code>
    1103         */
    1104        public FeatureType getSchema() {
    1105            return null;
    1106        }
    1107    }