content: /*
   *    This program is free software; you can redistribute it and/or modify
   *    it under the terms of the GNU General Public License as published by
   *    the Free Software Foundation; either version 2 of the License, or
   *    (at your option) any later version.
   *
   *    This program is distributed in the hope that it will be useful,
   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *    GNU General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   * ResultSetHelper.java
   * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
   *
   */
  
  
  package weka.gui.sql;
  
  import java.sql.ResultSet;
  import java.sql.ResultSetMetaData;
  import java.sql.Types;
  import java.util.Vector;
  
  /**
   * Represents an extended JTable, containing a table model based on a ResultSet
   * and the corresponding query.
   *
   * @author      FracPete (fracpete at waikato dot ac dot nz)
   * @version     $Revision: 7037 $
   */
  public class ResultSetHelper {
    
    /** the resultset to work on. */
    protected ResultSet m_ResultSet;
  
    /** whether we initialized. */
    protected boolean m_Initialized = false;
  
    /** the maximum number of rows to retrieve. */
    protected int m_MaxRows = 0;
  
    /** the number of columns. */
    protected int m_ColumnCount = 0;
  
    /** the number of rows. */
    protected int m_RowCount = 0;
  
    /** the column names. */
    protected String[] m_ColumnNames = null;
  
    /** whether a column is numeric. */
    protected boolean[] m_NumericColumns = null;
  
    /** the class for each column. */
    protected Class[] m_ColumnClasses = null;
  
    /**
     * initializes the helper, with unlimited number of rows.
     * 
     * @param rs        the resultset to work on
     */
    public ResultSetHelper(ResultSet rs) {
      this(rs, 0);
    }
  
    /**
     * initializes the helper, with the given maximum number of rows (less than
     * 1 means unlimited).
     * 
     * @param rs        the resultset to work on
     * @param max       the maximum number of rows to retrieve
     */
    public ResultSetHelper(ResultSet rs, int max) {
      super();
  
      m_ResultSet = rs;
      m_MaxRows   = max;
    }
  
    /**
     * initializes, i.e. reads the data, etc.
     */
    protected void initialize() {
      ResultSetMetaData     meta;
      int                   i;
      
      if (m_Initialized)
        return;
      
      try {
        meta = m_ResultSet.getMetaData();
  
        // columns names
        m_ColumnNames = new String[meta.getColumnCount()];
        for (i = 1; i <= meta.getColumnCount(); i++)
          m_ColumnNames[i - 1] = meta.getColumnLabel(i);
        
        // numeric columns
        m_NumericColumns = new boolean[meta.getColumnCount()];
        for (i = 1; i <= meta.getColumnCount(); i++)
          m_NumericColumns[i - 1] = typeIsNumeric(meta.getColumnType(i));
  
        // column classes
        m_ColumnClasses = new Class[meta.getColumnCount()];
        for (i = 1; i <= meta.getColumnCount(); i++) {
  	try {
  	  m_ColumnClasses[i - 1] = typeToClass(meta.getColumnType(i));
  	}
  	catch (Exception ex) {
  	  m_ColumnClasses[i - 1] = String.class;
  	}
        }
  
        // dimensions
        m_ColumnCount = meta.getColumnCount();
  
        // if the JDBC driver doesn't support scrolling we can't determine
        // the row count here
        if (m_ResultSet.getType() == ResultSet.TYPE_FORWARD_ONLY) {
  	m_RowCount = -1;
        }
        else {
  	m_RowCount = 0;
  	m_ResultSet.first();
  	if (m_MaxRows > 0) {
  	  try {
  	    m_ResultSet.absolute(m_MaxRows);
  	    m_RowCount = m_ResultSet.getRow();
  	  }
  	  catch (Exception ex) {
  	    // ignore it
  	  }
  	}
  	else {
  	  m_ResultSet.last();
  	  m_RowCount = m_ResultSet.getRow();
  	}
  
  	// sometimes, e.g. with a "desc <table>", we can't use absolute(int)
  	// and getRow()???
  	try {
  	  if ( (m_RowCount == 0) && (m_ResultSet.first()) ) {
  	    m_RowCount = 1;
  	    while (m_ResultSet.next()) {
  	      m_RowCount++;
  	      if (m_ResultSet.getRow() == m_MaxRows)
  		break;
  	    };
  	  }
  	}
  	catch (Exception e) {
  	  // ignore it
  	}
        }
  
        m_Initialized = true;
      }
      catch (Exception ex) {
        // ignore it
      }
    }
  
    /**
     * the underlying resultset.
     * 
     * @return		the resultset
     */
    public ResultSet getResultSet() {
      return m_ResultSet;
    }
  
    /**
     * returns the number of columns in the resultset.
     * 
     * @return		the number of columns
     */
    public int getColumnCount() {
      initialize();
  
      return m_ColumnCount;
    }
  
    /**
     * returns the number of rows in the resultset. If -1 then the number of
     * rows couldn't be determined, i.e., the cursors aren't scrollable.
     * 
     * @return		the number of rows, -1 if it wasn't possible to 
     * 			determine
     */
    public int getRowCount() {
      initialize();
  
      return m_RowCount;
    }
  
    /**
     * returns an array with the names of the columns in the resultset.
     * 
     * @return		the column names
     */
    public String[] getColumnNames() {
      initialize();
  
      return m_ColumnNames;
    }
  
    /**
     * returns an array that indicates whether a column is numeric or nor.
     * 
     * @return		the numeric columns
     */
    public boolean[] getNumericColumns() {
      initialize();
  
      return m_NumericColumns;
    }
  
    /**
     * returns the classes for the columns.
     * 
     * @return		the column classes
     */
    public Class[] getColumnClasses() {
      initialize();
  
      return m_ColumnClasses;
    }
  
    /**
     * whether a limit on the rows to retrieve was set.
     * 
     * @return		true if there's a limit
     */
    public boolean hasMaxRows() {
      return (m_MaxRows > 0);
    }
  
    /**
     * the maximum number of rows to retrieve, less than 1 means unlimited.
     * 
     * @return		the maximum number of rows
     */
    public int getMaxRows() {
      return m_MaxRows;
    }
  
    /**
     * returns an 2-dimensional array with the content of the resultset, the first
     * dimension is the row, the second the column (i.e., getCells()[y][x]).
     * Note: the data is not cached! It is always retrieved anew.
     * 
     * @return		the data
     */
    public Object[][] getCells() {
      int			i;
      int			n;
      Vector<Object[]>	result;
      Object[]		row;
      int			rowCount;
      boolean		proceed;
      
      initialize();
  
      result = new Vector<Object[]>();
  
      try {
        
        // do know the number of rows?
        rowCount = getRowCount();
        if (rowCount == -1) {
  	rowCount = getMaxRows();
  	proceed  = m_ResultSet.next();
        }
        else {
  	proceed = m_ResultSet.first();
        }
        
        if (proceed) {
  	i = 0;
  	while (true) {
  	  row = new Object[getColumnCount()];
  	  result.add(row);
  
  	  for (n = 0; n < getColumnCount(); n++) {
  	    try {
  	      // to get around byte arrays when using getObject(int)
  	      if (getColumnClasses()[n] == String.class)
  		row[n] = m_ResultSet.getString(n + 1);
  	      else
  		row[n] = m_ResultSet.getObject(n + 1);
  	    }
  	    catch (Exception e) {
  	      row[n] = null;
  	    }
  	  }
  
  	  // get next row, if possible
  	  if (i == rowCount - 1) {
  	    break;
  	  }
  	  else {
  	    // no more rows -> exit
  	    if (!m_ResultSet.next())
  	      break;
  	  }
  
  	  i++;
  	}
        }
      }
      catch (Exception e) {
        e.printStackTrace();
      }
  
      return result.toArray(new Object[result.size()][getColumnCount()]);
    }
  
    /**
     * Returns the class associated with a SQL type.
     *
     * @param type the SQL type
     * @return the Java class corresponding with the type
     */
    public static Class typeToClass(int type) {
      Class     result;
      
      switch (type) {
        case Types.BIGINT :
          result = Long.class;
          break;
        case Types.BINARY:
          result = String.class;
          break;
        case Types.BIT:
          result = Boolean.class;
          break;
        case Types.CHAR:
          result = Character.class;
          break;
        case Types.DATE:
          result = java.sql.Date.class;
          break;
        case Types.DECIMAL:
          result = Double.class;
          break;
        case Types.DOUBLE:
          result = Double.class;
          break;
        case Types.FLOAT:
          result = Float.class;
          break;
        case Types.INTEGER:
          result = Integer.class;
          break;
        case Types.LONGVARBINARY:
          result = String.class;
          break;
        case Types.LONGVARCHAR:
          result = String.class;
          break;
        case Types.NULL:
          result = String.class;
          break;
        case Types.NUMERIC:
          result = Double.class;
          break;
        case Types.OTHER:
          result = String.class;
          break;
        case Types.REAL:
          result = Double.class;
          break;
        case Types.SMALLINT:
          result = Short.class;
          break;
        case Types.TIME:
          result = java.sql.Time.class;
          break;
        case Types.TIMESTAMP:
          result = java.sql.Timestamp.class;
          break;
        case Types.TINYINT:
          result = Short.class;
          break;
        case Types.VARBINARY:
          result = String.class;
          break;
        case Types.VARCHAR:
          result = String.class;
          break;
        default:
          result = null;
      }
  
      return result;
    }
  
    /**
     * returns whether the SQL type is numeric (and therefore the justification
     * should be right).
     * 
     * @param type      the SQL type
     * @return          whether the given type is numeric
     */
    public static boolean typeIsNumeric(int type) {
      boolean     result;
      
      switch (type) {
        case Types.BIGINT :
          result = true;
          break;
        case Types.BINARY:
          result = false;
        case Types.BIT:
          result = false;
          break;
        case Types.CHAR:
          result = false;
          break;
        case Types.DATE:
          result = false;
          break;
        case Types.DECIMAL:
          result = true;
          break;
        case Types.DOUBLE:
          result = true;
          break;
        case Types.FLOAT:
          result = true;
          break;
        case Types.INTEGER:
          result = true;
          break;
        case Types.LONGVARBINARY:
          result = false;
          break;
        case Types.LONGVARCHAR:
          result = false;
          break;
        case Types.NULL:
          result = false;
          break;
        case Types.NUMERIC:
          result = true;
          break;
        case Types.OTHER:
          result = false;
          break;
        case Types.REAL:
          result = true;
          break;
        case Types.SMALLINT:
          result = true;
          break;
        case Types.TIME:
          result = false;
          break;
        case Types.TIMESTAMP:
          result = true;
          break;
        case Types.TINYINT:
          result = true;
          break;
        case Types.VARBINARY:
          result = false;
          break;
        case Types.VARCHAR:
          result = false;
          break;
        default:
          result = false;
      }
  
      return result;
    }
  }
  