import java.sql.*;
import java.util.*;

/**
* Facade pattern implementation 
* for JDBC
* This init method creates the
* connection to the database and retrieves
* additional database information.
* @author Douglas Lyon
* @version 1.00
*/
public class MetaBean {

	private String url = null;
	private String driver = null;
	private String userId = null;
	private String password = null;
	private Connection c = null;
	private Statement statement = null;
	private DatabaseMetaData dbmd = null;
	private ResultSet rs = null;
	private ResultSetMetaData rsmd = null;
	private ResultSet rs_tabletypes = null; 

	private boolean isReadOnly = false;
	private boolean usesLocalFiles = false; 	
	private String driverName = null;
	private String catalogName = null;
	private String productName = null;
	private String keyWords = null;
	private String tablename = null;
	private String currentquery= null;
	
	private String tableFields[]= null;
	private String tableNames[] = null;
	private String nonMSysTables[] = null;

/**
* Determines if database
* uses a local file. 
*
* The value is set using the DatabaseMetaData.
* usesLocalFiles() method
* contained in the java.sql package. This method is called and this value is 
* set in this classes init() method.
*
*	@return true if it does, false if not
*/
	public boolean getUsesLocalFiles() {
		return usesLocalFiles;
	}
	
/**
* Gets whether or not this connection to this database is read only. 
*
* The value is set using the Connection.isReadOnly() method 
* contained in the java.sql package. 
* This method is called and this value is 
* set in this classes init() method.
*
*	@return true if it is, false if not
*/
	public boolean getReadOnly() {
		return isReadOnly;
	}
	
/**
* Gets this Connection's current catalog name. 
*
* The value is set using the Connection.getCatalog() method 
* contained in the java.sql package. This method is called and this value is 
* set in this classes init() method.
*
*	@return the current catalog name or null */
	public String getCatalogName() {
		return catalogName;
	}
	
/**
* Gets the name of this JDBC driver.
*
* The value is set using the DatabaseMetaData.getDriverName() method 
* contained in the java.sql package. This method is called and this value is 
* set in this classes init() method.
*
*	@return the JDBC Driver name
*/
	public String getDriverName() {
		return driverName;
	}
	
/**
* Gets the name of this database product. 
*
* The value is set using the DatabaseMetaData.getDatabaseProductName() method 
* contained in the java.sql package. This method is called and this value is
* set in this classes init() method.
*
*	@return the database product name
*/
	public String getProductName() {
		return productName;
	}

/**
* Gets the ResultSet contained in this instance variable rs. 
*
*	@return this classes ResultSet
*/
	public ResultSet getResultSet() {
		return rs;
	}


/**
* Gets the value of this current table name. 
*
* The value is set using the SqlBean.setTableName() method 
*
*	@return the current table name
*/
	public String getTableName()	{
		return tablename;
	}
	
/**
* Gets the value of this current SQL.
*
* The value is set using the SqlBean.query() method 
*
*	@return the current SQL query
*/
	public String getCurrentQuery()	{
		return currentquery;
	}

	
/**
* Gets the table names contained in this current database. 
*
* The table names are placed in a ResultSet using the DatabaseMetaData.
* getTables() method
* From the ResultSet, the tables are added to a vector and then 
* converted into a String array. This method can be used 
  at anytime after the init() 
* method is called to set the DataBaseMetaData. 
*
*	@return the table names
*/
	public String[] getTableNames() {
		Vector tableVector = new Vector();
		try {
			rs = dbmd.getTables(null,null,null,null); 			
			rsmd = rs.getMetaData();
			while (rs.next())
			
				tableVector.addElement(rs.getString("TABLE_NAME")); 		
		}
		catch(SQLException e) {
			print(e);
		}
		int n = tableVector.size();
		tableNames =new String[n];
		for (int i=0; i < n; i++)
			tableNames[i] = (String)tableVector.elementAt(i); 		
			return tableNames;
	}
	
	
/**
* Gets the names of all the Non-System Tables in this Database. 
*
* Retrieves all the tables using the getTableNames() method. Then uses the 
* getNumberOfMSysTables() to determine the number of SystemTables contained in the 
* database and places those names in a String array. This method can be used at anytime 
* after the init() method is called to set the DataBaseMetaData. 
*	
* @return the names of the non-system tables 
*/
	public String[] getNonMSysTables() {
		
		String tn[] = getTableNames();
		int n = tableNames.length -	getNumberOfMSysTables(); 		
		nonMSysTables = new String[n];
		for (int i=0; i < n; i++)
			nonMSysTables[i]= tn[i+getNumberOfMSysTables()]; 		

		return nonMSysTables;
	}
	
	
/**
* Gets the number of the System Tables in this Database. 
*
* Used to help determine the table names in the getNonMSysTables() method. 
* Determine the number of SystemTables contained in the database my searching for 
* names starting with MSys.
*	
* @return the number of system tables
*/
	private int getNumberOfMSysTables() {
		
		int k = 0;
		for (int i=0; i < tableNames.length; i++) 			if (tableNames[i].startsWith("MSys")) 		 	k++;
		return k;
	}
	
/**
* Gets the table types available in this database connection. The results are ordered by table type. *
* The results are ordered by table type. Typical types are: 
* <br>"TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL", "TEMPORARY", 
"LOCAL TEMPORARY", "ALIAS", "SYNONYM". *
*	@return the current SQL query
*/

	public ResultSet getTableTypes() {
		return rs_tabletypes;
	}


	
/**
* Sets this classes resultset instance variable, rs, based on a provided SQL query. 
*	
* @param myquery the SQL query
*/
	public void setResultSet(String myquery) 	{
		rs = query(myquery);
	}
	

/**
* Sets this classes instance variable, userId, for this database connection. 
*
* This is the database user on whose behalf the Connection is being made for 
*	
* @param _userID the database UserId
*/
	public void setUserId(String _userId) { 		userId = _userId;
	}

/**
* Sets this classes instance variable, password, for this database connection. 
*
* The password associated with the database user on whose behalf the 
*	Connection is being made for.
*
* @param _password the database Password */
	public void setPassword(String _password) { 		password = _password;
	}

/**
* Sets this classes instance variable, url, for this database url. *
* The url is in the form of jdbc:subprotocol:subname *	
* @param _url the database url
*/
	public void setUrl(String _url) {
		url = _url;
	}

/**
* Sets the name of the instance variable, driver, which this class is loadeding. 
*
* This is the string representation of the driver being loaded to 
make the connection to the database. 
*	
* @param _driver the driver name
*/
	public void setDriver(String _driver) { 		
		driver = _driver;
	}
	
/**
* Sets this classes instance variable, tablename, for the current database tablename. *	
* @param _tablename the database tablename
*/
	public void setTableName(String _tablename) { 		
		tablename = _tablename;
	}

/**
* Constructor.
*/
	public MetaBean() {
	}
	
/**
* Loads specified driver and initializes the Connection to this Database.
 *
* <p>Loads the driver and Connects to the Database 
* <br>Retrieves and sets Database/
Connection Information as follows: 
* <br>&nbsp;&nbsp;&nbsp MetaData of this Database 
* <br>&nbsp;&nbsp;&nbsp	Read Only Property 
* <br>&nbsp;&nbsp;&nbsp	Uses Local Files Property 
* <br>&nbsp;&nbsp;&nbsp	Driver Name Used for the Connection 
* <br>&nbsp;&nbsp;&nbsp	Database Product Name 
* <br>&nbsp;&nbsp;&nbsp	Table Types in this Database */	
	public void init() {
		try {
			
			Class.forName(driver);
			c = DriverManager.getConnection(url,userId,password); 			dbmd = c.getMetaData();
			catalogName = c.getCatalog();
			isReadOnly = c.isReadOnly();
			usesLocalFiles = dbmd.usesLocalFiles(); 			driverName = dbmd.getDriverName();
			productName = dbmd.getDatabaseProductName(); 			rs_tabletypes = dbmd.getTableTypes(); 

			
		}
		catch (ClassNotFoundException e) {
			println(e);
		}
		catch (SQLException e) {
			print(e);
		}
		System.out.println("Opened Connection:"+url); 	}

/**
* Prints information about this database connection. 
* <p>
* <br>Prints the following information:
* <br>&nbsp;&nbsp;&nbsp The name of this database product 
* <br>&nbsp;&nbsp;&nbsp	This Connection's current catalog name
* <br>&nbsp;&nbsp;&nbsp	Is this connection in read-only mode 
* <br>&nbsp;&nbsp;&nbsp	Does this database store tables in a local files 
* <br>&nbsp;&nbsp;&nbsp	The name of this JDBC driver 
* <br>&nbsp;&nbsp;&nbsp	The SQL keywords of this database 
* <br>&nbsp;&nbsp;&nbsp	The table types of all the tables in this database 
* <br>&nbsp;&nbsp;&nbsp	The names of all the tables in this database 
* <br>&nbsp;&nbsp;&nbsp	The names of all the non-system tables in this database 
*/	
	public void printDataBaseInfo() {
		println("*****");
		println("productName="+productName);
		println("*****");
		println("catalogName="+catalogName);
		println("*****");
		println("is ReadOnly="+getReadOnly()); 		println("*****");
		println("usesLocalFiles="+getUsesLocalFiles()); 		println("*****");
		println("driverName="+driverName);
		println("*****");
		println("Non SL92 keywords:");
		println(getKeyWords());
		println("*****");
		println("TableTypes:");
		print(getTableTypes());
		println("*****");
		println("TableNames - All:");
		println(getTableNames());
		println("*****");
		println("TableNames - NonSystem:");
		println(getNonMSysTables());

	
	}
	
/**
* Prints information about this current ResultSet. 
* <p>
* <br>Prints the following information:
* <br>&nbsp;&nbsp;&nbsp The column names (fields) of this database 
* <br>&nbsp;&nbsp;&nbsp The type name of the columns (fields) used by this database 
*
* @param _rs the current ResultSet
*/
	public void printResultSetInfo(ResultSet _rs) { 		
		println("*****");
		println("Column Names:");
		println(getTableFields(getResultSetMetaData(_rs))); 		
		println("*****");
		println("Column Types:");
		println(getColumnTypeNames(getResultSetMetaData(_rs))); 		
		println("*****");
		println("Number of Rows:");
		println(Integer.toString((getNumberofRows(getRows(_rs))))); 		
		println("*****");
		println("Print The First Row:");
	}

/**
* Closes the connections to this database. */
	public void close() {
		
		try {
			
			rs.close();
			rs_tabletypes.close();
			statement.close();
			c.close();
			
			System.out.println("closed connection"); 		}
		catch (SQLException e) {
			print(e);
		}
	}
/**
* Creates a Result Set based on an sql query. *
* @param _sql the sql query
* @return the ResultSet
*/
	public ResultSet query(String sql) {
		
		try {
			statement = c.createStatement ();
			currentquery = sql;
			return statement.executeQuery(sql);
		}
		catch (SQLException e) {
			print(e);
		}
		return null;
	}

/**
* Gets the MetaData for a specified ResultSet. *
* @param _rs the ResultSet
* @return the ResultSetMetaData
*/
	public ResultSetMetaData getResultSetMetaData(ResultSet _rs) { 		
		try {
			return _rs.getMetaData();
		}
		catch(SQLException e) {
			print(e);
		}
		return null;
	}

/**
* Gets the number of columns in a ResultSet. *
* @param _rsmd the ResultSetMetaData
* @return number of Columns (fields)
*/
	public int getColumnCount(ResultSetMetaData _rsmd) { 		
		try {
			return _rsmd.getColumnCount();
		}
		catch(SQLException e) {
			print(e);
		}
		return 0;
	}
	
/**
* Gets the keywords associated with this database. *
* @return the keywords
*/

