5

Update

Confirmed as a bug on JTable can't format given Object as Number when columnClass is Double (bug ID: 7051636). Feel free to vote for it, or if if you have an alternate (better) work-around, post it as a comment to the report.


I'm building a JTable with a custom table model built extending AbstractTableModel. My model need to support empty row to be displayed and sorted. So I follwed this post to implement that, and now works pretty fine.

I still have a problem whith formatted field in a JTable. Suppose I have the following model:

public class MyModel extends AbstractTableModel{

    public Object[] types= {new Integer(0), ""};
    public static final Object EMPTY_ROW = "";

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
         return this.EMPTY_ROW;
    }
    public Class<? extends Object> getColumnClass(int c) {
      if (c > this.types.length - 1)
        return null;
      else
        return this.types[c].getClass();

    }
}

Everything works fine. But if I have a Double instead of an Integer:

public class MyModel extends AbstractTableModel{

        public Object[] types= {new Double(0.0), ""};
  .......

I'll get an Illegal Argument exception:

EDIT: new stack trace output after @Aaron Digulla suggestion

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Cannot format given Object as a Number at java.text.DecimalFormat.format(DecimalFormat.java:487) at java.text.Format.format(Format.java:140) at javax.swing.JTable$DoubleRenderer.setValue(JTable.java:5352) at javax.swing.table.DefaultTableCellRenderer.getTableCellRendererComponent(DefaultTableCellRenderer.java:237) at javax.swing.JTable.prepareRenderer(JTable.java:5720) at javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2072) at javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:1974) at javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1770) at javax.swing.plaf.ComponentUI.update(ComponentUI.java:143) at javax.swing.JComponent.paintComponent(JComponent.java:752) at javax.swing.JComponent.paint(JComponent.java:1029) at javax.swing.JComponent.paintChildren(JComponent.java:862) at javax.swing.JComponent.paint(JComponent.java:1038) at javax.swing.JViewport.paint(JViewport.java:747) at javax.swing.JComponent.paintChildren(JComponent.java:862) at javax.swing.JComponent.paint(JComponent.java:1038) at javax.swing.JComponent.paintChildren(JComponent.java:862) at javax.swing.JComponent.paint(JComponent.java:1038) at javax.swing.JComponent.paintChildren(JComponent.java:862) at javax.swing.JComponent.paint(JComponent.java:1038) at javax.swing.JLayeredPane.paint(JLayeredPane.java:567) at javax.swing.JComponent.paintChildren(JComponent.java:862) at javax.swing.JComponent.paintToOffscreen(JComponent.java:5131) at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:278) at javax.swing.RepaintManager.paint(RepaintManager.java:1224) at javax.swing.JComponent.paint(JComponent.java:1015) at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21) at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60) at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97) at java.awt.Container.paint(Container.java:1780) at java.awt.Window.paint(Window.java:3375) at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:796) at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:713) at javax.swing.RepaintManager.seqPaintDirtyRegions(RepaintManager.java:693) at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(SystemEventQueueUtilities.java:125) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209) at java.awt.EventQueue.dispatchEvent(EventQueue.java:597) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161) at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

Why this?

getValueAt returns always the same value to fill all tables entries with it. This is only for debug :

@Override
    public Object getValueAt(int rowIndex, int columnIndex) {
         return this.EMPTY_ROW;
    }

For example if i change to :

 @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
         return new Integer(3);
         //or return new Double(3.3);
         //return new String("foobar"); doesn't work
    }

all works fine even if some field of the table are String. It suggest to me that because an Integer and a Double can be transformed into String, this won't cause problem. Anyway I would like to understand why a generic Object like my EMPTY_ROW can be accepted as value of a declared Integer field while this don't work with Double fields.

EDIT2:

If I remove getClass method in my table model. It works. Anyway I would like to solve this without having to remove that method, even if this will force me to implement some custom render methods.

EDIT3:

here's an SSCCE. There is some errors while adding new values to the table, but hasn't nothing to do with rendering problems.

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Comparator;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SortOrder;
import javax.swing.RowSorter.SortKey;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;


public class TableExample extends JFrame{
    public static final  Object EMPTY_ROW = "";
    public class EmptyRowComparator<COLUMN_TYPE extends Comparable<COLUMN_TYPE>> implements Comparator<Object>{//extends RuleBasedCollator{

        private TableRowSorter<? extends AbstractTableMod> sorter;
    private int column;

        public EmptyRowComparator(TableRowSorter<? extends AbstractTableMod> sorter, int col) throws ParseException {
        //  super(arg0);
            this.sorter = sorter;
            this.column = col;
            // TODO Auto-generated constructor stub
        }



         private int getSortOrder() {
             SortOrder order = SortOrder.ASCENDING;
//           List<? extends SortKey> keys = sorter.getSortKeys();
//           sorter.getSortKeys();
//       

             for (SortKey sortKey : sorter.getSortKeys()) {
                 if (sortKey.getColumn() == this.column) {
                     order = sortKey.getSortOrder();
                     break;
                 }
             }
             return order == SortOrder.ASCENDING ? 1 : -1;
         }



        @Override
        public int hashCode() {
            // TODO Auto-generated method stub
            return 0;
        }

        @Override
        public int compare(Object arg0, Object arg1) {
            // TODO Auto-generated method stub
            //System.out.println("Comparing Integer arg0 " + arg0 + " arg1 " + arg1);
            boolean empty1 = arg0 == EMPTY_ROW;
            boolean empty2 = arg1 == EMPTY_ROW;
            if (empty1 && empty2) {
                return 0;
            }
            else if (empty1) {
                return 1 * getSortOrder();
            }
            else if (empty2) {
                return -1 * getSortOrder();
            }
            return ((Comparable<COLUMN_TYPE>) (COLUMN_TYPE)arg0).compareTo((COLUMN_TYPE)arg1);
        //  return 0;
        }

    }

    public class ConcreteTable extends AbstractTableMod{

        //
        private static final long serialVersionUID = 4672561280810649603L;
        private String[] columnNames = {"ID",
                                        "description"};


        Class[] types = {Integer.class, String.class};
        //Object[] types = {Double.class, String.class};
        private int minimumDisplayedRow;


        public ConcreteTable(){
            //System.out.println("DEBUG ARRAY length " + data.length);
            this.minimumDisplayedRow = 10;
            this.datas = new ArrayList<ArrayList<Object>>();
            for (int i = 0 ; i < this.minimumDisplayedRow  ; i++){
                this.addEmptyRow();
            }
            for (int i = 0 ; i < 5 ; i++){
                ArrayList<Object> row = new ArrayList<Object>();
                row.add(new Integer(i));
                row.add(new String("prova " + i));
                this.addRow(row);
            }

        }


        public String getColumnName(int col) {
            System.out.println("getColumnName " + col + " = " + columnNames[col]);
            return columnNames[col];
        }

        @Override
        protected Class[] getTypeArray() {
            // TODO Auto-generated method stub
            return this.types;
        }

        @Override
        protected ArrayList<Integer> getKeysColumnIndex() {
            // TODO Auto-generated method stub
            ArrayList<Integer> keys = new ArrayList<Integer>();
            keys.add(0);
            return keys;
        }
        public boolean isCellEditable(int row, int col) {
            System.out.println("isCellEditable row " + row + " col " + col);
            if (col == 1){
                System.out.println("TRUE");
                return true;
            }

            return false;
        }
        /*note: generated keys must be in the same order they appear in the table*/
        @Override
        protected Object getGeneratedKeys(int col) {
            // TODO Auto-generated method stub
            if (col != 0 )
                return null;
            return new Integer(this.rowNumber);
        }
        @Override
        protected int getMinimumDisplayedRow() {
            // TODO Auto-generated method stub
            return this.minimumDisplayedRow;
        }


    }

    public abstract class AbstractTableMod extends AbstractTableModel {


        ArrayList<ArrayList<Object>> datas ;
        protected int rowNumber = 0;
        protected abstract Class[] getTypeArray();
        protected abstract ArrayList<Integer> getKeysColumnIndex();
        protected abstract Object getGeneratedKeys(int col);
        protected abstract int getMinimumDisplayedRow();

        public int getRowCount(){
            return this.datas.size() ;
        }
        @Override
        public int getColumnCount() {
            return this.getTypeArray().length;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {

            if (rowIndex >= this.rowNumber ){
                return EMPTY_ROW;
            }

            try{

                ArrayList<Object> row = this.datas.get(rowIndex);
                if (row == null)
                    return null;
                Object obj = row.get(columnIndex);
                return obj;
            }catch(IndexOutOfBoundsException e){
                return null;
            }


        }
        public void setValueAt(Object value, int row, int col) {

            //System.out.println("setValueAt object : " + value.getClass().getName());
            Class<? extends Object> targetColClass = this.getColumnClass(col);
            if (!targetColClass.isInstance(value))
                return;
            if (value instanceof String){
                String stringVal = (String)value;
                if (stringVal.compareTo("") == 0)
                    return;
            }
            if (row >= this.rowNumber){
                ArrayList<Object> newRow = new ArrayList<Object>();
                ArrayList<Integer> keysIndexList = this.getKeysColumnIndex();

                for (int i = 0 ; i < this.getColumnCount(); i++){
                    if (i == col){
                        newRow.add(value);
                    }else if (keysIndexList.contains(i)){
                        newRow.add(this.getGeneratedKeys(i));
                    }else{
                        newRow.add(EMPTY_ROW);
                    }
                }
                this.addRow(newRow);
            }else{
                this.datas.get(row).set(col, value);
            }
            this.fireTableCellUpdated(row, col);

        }
        public Class<? extends Object> getColumnClass(int c) {
            System.out.println("AbstractTable: getColumnClass");
            if (c > this.getTypeArray().length - 1)
                return null;
            else
                return this.getTypeArray()[c];
        }

        public void addEmptyRow(){
            ArrayList<Object> emptyRow = new ArrayList<Object>();
            for (int i = 0 ; i < this.getTypeArray().length; i++){
                emptyRow.add(EMPTY_ROW);
            }
            this.datas.add(emptyRow);
        }
        public void addRow(ArrayList<Object> row){
            Object[] types = this.getTypeArray();
            if (types.length != row.size())
                return;
            for (int i = 0 ; i < row.size() ; i++){
                Class<? extends Object> targetColClass = this.getColumnClass(i);
                Object rowItem = row.get(i);
            }
            this.datas.add(this.rowNumber, row);
            this.rowNumber++;
            if (this.rowNumber < this.getMinimumDisplayedRow())
                this.datas.remove(this.datas.size() -1 );
            this.fireTableRowsInserted(this.rowNumber , this.rowNumber  );

        }
    }
    public TableExample(){
        super("JTable example");
        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));



        ConcreteTable model = new ConcreteTable();
        JTable tab = new JTable(model);
        TableRowSorter<ConcreteTable> sorter = new TableRowSorter<ConcreteTable>(model);



        try {

            sorter.setComparator(0, new EmptyRowComparator<Integer>(sorter,0));
            sorter.setComparator(1, new EmptyRowComparator<String>(sorter,1));

        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        tab.setRowSorter(sorter);
        JScrollPane table = new JScrollPane(tab);

        this.getContentPane().add(table);
        this.setSize(600, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
            new TableExample();
    }

}

If you try to change

Class[] types = {Integer.class, String.class}; 

with :

Class[] types = {Double.class, String.class};

you can see the problem.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Heisenbug
  • 38,762
  • 28
  • 132
  • 190
  • @0verbose initial Double value isn't 0.0 – mKorbel May 31 '11 at 13:14
  • @mKorbel: was a transcription error. Neither with 0.0 works – Heisenbug May 31 '11 at 13:22
  • 1
    For better help sooner, post an [SSCCE](http://pscode.org/sscce.html). – Andrew Thompson Jun 04 '11 at 16:59
  • @Andrew Thompson: yes, you are right. I'll do it in a few minutes. – Heisenbug Jun 04 '11 at 17:12
  • @Andrew Thompson: I've edited my answer. You will find an SSCCE showing the problem. thanks for your attention. Maybe you will found some errors there, but I had to remove a lot of lines from my orignial code to make it short. Any help will be much appreciated. – Heisenbug Jun 04 '11 at 17:50
  • @0verbose: (scratches head) An SSCCE is supposed to be a single source file, for the convenience of the helper. You could add the classes with no `main()` into the source with `main()` if you reduce the class visibility to 'default'. And just a slight point. Although I'd be interested to have a look at the SSCCE, I make no *guarantee* that I *personally* can progress the problem. ;) – Andrew Thompson Jun 04 '11 at 18:30
  • @Andrew Thompson: sorry. I'm using eclipse and I just copyed the three classes I needed. I'll fix it right now. No problem if you have no guarantees :), give a try if you have some free time. You already have help me a lot with other questions.. – Heisenbug Jun 04 '11 at 18:42
  • @Andrew Thompson: done. Now you should be able to compile and execute. – Heisenbug Jun 04 '11 at 18:52
  • I've been having a look over it but have few ideas. Noticed that if I shove `Doubles` into the first column but claim they are `Integers` in the `getColumnClass()` the data loads and sorts as expected. It seems like a complete hack, but is there any special reason you have to declare them as `Double` rather than `Integer`? Another thought is that this does seem like a JRE bug. Have you raised a report? If not, please do so. – Andrew Thompson Jun 04 '11 at 19:29
  • @Andrew Thompson. I wanted to declare them double because they are double. Looking at the error generated declaring it like double, I felt a bit scared, because I thought was my fault and could affect other part of my implementation.Also I would like to implement a clean solution in this part of my project because these tables will be used in a lot of other parts of my application. Anyway, I'll declare it like integer until I'll get some answer. Honestly I've never raised a bug report (never found a bug in a library since now). I'll see which are the procedures and then I'll do it. thanks – Heisenbug Jun 04 '11 at 19:55
  • @0verbose are you want to see any data Class and works correctly??? with your Example ..... – mKorbel Jun 04 '11 at 20:25
  • @mKorbel: not any..I just want to understand why if I supply to a JTable a object like EMPTY_ROW it will be formatted correctly is the field is of type Integer, instead don't work if the field is typed Double. The additional code you see is due for providing an SSCCE from a more complicated solution. A lot of part of code are omissed. – Heisenbug Jun 04 '11 at 20:36
  • @0verbose ok agreed, your code doesn't me any sence, cos works in form you posted, hint what returs Object from JTable, thanks for idea for me solved – mKorbel Jun 04 '11 at 20:48
  • @Andrew Thompson: finally I reported the bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7051636 – Heisenbug Jun 07 '11 at 10:23
  • @0verbose: Thanks for reporting that. I edited the question to add that info. as an update. – Andrew Thompson Jun 07 '11 at 10:31
  • @Andrew Thompson: thanks for the edit. Does the fact that the bug has been published confirm that it is effectivly a bug? – Heisenbug Jun 07 '11 at 10:41
  • @Andrew Thompson hmmm, are you tried my example, I leaved comment about that by 0verbose decision, post here only runnable code for futures readers, this JTable-TableRowSorter-RowFilter-NestedComparator-AccesibleContext accepted only Integer and String, AFAIK there are (lots) numbers of Object classes that's refused by TableRowSorter&Comparator as only Integer, I can't to wrote by English correctly, but pretty sure accepted (by Nested Methods declared for TableRowSorter&Comparator) String Class as default Class returned by Object from JTable too... :-) – mKorbel Jun 07 '11 at 10:45
  • @0verbose: "Does the fact that the bug has been published confirm that it is effectively a bug?" I think so. I just had to look at the part that reads `State 1-Dispatched, bug`. – Andrew Thompson Jun 07 '11 at 11:09
  • @mKorbel: "are you tried my example(?)" No (wait) ..yes. It seems to sort the double values just fine. Perhaps it should be entered as a 'work-around' to that bug ( after the typo. is fixed ;)? Cannot say for sure, as I have not yet done more than compile & run the code & sort some `Double` columns. – Andrew Thompson Jun 07 '11 at 11:13

4 Answers4

5

how did Walter Laan says in his thread

Never give up! Never surrender!

EDIT: I can't resist, but due to my poor English I dare not to commenting why, where and how is that possible, nor works correctly, for confirmations I added Rob's two (little bit) modified class for TableColumnRendering ...,

import java.awt.EventQueue;
import java.math.RoundingMode;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.RowSorter.SortKey;
import javax.swing.SwingConstants;
import javax.swing.table.*;

public class TableExample extends JFrame {

    public static final Object EMPTY_ROW = "";
    private static final long serialVersionUID = 1L;
    private JTable tab;
    private Calendar cal;
    private Date dateWithOutTime = new java.util.Date();
    private SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");//standard continental EU date format

    public class EmptyRowComparator<COLUMN_TYPE extends Comparable<COLUMN_TYPE>> implements Comparator<Object> {//extends RuleBasedCollator{

        private TableRowSorter<? extends AbstractTableMod> sorter;
        private int column;

        public EmptyRowComparator(TableRowSorter<? extends AbstractTableMod> sorter, int col) throws ParseException {
            this.sorter = sorter;
            this.column = col;
        }

        private int getSortOrder() {
            SortOrder order = SortOrder.ASCENDING;
            for (SortKey sortKey : sorter.getSortKeys()) {
                if (sortKey.getColumn() == this.column) {
                    order = sortKey.getSortOrder();
                    break;
                }
            }
            return order == SortOrder.ASCENDING ? 1 : -1;
        }

        @Override
        public int hashCode() {
            return 0;
        }

        @Override
        @SuppressWarnings("unchecked")
        public int compare(Object arg0, Object arg1) {
            boolean empty1 = arg0 == EMPTY_ROW;
            boolean empty2 = arg1 == EMPTY_ROW;
            if (empty1 && empty2) {
                return 0;
            } else if (empty1) {
                return 1 * getSortOrder();
            } else if (empty2) {
                return -1 * getSortOrder();
            }
            return ((Comparable<COLUMN_TYPE>) (COLUMN_TYPE) arg0).compareTo((COLUMN_TYPE) arg1);
        }
    }

    public class ConcreteTable extends AbstractTableMod {

        private static final long serialVersionUID = 4672561280810649603L;
        private String[] columnNames = {"Integer", "String", "Integer", "Double", "Boolean", "Double", "String", "Boolean", "Date"};
        private Class<?>[] types = {Integer.class, String.class, String.class, String.class, String.class,
            String.class, String.class, String.class, String.class};
        private int minimumDisplayedRow;

        public ConcreteTable() {
            this.minimumDisplayedRow = 10;
            this.datas = new ArrayList<ArrayList<Object>>();
            for (int i = 0; i < this.minimumDisplayedRow; i++) {
                this.addEmptyRow();
            }
            Random rnd = new Random();
            for (int i = 0; i < 7; i++) {
                ArrayList<Object> row = new ArrayList<Object>();
                row.add(i);
                row.add(((rnd.nextInt(25)) + "prova"));
                row.add(rnd.nextInt(25));
                row.add(rnd.nextInt(25) + 3.14);
                row.add((i % 2 == 0) ? true : false);
                row.add(rnd.nextInt(25) + 3.14);
                row.add(((rnd.nextInt(25)) + "prova"));
                row.add((i % 2 == 0) ? false : true);
                cal = Calendar.getInstance();
                cal.add(Calendar.DATE, -rnd.nextInt(25));
                dateWithOutTime = cal.getTime();
                String nullTimeForDateString = sdf.format(dateWithOutTime);
                try {
                    dateWithOutTime = sdf.parse(nullTimeForDateString);
                } catch (ParseException ex) {
                }
                row.add(dateWithOutTime);
                this.addRow(row);

            }
        }

        @Override
        public String getColumnName(int col) {
            System.out.println("getColumnName " + col + " = " + columnNames[col]);
            return columnNames[col];
        }

        @Override
        protected Class<?>[] getTypeArray() {
            return this.types;
        }

        @Override
        protected ArrayList<Integer> getKeysColumnIndex() {
            ArrayList<Integer> keys = new ArrayList<Integer>();
            keys.add(0);
            return keys;
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            System.out.println("isCellEditable row " + row + " col " + col);
            if (col == 1) {
                System.out.println("TRUE");
                return true;
            }
            return false;
        }

        @Override
        protected Object getGeneratedKeys(int col) {
            if (col != 0) {
                return null;
            }
            return new Integer(this.rowNumber);
        }

        @Override
        protected int getMinimumDisplayedRow() {
            return this.minimumDisplayedRow;
        }
    }

    public abstract class AbstractTableMod extends AbstractTableModel {

        private static final long serialVersionUID = 1L;
        protected ArrayList<ArrayList<Object>> datas;
        protected int rowNumber = 0;

        protected abstract Class<?>[] getTypeArray();

        protected abstract ArrayList<Integer> getKeysColumnIndex();

        protected abstract Object getGeneratedKeys(int col);

        protected abstract int getMinimumDisplayedRow();

        public int getRowCount() {
            return this.datas.size();
        }

