001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }