Wednesday, July 15, 2009

Implementing Jtable Sorting + Currency Rounding + Comma Separation

Illustrating one of the test cases where you have to sort the currency values in the JTable without losing precision.

Some facts:
1. Double values rounds off decimal values leading to loss of precision in Currency values
2. Only Double can show numerics (having decimal points) in Currency format (Comma separated values)
3. JTable when given String representation of Double values cannot recognize it as Double by itself.
4. There are no direct apis in Double to control decimal limits and round off

Due to above list of limitations, it is bit complex to implement all the three functionalities together. But there is a workaround/solution for this. See the code below:


package testing;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Comparator;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

/**
*
* @author vijay
*/
public class JTableSortDemo {

public static void main(String[] args) throws Exception {
// Decimal format to define the required format with #Fraction digits
DecimalFormat df = (DecimalFormat) NumberFormat.getInstance();
df.setMinimumFractionDigits(6);
df.setMaximumFractionDigits(10);

// Heeaders
String columnNames[] = {"Expected (String value)", "Double (Formatted Numeric) Value", "Double without formatting(has roundoff)"};

// Input values
String[] strKeys = {"6304482644123.000003", "6304482644.4012", "63044.00", "1235482644.95", "1235482644.10", "1235482644.00"};
double[] dDatas = {6304482644123.000003, 6304482644.4012, 63044.00, 1235482644.95, 1235482644.10, 1235482644.00};

Object[][] data = new Object[dDatas.length][columnNames.length];
String strDF = null;
for (int i = 0; i < dDatas.length; i++) {
strDF = df.format(dDatas[i]);
data[i] = new Object[]{strKeys[i], df.format(dDatas[i]), dDatas[i]};
}

// Creating tablemodel
TableModel model = new DefaultTableModel(data, columnNames) {

public Class getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};

Comparator comparator = new Comparator() {

public int compare(String s1, String s2) {
Double dbl1 = Double.valueOf(s1.replaceAll(",", ""));
Double dbl2 = Double.valueOf(s2.replaceAll(",", ""));
return dbl1.compareTo(dbl2);
}
};


// Create table and associate sorter
JTable table = new JTable(model);
TableRowSorter sorter = new TableRowSorter(model);

// 1 is the column where the fraction values are not lost
sorter.setComparator(1, comparator);
table.setRowSorter(sorter);

JScrollPane scrollPane = new JScrollPane(table);

// Add table to container
JFrame frame = new JFrame("Sorting Table");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane);
frame.setSize(300, 200);
frame.setVisible(true);
frame.pack();
}
}


The solution incorporated in above code is to write user defined comparator which converts the String values to double (only during comparison).

In detail:

i) Use the DecimalFormat to retain the decimal values to 'n' number of digits using
setMinimumFractionDigits();
setMaximumFractionDigits();


ii) After applying the decimal format, we will have a string shown in the JTable (with comma).


iii) Now while sorting the particular column(2nd column in above sample), we can override the the default functionality of comparator of TableRowSorter class. Default behaviour is to sort it as String. The solution is to write a comparator like below and set it to the TableRowSorter.


Comparator comparator = new Comparator() {
public int compare(String s1, String s2) {
Double dbl1 = Double.valueOf(s1.replaceAll(",", ""));
Double dbl2 = Double.valueOf(s2.replaceAll(",", ""));
return dbl1.compareTo(dbl2);
}
};


Above code chunk, removes comma and converts it to double only for comparison and then returns the result.