Friday, August 25, 2006

Custom Cell Renderers and Custom Cell Editors in Swing - Part III

Now lets take a look at how the CustomJTable code looks like. In order to allow external controllers to specify which column of the JTable will render itself as JRadioButtons, we have a method called setRadioGroup.  We also declare the custom Table Model which hides our internal structure and works the Model magic of Swing by extending AbstractTableModel.

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

public class CustomJTable extends JTable {

  /**
   * Give support for Grouping Radio Buttons in
   * different rows of a specific column in the table
   <br>
   * Optionally also allow a CellRendererValidator
   * which can choose which rows in the table will
   * have the radio button visible
   
   @param columnIndex -
   *            The column to which the grouping must
   *            be enabled
   
   @param validator -
   *            An optional CellRendererValidator to
   *            choose which columns have visible
   *            radio buttons. By default all column
   *            will show the radiobutton
   
   @see CellRendererValidator
   */
  public void setRadioGroup(int columnIndex,
      CellRendererValidator validator) {
    getColumnModel().getColumn(columnIndex)
        .setCellEditor(
            new RadioColumnCellEditor());
    getColumnModel().getColumn(columnIndex)
        .setCellRenderer(
            new RadioColumnRenderer(
                validator));
  }

  public boolean isCellEditable(int rowIndex,
      int vColIndex) {
    return true;
  }

  public Class getColumnClass(int column) {
    Object value = getValueAt(0, column);
    if (value != null)
      return value.getClass();
    else
      return super.getColumnClass(column);
  }

  /**
   * CellRendererValidator - This is an interface
   * that will be implemented by any class that
   * wishes <br>
   * to specify whether a CellRenderer is valid for a
   * specific column and row. <br>
   * This is pretty useful when specific rows of a
   * column will <i>NOT </i> the renderer set for the
   * entire column
   
   @author rajeshv
   */
  public static interface CellRendererValidator {
    public boolean isValid(JTable table,
        int rowIndex, int colIndex);
  }

  public static class CustomJTableModel extends
      AbstractTableModel {
    private List dataList = null;

    private String[] properties = null;

    public CustomJTableModel(List list) {
      dataList = list;
      LinkedHashMap map =
          (LinkedHashMapdataList.get(0);
      Set keySet = map.keySet();
      properties = new String[keySet.size()];
      int index = 0;
      for (Iterator iter = keySet.iterator(); iter
          .hasNext();) {
        properties[index++=
            (Stringiter.next();
      }
    }

    public int getColumnCount() {
      LinkedHashMap table =
          (LinkedHashMapdataList.get(0);
      return table.size();
    }

    public Object getValueAt(int rowIndex,
        int columnIndex) {
      LinkedHashMap table =
          (LinkedHashMapdataList
              .get(rowIndex);
      return table.get(properties[columnIndex]);
    }

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

    public String getColumnName(int columnIndex) {
      return properties[columnIndex];
    }

    public Class getColumnClass(int columnIndex) {
      return String.class;
    }

    public void setValueAt(Object aValue,
        int rowIndex, int columnIndex) {
      LinkedHashMap table =
          (LinkedHashMapdataList
              .get(rowIndex);
      table.put(properties[columnIndex], aValue);
      fireTableCellUpdated(rowIndex, columnIndex);
    }

    public boolean isCellEditable(int rowIndex,
        int columnIndex) {
      return false;
    }
  }

  public static void main(String[] args) {
    CustomJTable table = new CustomJTable();
    LinkedHashMap firstRow = new LinkedHashMap();
    firstRow.put("column1""test1");
    firstRow.put("column2", Boolean.TRUE);
    LinkedHashMap secondRow = new LinkedHashMap();
    secondRow.put("column1""test1");
    secondRow.put("column2", Boolean.FALSE);
    LinkedHashMap thirdRow = new LinkedHashMap();
    thirdRow.put("column1""test1");
    thirdRow.put("column2""test");

    List dataList = new ArrayList();
    dataList.add(firstRow);
    dataList.add(secondRow);
    dataList.add(thirdRow);
    table
        .setModel(new CustomJTable.CustomJTableModel(
            dataList));
    table
        .setRadioGroup(
            1,
            new CustomJTable.CellRendererValidator(){
              public boolean isValid(
                  JTable table,
                  int rowIndex,
                  int colIndex) {
                if (rowIndex == 2) {
                  return false;
                }
                return true;
            });
    JFrame frame = new JFrame();
    frame
        .setDefaultCloseOperation(
                        
JFrame.EXIT_ON_CLOSE);
    frame.setSize(600600);
    frame.getContentPane().add(table);
    frame.setVisible(true);
  }
}

Thursday, August 24, 2006

Custom Cell Renderers and Custom Cell Editors in Swing - Part II


RadioColumnEditor
:
 The Editor is pretty much standard and the method to note is getTableCellEditorComponent() which contains the core logic.



import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventObject;

import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableModel;

public class RadioColumnCellEditor implements
    ActionListener, TableCellEditor {
  Color currentColor;

  JRadioButton button;

  boolean state;

  public RadioColumnCellEditor() {
    super();
    button = new JRadioButton();
    button.setOpaque(false);
    button.setBackground(Color.WHITE);
    button.addActionListener(this);
  }

  protected EventListenerList listenerList =
      new EventListenerList();

  protected Object value;

  protected ChangeEvent changeEvent = null;

  protected int clickCountToStart = 1;

  public void setCellEditorValue(Object value) {
    this.value = value;
  }

  /**
   * Add a listener to the list that's notified when
   * the editor starts, stops, or cancels editing.
   */
  public void addCellEditorListener(
      CellEditorListener l) {
    listenerList.add(CellEditorListener.class, l);
  }

  /**
   * Remove a listener from the list that's notified
   */
  public void removeCellEditorListener(
      CellEditorListener l) {
    listenerList.remove(CellEditorListener.class,
        l);
  }

  protected void fireEditingStopped() {
    Object[] listeners =
        listenerList.getListenerList();
    for (int i = listeners.length - 2; i >= 0; i -=
        2) {
      if (listeners[i== CellEditorListener.class) {
        if (changeEvent == null)
          changeEvent =
              new ChangeEvent(this);
        ((CellEditorListenerlisteners[i + 1])
            .editingStopped(changeEvent);
      }
    }
  }

  protected void fireEditingCanceled() {
    Object[] listeners =
        listenerList.getListenerList();
    for (int i = listeners.length - 2; i >= 0; i -=
        2) {
      if (listeners[i== CellEditorListener.class) {
        if (changeEvent == null)
          changeEvent =
              new ChangeEvent(this);
        ((CellEditorListenerlisteners[i + 1])
            .editingCanceled(changeEvent);
      }
    }
  }

  public void actionPerformed(ActionEvent event) {
    fireEditingStopped();
  }

  // Implement the one CellEditor method that
  // AbstractCellEditor doesn't.
  public Object getCellEditorValue() {
    return new Boolean(button.isSelected());
  }

  // Implement the one method defined by
  // TableCellEditor.
  public Component getTableCellEditorComponent(
      JTable table, Object value,
      boolean isSelected, int row, int column) {

    if (!value.getClass().getName().equals(
        Boolean.class.getName())) {
      return null;
    }
    button.setSelected(value != null
        && value.getClass().getName().equals(
            Boolean.class.getName())
        && ((Booleanvalue).booleanValue());
    TableModel model = table.getModel();
    // Make all the radioButtons, except the
    // selected
    // TRUE
    for (int i = 0; i < model.getRowCount(); i++) {
      if (model.getValueAt(i, column!= null
          && model
              .getValueAt(i, column)
              .getClass()
              .getName()
              .equals(
                  Boolean.class
                      .getName())) {
        model.setValueAt(Boolean.FALSE, i,
            column);
      }
    }
    model.setValueAt(Boolean.TRUE, row, column);
    button
        .setHorizontalAlignment(
                              
SwingConstants.CENTER);
    return button;
  }

  public boolean stopCellEditing() {
    fireEditingStopped();
    return true;
  }

  public void cancelCellEditing() {
    fireEditingCanceled();
  }

  public boolean shouldSelectCell(EventObject eo) {
    return true;
  }

  public boolean isCellEditable(EventObject eo) {
    return true;
  }
}

Custom Cell Renderers and Custom Cell Editors in Swing - Part I

JTable provides the ability for individual columns to have different renderers and editors. This is very powerful functionality because you can have a Radio Button in one of the columns or a JButton or JComboBox in some columns.


I went ahead a wrote a small JTable Component that displays a Radio Button in one of its columns. I also customized the component to display the radio button only for specific rows in the column and also added functionality to behave as a radio button group!


The JTable that I wrote has the ability for some external controller to specify
which rows of a particular column must render its value as RadioButtons.
I achieved this by writing an interface with a single method which provided
any renderer with the ability to check if the current row, column can be
rendered by itself.


  /**
   * CellRendererValidator - This is an interface 
   * that will be implemented by any class that 
   wishes <br>to specify whether a CellRenderer 
   * is valid for a specific column and row. <br>
   * This is pretty useful when specific rows of a 
   * column will <i>NOT </i> the renderer set for 
   * the entire column
   
   @author rajeshv
   */
  public static interface CellRendererValidator {
    public boolean isValid(JTable table, 
                          int rowIndex,int colIndex);
  }

I also wrote a TableCellRenderer and TableCellEditor for the RadioButton Column. Here the code for Both.


RadioColumnRenderer:



import java.awt.Color;
import java.awt.Component;

import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.TableCellRenderer;

public class RadioColumnRenderer extends JRadioButton
    implements TableCellRenderer {
  private CustomJTable.CellRendererValidator 
                                    validator = 
null;

  /**
   
   */
  public RadioColumnRenderer(
      CustomJTable.CellRendererValidator validator) {
    this.validator = validator;
  }

  public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected,
      boolean hasFocus, int row, int column) {
    // this is necessary because we may have 
    // values that are not boolean for
    // som of the columns
    this.setSelected(value != null
        && value.getClass().getName().equals(
            Boolean.class.getName())
        && ((Booleanvalue).booleanValue());

    this.setHorizontalAlignment(
                         
SwingConstants.CENTER);
    this.setOpaque(false);
    this.setBackground(Color.WHITE);
    // request the validator to concur that this 
    // column can be rendered as a JRadioButton
    if (validator != null) {
      if (validator.isValid(table, row, column)) {
        return this;
      }
      return null;
    }
    return this;
  }
}

Wednesday, August 09, 2006

Properties extends from HashTable - Sun's mess up

The basic idea of inheritance in the Object Oriented Paradigm is based on "is-a" kind of relationship. Any violation of this violates OO.

Its very interesting to note that, in Sun's java SDK, we have java.util.Properties extending from java.util.Hashtable. That actually sent me guessing.

If Properties was a class that will handle only string key-value pairs, what was the necessity to extend from Hashtable which supports key - Object pairs? I believe the ugly inheritance-composition confusion had forced one of Sun's engineers to make such a choice.

The store method of Properties tries to cast all values to String :P!

Properties doesnt even stop you from using Hashtables methods of put and get!

Well the actual way to have implemented properties must have been to use a Hashtable inside properties (Composition) and then expose whatever methods that you want to.

I am searching for other examples within the SDK where such a blunder still exists :)

Exception Handling in Java - Inheritance

The other day somebody asked a question about exception handling in java. The question was this:
When we override a method in java which throws an exception, why is that we cannot change the exception that the parent class throws?

Well, I set to find out what are the possible scenarios that can occur when we throw different exceptions when overriding methods.

The first case I encounted was when I had a class BaseClass which had a method called testException() throws IOException. When I override this method from a class InheritedClass with a declaration like testException() throws ArrayIndexOutOfBoundsException, the compiler did not complain. I later realised that AIOOBE is a runtime exception so behavior was right.

The second case was when I declared the overriding method to throw an exception of type which happened to be a subclass of the parent exception class. So if I declare testException() throws FileNotFoundException, with the parent class method throwing IOException the compiler behavior was as I expected. Because the subclass can always be auto casted, it did not complain.

The third case was if we try the opposite of the second case. i.r BaseClass - testException() throws FileNotFoundException and the subclass InheritedClass declares, testException() throws IOException. As expected the compiler immedtiately complained of incompatibility between the declarations from the base class to the inherited class.

The fourth case was if I try and throw a totally unrelated exception in the overriding methods throws clause, like BaseClass testException() throws IOException and InheritedClass declares testException() throws SQLException, the compiler again complained of incompatibility.

Summing up:
Case 1: Overriding method throws runtime exception - Allowed
Case 2:Overriding method throws subclass of the exception type declared by the parent method - Allowed
Case 3: Overriding method throws base class of exception type declared by the parent method - NOT Allowed
Case 4: Overriding method throws totally unrelated compile time exception replacing parent method throws clause - NOT Allowed

Basically I realised that it really did not make sense for an overriding method to throw any other class of exception than what the method in the parent class threw. Java ofcourse allows you the possiblity to add exceptions in overriding methods.

Thursday, August 03, 2006

Accessing MSSQL Server Database Transaction Log via JDBC

Wanting to access the transaction log, I found that dbcc is a god way to do it via the SQL Query Analyzer. However, programatically via jdbc, I was curious whether the same command will work returning a result set. It turned out to be nothing to access the transaction log using dbcc and the MSSQLServer's jdbc driver. I added three jar files in the classpath: msbase.jar, mssqlserver.jar, msutil.jar and started writing the program :

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;

public class TestMSQLServer {

  public static void testDBCC() {
    String DRIVER =
        "com.microsoft.jdbc."
            "sqlserver.SQLServerDriver";
    String DBURL =
        "jdbc:microsoft:sqlserver: "
            "//localhost:1433;";

    Connection connection = null;
    Statement stmt = null;
    ResultSet rs = null;
    ResultSetMetaData metadata = null;
    try {
      Class.forName(DRIVER);
      connection =
          DriverManager.getConnection(DBURL,
              "sa""sa");
      stmt = connection.createStatement();
      rs =
          stmt
              .executeQuery("dbcc log (master,2)");
      metadata = rs.getMetaData();
      int noOfColumns =
          metadata.getColumnCount();
      int noOfDashes = 0;
      for (int i = 1; i <= noOfColumns; i++) {
        String columnName =
            metadata.getColumnName(i);
        System.out.print(columnName + "\t");
        noOfDashes +=
            (columnName.length() 4);
      }
      System.out.println();
      for (int i = 0; i < noOfDashes; i++) {
        System.out.print("-");
      }
      System.out.println();
      while (rs.next()) {
        for (int i = 1; i <= noOfColumns; i++) {
          System.out.print(rs
              .getObject(metadata
                  .getColumnName(i))
              "\t");
        }

        System.out.println();

      }
    catch (Exception e) {
      e.printStackTrace();
    finally {
      try {
        if (rs != null) {
          rs.close();
        }
        if (stmt != null) {
          stmt.close();
        }
        if (connection != null) {
          connection.close();
        }
      catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) {
    testDBCC();
  }

}