        @Override
        public int getColumnCount() {
            return this.getTypeArray().length;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            if (rowIndex >= this.rowNumber) {
                return EMPTY_ROW;
            }
            try {
                ArrayList<Object> row = this.datas.get(rowIndex);
                if (row == null) {
                    return null;
                }
                Object obj = row.get(columnIndex);
                return obj;
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        }

        @Override
        public void setValueAt(Object value, int row, int col) {
            Class<? extends Object> targetColClass = this.getColumnClass(col);
            if (!targetColClass.isInstance(value)) {
                return;
            }
            if (value instanceof String) {
                String stringVal = (String) value;
                if (stringVal.compareTo("") == 0) {
                    return;
                }
            }
            if (row >= this.rowNumber) {
                ArrayList<Object> newRow = new ArrayList<Object>();
                ArrayList<Integer> keysIndexList = this.getKeysColumnIndex();
                for (int i = 0; i < this.getColumnCount(); i++) {
                    if (i == col) {
                        newRow.add(value);
                    } else if (keysIndexList.contains(i)) {
                        newRow.add(this.getGeneratedKeys(i));
                    } else {
                        newRow.add(EMPTY_ROW);
                    }
                }
                this.addRow(newRow);
            } else {
                this.datas.get(row).set(col, value);
            }
            this.fireTableCellUpdated(row, col);
        }

        @Override
        @SuppressWarnings("unchecked")
        public Class<? extends Object> getColumnClass(int c) {
            if (c > this.getTypeArray().length - 1) {
                return null;
            } else {
                return this.getTypeArray()[c];
            }
        }

        public void addEmptyRow() {
            ArrayList<Object> emptyRow = new ArrayList<Object>();
            for (int i = 0; i < this.getTypeArray().length; i++) {
                emptyRow.add(EMPTY_ROW);
            }
            this.datas.add(emptyRow);
        }

        public void addRow(ArrayList<Object> row) {
            Object[] types = this.getTypeArray();
            if (types.length != row.size()) {
                return;
            }
            for (int i = 0; i < row.size(); i++) {
                Class<? extends Object> targetColClass = this.getColumnClass(i);
                Object rowItem = row.get(i);
            }
            this.datas.add(this.rowNumber, row);
            this.rowNumber++;
            if (this.rowNumber < this.getMinimumDisplayedRow()) {
                this.datas.remove(this.datas.size() - 1);
            }
            this.fireTableRowsInserted(this.rowNumber, this.rowNumber);
        }
    }

    public TableExample() {
        super("JTable example");
        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
        ConcreteTable model = new ConcreteTable();
        tab = new JTable(model);
        TableRowSorter<ConcreteTable> sorter = new TableRowSorter<ConcreteTable>(model);
        try {
            sorter.setComparator(0, new EmptyRowComparator<Integer>(sorter, 0));
            sorter.setComparator(1, new EmptyRowComparator<String>(sorter, 1));
            sorter.setComparator(2, new EmptyRowComparator<String>(sorter, 2));
            sorter.setComparator(3, new EmptyRowComparator<String>(sorter, 3));
            sorter.setComparator(4, new EmptyRowComparator<String>(sorter, 4));
            sorter.setComparator(5, new EmptyRowComparator<String>(sorter, 5));
            sorter.setComparator(6, new EmptyRowComparator<String>(sorter, 6));
            sorter.setComparator(7, new EmptyRowComparator<String>(sorter, 7));
            sorter.setComparator(8, new EmptyRowComparator<String>(sorter, 8));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        tab.setRowSorter(sorter);
        JScrollPane table = new JScrollPane(tab);
        this.getContentPane().add(table);
        this.setSize(800, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setRenderers();
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                setVisible(true);
            }
        });
        //TableExample tableExample = new TableExample();
    }

    public void setRenderers() {
        TableColumnModel m = tab.getColumnModel();
        //"Integer", "String", "Interger", "Double", "Boolean", "Double", "String", "Boolean", "Date"
        m.getColumn(0).setCellRenderer(NumberRenderer.getIntegerRenderer());
        m.getColumn(2).setCellRenderer(NumberRenderer.getIntegerRenderer());
        m.getColumn(3).setCellRenderer(NumberRenderer.getDoubleRenderer5());
        m.getColumn(5).setCellRenderer(NumberRenderer.getDoubleRenderer3());
        m.getColumn(8).setCellRenderer(FormatRenderer.getDateRenderer());
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                TableExample tableExample = new TableExample();
            }
        });
        TableExample tableExample = new TableExample();
    }
}

class FormatRenderer extends DefaultTableCellRenderer {

    private static final long serialVersionUID = 1L;
    private Format formatter;
    private static DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy");//standard continental EU date format

    FormatRenderer(Format formatter) {
        this.formatter = formatter;
    }

