swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/table/NumberEditorExtIssues.java
/*
 * $Id$
 *
 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package org.jdesktop.swingx.table;

import java.awt.Point;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.logging.Logger;

import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.KeyStroke;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.text.InternationalFormatter;
import javax.swing.text.NumberFormatter;

import org.jdesktop.swingx.InteractiveTestCase;
import org.jdesktop.swingx.JXFrame;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.table.DatePickerCellEditor;
import org.jdesktop.swingx.table.NumberEditorExt;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * TODO add type doc
 * 
 * @author Jeanette Winzenburg
 */
@RunWith(JUnit4.class)
public class NumberEditorExtIssues extends InteractiveTestCase {
    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(NumberEditorExtIssues.class.getName());
    
    private static final String TOO_BIG_INTEGER = "11111111111111111111111111";
    private static final int INTEGER_COLUMN = 0;
    /** a table with a model which has column class Integer in INTEGER_COLUMN. */
    private JXTable table;
    /** a NumberEditorExt configured with IntegerFormat. */
    private NumberEditorExt cellEditor;

    private NumberEditorExt cellEditorStrict;

    public static void main(String[] args) {
        NumberEditorExtIssues test = new NumberEditorExtIssues();
        try {
            test.runInteractiveTests();
//            test.runInteractiveTests("interactive.*Number.*");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void interactiveNumberEditorKeepsEscape() {
        JFormattedTextField field = new JFormattedTextField(new Date());
        KeyStroke keyStroke = KeyStroke.getKeyStroke("ESCAPE");
        LOG.info("actionForEscape: " + field.getInputMap().get(keyStroke ));
        new NumberEditorExt(true);
        LOG.info("actionForEscape: " + field.getInputMap().get(keyStroke ));
        JXFrame frame = wrapInFrame(field, "textfield escape");
        show(frame, 400, 300);
    }
    /**
     * Issue #1236-swingx: NumberEditorExt only handles columns with type
     *    Number.
     *    
     * Perceived as overly restrictive, fails f.i. in usage in property sheet.   
     * 
     */
//    @Test
    public void interactiveMixedColumnEditors() {
        // mixed types in column
        Object[] values = new Object[] {new Integer(10), new Double(10.5), new Date(),
                new JLabel("dummy"),
                new Point(4, 6)};
        DefaultTableModel model = new DefaultTableModel(10, 2) {

            @Override
            public Class<?> getColumnClass(int columnIndex) {
                if (columnIndex == getColumnCount() - 1) {
                    return Point.class;
                }
                return super.getColumnClass(columnIndex);
            }
            
        };
        for (int i = 0; i < values.length; i++) {
            model.setValueAt(values[i], i, 0);
            model.setValueAt(new Point(1, i), i, 1);
        }
        
        JXTable table = new JXTable(model) {

            @Override
            public TableCellEditor getCellEditor(int row, int column) {
                Object value = getValueAt(row, column);
                if (value instanceof Number) {
                    return getDefaultEditor(Number.class);
                }
                if (value instanceof Date) {
                    return getDefaultEditor(Date.class);
                }
                if (value instanceof JLabel) {
                    // pathological: something with a string param constructor
                    return getDefaultEditor(Number.class);
                }
                return super.getCellEditor(row, column);
            }
            
        };
        table.setDefaultEditor(Date.class, new DatePickerCellEditor());
//        table.setDefaultEditor(Number.class, new NumberEditorExt(false));
        JXFrame frame = showWithScrollingInFrame(table, "per-cell number editor? ");
        JFormattedTextField field = new JFormattedTextField(new Date());
        addStatusComponent(frame, field);
    }
    
    /**
     * Issue #1183-swingx: NumberEditorExt throws in getCellEditorValue if
     *   Integer (short, byte..) below/above min/max.
     *   
     * Check IllegalStateException as doc'ed - the strict version doesn't. 
     * Need to check the delegate implementation?
     */
    @Test(expected = IllegalStateException.class)
    public void testEditorStrictValueIllegalState() {
        JFormattedTextField field = (JFormattedTextField) cellEditorStrict
                .getTableCellEditorComponent(table, Integer.MAX_VALUE, false, 0, INTEGER_COLUMN);
        // add valid digit - but exceeds Integer bounds so must not return true
        field.setText(field.getText() + "9");
        cellEditorStrict.getCellEditorValue();
    }
 
    /**
     * Issue ??-swingx: editor with strict number formatter throws on 
     *    committing null value.
     *   
     *  happens only if active editor in table. Use non-strict for comparison.
     *  InternationalFormatter with bounds throws as well. But not using
     *  strict catches this in isValid, that is stopCellEditing returns false.
     */
    @Test
    public void testEditorNullValue() {
        table.getColumn(INTEGER_COLUMN).setCellEditor(cellEditor);
        ((InternationalFormatter) cellEditor.getComponent().getFormatter()).setMinimum(0);
        table.editCellAt(0, INTEGER_COLUMN);
        assertTrue(cellEditor.stopCellEditing());
    }
    

//--------------------- core issues

    /**
     * This is not really an issue: Double values exceeding the bounds are Infinity.
     */
    @Test (expected = ParseException.class)
    public void testNumberFormatterDouble() throws ParseException {
        NumberFormat format = NumberFormat.getInstance();
        // no need to do anything special - parsing of doubles fails if out-off range?
        NumberFormatter formatter = new NumberFormatter(format);
        String text = "9" + new Double(Double.MAX_VALUE).toString();
        Number number = (Number) formatter.stringToValue(text);
        LOG.info("number: " + number);
    }

    /**
     * Formatted text field commit can't handle empty string.
     * 
     * @throws ParseException
     */
    @Test
    public void testTextFieldWithEmptyString() throws ParseException {
        NumberFormat format = NumberFormat.getIntegerInstance();
        JFormattedTextField field = new JFormattedTextField(format);
        field.setText("");
        field.commitEdit();
    }
    
    /**
     * NumberFormatter parsing can't handle null.
     * 
     * @throws ParseException
     */
    @Test
    public void testNumberFormatterEmptyStringValue() throws ParseException {
        NumberFormat format = NumberFormat.getIntegerInstance();
        NumberFormatter formatter = new NumberFormatter(format);
        formatter.stringToValue("");
    }
    
    /**
     * NumberFormat parsing cant handle empty string.
     * 
     * @throws ParseException
     */
    @Test
    public void testNumberFormatEmptyStringValue() throws ParseException {
        NumberFormat format = NumberFormat.getIntegerInstance();
        format.parse("");
    }
    
    /**
     * NumberFormatter parsing can't handle null.
     * @throws ParseException
     */
    @Test
    public void testNumberFormatterNullValue() throws ParseException {
        NumberFormat format = NumberFormat.getIntegerInstance();
        NumberFormatter formatter = new NumberFormatter(format);
        formatter.stringToValue(null);
    }
    
    /**
     * NumberFormat parse can't handle null.
     * 
     * @throws ParseException
     */
    @Test
    public void testNumberFormatNullValue() throws ParseException {
        NumberFormat format = NumberFormat.getIntegerInstance();
        format.parse(null);
    }
    

    @Test (expected = ParseException.class)
    public void testNumberFormatterMinMax() throws ParseException {
        NumberFormat format = NumberFormat.getIntegerInstance();
        NumberFormatter formatter = new NumberFormatter(format);
        formatter.setMaximum(Integer.MAX_VALUE);
        formatter.setMinimum(Integer.MIN_VALUE);
        formatter.stringToValue(TOO_BIG_INTEGER);
    }

    @Test
    public void testNumberFormatInteger() throws ParseException {
        NumberFormat format = NumberFormat.getIntegerInstance();
        // this passes - everything fitting into double range is acceptable
        format.parse(TOO_BIG_INTEGER);
        // this blows - must fit into Integer.MIN/MAX
        new Integer(TOO_BIG_INTEGER);
    }
    
    @Before
    @Override
    public void setUp() throws Exception {
        DefaultTableModel model = new DefaultTableModel(5, 1) {

            @Override
            public Class<?> getColumnClass(int columnIndex) {
                if (columnIndex == INTEGER_COLUMN)
                    return Integer.class;
                return super.getColumnClass(columnIndex);
            }
            
        };
        table = new JXTable(model);
        cellEditor = new NumberEditorExt(NumberFormat.getIntegerInstance());
        cellEditorStrict = new NumberEditorExt(NumberFormat.getIntegerInstance(), true);
    }


}
