# PostgreSQLIndexExporter.java

    /*
     * Copyright (c) 2010, Stanislav Muhametsin. All Rights Reserved.
     * Copyright (c) 2012, Paul Merlin. All Rights Reserved.
     *
     * Licensed  under the  Apache License,  Version 2.0  (the "License");
     * you may not use  this file  except in  compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed  under the  License is distributed on an "AS IS" BASIS,
     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
     * implied.
     *
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.qi4j.index.sql.support.postgresql;

    import java.io.IOException;
    import java.io.PrintStream;
    import java.io.PrintWriter;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Types;
    import java.util.HashMap;
    import java.util.Map;
    import javax.sql.DataSource;
    import org.qi4j.api.injection.scope.Service;
    import org.qi4j.api.injection.scope.This;
    import org.qi4j.api.injection.scope.Uses;
    import org.qi4j.api.service.ServiceDescriptor;
    import org.qi4j.index.sql.support.common.GenericDatabaseExplorer;
    import org.qi4j.index.sql.support.common.GenericDatabaseExplorer.ColumnInfo;
    import org.qi4j.index.sql.support.common.GenericDatabaseExplorer.DatabaseProcessor;
    import org.qi4j.index.sql.support.common.GenericDatabaseExplorer.ForeignKeyInfo;
    import org.qi4j.index.sql.support.skeletons.SQLDBState;
    import org.qi4j.library.sql.common.SQLUtil;
    import org.qi4j.spi.query.IndexExporter;
    import org.sql.generation.api.vendor.SQLVendor;

    public class PostgreSQLIndexExporter
        implements IndexExporter
    {

        @This
        private SQLDBState _state;

        @Uses
        private ServiceDescriptor descriptor;

        @Service
        private DataSource _dataSource;

        private static final String SEPARATOR = "-----------------------------------------------";

        private static final Map<Integer, String> TYPE_STRINGS;

        static
        {
            TYPE_STRINGS = new HashMap<>( 16 );
            TYPE_STRINGS.put( Types.BIGINT, "BIGINT" );
            TYPE_STRINGS.put( Types.BOOLEAN, "BOOLEAN" );
            TYPE_STRINGS.put( Types.CHAR, "CHAR" );
            TYPE_STRINGS.put( Types.DATE, "DATE" );
            TYPE_STRINGS.put( Types.DECIMAL, "DECIMAL" );
            TYPE_STRINGS.put( Types.DOUBLE, "DOUBLE" );
            TYPE_STRINGS.put( Types.FLOAT, "FLOAT" );
            TYPE_STRINGS.put( Types.INTEGER, "INTEGER" );
            TYPE_STRINGS.put( Types.NULL, "NULL" );
            TYPE_STRINGS.put( Types.NUMERIC, "NUMERIC" );
            TYPE_STRINGS.put( Types.REAL, "REAL" );
            TYPE_STRINGS.put( Types.SMALLINT, "SMALLINT" );
            TYPE_STRINGS.put( Types.TIME, "TIME" );
            TYPE_STRINGS.put( Types.TIMESTAMP, "TIMESTAMP" );
            TYPE_STRINGS.put( Types.VARCHAR, "VARCHAR" );
            TYPE_STRINGS.put( Types.VARBINARY, "VARBINARY" );
        }

        @Override
        public void exportFormalToWriter( final PrintWriter out )
            throws IOException, UnsupportedOperationException
        {
            Connection connection = null;
            try
            {
                connection = this._dataSource.getConnection();
                GenericDatabaseExplorer.visitDatabaseTables( connection, null,
                                                             this._state.schemaName().get(), null, new DatabaseProcessor()
                {

                    @Override
                    public void endProcessColumns( String schemaName, String tableName, String tableRemarks )
                    {
                        out.write( "</columns>" + "\n" );
                    }

                    @Override
                    public void endProcessRows( String schemaName, String tableName, String tableRemarks )
                    {
                        out.write( "</rows>" + "\n" );
                    }

                    @Override
                    public void endProcessTableInfo( String schemaName, String tableName, String tableRemarks )
                    {
                        out.write( "</table>" + "\n" );
                    }

                    @Override
                    public void endProcessSchemaInfo( String schemaName )
                    {
                        out.write( "</schema>" + "\n" );
                    }

                    @Override
                    public void endProcessRowInfo( String schemaName, String tableName, Object[] rowContents )
                    {
                        out.write( "</row>" + "\n" );
                    }

                    @Override
                    public void endProcessColumnInfo( String schemaName, String tableName, ColumnInfo colInfo,
                                                      ForeignKeyInfo fkInfo )
                    {
                    }

                    @Override
                    public void beginProcessTableInfo( String schemaName, String tableName, String tableRemarks )
                    {
                        out.write( "<table name=\"" + tableName + "\" remarks=\"" + tableRemarks + "\">" + "\n" );
                    }

                    @Override
                    public void beginProcessColumns( String schemaName, String tableName, String tableRemarks )
                    {
                        out.write( "<columns>" + "\n" );
                    }

                    @Override
                    public void beginProcessColumnInfo( String schemaName, String tableName, ColumnInfo colInfo,
                                                        ForeignKeyInfo fkInfo )
                    {
                        String defaultValue = colInfo.getDefaultValue();
                        if( defaultValue.startsWith( "'" ) )
                        {
                            defaultValue = defaultValue.substring( 1, defaultValue.length() - 1 );
                        }
                        out.write( "<column name=\"" + colInfo.getName() + "\" colType=\"" + colInfo.getTypeName()
                                   + "\" colSize=\"" + colInfo.getSize() + "\" colScale=\""
                                   + //
                            colInfo.getScale() + "\" nullable=\"" + colInfo.getNullable() + "\" default=\""
                                   + defaultValue + "\" " //
                        );
                        if( fkInfo != null )
                        {
                            out.write( "refSchemaName=\"" + fkInfo.getPkSchemaName() + "\" refTableName=\""
                                       + fkInfo.getPkTableName() + "\" refPKColumnName=\""
                                       + fkInfo.getPkTablePKColumnName()
                                       + //
                                "\" onUpdate=\"" + fkInfo.getOnUpdateAction() + "\" onDelete=\""
                                       + fkInfo.getOnDeleteAction() + "\" deferrability=\"" + fkInfo.getDeferrability()
                                       + "\" " //
                            );
                        }

                        out.write( "/>" + "\n" );
                    }

                    @Override
                    public void beginProcessSchemaInfo( String schemaName )
                    {
                        out.write( "<schema name=\"" + schemaName + "\">" + "\n" );
                    }

                    @Override
                    public void beginProcessRows( String schemaName, String tableName, String tableRemarks )
                    {
                        out.write( "<rows>" + "\n" );
                    }

                    @Override
                    public void beginProcessRowInfo( String schemaName, String tableName, Object[] rowContents )
                    {
                        out.write( "<row>" + "\n" );
                        for( Integer x = 0; x < rowContents.length; ++x )
                        {
                            out.write( "<value index=\"" + x + "\" >" + rowContents[x] + "</value>" + "\n" );
                        }
                    }
                }, this.descriptor.metaInfo( SQLVendor.class ) );
            }
            catch( SQLException sqle )
            {
                // TODO Just wrap around for now...
                throw new IOException( sqle );
            }
            finally
            {
                SQLUtil.closeQuietly( connection );
            }
        }

        @Override
        public void exportReadableToStream( final PrintStream out )
            throws IOException, UnsupportedOperationException
        {
            Connection connection = null;
            try
            {
                connection = this._dataSource.getConnection();
                GenericDatabaseExplorer.visitDatabaseTables( connection, null,
                                                             this._state.schemaName().get(), null, new DatabaseProcessor()
                {

                    @Override
                    public void endProcessTableInfo( String schemaName, String tableName, String tableRemarks )
                    {
                        out.print( "\n\n\n" );
                    }

                    @Override
                    public void endProcessSchemaInfo( String schemaName )
                    {
                        out.print( "\n\n" );
                    }

                    @Override
                    public void endProcessRowInfo( String schemaName, String tableName, Object[] rowContents )
                    {

                    }

                    @Override
                    public void endProcessColumnInfo( String schemaName, String tableName, ColumnInfo colInfo,
                                                      ForeignKeyInfo fkInfo )
                    {

                    }

                    @Override
                    public void endProcessColumns( String schemaName, String tableName, String tableRemarks )
                    {
                        out.print( SEPARATOR + "\n" + SEPARATOR + "\n" );
                    }

                    @Override
                    public void endProcessRows( String schemaName, String tableName, String tableRemarks )
                    {
                        out.print( SEPARATOR + "\n" + SEPARATOR + "\n" );
                    }

                    private String parseSQLType( ColumnInfo colInfo )
                    {
                        String result = colInfo.getTypeName();
                        Integer sqlType = colInfo.getSQLType();
                        if( TYPE_STRINGS.containsKey( sqlType ) )
                        {
                            result = TYPE_STRINGS.get( sqlType );
                            if( sqlType == Types.DECIMAL || sqlType == Types.NUMERIC )
                            {
                                result = result + "(" + colInfo.getSize() + ", " + colInfo.getScale() + ")";
                            }
                            else if( sqlType == Types.VARCHAR || sqlType == Types.VARBINARY )
                            {
                                result = result + "(" + colInfo.getSize() + ")";
                            }
                        }
                        return result;
                    }

                    @Override
                    public void beginProcessColumnInfo( String schemaName, String tableName, ColumnInfo colInfo,
                                                        ForeignKeyInfo fkInfo )
                    {
                        out.print( colInfo.getName() + ":" + this.parseSQLType( colInfo ) + "[nullable:"
                                   + colInfo.getNullable() + "; default: " + colInfo.getDefaultValue() + "]" );
                        if( fkInfo != null )
                        {
                            out.print( " -> " + fkInfo.getPkSchemaName() + "." + fkInfo.getPkTableName() + "("
                                       + fkInfo.getPkTablePKColumnName() + ")[ON UPDATE " + fkInfo.getOnUpdateAction()
                                       + ", ON DELETE " + fkInfo.getOnDeleteAction() + ", " + fkInfo.getDeferrability() + "]" );
                        }
                        out.print( "\n" );
                    }

                    @Override
                    public void beginProcessTableInfo( String schemaName, String tableName, String tableRemarks )
                    {
                        out.print( "Table: " + schemaName + "." + tableName
                                   + ( tableRemarks == null ? "" : " (" + tableRemarks + ")" ) + "\n" );
                    }

                    @Override
                    public void beginProcessSchemaInfo( String schemaName )
                    {
                        out.print( //
                            "\n\n" + "Schema: " + schemaName + "\n" + //
                            SEPARATOR + "\n\n\n" //
                        );
                    }

                    @Override
                    public void beginProcessRowInfo( String schemaName, String tableName, Object[] rowContents )
                    {
                        for( Integer x = 0; x < rowContents.length; ++x )
                        {
                            Object value = rowContents[x];
                            out.print( value == null ? "NULL" : ( "\"" + value + "\"" ) );
                            if( x + 1 < rowContents.length )
                            {
                                out.print( " ; " );
                            }
                        }
                        out.print( "\n" );
                    }

                    @Override
                    public void beginProcessColumns( String schemaName, String tableName, String tableRemarks )
                    {
                        out.print( SEPARATOR + "\n" + SEPARATOR + "\n" );
                    }

                    @Override
                    public void beginProcessRows( String schemaName, String tableName, String tableRemarks )
                    {

                    }
                }, this.descriptor.metaInfo( SQLVendor.class ) );
            }
            catch( SQLException sqle )
            {
                // TODO Just wrap around for now...
                throw new IOException( sqle );
            }
            finally
            {
                SQLUtil.closeQuietly( connection );
            }
        }
    }