/*******************************************************************************
 * Copyright (c) 2012, 2020 Original authors and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.examples.examples._110_Editing;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.config.EditableRule;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.convert.DefaultBooleanDisplayConverter;
import org.eclipse.nebula.widgets.nattable.data.convert.DisplayConverter;
import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter;
import org.eclipse.nebula.widgets.nattable.data.validate.DataValidator;
import org.eclipse.nebula.widgets.nattable.data.validate.DefaultNumericDataValidator;
import org.eclipse.nebula.widgets.nattable.data.validate.IDataValidator;
import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.PricingTypeBean;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.RowDataListFixture;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.edit.action.ToggleCheckBoxColumnAction;
import org.eclipse.nebula.widgets.nattable.edit.editor.CheckBoxCellEditor;
import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;
import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;
import org.eclipse.nebula.widgets.nattable.examples.AbstractNatExample;
import org.eclipse.nebula.widgets.nattable.examples.fixtures.PricingTypeBeanDisplayConverter;
import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultGridLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.ColumnHeaderCheckBoxPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.ComboBoxPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.BeveledBorderDecorator;
import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.CellPainterDecorator;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
import org.eclipse.nebula.widgets.nattable.style.Style;
import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
import org.eclipse.nebula.widgets.nattable.ui.matcher.CellPainterMouseEventMatcher;
import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
import org.eclipse.nebula.widgets.nattable.ui.menu.HeaderMenuConfiguration;
import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

public class EditableGridExample extends AbstractNatExample {

    public static void main(String[] args) {
        StandaloneNatExampleRunner.run(new EditableGridExample());
    }

    @Override
    public String getDescription() {
        return "This example demonstrates various edit and styling functionality. All of the functionality in the DefaultGridExample "
                + "is available here. Also note that:\n"
                + "\n"
                + "* CUSTOM ALIGNMENT: All numeric fields are right-aligned, and the Security Description field is left-aligned.\n"
                + "* CUSTOM FORMATTING: The Bid and Ask fields are formatted to 2 decimal places normally, but in edit mode they are "
                + "formatted to 6 decimal places.\n"
                + "* EDITABLE/NON-EDITABLE FIELDS: Spread is a non-editable calculated field that is formatted to 6 decimal places.\n"
                + "* SINGLE AND MULTI-CELL EDIT for text, checkbox, and combo box fields. Note that in order to invoke multi-cell "
                + "edit, all of the selected cells must have the same editor type and be editable.\n"
                + "* VALIDATION: The ISIN field has validation enabled which requires data to be in the form ABC123 (first three alpha "
                + "characters, afterwards numeric). If the validation rule is violated, the characters are highlighted in red and the "
                + "editor will not allow you to enter the value. Also note that if an ISIN field and a different text field are "
                + "selected for multi-cell edit, the multi-cell editor will require a value that is valid for all selected cells.";
    }

    public static final String ASK_PRICE_CONFIG_LABEL = "askPriceConfigLabel";
    public static final String SECURITY_ID_CONFIG_LABEL = "SecurityIdConfigLabel";
    public static final String SECURITY_ID_EDITOR = "SecurityIdEditor";
    public static final String BID_PRICE_CONFIG_LABEL = "bidPriceConfigLabel";
    public static final String LOT_SIZE_CONFIG_LABEL = "lotSizeConfigLabel";
    public static final String SPREAD_CONFIG_LABEL = "spreadConfigLabel";

    public static final String FORMAT_DATE_CONFIG_LABEL = "formatDateConfigLabel";
    public static final String FORMAT_DOUBLE_2_PLACES_CONFIG_LABEL = "formatDouble2PlacesConfigLabel";
    public static final String FORMAT_DOUBLE_6_PLACES_CONFIG_LABEL = "formatDouble6PlacesConfigLabel";
    public static final String FORMAT_IN_MILLIONS_CONFIG_LABEL = "formatInMilliosConfigLabel";
    public static final String FORMAT_PRICING_TYPE_CONFIG_LABEL = "formatPricingTypeConfigLabel";

    public static final String ALIGN_CELL_CONTENTS_LEFT_CONFIG_LABEL = "alignCellContentsLeftConfigLabel";
    public static final String ALIGN_CELL_CONTENTS_RIGHT_CONFIG_LABEL = "alignCellContentsRightConfigLabel";

    public static final String CHECK_BOX_CONFIG_LABEL = "checkBox";
    public static final String CHECK_BOX_EDITOR_CONFIG_LABEL = "checkBoxEditor";
    public static final String COMBO_BOX_CONFIG_LABEL = "comboBox";
    public static final String COMBO_BOX_EDITOR_CONFIG_LABEL = "comboBoxEditor";

    @Override
    public Control createExampleControl(Composite parent) {
        DefaultGridLayer gridLayer = new DefaultGridLayer(
                RowDataListFixture.getList(),
                RowDataListFixture.getPropertyNames(),
                RowDataListFixture.getPropertyToLabelMap());

        DataLayer columnHeaderDataLayer = (DataLayer) gridLayer.getColumnHeaderDataLayer();
        columnHeaderDataLayer.setConfigLabelAccumulator(new ColumnLabelAccumulator());

        final DataLayer bodyDataLayer = (DataLayer) gridLayer
                .getBodyDataLayer();
        IDataProvider dataProvider = bodyDataLayer.getDataProvider();

        // NOTE: Register the accumulator on the body data layer.
        // This ensures that the labels are bound to the column index and are
        // unaffected by column order.
        final ColumnOverrideLabelAccumulator columnLabelAccumulator = new ColumnOverrideLabelAccumulator(
                bodyDataLayer);
        bodyDataLayer.setConfigLabelAccumulator(columnLabelAccumulator);

        NatTable natTable = new NatTable(parent, gridLayer, false);

        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
        natTable.addConfiguration(new HeaderMenuConfiguration(natTable));
        natTable.addConfiguration(editableGridConfiguration(
                columnLabelAccumulator, dataProvider));

        final ColumnHeaderCheckBoxPainter columnHeaderCheckBoxPainter = new ColumnHeaderCheckBoxPainter(
                bodyDataLayer);
        final ICellPainter column9HeaderPainter = new BeveledBorderDecorator(
                new CellPainterDecorator(new TextPainter(), CellEdgeEnum.RIGHT,
                        columnHeaderCheckBoxPainter));
        natTable.addConfiguration(new AbstractRegistryConfiguration() {
            @Override
            public void configureRegistry(IConfigRegistry configRegistry) {
                configRegistry.registerConfigAttribute(
                        CellConfigAttributes.CELL_PAINTER,
                        column9HeaderPainter, DisplayMode.NORMAL,
                        ColumnLabelAccumulator.COLUMN_LABEL_PREFIX + 9);
            }

            @Override
            public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
                uiBindingRegistry.registerFirstSingleClickBinding(
                        new CellPainterMouseEventMatcher(
                                GridRegion.COLUMN_HEADER,
                                MouseEventMatcher.LEFT_BUTTON,
                                columnHeaderCheckBoxPainter),
                        new ToggleCheckBoxColumnAction(
                                columnHeaderCheckBoxPainter, bodyDataLayer));
            }
        });

        natTable.configure();

        return natTable;
    }

    public static AbstractRegistryConfiguration editableGridConfiguration(
            final ColumnOverrideLabelAccumulator columnLabelAccumulator,
            final IDataProvider dataProvider) {

        return new AbstractRegistryConfiguration() {

            @Override
            public void configureRegistry(IConfigRegistry configRegistry) {

                EditableGridExample.registerConfigLabelsOnColumns(columnLabelAccumulator);

                registerISINValidator(configRegistry);
                registerAskPriceValidator(configRegistry, dataProvider);
                registerBidPriceValidator(configRegistry);

                registerSecurityDescriptionCellStyle(configRegistry);
                registerPricingCellStyle(configRegistry);

                registerPriceFormatter(configRegistry);
                registerDateFormatter(configRegistry);
                registerLotSizeFormatter(configRegistry);

                registerCheckBoxEditor(configRegistry, new CheckBoxPainter(),
                        new CheckBoxCellEditor());
                registerComboBox(
                        configRegistry,
                        new ComboBoxPainter(),
                        new ComboBoxCellEditor(
                                Arrays.asList(
                                        new PricingTypeBean("MN"),
                                        new PricingTypeBean("AT"))));

                registerEditableRules(configRegistry, dataProvider);
            }

        };
    }

    private static void registerConfigLabelsOnColumns(ColumnOverrideLabelAccumulator columnLabelAccumulator) {
        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.SECURITY_ID_PROP_NAME),
                SECURITY_ID_EDITOR, SECURITY_ID_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.SECURITY_DESCRIPTION_PROP_NAME),
                ALIGN_CELL_CONTENTS_LEFT_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.ISSUE_DATE_PROP_NAME),
                FORMAT_DATE_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.PRICING_TYPE_PROP_NAME),
                COMBO_BOX_CONFIG_LABEL, COMBO_BOX_EDITOR_CONFIG_LABEL,
                FORMAT_PRICING_TYPE_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.BID_PRICE_PROP_NAME),
                BID_PRICE_CONFIG_LABEL,
                FORMAT_DOUBLE_6_PLACES_CONFIG_LABEL,
                FORMAT_DOUBLE_2_PLACES_CONFIG_LABEL,
                ALIGN_CELL_CONTENTS_RIGHT_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.ASK_PRICE_PROP_NAME),
                ASK_PRICE_CONFIG_LABEL,
                FORMAT_DOUBLE_6_PLACES_CONFIG_LABEL,
                FORMAT_DOUBLE_2_PLACES_CONFIG_LABEL,
                ALIGN_CELL_CONTENTS_RIGHT_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.SPREAD_PROP_NAME),
                SPREAD_CONFIG_LABEL, FORMAT_DOUBLE_6_PLACES_CONFIG_LABEL,
                ALIGN_CELL_CONTENTS_RIGHT_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.LOT_SIZE_PROP_NAME),
                LOT_SIZE_CONFIG_LABEL, FORMAT_IN_MILLIONS_CONFIG_LABEL,
                ALIGN_CELL_CONTENTS_RIGHT_CONFIG_LABEL);

        columnLabelAccumulator.registerColumnOverrides(
                RowDataListFixture.getColumnIndexOfProperty(RowDataListFixture.PUBLISH_FLAG_PROP_NAME),
                CHECK_BOX_EDITOR_CONFIG_LABEL, CHECK_BOX_CONFIG_LABEL);
    }

    private static void registerSecurityDescriptionCellStyle(IConfigRegistry configRegistry) {
        Style cellStyle = new Style();
        cellStyle.setAttributeValue(
                CellStyleAttributes.HORIZONTAL_ALIGNMENT,
                HorizontalAlignmentEnum.LEFT);
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.CELL_STYLE,
                cellStyle,
                DisplayMode.NORMAL,
                ALIGN_CELL_CONTENTS_LEFT_CONFIG_LABEL);
    }

    private static void registerPricingCellStyle(IConfigRegistry configRegistry) {
        Style cellStyle = new Style();
        cellStyle.setAttributeValue(
                CellStyleAttributes.HORIZONTAL_ALIGNMENT,
                HorizontalAlignmentEnum.RIGHT);
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.CELL_STYLE,
                cellStyle,
                DisplayMode.NORMAL,
                ALIGN_CELL_CONTENTS_RIGHT_CONFIG_LABEL);
    }

    private static void registerCheckBoxEditor(IConfigRegistry configRegistry,
            ICellPainter checkBoxCellPainter, ICellEditor checkBoxCellEditor) {
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.CELL_PAINTER, checkBoxCellPainter,
                DisplayMode.NORMAL, CHECK_BOX_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.DISPLAY_CONVERTER,
                new DefaultBooleanDisplayConverter(), DisplayMode.NORMAL,
                CHECK_BOX_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITOR, checkBoxCellEditor,
                DisplayMode.NORMAL, CHECK_BOX_EDITOR_CONFIG_LABEL);
    }

    private static void registerComboBox(IConfigRegistry configRegistry,
            ICellPainter comboBoxCellPainter, ICellEditor comboBoxCellEditor) {
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.CELL_PAINTER, comboBoxCellPainter,
                DisplayMode.NORMAL, COMBO_BOX_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITOR, comboBoxCellEditor,
                DisplayMode.NORMAL, COMBO_BOX_EDITOR_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITOR, comboBoxCellEditor,
                DisplayMode.EDIT, COMBO_BOX_EDITOR_CONFIG_LABEL);

        configRegistry.registerConfigAttribute(
                CellConfigAttributes.DISPLAY_CONVERTER,
                new PricingTypeBeanDisplayConverter(), DisplayMode.NORMAL,
                FORMAT_PRICING_TYPE_CONFIG_LABEL);
    }

    private static void registerISINValidator(IConfigRegistry configRegistry) {

        TextCellEditor textCellEditor = new TextCellEditor();
        textCellEditor.setErrorDecorationEnabled(true);
        textCellEditor.setDecorationPositionOverride(SWT.LEFT | SWT.TOP);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITOR, textCellEditor,
                DisplayMode.NORMAL, SECURITY_ID_EDITOR);

        configRegistry.registerConfigAttribute(
                EditConfigAttributes.DATA_VALIDATOR, getSecurtityIdValidator(),
                DisplayMode.EDIT, SECURITY_ID_CONFIG_LABEL);
    }

    private static void registerDateFormatter(IConfigRegistry configRegistry) {
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.DISPLAY_CONVERTER, getDateFormatter(),
                DisplayMode.NORMAL, FORMAT_DATE_CONFIG_LABEL);
    }

    private static void registerAskPriceValidator(
            IConfigRegistry configRegistry, IDataProvider dataProvider) {
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.DATA_VALIDATOR,
                getAskPriceValidator(dataProvider), DisplayMode.EDIT,
                ASK_PRICE_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.DATA_VALIDATOR,
                getAskPriceValidator(dataProvider), DisplayMode.NORMAL,
                ASK_PRICE_CONFIG_LABEL);
    }

    private static void registerBidPriceValidator(IConfigRegistry configRegistry) {
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.DATA_VALIDATOR,
                new DefaultNumericDataValidator(), DisplayMode.EDIT,
                BID_PRICE_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.DATA_VALIDATOR,
                new DefaultNumericDataValidator(), DisplayMode.NORMAL,
                BID_PRICE_CONFIG_LABEL);
    }

    private static void registerPriceFormatter(IConfigRegistry configRegistry) {
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.DISPLAY_CONVERTER,
                getDoubleDisplayConverter(2), DisplayMode.NORMAL,
                FORMAT_DOUBLE_2_PLACES_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.DISPLAY_CONVERTER,
                getDoubleDisplayConverter(6), DisplayMode.NORMAL,
                FORMAT_DOUBLE_6_PLACES_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.DISPLAY_CONVERTER,
                getDoubleDisplayConverter(6), DisplayMode.EDIT,
                FORMAT_DOUBLE_6_PLACES_CONFIG_LABEL);
    }

    private static void registerEditableRules(IConfigRegistry configRegistry,
            IDataProvider dataProvider) {
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITABLE_RULE,
                IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT,
                SECURITY_ID_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITABLE_RULE,
                IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT,
                COMBO_BOX_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITABLE_RULE,
                IEditableRule.ALWAYS_EDITABLE, DisplayMode.EDIT,
                CHECK_BOX_CONFIG_LABEL);

        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITABLE_RULE,
                getEditRule(dataProvider), DisplayMode.EDIT,
                ASK_PRICE_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITABLE_RULE,
                getEditRule(dataProvider), DisplayMode.EDIT,
                BID_PRICE_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITABLE_RULE,
                getEditRule(dataProvider), DisplayMode.EDIT,
                LOT_SIZE_CONFIG_LABEL);
        configRegistry.registerConfigAttribute(
                EditConfigAttributes.CELL_EDITABLE_RULE,
                IEditableRule.NEVER_EDITABLE, DisplayMode.EDIT,
                SPREAD_CONFIG_LABEL);
    }

    private static void registerLotSizeFormatter(IConfigRegistry configRegistry) {
        configRegistry.registerConfigAttribute(
                CellConfigAttributes.DISPLAY_CONVERTER,
                getMillionsDisplayConverter(), DisplayMode.NORMAL,
                FORMAT_IN_MILLIONS_CONFIG_LABEL);
    }

    /**
     * Edit rule: If publish flag is true - bid, ask and size can be edited.
     */
    private static IEditableRule getEditRule(final IDataProvider dataProvider) {
        return new EditableRule() {
            @Override
            public boolean isEditable(int columnIndex, int rowIndex) {
                int columnIndexOfPublishFlag = RowDataListFixture
                        .getColumnIndexOfProperty(RowDataListFixture.PUBLISH_FLAG_PROP_NAME);
                return ((Boolean) dataProvider.getDataValue(
                        columnIndexOfPublishFlag, rowIndex)).booleanValue();
            }
        };
    }

    /**
     * Format: Double to a specified number of decimal places
     */
    private static IDisplayConverter getDoubleDisplayConverter(
            final int decimalPlaces) {
        return new DisplayConverter() {
            NumberFormat numberFormatter = NumberFormat.getInstance();

            @Override
            public Object canonicalToDisplayValue(Object canonicalValue) {
                if (canonicalValue == null) {
                    return "";
                }
                this.numberFormatter.setMaximumFractionDigits(decimalPlaces);
                this.numberFormatter.setMinimumFractionDigits(decimalPlaces);
                return this.numberFormatter.format(Double.valueOf(canonicalValue
                        .toString()));
            }

            @Override
            public Object displayToCanonicalValue(Object displayValue) {
                try {
                    return this.numberFormatter.parse((String) displayValue);
                } catch (ParseException e) {
                    return null;
                }
            }

        };
    }

    /**
     * Format: String date value using a SimpleDateFormat
     */
    private static IDisplayConverter getDateFormatter() {
        return new DisplayConverter() {
            private final SimpleDateFormat dateFormatter = new SimpleDateFormat(
                    "MM-dd-yy");

            @Override
            public Object canonicalToDisplayValue(Object canonicalValue) {
                return this.dateFormatter.format((Date) canonicalValue);
            }

            @Override
            public Object displayToCanonicalValue(Object displayValue) {
                return this.dateFormatter.parse(displayValue.toString(),
                        new ParsePosition(0));
            }
        };
    }

    /**
     * Format: Number as millions (with commas). Convert to int.
     */
    private static IDisplayConverter getMillionsDisplayConverter() {
        return new DisplayConverter() {
            NumberFormat numberFormatter = new DecimalFormat("###,###,###");

            @Override
            public Object canonicalToDisplayValue(Object canonicalValue) {
                if (canonicalValue == null) {
                    return null;
                }
                return this.numberFormatter.format(Integer.valueOf(canonicalValue
                        .toString()));
            }

            @Override
            public Object displayToCanonicalValue(Object displayValue) {
                return (this.numberFormatter.parse(displayValue.toString(),
                        new ParsePosition(0))).intValue();
            }
        };
    }

    /**
     * Validate: Ask price > Bid Price
     */
    private static IDataValidator getAskPriceValidator(
            final IDataProvider dataProvider) {

        return new DataValidator() {

            @Override
            public boolean validate(int columnIndex, int rowIndex,
                    Object newValue) {
                try {
                    int indexOfBidPrice = RowDataListFixture
                            .getColumnIndexOfProperty(RowDataListFixture.BID_PRICE_PROP_NAME);
                    double bidPrice = ((Double) dataProvider.getDataValue(
                            indexOfBidPrice, rowIndex)).doubleValue();
                    double askPrice = Double
                            .valueOf(newValue.toString())
                            .doubleValue();
                    return askPrice > bidPrice;
                } catch (Exception ex) {
                    return false;
                }
            }
        };
    }

    /**
     * Validate: First three chars must be alphabetic and the following must be
     * numeric
     */
    private static IDataValidator getSecurtityIdValidator() {
        return new DataValidator() {

            @Override
            public boolean validate(int columnIndex, int rowIndex, Object newValue) {
                boolean valid = false;

                if (newValue != null) {
                    String value = (String) newValue;
                    if (value.length() > 3) {
                        String alphabeticPart = value.substring(0, 2);
                        String numericPart = value.substring(3, value.length());
                        valid = isAlpha(alphabeticPart)
                                && isNumeric(numericPart);
                    } else {
                        valid = isAlpha(value);
                    }
                }

                if (!valid) {
                    throw new ValidationFailedException("Security Id must be 3 alpha characters optionally followed by numbers");
                }

                return valid;
            }

            private boolean isAlpha(String str) {
                for (int i = 0; i < str.length(); i++) {
                    if (!Character.isLetter(str.charAt(i)))
                        return false;
                }
                return true;
            }

            private boolean isNumeric(String str) {
                for (int i = 0; i < str.length(); i++) {
                    if (!Character.isDigit(str.charAt(i)))
                        return false;
                }
                return true;
            }
        };
    }
}
