/**
 * An adaptor, transforming the JDBC interface to the TableModel interface.
 *
 * @version 1.20 09/25/97
 * @author Philip Milne
 *
 * *** adapted by sanjay sthapit 5/98 ***
 */

import java.util.Vector;
import java.sql.*;
import com.sun.java.swing.table.AbstractTableModel;
import com.sun.java.swing.event.TableModelEvent;

public class JDBCAdapter extends AbstractTableModel {
	dbBrowser			myparent;
    Connection          connection;
    Statement           statement;
    ResultSet           resultSet;
    String[]            columnNames = {};
    Class[]             columnTpyes = {};

	String				currentTable; // hack alert!! this is necessary cuz jdbc:odbc
									  // driver metadata does not return table names
									  // with getTableName(int column)

    Vector		rows = new Vector();
    ResultSetMetaData   metaData;

    public JDBCAdapter(String url, String driverName,
                       String user, String passwd, dbBrowser mp) {
        try {
			myparent = mp;

            Class.forName(driverName);
            myparent.msg("Opening db connection");

            connection	= DriverManager.getConnection(url, user, passwd);
            statement	= connection.createStatement();
        }
        catch (ClassNotFoundException ex) {
            myparent.msg("Cannot find the database driver classes.");
            myparent.msg(ex.toString());
        }
        catch (SQLException ex) {
            myparent.msg("Cannot connect to this database.");
            myparent.msg(ex.toString());
        }
     }

    public void executeQuery(String query) {
        if (connection == null || statement == null) {
            myparent.msg("There is no database to execute the query.");
            return;
        }
        try {
            resultSet = statement.executeQuery(query);
            metaData = resultSet.getMetaData();

            int numberOfColumns =  metaData.getColumnCount();
            columnNames = new String[numberOfColumns];
            // Get the column names and cache them.
            // Then we can close the connection.
            for(int column = 0; column < numberOfColumns; column++) {
                columnNames[column] = metaData.getColumnLabel(column+1);
            }

            // Get all rows.
            rows = new Vector();
            while (resultSet.next()) {
                Vector newRow = new Vector();
                for (int i = 1; i <= getColumnCount(); i++) {
	            newRow.addElement(resultSet.getObject(i));
                }
                rows.addElement(newRow);
            }
            //  close(); Need to copy the metaData, bug in jdbc:odbc driver.
            fireTableChanged(null); // Tell the listeners a new table has arrived.
        }
        catch (SQLException ex) {
            myparent.msg(ex.toString());
        }
    }

    public void close() throws SQLException {
        myparent.msg("Closing db connection");
        resultSet.close();
        statement.close();
        connection.close();
    }

    protected void finalize() throws Throwable {
        close();
        super.finalize();
    }

    //////////////////////////////////////////////////////////////////////////
    //
    //             Implementation of the TableModel Interface
    //
    //////////////////////////////////////////////////////////////////////////

    // MetaData

    public String getColumnName(int column) {
        if (columnNames[column] != null) {
            return columnNames[column];
        } else {
            return "";
        }
    }

    public Class getColumnClass(int column) {
        int type;
        try {
            type = metaData.getColumnType(column+1);
        }
        catch (SQLException e) {
            return super.getColumnClass(column);
        }

        switch(type) {
        case Types.CHAR:
        case Types.VARCHAR:
        case Types.LONGVARCHAR:
            return String.class;

        case Types.BIT:
            return Boolean.class;

        case Types.TINYINT:
        case Types.SMALLINT:
        case Types.INTEGER:
            return Integer.class;

        case Types.BIGINT:
            return Long.class;

        case Types.FLOAT:
        case Types.DOUBLE:
            return Double.class;

        case Types.DATE:
            return java.sql.Date.class;

        default:
            return Object.class;
        }
    }