	public String getKeyWords(){
		
		try{
			return dbmd.getSQLKeywords();
		}
		catch (SQLException e)	{
			print(e);
		}
		return null;
	}

/**
* Gets the database types of the columns in a ResultSet. 
*
* These are the type name used by this database. If the column type is a user-defined type, 
* then a fully-qualified type name is returned. *
* @param _rsmd the ResultSetMetaData
* @return the column types
*/

	public String [] getColumnTypeNames(ResultSetMetaData _rsmd) { 

		int count = getColumnCount(_rsmd);
		
		String sa [] = new String[count];
		try {
			for (int i=0; i < sa.length; i++) {
				sa[i] = _rsmd.getColumnTypeName(i+1); 			
				}
		}
		catch(SQLException e) {
			print(e);
		}
		return sa;
	}

/**
* Converts a row in a ResultSet to a String. 
*
* @param _rs the ResultSet pointed to a particular row 
* @return the contents of the ResultSet
*/
	public String [] getRowAsString(ResultSet _rs) { 

		int N = getColumnCount(getResultSetMetaData(_rs)); 		
		String s[] = new String[N];
		try {
			for (int i =0; i < N; i++)
				s[i] = _rs.getString(i+1);
		}
		catch(SQLException e) {
			print(e);
		}
		return s;
	}

/**
* Converts a ResultSet to a Vector of Strings. *
* @param _rs the ResultSet
* @return the vector containing the ResultSet with each row as a String */

