001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/tools/datastore/DDLGenerator.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstraße 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    package org.deegree.tools.datastore;
044    
045    import java.io.File;
046    import java.io.FileWriter;
047    import java.io.IOException;
048    import java.io.PrintWriter;
049    import java.net.MalformedURLException;
050    import java.net.URL;
051    import java.util.ArrayList;
052    import java.util.Collection;
053    import java.util.HashMap;
054    import java.util.Iterator;
055    import java.util.LinkedHashMap;
056    import java.util.Map;
057    
058    import org.deegree.datatypes.Types;
059    import org.deegree.datatypes.UnknownTypeException;
060    import org.deegree.framework.xml.XMLParsingException;
061    import org.deegree.framework.xml.schema.XMLSchemaException;
062    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
063    import org.deegree.io.datastore.schema.MappedFeatureType;
064    import org.deegree.io.datastore.schema.MappedGMLId;
065    import org.deegree.io.datastore.schema.MappedGMLSchema;
066    import org.deegree.io.datastore.schema.MappedGMLSchemaDocument;
067    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
068    import org.deegree.io.datastore.schema.MappedPropertyType;
069    import org.deegree.io.datastore.schema.MappedSimplePropertyType;
070    import org.deegree.io.datastore.schema.TableRelation;
071    import org.deegree.io.datastore.schema.content.MappingField;
072    import org.deegree.io.datastore.schema.content.MappingGeometryField;
073    import org.deegree.io.datastore.schema.content.SimpleContent;
074    import org.deegree.model.crs.UnknownCRSException;
075    import org.deegree.model.feature.schema.FeatureType;
076    import org.deegree.model.feature.schema.PropertyType;
077    import org.xml.sax.SAXException;
078    
079    /**
080     * Abstract base class for DDL generation from annotated GML schema files.
081     * <p>
082     * This abstract base class only implements the functionality needed to retrieve the necessary tables and columns used
083     * in an annotated GML schema. DDL generation is dependent on the specific SQL backend to be used, so this is
084     * implemented in concrete extensions of this class.
085     * 
086     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
087     * @author last edited by: $Author: mschneider $
088     * 
089     * @version $Revision: 8095 $, $Date: 2007-09-04 14:17:42 +0200 (Di, 04 Sep 2007) $
090     */
091    public abstract class DDLGenerator {
092    
093        protected static final String FT_PREFIX = "FT_";
094    
095        protected static final int FEATURE_TYPE_TABLE = 0;
096    
097        protected static final int JOIN_TABLE = 1;
098    
099        protected static final int MULTI_PROPERTY_TABLE = 2;
100    
101        protected MappedGMLSchema schema;
102    
103        // key type: String (table names), value type: TableDefinition
104        protected Map<String, TableDefinition> tables = new HashMap<String, TableDefinition>();
105    
106        /**
107         * Creates a new instance of <code>DDLGenerator</code> from the given parameters.
108         * 
109         * @param schemaURL
110         * @throws MalformedURLException
111         * @throws IOException
112         * @throws SAXException
113         * @throws XMLParsingException
114         * @throws XMLSchemaException
115         * @throws UnknownCRSException
116         */
117        protected DDLGenerator( URL schemaURL ) throws MalformedURLException, IOException, SAXException,
118                                XMLParsingException, XMLSchemaException, UnknownCRSException {
119    
120            System.out.println( Messages.format( "LOADING_SCHEMA_FILE", schemaURL ) );
121            MappedGMLSchemaDocument schemaDoc = new MappedGMLSchemaDocument();
122            schemaDoc.load( schemaURL );
123            schema = schemaDoc.parseMappedGMLSchema();
124            FeatureType[] featureTypes = schema.getFeatureTypes();
125            int concreteCount = 0;
126            for ( int i = 0; i < featureTypes.length; i++ ) {
127                if ( !featureTypes[i].isAbstract() ) {
128                    concreteCount++;
129                }
130            }
131            System.out.println( Messages.format( "SCHEMA_INFO", new Integer( featureTypes.length ),
132                                                 new Integer( featureTypes.length - concreteCount ),
133                                                 new Integer( concreteCount ) ) );
134            System.out.println( Messages.getString( "RETRIEVING_TABLES" ) );
135            buildTableMap();
136        }
137    
138        /**
139         * Returns all table definitions of the given type.
140         * 
141         * @param type
142         *            FEATURE_TYPE_TABLE, JOIN_TABLE or MULTI_PROPERTY_TABLE
143         * @return all table definitions of the given type.
144         */
145        protected TableDefinition[] getTables( int type ) {
146            Collection<TableDefinition> tableList = new ArrayList<TableDefinition>();
147            Iterator iter = this.tables.keySet().iterator();
148            while ( iter.hasNext() ) {
149                String tableName = (String) iter.next();
150                TableDefinition table = this.tables.get( tableName );
151                if ( table.getType() == type ) {
152                    tableList.add( table );
153                }
154            }
155            return tableList.toArray( new TableDefinition[tableList.size()] );
156        }
157    
158        /**
159         * Returns the table definition for the table with the given name. If no such definition exists, a new table
160         * definition is created and added to the internal <code>tables</code> map.
161         * 
162         * @param tableName
163         *            table definition to look up
164         * @param type
165         *            type of the table (only respected, if a new TableDefinition instance is created)
166         * @return the table definition for the table with the given name.
167         */
168        private TableDefinition lookupTableDefinition( String tableName, int type ) {
169            TableDefinition table = this.tables.get( tableName );
170            if ( table == null ) {
171                table = new TableDefinition( tableName, type );
172                this.tables.put( tableName, table );
173            }
174            return table;
175        }
176    
177        /**
178         * Collects the referenced tables and their columns from the input schema. Builds the member map <code>tables</code>
179         * from this data.
180         */
181        private void buildTableMap() {
182            FeatureType[] featureTypes = schema.getFeatureTypes();
183            for ( int i = 0; i < featureTypes.length; i++ ) {
184                if ( !featureTypes[i].isAbstract() ) {
185                    buildTableMap( (MappedFeatureType) featureTypes[i] );
186                }
187            }
188        }
189    
190        /**
191         * Collects the tables and their columns used in the annotation of the given feature type. Builds the member map
192         * <code>tables</code> from this data.
193         * 
194         * @param featureType
195         *            feature type to process
196         */
197        private void buildTableMap( MappedFeatureType featureType ) {
198            TableDefinition table = lookupTableDefinition( featureType.getTable(), FEATURE_TYPE_TABLE );
199    
200            addGMLIdColumns( featureType.getGMLId(), table );
201    
202            PropertyType[] properties = featureType.getProperties();
203            for ( int i = 0; i < properties.length; i++ ) {
204                MappedPropertyType property = (MappedPropertyType) properties[i];
205                if ( property instanceof MappedSimplePropertyType ) {
206                    buildTableMap( (MappedSimplePropertyType) property, table );
207                } else if ( property instanceof MappedGeometryPropertyType ) {
208                    buildTableMap( (MappedGeometryPropertyType) property, table );
209                } else if ( property instanceof MappedFeaturePropertyType ) {
210                    buildTableMap( (MappedFeaturePropertyType) property, table );
211                } else {
212                    throw new RuntimeException( Messages.format( "ERROR_UNEXPECTED_PROPERTY_TYPE",
213                                                                 property.getClass().getName() ) );
214                }
215            }
216        }
217    
218        /**
219         * Adds the columns used in the given <code>MappedGMLId</code> to the also given <code>TableDefinition</code>.
220         * 
221         * @param gmlId
222         *            columns are taken from this gmlId mapping
223         * @param table
224         *            columns are added to this table definition
225         */
226        private void addGMLIdColumns( MappedGMLId gmlId, TableDefinition table ) {
227            MappingField[] idFields = gmlId.getIdFields();
228            for ( int i = 0; i < idFields.length; i++ ) {
229                ColumnDefinition column = new ColumnDefinition( idFields[i].getField(), idFields[i].getType(), false,
230                                                                false, -1 );
231                table.addColumn( column );
232            }
233        }
234    
235        /**
236         * Collects the tables and their columns used in the annotation of the given simple property type. Builds the
237         * <code>table</code> member map from this data.
238         * <p>
239         * If the data for the property is stored in a related table, the table and column information used on the path to
240         * this table is also added to the <code>tables</code> member map.
241         * 
242         * @param simpleProperty
243         *            simple property type to process
244         * @param table
245         *            table definition associated with the property definition
246         */
247        private void buildTableMap( MappedSimplePropertyType simpleProperty, TableDefinition table ) {
248            Collection<ColumnDefinition> newColumns = new ArrayList<ColumnDefinition>();
249            // array must always have length 1
250            TableRelation[] relations = simpleProperty.getTableRelations();
251            if ( simpleProperty.getMaxOccurs() != 1 && ( relations == null || relations.length < 1 ) ) {
252                throw new RuntimeException( Messages.format( "ERROR_INVALID_PROPERTY_DEFINITION", simpleProperty.getName() ) );
253            }
254    
255            SimpleContent content = simpleProperty.getContent();
256            if ( content instanceof MappingField ) {
257                MappingField mf = (MappingField) content;
258                if ( relations == null || relations.length == 0 ) {
259                    newColumns.add( new ColumnDefinition( mf.getField(), mf.getType(), simpleProperty.getMinOccurs() == 0,
260                                                          false, -1 ) );
261                } else {
262                    TableRelation firstRelation = relations[0];
263                    MappingField[] fromFields = firstRelation.getFromFields();
264                    for ( int i = 0; i < fromFields.length; i++ ) {
265                        MappingField fromField = fromFields[i];
266                        newColumns.add( new ColumnDefinition( fromField.getField(), fromField.getType(), false, false, -1 ) );
267                    }
268                    buildTableMap( relations, mf );
269                }
270            } else {
271                String msg = "Ignoring property '" + simpleProperty + "' - has virtual content.";
272                System.out.println( msg );
273            }
274            table.addColumns( newColumns );
275        }
276    
277        /**
278         * Collects the tables and their columns used in the annotation of the given geometry property type. Builds the
279         * <code>table</code> member map from this data.
280         * <p>
281         * If the geometry for the property is stored in a related table, the table and column information used on the path
282         * to this table is also added to the <code>tables</code> member map.
283         * 
284         * @param geometryProperty
285         *            feature property type to process
286         * @param table
287         *            table definition associated with the property definition
288         */
289        private void buildTableMap( MappedGeometryPropertyType geometryProperty, TableDefinition table ) {
290            Collection<ColumnDefinition> newColumns = new ArrayList<ColumnDefinition>();
291            TableRelation[] relations = geometryProperty.getTableRelations();
292            if ( geometryProperty.getMaxOccurs() != 1 && ( relations == null || relations.length < 1 ) ) {
293                throw new RuntimeException( Messages.format( "ERROR_INVALID_PROPERTY_DEFINITION",
294                                                             geometryProperty.getName() ) );
295            }
296            if ( relations == null || relations.length == 0 ) {
297                newColumns.add( new ColumnDefinition( geometryProperty.getMappingField().getField(),
298                                                      geometryProperty.getMappingField().getType(),
299                                                      geometryProperty.getMinOccurs() == 0, true,
300                                                      geometryProperty.getMappingField().getSRS() ) );
301            } else {
302                TableRelation firstRelation = relations[0];
303                MappingField[] fromFields = firstRelation.getFromFields();
304                for ( int i = 0; i < fromFields.length; i++ ) {
305                    MappingField fromField = fromFields[i];
306                    newColumns.add( new ColumnDefinition( fromField.getField(), fromField.getType(), false, true,
307                                                          geometryProperty.getMappingField().getSRS() ) );
308                }
309                buildTableMap( relations, geometryProperty.getMappingField() );
310            }
311            table.addColumns( newColumns );
312        }
313    
314        /**
315         * Collects the tables and their columns used in the annotation of the given feature property type. Builds the
316         * <code>table</code> member map from this data.
317         * <p>
318         * The table and column information used on the path to the table of the feature type is also added to the
319         * <code>tables</code> member map.
320         * 
321         * @param featureProperty
322         *            feature property type to process
323         * @param table
324         *            table definition associated with the property definition
325         */
326        private void buildTableMap( MappedFeaturePropertyType featureProperty, TableDefinition table ) {
327            Collection<ColumnDefinition> newColumns = new ArrayList<ColumnDefinition>();
328    
329            // array must always have length 1
330            TableRelation[] relations = featureProperty.getTableRelations();
331    
332            // target feature type table must always be accessed via 'Relation'-elements
333            if ( relations == null || relations.length < 1 ) {
334                throw new RuntimeException( Messages.format( "ERROR_INVALID_FEATURE_PROPERTY_DEFINITION_1",
335                                                             featureProperty.getName() ) );
336            }
337    
338            // maxOccurs > 1: target feature type table must be accessed via join table
339            if ( featureProperty.getMaxOccurs() != 1 && ( relations.length < 2 ) ) {
340                throw new RuntimeException( Messages.format( "ERROR_INVALID_FEATURE_PROPERTY_DEFINITION_2",
341                                                             featureProperty.getName() ) );
342            }
343    
344            // add this feature type's key columns to current table
345            TableRelation firstRelation = relations[0];
346            MappingField[] fromFields = firstRelation.getFromFields();
347            boolean isNullable = featureProperty.getMinOccurs() == 0 && relations.length == 1;
348            for ( int i = 0; i < fromFields.length; i++ ) {
349                MappingField fromField = fromFields[i];
350                newColumns.add( new ColumnDefinition( fromField.getField(), fromField.getType(), isNullable, false, -1 ) );
351            }
352            table.addColumns( newColumns );
353    
354            MappedFeatureType contentType = featureProperty.getFeatureTypeReference().getFeatureType();
355            buildTableMap( relations, featureProperty, contentType );
356        }
357    
358        /**
359         * Collects the tables and their columns used in the relation tables from a simple/geometry property to it's content
360         * table. Builds the <code>table</code> member map from this data.
361         * 
362         * @param relations
363         *            relation tables from annotation of property type
364         * @param targetField
365         *            holds the properties data
366         */
367        private void buildTableMap( TableRelation[] relations, MappingField targetField ) {
368    
369            // process tables used in 'To'-element of each 'Relation'-element
370            for ( int i = 0; i < relations.length; i++ ) {
371                String tableName = relations[i].getToTable();
372                TableDefinition table = lookupTableDefinition( tableName, MULTI_PROPERTY_TABLE );
373                MappingField[] toFields = relations[i].getToFields();
374                for ( int j = 0; j < toFields.length; j++ ) {
375                    ColumnDefinition column = new ColumnDefinition( toFields[j].getField(), toFields[j].getType(), false,
376                                                                    false, -1 );
377                    table.addColumn( column );
378                }
379            }
380    
381            // process table used in 'To'-element of last 'Relation'-element (targetField refers to
382            // this)
383            ColumnDefinition column = null;
384            if ( targetField instanceof MappingGeometryField ) {
385    
386                column = new ColumnDefinition( targetField.getField(), targetField.getType(), false, true,
387                                               ( (MappingGeometryField) targetField ).getSRS() );
388            } else {
389                column = new ColumnDefinition( targetField.getField(), targetField.getType(), false, false, -1 );
390            }
391    
392            TableDefinition table = lookupTableDefinition( relations[relations.length - 1].getToTable(),
393                                                           MULTI_PROPERTY_TABLE );
394            table.addColumn( column );
395        }
396    
397        /**
398         * Collects the tables and their columns used in the relation tables from a feature property to it's content feature
399         * type. Builds the <code>table</code> member map from this data.
400         * 
401         * @param relations
402         *            relation tables from annotation of feature property type
403         * @param property
404         * @param targetType
405         *            type contained in the feature property
406         */
407        private void buildTableMap( TableRelation[] relations, MappedPropertyType property, MappedFeatureType targetType ) {
408    
409            TableDefinition table = lookupTableDefinition( relations[0].getFromTable(), FEATURE_TYPE_TABLE );
410    
411            // process tables used in 'To'-element of each 'Relation'-element (except the last)
412            for ( int i = 0; i < relations.length - 1; i++ ) {
413                String tableName = relations[i].getToTable();
414                table = lookupTableDefinition( tableName, JOIN_TABLE );
415                MappingField[] toFields = relations[i].getToFields();
416                for ( int j = 0; j < toFields.length; j++ ) {
417                    ColumnDefinition column = new ColumnDefinition( toFields[j].getField(), toFields[j].getType(), false,
418                                                                    false, -1 );
419                    table.addColumn( column );
420                }
421            }
422    
423            // process table used in 'To'-element of last 'Relation'-element
424            MappedFeatureType[] concreteTypes = targetType.getConcreteSubstitutions();
425            MappingField[] toFields = relations[relations.length - 1].getToFields();
426    
427            // if it refers to several target tables (target feature type is abstract), an additional
428            // column is needed (which determines the target feature type)
429            if ( concreteTypes.length > 1 ) {
430                String typeColumn = "featuretype";
431                if ( relations.length == 1 ) {
432                    typeColumn = FT_PREFIX + property.getName().getLocalName().toUpperCase();
433                }
434                ColumnDefinition column = new ColumnDefinition( typeColumn, Types.VARCHAR, property.getMinOccurs() == 0,
435                                                                false, -1 );
436                table.addColumn( column );
437            }
438            for ( int i = 0; i < concreteTypes.length; i++ ) {
439                MappedFeatureType concreteType = concreteTypes[i];
440                String tableName = concreteType.getTable();
441                table = lookupTableDefinition( tableName, FEATURE_TYPE_TABLE );
442                for ( int j = 0; j < toFields.length; j++ ) {
443                    ColumnDefinition column = new ColumnDefinition( toFields[j].getField(), toFields[j].getType(), false,
444                                                                    false, -1 );
445                    table.addColumn( column );
446                }
447            }
448    
449            // process tables used in 'From'-element of each 'Relation'-element (except the first)
450            for ( int i = 1; i < relations.length; i++ ) {
451                String tableName = relations[i].getFromTable();
452                if ( i != relations.length - 1 ) {
453                    table = lookupTableDefinition( tableName, JOIN_TABLE );
454                } else {
455                    table = lookupTableDefinition( tableName, FEATURE_TYPE_TABLE );
456                }
457                MappingField[] fromFields = relations[i].getFromFields();
458                for ( int j = 0; j < fromFields.length; j++ ) {
459                    ColumnDefinition column = new ColumnDefinition( fromFields[j].getField(), fromFields[j].getType(),
460                                                                    false, false, -1 );
461                    table.addColumn( column );
462                }
463            }
464        }
465    
466        /**
467         * Generates the DDL statements that can be used to build a relational schema that backs the GML schema.
468         * 
469         * @param outputFile
470         * @throws IOException
471         */
472        public void generateCreateScript( String outputFile )
473                                throws IOException {
474            PrintWriter writer = new PrintWriter( new FileWriter( outputFile ) );
475            TableDefinition[] tables = getTables( FEATURE_TYPE_TABLE );
476            System.out.println( Messages.format( "CREATE_FEATURE_TYPE", new Integer( tables.length ) ) );
477            writer.println( "/* CREATE FEATURE TABLES (" + tables.length + ") */\n" );
478            for ( int i = 0; i < tables.length; i++ ) {
479                writer.println( generateCreateStatements( tables[i] ) );
480            }
481            tables = getTables( JOIN_TABLE );
482            if ( tables.length != 0 ) {
483                writer.println( "/* CREATE JOIN TABLES (" + tables.length + ") */\n" );
484            }
485            System.out.println( Messages.format( "CREATE_JOIN_TABLES", new Integer( tables.length ) ) );
486            for ( int i = 0; i < tables.length; i++ ) {
487                writer.println( generateCreateStatements( tables[i] ) );
488            }
489            tables = getTables( MULTI_PROPERTY_TABLE );
490            if ( tables.length != 0 ) {
491                writer.println( "/* CREATE PROPERTY TABLES (" + tables.length + ") */\n" );
492            }
493            System.out.println( Messages.format( "CREATE_PROPERTY_TABLES", new Integer( tables.length ) ) );
494            for ( int i = 0; i < tables.length; i++ ) {
495                writer.println( generateCreateStatements( tables[i] ) );
496            }
497            writer.close();
498        }
499    
500        /**
501         * Generates the DDL statements necessary for the creation of the given table definition. Must be overwritten by the
502         * concrete implementation.
503         * 
504         * @param table
505         * @return the DDL statements necessary for the creation of the given table definition
506         */
507        protected abstract StringBuffer generateCreateStatements( TableDefinition table );
508    
509        /**
510         * Generates the DDL statements that can be used to remove the relational schema again.
511         * 
512         * @param outputFile
513         * @throws IOException
514         */
515        public void generateDropScript( String outputFile )
516                                throws IOException {
517            PrintWriter writer = new PrintWriter( new FileWriter( outputFile ) );
518            TableDefinition[] tables = getTables( FEATURE_TYPE_TABLE );
519            System.out.println( Messages.format( "DROP_FEATURE_TYPE", new Integer( tables.length ) ) );
520            writer.println( "/* DROP FEATURE TABLES (" + tables.length + ") */\n" );
521            for ( int i = 0; i < tables.length; i++ ) {
522                writer.println( generateDropStatements( tables[i] ) );
523            }
524            tables = getTables( JOIN_TABLE );
525            writer.println( "/* DROP JOIN TABLES (" + tables.length + ") */\n" );
526            System.out.println( Messages.format( "DROP_JOIN_TABLES", new Integer( tables.length ) ) );
527            for ( int i = 0; i < tables.length; i++ ) {
528                writer.println( generateDropStatements( tables[i] ) );
529            }
530            tables = getTables( MULTI_PROPERTY_TABLE );
531            writer.println( "/* DROP PROPERTY TABLES (" + tables.length + ") */\n" );
532            System.out.println( Messages.format( "DROP_PROPERTY_TABLES", new Integer( tables.length ) ) );
533            for ( int i = 0; i < tables.length; i++ ) {
534                writer.println( generateDropStatements( tables[i] ) );
535            }
536            writer.close();
537        }
538    
539        /**
540         * Generates the DDL statements necessary for the removal of the given table definition. Must be overwritten by the
541         * concrete implementation.
542         * 
543         * @param table
544         * @return the DDL statements necessary for the removal of the given table definition
545         */
546        protected abstract StringBuffer generateDropStatements( TableDefinition table );
547    
548        /**
549         * @param args
550         * @throws IOException
551         * @throws SAXException
552         * @throws XMLParsingException
553         * @throws XMLSchemaException
554         * @throws UnknownCRSException
555         */
556        public static void main( String[] args )
557                                throws IOException, SAXException, XMLParsingException, XMLSchemaException,
558                                UnknownCRSException {
559    
560            if ( args.length != 4 ) {
561                System.out.println( "Usage: DDLGenerator [FLAVOUR] <input.xsd> <create.sql> <drop.sql>" );
562                System.exit( 0 );
563            }
564    
565            String flavour = args[0];
566            String schemaFile = args[1];
567            String createFile = args[2];
568            String dropFile = args[3];
569    
570            DDLGenerator generator = null;
571            if ( "POSTGIS".equals( flavour ) ) {
572                generator = new PostGISDDLGenerator( new File( schemaFile ).toURL() );
573            } else {
574                System.out.println( Messages.format( "ERROR_UNSUPPORTED_FLAVOUR", flavour ) );
575            }
576    
577            generator.generateCreateScript( createFile );
578            generator.generateDropScript( dropFile );
579        }
580    
581        /**
582         * Returns a string representation of the object.
583         * 
584         * @return a string representation of the object.
585         */
586        @Override
587        public String toString() {
588            StringBuffer sb = new StringBuffer( Messages.getString( "RELATIONAL_SCHEMA" ) );
589            sb.append( '\n' );
590    
591            TableDefinition[] tables = getTables( FEATURE_TYPE_TABLE );
592            sb.append( '\n' );
593            sb.append( tables.length );
594            sb.append( " feature type tables\n\n" );
595            for ( int i = 0; i < tables.length; i++ ) {
596                sb.append( tables[i] );
597                sb.append( '\n' );
598            }
599    
600            sb.append( '\n' );
601            tables = getTables( JOIN_TABLE );
602            sb.append( tables.length );
603            sb.append( " join tables\n\n" );
604            for ( int i = 0; i < tables.length; i++ ) {
605                sb.append( tables[i] );
606                sb.append( '\n' );
607            }
608    
609            sb.append( '\n' );
610            tables = getTables( MULTI_PROPERTY_TABLE );
611            sb.append( tables.length );
612            sb.append( " property tables\n\n" );
613            for ( int i = 0; i < tables.length; i++ ) {
614                sb.append( tables[i] );
615                sb.append( '\n' );
616            }
617            return sb.toString();
618        }
619    
620        class TableDefinition {
621    
622            private int type;
623    
624            private String tableName;
625    
626            private Map<String, ColumnDefinition> columnsMap = new LinkedHashMap<String, ColumnDefinition>();
627    
628            TableDefinition( String tableName, int type ) {
629                this.type = type;
630                this.tableName = tableName;
631            }
632    
633            String getName() {
634                return this.tableName;
635            }
636    
637            int getType() {
638                return this.type;
639            }
640    
641            ColumnDefinition[] getColumns() {
642                Collection<ColumnDefinition> columns = new ArrayList<ColumnDefinition>();
643                Iterator iter = columnsMap.keySet().iterator();
644                while ( iter.hasNext() ) {
645                    String columnName = (String) iter.next();
646                    columns.add( columnsMap.get( columnName ) );
647                }
648                return columns.toArray( new ColumnDefinition[columns.size()] );
649            }
650    
651            ColumnDefinition[] getPKColumns() {
652                Collection<ColumnDefinition> columns = new ArrayList<ColumnDefinition>();
653                Iterator iter = columnsMap.keySet().iterator();
654                while ( iter.hasNext() ) {
655                    String columnName = (String) iter.next();
656                    ColumnDefinition column = columnsMap.get( columnName );
657                    if ( column.isPartOfPK() ) {
658                        columns.add( columnsMap.get( columnName ) );
659                    }
660                }
661                return columns.toArray( new ColumnDefinition[columns.size()] );
662            }
663    
664            ColumnDefinition getColumn( String name ) {
665                return columnsMap.get( name );
666            }
667    
668            void addColumn( ColumnDefinition column ) {
669                ColumnDefinition oldColumn = columnsMap.get( column.getName() );
670                if ( oldColumn != null ) {
671                    if ( !( column.getType() == oldColumn.getType() ) ) {
672                        String msg = Messages.format( "ERROR_COLUMN_DEFINITION_TYPES", column.getName(),
673                                                      oldColumn.isNullable() ? "NULLABLE" : "NOT NULLABLE",
674                                                      column.isNullable() ? "NULLABLE" : "NOT NULLABLE" );
675                        throw new RuntimeException( msg );
676    
677                    }
678                    if ( oldColumn.isPartOfPK() ) {
679                        column = oldColumn;
680                    }
681                }
682                columnsMap.put( column.getName(), column );
683            }
684    
685            void addColumns( Collection columns ) {
686                Iterator iter = columns.iterator();
687                while ( iter.hasNext() ) {
688                    ColumnDefinition column = (ColumnDefinition) iter.next();
689                    addColumn( column );
690                }
691            }
692    
693            @Override
694            public String toString() {
695                StringBuffer sb = new StringBuffer();
696                sb.append( Messages.format( "TABLE", this.tableName ) );
697                sb.append( Messages.getString( "PRIMARY_KEY" ) );
698                ColumnDefinition[] pkColumns = getPKColumns();
699                for ( int i = 0; i < pkColumns.length; i++ ) {
700                    sb.append( '"' );
701                    sb.append( pkColumns[i].getName() );
702                    sb.append( '"' );
703                    if ( i != pkColumns.length - 1 ) {
704                        sb.append( ", " );
705                    }
706                }
707                sb.append( '\n' );
708                Iterator columnNameIter = this.columnsMap.keySet().iterator();
709                while ( columnNameIter.hasNext() ) {
710                    String columnName = (String) columnNameIter.next();
711                    ColumnDefinition column = this.columnsMap.get( columnName );
712                    try {
713                        sb.append( Messages.format( "COLUMN", columnName,
714                                                    Types.getTypeNameForSQLTypeCode( column.getType() ) + ":"
715                                                                            + column.getType(),
716                                                    new Boolean( column.isNullable() ) ) );
717                    } catch ( UnknownTypeException e ) {
718                        // TODO Auto-generated catch block
719                        e.printStackTrace();
720                    }
721                    sb.append( '\n' );
722                }
723                return sb.toString();
724            }
725        }
726    
727        class ColumnDefinition {
728    
729            private String columnName;
730    
731            private int type;
732    
733            private boolean isNullable;
734    
735            private boolean isGeometryColumn;
736    
737            private int srsCode;
738    
739            private boolean isPartOfPK;
740    
741            ColumnDefinition( String columnName, int type, boolean isNullable, boolean isGeometryColumn, int srsCode ) {
742                this.columnName = columnName;
743                this.type = type;
744                this.isNullable = isNullable;
745                this.isGeometryColumn = isGeometryColumn;
746                this.srsCode = srsCode;
747            }
748    
749            ColumnDefinition( String columnName, int type, boolean isNullable, boolean isPartOfPK,
750                              boolean isGeometryColumn, int srsCode ) {
751                this( columnName, type, isNullable, isGeometryColumn, srsCode );
752                this.isPartOfPK = isPartOfPK;
753            }
754    
755            String getName() {
756                return this.columnName;
757            }
758    
759            int getType() {
760                return this.type;
761            }
762    
763            boolean isNullable() {
764                return this.isNullable;
765            }
766    
767            boolean isGeometry() {
768                return this.isGeometryColumn;
769            }
770    
771            int getSRS() {
772                return this.srsCode;
773            }
774    
775            boolean isPartOfPK() {
776                return this.isPartOfPK;
777            }
778        }
779    }