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