    public boolean isCellEditable(int row, int column) {
        try {
            return metaData.isWritable(column+1);
        }
        catch (SQLException e) {
            return false;
        }
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    // Data methods

    public int getRowCount() {
        return rows.size();
    }

    public Object getValueAt(int aRow, int aColumn) {
        Vector row = (Vector)rows.elementAt(aRow);
        return row.elementAt(aColumn);
    }

    public String dbRepresentation(int column, Object value) {
        int type;

        if (value == null) {
            return "null";
        }

        try {
            type = metaData.getColumnType(column+1);
        }
        catch (SQLException e) {
            return value.toString();
        }

        switch(type) {
        case Types.INTEGER:
        case Types.DOUBLE:
        case Types.FLOAT:
            return value.toString();
        case Types.BIT:
            return ((Boolean)value).booleanValue() ? "true" : "false";
        case Types.DATE:
            return value.toString(); // This will need some conversion.
        default:
            return "'"+value.toString()+"'";
        }

    }

    public void setValueAt(Object value, int row, int column) {
        try {

            // String tableName = metaData.getTableName(column+1);
			String tableName	= currentTable; // hack alert!! see bug next comment
            // Some of the drivers seem buggy, tableName should not be null.
            if (tableName == null) {
                myparent.msg("Table name returned null.");
            }
            String columnName = getColumnName(column);
            String query =
                "UPDATE \""+tableName+
                "\" SET "+columnName+" = "+dbRepresentation(column, value)+
                " WHERE ";
            // We don't have a model of the schema so we don't know the
            // primary keys or which columns to lock on. To demonstrate
            // that editing is possible, we'll just lock on everything.
            for(int col = 0; col<getColumnCount(); col++) {
                String colName = getColumnName(col);
                if (colName.equals("")) {
                    continue;
                }
                if (col != 0) {
                    query = query + " AND ";
                }
                query = query + colName +" = "+
                    dbRepresentation(col, getValueAt(row, col));
            }
            myparent.msg(query);
            // myparent.msg("Not sending update to database");
            statement.executeQuery(query);
        } // try
        catch (SQLException ex) {
            // e.printStackTrace();
            myparent.msg(ex.toString());
        }

        Vector dataRow = (Vector)rows.elementAt(row);
        dataRow.setElementAt(value, column); 
		myparent.fillColumnChoices();

    }

	 /*
	  * This method returns a Vector containing a list of all the catalogs
	  * available in the current database. If catalogs are not supported by
	  * the database, a null list is returned. Since the most likely application
	  * of this data will be to create a tree or pick list of all available
	  * catalogs, the data is not returned as a DataTable.
	  */

	  public Vector getCatalogNames()
	  {
	   Vector catalogName= new Vector();
	   try {
	        DatabaseMetaData dbmd = connection.getMetaData();
	        if (dbmd.supportsCatalogsInTableDefinitions())
	         {
	          ResultSet catalogRS = dbmd.getCatalogs();
	          while (catalogRS.next())
	           {
	             catalogName.addElement(catalogRS.getString("TABLE_CAT"));
	           }
	          }
	         else
	          {
	            catalogName.addElement("");
	            myparent.msg ("Catalog names not supported.");
	          }
	        }
	        catch (SQLException e)
	         {
	          myparent.msg ("GetCatalogNames Failed: " + e.getMessage());
	         }
	   return catalogName;
	  }
  
	  /* 
	   * This method returns a list of all schemas available in the database.
	   */
   
	  public Vector getSchemaNames()
	  {
	   Vector schemaNames = new Vector ();
	   try {
	        DatabaseMetaData dbmd = connection.getMetaData();
	        ResultSet schemaNameResultSet = dbmd.getSchemas();
	        while ( schemaNameResultSet.next() )
	         {
	          schemaNames.addElement(schemaNameResultSet.getString("TABLE_SCHEM"));
	         }
	        }
	        catch (SQLException e)
	         {
	           myparent.msg ("GetSchema  Failed: " + e.getMessage());
	         }
		    return schemaNames;
	   }

	  /*
	   * This method returns an array of Strings containing the names
	   * of all the table types the database accepts.
	   */
	    
	  public String[] getTableTypes()
	  {
	   Vector tableTypes= new Vector();
	   try {
	        DatabaseMetaData dbmd = connection.getMetaData();
	        ResultSet tableTypesRS = dbmd.getTableTypes();
	        while (tableTypesRS.next())
	         {
	          tableTypes.addElement(tableTypesRS.getString("TABLE_TYPE"));
	         }        
	        }
	        catch (SQLException e)
	         {
	          myparent.msg ("GetTableTypes Failed: " + e.getMessage());
	         }
	   // Convert Vector to an Array of Strings
	   int mycount = tableTypes.size();      
	   for (int i=0; i< tableTypes.size(); i++) {
		   String temp = tableTypes.elementAt(i).toString();
		   if (temp.equals("SYSTEM TABLE"))
			   mycount--;
	   }
	   String [] types = new String[mycount];
	   for (int i=0; i< mycount; i++)
	     {
		   String temp = tableTypes.elementAt(i).toString();
		   if (!temp.equals("SYSTEM TABLE")) {
				types[i] = temp;
				// myparent.msg ("Table Type: " + types[i]);
		   }
		}
	   return types;
	  }
  
	  /*
	   * This method is used to return a list of all tables that are contained
	   * in the catalog and schema specified and are of a single table type.
	   */
	   
	   public Vector getTableNames(	String catalog, 
							        String schema, 
									String tableType)
	   {
	    Vector tableNames = new Vector();
	    String[] tableTypes = new String[1];
	    tableTypes[0] = tableType;
	
	    try {
	         DatabaseMetaData dbmd = connection.getMetaData();
	         ResultSet tableNamesRS = dbmd.getTables(catalog,schema,"%",tableTypes);
	         while (tableNamesRS.next())
	          {
	           tableNames.addElement(tableNamesRS.getString("TABLE_NAME"));
	          }
	         }
	         catch (SQLException e)
	          {
	           myparent.msg ("SetTableNames Failed: " + e.getMessage());
	          }
	    return tableNames;
	   }

	  /* 
	   * This method is also used to return a list of tables contained in
	   * the specified catalog and schema. However, this method takes
	   * an array listing the tableTypes. 
	   */
   
	 public Vector getTableNames( String catalog, 
							      String schema, 
							      String[] tableTypes)
	   {
	    Vector tableNames = new Vector();

	    try {
	         DatabaseMetaData dbmd = connection.getMetaData();
	         ResultSet tableNamesRS = dbmd.getTables(catalog,schema,"%",tableTypes);
	         while (tableNamesRS.next())
	          {
	           tableNames.addElement(tableNamesRS.getString("TABLE_NAME"));
	          }
	         }
	         catch (SQLException e)
	          {
	           myparent.msg ("SetTableNames Failed: " + e.getMessage());
	          }
	    return tableNames;
	   }


}