    @Override
    public void setValue(Object value) {
        try {
            if ((value != null)) {
                if ((value instanceof Number) || (value instanceof Date)) {
                    setHorizontalAlignment(SwingConstants.RIGHT);
                    value = formatter.format(value);
                }
            }
        } catch (IllegalArgumentException e) {
        }
        super.setValue(value);
    }

    public static FormatRenderer getDateRenderer() {
        return new FormatRenderer(dateFormat);
    }
}

class NumberRenderer extends FormatRenderer {

    private static final long serialVersionUID = 1L;
    private static Number numberValue;
    private static NumberFormat nf;

    NumberRenderer(NumberFormat formatter) {
        super(formatter);
        setHorizontalAlignment(SwingConstants.RIGHT);
    }

    public static NumberRenderer getIntegerRenderer() {
        return new NumberRenderer(NumberFormat.getIntegerInstance());
    }

    public static NumberRenderer getDoubleRenderer3() {
        nf = NumberFormat.getNumberInstance();
        nf.setMinimumFractionDigits(3);
        nf.setMaximumFractionDigits(3);
        nf.setRoundingMode(RoundingMode.HALF_UP);
        return new NumberRenderer(nf);
    }

    public static NumberRenderer getDoubleRenderer5() {
        nf = NumberFormat.getNumberInstance();
        nf.setMinimumFractionDigits(5);
        nf.setMaximumFractionDigits(5);
        nf.setRoundingMode(RoundingMode.HALF_UP);
        return new NumberRenderer(nf);
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • first of all thanks for your answer. I'll have a look right now. I'm consedering also your suggestion on using 2 JTable workaround. But I would still like to try solving this problem this way: Never Give Up Never Surrender! Thanks for the time spent writing all that code! :) – Heisenbug May 31 '11 at 18:20
  • @0verbose yes maybe is possible to write, maybe somebody knows about that, really sorry for that :-( – mKorbel May 31 '11 at 19:00
  • I read your code. Seems to work, right? You have empty lines in your table, assigned EMPTY_ROW to column with type Double. Honestly I can't figure out what I am doing wrong. – Heisenbug May 31 '11 at 19:37
  • @0verbose not this code works but RowFilter is chained with http://download.oracle.com/javase/6/docs/api/javax/swing/DefaultRowSorter.ModelWrapper.html thar's allows only Integer, there maybe start and ends your idea, as I wrote I really tried all possible hack to OverRode Models and implemeted methods that accepted only Integer value (null), author of this code is Java Generic Guru ..., I'll edit this code and send you answer to your question, really without deepest knowledge abour chained Methods..., really no idea here my knowledge ends, maybe once day kleopatra, with lots of -1 for us – mKorbel May 31 '11 at 19:58
  • @0verbose maybe is time to go to the authors Forum and there ask your question, nobody knows – mKorbel May 31 '11 at 20:17
  • @mKorbel: wait a moment. Maybe I'm a bit confused after all these hours being coding. Anyway, forget for a moment the fact that I want also sorter the rows. Error with double value occur before sorting anything. It occurs simply when the library is trying to render an EMPTY_ROW in a cell of type Double. But this seems not to happen in the code you post. Am I right? – Heisenbug May 31 '11 at 20:46
3

reall problem is noting to do with previous debate, there is plain vanilla (compare my missing English skills), are you think that that's really Bug, or if I showed ViceVersaView, maybe then somone can be able to comment that chains for JTable + TableModel + Comparator (TableRowSorter for JTable API) in English language, eeeeeerghhh really my bad in this case

import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.table.*;

public class ViceVersaBugFromTableModelAndComparator {

    private String[] columnNames = {"String", "Integer", "Boolean", "Double", "Date"};
    private Object[][] data = {
        {"aaa", 12, true, .15, new Date()},
        {"bbb", 5, false, 100.01, new Date()},
        {"CCC", 92, true, 15.2, new Date()},
        {"DDD", 0, false, 10.80, new Date()},
        {"abc", 5, true, 4.11, new Date()},
        {"bae", 31, false, 100.01, new Date()},
        {"CAX", 27, true, 2.3, new Date()},
        {"AXD", 4, false, 50.00, new Date()},
        {"abc", 3, true, 1.5, new Date()},
        {"bae", 5, false, 1000, new Date()}, //java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
        //{"bae", 5, false, 1000.0, new Date()}, //un-comment for correctness
        {"CAX", 2, true, 21.7, new Date()},
        {"AXD", 2, false, 5.30, new Date()}
    };
    private TableModel model = new DefaultTableModel(data, columnNames) {

        private static final long serialVersionUID = 1L;

        @Override
        public Class<?> getColumnClass(int column) {
            return String.class;// again java.lang.ClassCastException 
            //return getValueAt(0, column).getClass(); //un-comment for correctness
        }
    };
    private JTable table = new JTable(model);
    private JTableHeader header;

    static class TestTableRowSorter extends TableRowSorter<TableModel> {

        public TestTableRowSorter(TableModel m) {
            super(m);
        }

        @Override
        public void toggleSortOrder(int column) {
        }

        public void wrapToggleSortOrder(int column) {
            super.toggleSortOrder(column);
        }
    }
    private Timer timer = new Timer(400, new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("single");
            JTable table = header.getTable();
            RowSorter sorter;
            if (pt != null && table != null && (sorter = table.getRowSorter()) != null) {
                int columnIndex = header.columnAtPoint(pt);
                if (columnIndex != -1) {
                    columnIndex = table.convertColumnIndexToModel(columnIndex);
                    ((TestTableRowSorter) sorter).wrapToggleSortOrder(columnIndex);
                }
            }
        }
    });
    private Point pt;