	public Vector getRows(ResultSet _rs) {
		Vector v = new Vector();
		while(nextRow(_rs))
			v.addElement(getRowAsString(_rs));
		return v;
	}
	
/**
* Returns the size of a vector. 
Used with the getRows() method that converts a ResultSet to a vector. 
*
* @param v the Vector
* @return the number of rows
*/
	public int getNumberofRows(Vector v) {
		return v.size();
	}
	
/**
* Moves to the top of this RecordSet.
*
* @param _rs the ResultSet
*/
	public void moveToTop(ResultSet _rs) {
		try{
			_rs.beforeFirst();
		}
		catch(SQLException e) {
			print(e);
		}	
	}

/**
* Prints the contents of a provided ResultSet. *
* @param _rs the ResultSet
*/
	public void print(ResultSet _rs) {

		int i;
		String cn[] = getTableFields(getResultSetMetaData(_rs)); 		
		println(cn);
		boolean more = false;
		while (more = nextRow(_rs))
			println(getRowAsString(_rs));
	}

/**
* Prints the contents of a provided Object. * Uses the toString() method for the
* provided object.
*
* @param o the Object
*/
	
	public void println(Object o) {
		
		System.out.println(o);
	}

/**
* Prints the contents of a String array. 
*
* @param s[] the array
*/

	public void println(String s[]) {

		for (int i=0; i < s.length; i++)
			System.out.print(s[i]+'\t');
		System.out.println();
			
	}

/**
* Prints messages about this SQL Exception. *
* @param ex the exception
*/

	private void print(SQLException ex) {
		println ("\n*** SQLException caught ***\n"); 		
		while (ex != null) {
			println ("SQLState: " +	ex.getSQLState ()); 			
			println ("Message: " + ex.getMessage ()); 			
			println ("Vendor: " + ex.getErrorCode ()); 			
			ex = ex.getNextException ();
			println ("");
		}
		ex.printStackTrace ();
	}


/**
* Moves to the next row of a provided ResultSet. *
* @param _rs the ResultSet
*/
	public boolean nextRow(ResultSet _rs) { 		try {
			return _rs.next();
		}
		catch(SQLException e) {
			return false;
		}
	}

/**
* Gets the names of the columns (fields) in a provided ResultSet. *
* @param _rs the ResultSetMetaData
* @return the table names
*/
	public String[] getTableFields(ResultSetMetaData _rsmd) { 

		String s []	= new String[getColumnCount(_rsmd)]; 		try {
			for (int i=1; i <= s.length; i++)
				s[i-1]=_rsmd.getColumnLabel(i);
		}
		catch(SQLException e) {
			print(e);
		}
		tableFields = s;
		return s;
	}

/**
* Gets this DataBaseMetaData object.
*
* @return this DataBaseMetaData
*/

	public DatabaseMetaData getDatabaseMetaData() { 

		return dbmd;
	}

/**
* Process an SQL INSERT, DELETE or UPDATE statement string. 
*
* Additionally, this could be an SQL
* statement that returns nothing such as SQL DDL statments. 
*
* @param sql the SQL INSERT, UPDATE or DELETE statement
* or an SQL statement that returns nothing 
*/

	public void modifyDatabase(String sql) { 

		try {
			Statement s = c.createStatement();
			int insertResult = s.executeUpdate(sql); 			
			println("UPDATE SUCCESSFUL");
			println("SQL Statment= " + sql);
		}
		catch (SQLException e) {
			println("UPDATE NOT SUCCESSFUL");
			println("SQL Statment= " + sql);
			print(e);
		}
	}

/**
* Main entry point for application.
*
* @param args[] the command line arguments 
*/

	public static void main(String args[]) { 		
		MetaBean sb = new MetaBean();
		// The driver and DSN are parameters.
		sb.setUrl("jdbc:odbc:Addresses"); 		
		sb.setDriver("sun.jdbc.odbc.JdbcOdbcDriver"); 		
		sb.setUserId("");
		sb.setPassword("");
		sb.println("Calling Init Method");
		sb.init();
		sb.println("****************");
		sb.println("Tables");
		sb.println(sb.getTableNames());
		// the following are not standard...
		
		String tn[] = sb.getNonMSysTables();
		sb.println(tn);

		sb.println("****************");
		sb.println("Calling printDataBaseInfo Method"); 		
		sb.printDataBaseInfo();
		sb.setTableName(tn[0]);
		// table name is a parameter we got from
		// the data base itself. 
		// There might have been more than one....
		sb.println("****************");
		sb.println(
			"Calling printResultSetInfo Method for Table Name: " 
				+ sb.getTableName()); 		
		
		sb.printResultSetInfo(
			sb.query("SELECT * FROM "+ sb.getTableName()));
		sb.println("****************");
		sb.println("Calling Method to Print the Result Set for Table Name: " 
			+ sb.getTableName() ); 		
		sb.print(sb.query(
			"SELECT * FROM "
			+sb.getTableName())); 		
		sb.close();
	}

}