    public JComponent makeUI() {
        timer.setRepeats(false);
        table.setRowSorter(new TestTableRowSorter(model));
        header = table.getTableHeader();
        header.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(final MouseEvent e) {
                if (timer.isRunning() && !e.isConsumed() && e.getClickCount() > 1) {
                    System.out.println("double");
                    pt = null;
                    timer.stop();
                } else {
                    pt = e.getPoint();
                    timer.restart();
                }
            }
        });
        JPanel p = new JPanel(new BorderLayout());
        p.add(new JScrollPane(table));
        return p;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(new ViceVersaBugFromTableModelAndComparator().makeUI());
        f.setSize(820, 240);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
2

The problem is not the double but something else. As you can see in the stack trace, the table has special support for double values (javax.swing.JTable$DoubleRenderer).

The problem us that the value passed to setValue() is not a Double but something else.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • if I comment setValue in order to make it doing nothing problem still remain. Anyway, yes, sometimes a public static final Object EMPTY_ROW = ""; object to allow empty row displaying and sorting like suggested here: http://forums.oracle.com/forums/thread.jspa?threadID=1349003&start=0&tstart=0 . This causes no problem with Integer – Heisenbug May 31 '11 at 13:57
  • Set a breakpoint in the code and check what type is passed to `setValue()`. Anything else is just guessing. – Aaron Digulla May 31 '11 at 14:00
  • sorry. I misanderstood your comment. You told setValue (which is a JTable method) and I undestood setValueAt (that is my custom model method). How can I set a breakpoint to a JTable method? I have no source.. – Heisenbug May 31 '11 at 14:06
  • and i never call that method explicetly. – Heisenbug May 31 '11 at 14:08
  • If you installed the Java SDK, there should be a "src.zip" file in the root directory of the Java installation. If it's missing, download Java again. Eclipse should find the source automatically and show it to you when you open the type `JTable` (or when you click on the line in the stack trace). – Aaron Digulla Jun 01 '11 at 08:46
  • In my answer, I talk about `DoubleRenderer.setValue()`. The "Unknown Source" worries me, though. it means your version of Java doesn't have debug symbols. Make sure that you a) use a SDK while developing, b) that your SDK contains debug symbols, c) don't tell the command `java` to strip debug symbols when it loads classes. – Aaron Digulla Jun 01 '11 at 08:50
  • thanks for your suggestions. (And for your patience too). SDK is installed correctly. src.zip is on sdk folder. For what concern debug symbol honestly I dont' know what they are and how to check for their presence using Eclipse. If you haven't time to show me the right direction, I'll post another question on this topic. Thank you so much. – Heisenbug Jun 01 '11 at 14:48
  • @Aaron Digulla: I posted a new question on your considerations. If you would like to have look, here's the link : http://stackoverflow.com/questions/6203321/java-developement-under-eclipse-debbugging-symbols-and-sources-issues – Heisenbug Jun 01 '11 at 15:08
0

The thing is: when you format the Double value, it becomes a string, you can't return it back cause the column only allows Double.class. So if you change the Double.class to String.class, it will work.

Steve
  • 1
  • 1