ArduinoTableSignal.java

/*******************************************************************************
 * jArduino: Arduino C++ Code Generation From Java
 * Copyright 2020 Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package net.sourceforge.jarduino.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractCellEditor;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableRowSorter;

import net.sourceforge.jarduino.message.ArduinoMessage;
import net.sourceforge.jarduino.message.ArduinoSignal;
import net.sourceforge.jarduino.message.ArduinoSignalDefinition;
import net.sourceforge.jarduino.message.ArduinoSignalFactor;
import net.sourceforge.jarduino.message.ArduinoSignalRange;
import net.sourceforge.jarduino.util.ArduinoField;

/**
 * Message Signal Table.
 */
public class ArduinoTableSignal {
    /**
     * Colors.
     */
    private static final Color[] COLORS = {
            Color.decode("#FF0000"), Color.decode("#FF7F00"), Color.decode("#FFD400"), Color.decode("#FFFF00"),
            Color.decode("#BFFF00"), Color.decode("#6AFF00"), Color.decode("#00EAFF"), Color.decode("#0095FF"),
            Color.decode("#0040FF"), Color.decode("#AA00FF"), Color.decode("#FF00AA"), Color.decode("#EDB9B9"),
            Color.decode("#E7E9B9"), Color.decode("#B9EDE0"), Color.decode("#B9D7ED"), Color.decode("#DCB9ED"),
            Color.decode("#8F2323"), Color.decode("#8F6A23"), Color.decode("#4F8F23"), Color.decode("#23628F"),
            Color.decode("#6B238F"), Color.decode("#000000"), Color.decode("#737373"), Color.decode("#CCCCCC")
    };

    /**
     * Normal border.
     */
    private static final Border BORDER_NORMAL = new LineBorder(Color.black);

    /**
     * Error border.
     */
    private static final Border BORDER_ERROR = new LineBorder(Color.red);

    /**
     * Rounding factor (add 0.5 to double before casting to long to get rounding right).
     */
    private static final double ROUNDING = 0.5;

    /**
     * Small width.
     */
    private static final int WIDTH_SMALL = 20;

    /**
     * Medium width.
     */
    private static final int WIDTH_MEDIUM = 40;

    /**
     * Large width.
     */
    private static final int WIDTH_LARGE = 150;

    /**
     * The owning panel.
     */
    private final ArduinoPanelSignal theOwner;

    /**
     * The Message table.
     */
    private final JTable theTable;

    /**
     * The signals list.
     */
    private final List<ArduinoSignal> theSignals;

    /**
     * The bitMaps list.
     */
    private final List<byte[]> theBitMaps;

    /**
     * The values Map.
     */
    private final Map<ArduinoSignal, Object> theValues;

    /**
     * The colours Map.
     */
    private final Map<ArduinoSignal, Color> theColours;

    /**
     * The model.
     */
    private final ArduinoSignalModel theModel;

    /**
     * The Multiplex column.
     */
    private final TableColumn theMultiCol;

    /**
     * Is the multiplex column visible?
     */
    private boolean multiVisible;

    /**
     * The selected message.
     */
    private ArduinoMessage theMessage;

    /**
     * Constructor.
     *
     * @param pOwner the owning panel
     */
    ArduinoTableSignal(final ArduinoPanelSignal pOwner) {
        /* Store owner */
        theOwner = pOwner;

        /* Create components */
        theTable = new JTable();

        /* Create the lists and maps */
        theSignals = new ArrayList<>();
        theBitMaps = new ArrayList<>();
        theValues = new HashMap<>();
        theColours = new HashMap<>();

        /* Create the table model */
        theModel = new ArduinoSignalModel();
        theTable.setModel(theModel);

        /* Set the row filter */
        final TableRowSorter<ArduinoSignalModel> mySorter = new TableRowSorter<>(theModel);
        mySorter.setRowFilter(new MultiFilter());
        theTable.setRowSorter(mySorter);

        /* Configure the columns */
        theMultiCol = configureColumns();
        multiVisible = true;
    }

    /**
     * Configure the columns.
     * @return the multiplex column
     */
    private TableColumn configureColumns() {
        /* Create renderers */
        final TableCellRenderer myRightRenderer = new AlignmentRenderer(SwingConstants.RIGHT);
        final TableCellRenderer myCenterRenderer = new AlignmentRenderer(SwingConstants.CENTER);
        final TableCellRenderer myColorRenderer = new ColorRenderer();
        final TableCellEditor myCellEditor = new DataCellEditor();

        /* Access the column model */
        final TableColumnModel myModel = theTable.getColumnModel();

        /* Set the signal column */
        TableColumn myColumn = myModel.getColumn(ArduinoSignalModel.COL_SIGNAL);
        myColumn.setPreferredWidth(WIDTH_LARGE);
        myColumn.setCellRenderer(myRightRenderer);

        /* Set the value column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_VALUE);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myCenterRenderer);
        myColumn.setCellEditor(myCellEditor);

        /* Set the color column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_COLOR);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setMaxWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myColorRenderer);

        /* Set the startBit column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_START);
        myColumn.setPreferredWidth(WIDTH_SMALL);
        myColumn.setMaxWidth(WIDTH_SMALL);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the length column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_LENGTH);
        myColumn.setPreferredWidth(WIDTH_SMALL);
        myColumn.setMaxWidth(WIDTH_SMALL);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the endian column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_ENDIAN);
        myColumn.setPreferredWidth(WIDTH_SMALL);
        myColumn.setMaxWidth(WIDTH_SMALL);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the units column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_UNITS);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the factor column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_FACTOR);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the offset column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_OFFSET);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the min column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_MIN);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the max column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_MAX);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Handle the multiplex column */
        myColumn = myModel.getColumn(ArduinoSignalModel.COL_MULTI);
        myColumn.setPreferredWidth(WIDTH_SMALL);
        myColumn.setCellRenderer(myCenterRenderer);
        return myColumn;
    }

    /**
     * Obtain the component.
     *
     * @return the component
     */
    JComponent getComponent() {
        return theTable;
    }

    /**
     * Obtain color for index.
     *
     * @param pIndex the index
     * @return the colour
     */
    private static Color getColourForIndex(final int pIndex) {
        return COLORS[pIndex % COLORS.length];
    }

    /**
     * Configure the table.
     *
     * @param pMessage the message
     */
    void configureTable(final ArduinoMessage pMessage) {
        /* store the message */
        theMessage = pMessage;

        /* Refresh the table */
        theModel.refresh();

        /* Determine whether multiplex column should be visible */
        final boolean bMultiVisible = theMessage.hasMultiplex();

        /* If we should modify the columns */
        if (bMultiVisible != multiVisible) {
            /* Access the table column model */
            final TableColumnModel myColModel = theTable.getColumnModel();

            /* If we need to remove the column */
            if (multiVisible) {
                myColModel.removeColumn(theMultiCol);

                /* else restore the column and move to correct position */
            } else {
                myColModel.addColumn(theMultiCol);
                myColModel.moveColumn(ArduinoSignalModel.COL_MAX, ArduinoSignalModel.COL_MULTI);
            }

            /* Flip the flag */
            multiVisible = !multiVisible;
        }
    }

    /**
     * Obtain number of rows.
     *
     * @return the the number of rows
     */
    int getNumRows() {
        return theSignals.size();
    }

    /**
     * Obtain color for row.
     *
     * @param pRowIndex the row index
     * @return the color
     */
    Color getColorForRow(final int pRowIndex) {
        final ArduinoSignal mySignal = theSignals.get(pRowIndex);
        return theColours.get(mySignal);
    }

    /**
     * Obtain bitmap for row.
     *
     * @param pRowIndex the row index
     * @return the bitMap
     */
    byte[] getBitMapForRow(final int pRowIndex) {
        return theBitMaps.get(pRowIndex);
    }

    /**
     * loadValues.
     */
    void loadValues() {
        /* Clear the maps */
        theValues.clear();
        theColours.clear();

        /* Loop through the signals */
        Long myMultiplex = ArduinoSignal.MULTI_NONE;
        int myColorId = 0;
        for (ArduinoSignal mySignal : theSignals) {
            /* If the value is relevant */
            final Number myMId = mySignal.getMultiplexId();
            if (myMId.equals(ArduinoSignal.MULTI_NONE)
                    || myMId.equals(myMultiplex)) {
                /* Access the value and store into map */
                final Number myValue = getValueForSignal(mySignal);
                theValues.put(mySignal, myValue);

                /* Store colour for index */
                theColours.put(mySignal, getColourForIndex(myColorId++));

                /* Store multiplex value if we have it */
                if (mySignal.isMultiplex()) {
                    myMultiplex = myValue.longValue();
                }
            }
        }

        /* Update the table */
        if (theModel != null) {
            theModel.fireTableDataChanged();
        }
    }

    /**
     * Obtain value for signal.
     *
     * @param pSignal the signal
     * @return the value
     */
    private Number getValueForSignal(final ArduinoSignal pSignal) {
        /* Access the data buffer */
        final byte[] myData = theOwner.getData();

        /* Access the raw value */
        final ArduinoSignalDefinition myDef = pSignal.getDefinition();
        final long myValue = myDef.getDataType().isLittleEndian()
                             ? ArduinoField.parseLEField(myData, myDef.getStartBit(), myDef.getBitLength(), myDef.getDataType().isSigned())
                             : ArduinoField.parseBEField(myData, myDef.getStartBit(), myDef.getBitLength(), myDef.getDataType().isSigned());

        /* Apply the adjustment */
        final ArduinoSignalFactor myFactor = pSignal.getFactor();
        return myFactor.isFloat()
               ? (Number) (myFactor.getFactor().doubleValue() * myValue + myFactor.getOffset().doubleValue())
               : (Number) (myFactor.getFactor().longValue() * myValue + myFactor.getOffset().longValue());
    }

    /**
     * Table Model.
     */
    private class ArduinoSignalModel
            extends AbstractTableModel {
        /**
         * Serial Id.
         */
        private static final long serialVersionUID = 3030910683228702589L;

        /**
         * The signal column.
         */
        private static final int COL_SIGNAL = 0;

        /**
         * The value column.
         */
        private static final int COL_VALUE = COL_SIGNAL + 1;

        /**
         * The startBits column.
         */
        private static final int COL_START = COL_VALUE + 1;

        /**
         * The length column.
         */
        private static final int COL_LENGTH = COL_START + 1;

        /**
         * The endian column.
         */
        private static final int COL_ENDIAN = COL_LENGTH + 1;

        /**
         * The multiplex column.
         */
        private static final int COL_MULTI = COL_ENDIAN + 1;

        /**
         * The color column.
         */
        private static final int COL_COLOR = COL_MULTI + 1;

        /**
         * The units column.
         */
        private static final int COL_UNITS = COL_COLOR + 1;

        /**
         * The factor column.
         */
        private static final int COL_FACTOR = COL_UNITS + 1;

        /**
         * The offset column.
         */
        private static final int COL_OFFSET = COL_FACTOR + 1;

        /**
         * The minimum column.
         */
        private static final int COL_MIN = COL_OFFSET + 1;

        /**
         * The maximum column.
         */
        private static final int COL_MAX = COL_MIN + 1;

        /**
         * Constructor.
         */
        ArduinoSignalModel() {
        }

        /**
         * Refresh.
         */
        void refresh() {
            /* Clear the signals */
            theSignals.clear();

            /* Add the signals */
            theSignals.addAll(theMessage.getAllSignals());

            /* load the values and bitMaps */
            loadValues();
            loadBitMaps();

            /* Refresh the table */
            fireTableDataChanged();
        }

        /**
         * loadBitMaps.
         */
        private void loadBitMaps() {
            /* Clear the maps */
            theBitMaps.clear();

            /* Loop through the signals */
            for (ArduinoSignal mySignal : theSignals) {
                /* Access the value and store into map */
                final byte[] myBitMap = getBitMapForSignal(mySignal);
                theBitMaps.add(myBitMap);
            }
        }

        @Override
        public int getRowCount() {
            return theSignals.size();
        }

        @Override
        public String getColumnName(final int pColIndex) {
            switch (pColIndex) {
                case COL_SIGNAL:
                    return "Signal";
                case COL_VALUE:
                    return "Value";
                case COL_START:
                    return "S";
                case COL_LENGTH:
                    return "L";
                case COL_ENDIAN:
                    return "E";
                case COL_MULTI:
                    return "M";
                case COL_COLOR:
                    return "Color";
                case COL_UNITS:
                    return "Units";
                case COL_FACTOR:
                    return "Factor";
                case COL_OFFSET:
                    return "Offset";
                case COL_MIN:
                    return "Min";
                case COL_MAX:
                    return "Max";
                default:
                    return null;
            }
        }

        @Override
        public int getColumnCount() {
            return COL_MAX + 1;
        }

        @Override
        public boolean isCellEditable(final int pRowIndex,
                                      final int pColIndex) {
            final ArduinoSignal mySignal = theSignals.get(pRowIndex);
            return pColIndex == COL_VALUE && theValues.get(mySignal) != null;
        }

        @Override
        public Object getValueAt(final int pRowIndex,
                                 final int pColIndex) {
            final ArduinoSignal mySignal = theSignals.get(pRowIndex);
            final ArduinoSignalDefinition myDef = mySignal.getDefinition();
            final ArduinoSignalFactor myFactor = mySignal.getFactor();
            final ArduinoSignalRange myRange = mySignal.getRange();
            final long myMId = mySignal.getMultiplexId();
            switch (pColIndex) {
                case COL_SIGNAL:
                    return mySignal.getName();
                case COL_VALUE:
                    return theValues.get(mySignal);
                case COL_START:
                    return myDef.getStartBit();
                case COL_LENGTH:
                    return myDef.getBitLength();
                case COL_ENDIAN:
                    return myDef.getDataType().isLittleEndian()
                           ? "L"
                           : "B";
                case COL_MULTI:
                    if (mySignal.isMultiplex()) {
                        return "M";
                    }
                    return myMId == -1
                           ? null
                           : myMId;
                case COL_COLOR:
                    return theColours.get(mySignal);
                case COL_UNITS:
                    return mySignal.getUnits();
                case COL_FACTOR:
                    return myFactor.getFactor();
                case COL_OFFSET:
                    return myFactor.getOffset();
                case COL_MIN:
                    return myRange.unBounded()
                           ? null
                           : myRange.getMinimum();
                case COL_MAX:
                    return myRange.unBounded()
                           ? null
                           : myRange.getMaximum();
                default:
                    return null;
            }
        }

        @Override
        public void setValueAt(final Object pValue,
                               final int pRowIndex,
                               final int pColIndex) {
            /* Access signal and set value */
            final ArduinoSignal mySignal = theSignals.get(pRowIndex);
            setValueForSignal(mySignal, (Number) pValue);

            /* reload values */
            loadValues();

            /* Update the hex and binary */
            theOwner.updateHexAndBinary();
            theOwner.updateBitColors();
        }

        /**
         * Obtain bitMap for signal.
         *
         * @param pSignal the signal
         * @return the value
         */
        private byte[] getBitMapForSignal(final ArduinoSignal pSignal) {
            /* Access the data buffer */
            final byte[] myData = new byte[theMessage.getLength()];

            /* Access the raw value */
            final ArduinoSignalDefinition myDef = pSignal.getDefinition();
            if (myDef.getDataType().isLittleEndian()) {
                ArduinoField.setLEFieldBits(myData, myDef.getStartBit(), myDef.getBitLength());
            } else {
                ArduinoField.setBEFieldBits(myData, myDef.getStartBit(), myDef.getBitLength());
            }

            /* Return the bytes */
            return myData;
        }

        /**
         * Set value for signal.
         *
         * @param pSignal the signal
         * @param pValue  the value
         */
        private void setValueForSignal(final ArduinoSignal pSignal,
                                       final Number pValue) {
            /* Access the data buffer */
            final byte[] myData = theOwner.getData();

            /* Apply the adjustment */
            final ArduinoSignalFactor myFactor = pSignal.getFactor();
            final long myValue = myFactor.isFloat()
                                 ? (long) (ROUNDING + ((pValue.doubleValue() - myFactor.getOffset().doubleValue()) / myFactor.getFactor().doubleValue()))
                                 : (pValue.longValue() - myFactor.getOffset().longValue()) / myFactor.getFactor().longValue();

            /* Set the raw value */
            final ArduinoSignalDefinition myDef = pSignal.getDefinition();
            if (myDef.getDataType().isLittleEndian()) {
                ArduinoField.setLEField(myData, myDef.getStartBit(), myDef.getBitLength(), myValue);
            } else {
                ArduinoField.setBEField(myData, myDef.getStartBit(), myDef.getBitLength(), myValue);
            }
        }
    }

    /**
     * Color Renderer.
     */
    private static class ColorRenderer extends DefaultTableCellRenderer {
        @Override
        public void setValue(final Object pValue) {
            setBackground((Color) pValue);
        }
    }

    /**
     * Simple Alignment Renderer.
     */
    private class AlignmentRenderer extends DefaultTableCellRenderer {
        /**
         * Serial Id.
         */
        private static final long serialVersionUID = -6387123991399458414L;

        /**
         * Number format.
         */
        private final NumberFormat myFormatter = NumberFormat.getInstance();

        /**
         * Constructor.
         *
         * @param pAlignment the alignment
         */
        AlignmentRenderer(final int pAlignment) {
            setHorizontalAlignment(pAlignment);
        }

        @Override
        public JComponent getTableCellRendererComponent(final JTable pTable,
                                                        final Object pValue,
                                                        final boolean pSelected,
                                                        final boolean hasFocus,
                                                        final int pRow,
                                                        final int pCol) {
            /* Determine the active signal */
            final int myRow = pTable.convertRowIndexToModel(pRow);
            final ArduinoSignal mySignal = theSignals.get(myRow);

            /* Obtain the string representation */
            String myValue = pValue == null
                             ? null
                             : pValue.toString();

            /* If this is a double */
            if (pValue instanceof Double) {
                /* Find out how many decimals we have */
                final int myNumDecimals = mySignal.getFactor().getNumDecimals();
                myFormatter.setMaximumFractionDigits(myNumDecimals);
                myFormatter.setMinimumFractionDigits(myNumDecimals);

                /* Truncate the decimals */
                myValue = myFormatter.format(((Double) pValue).doubleValue());
            }

            /* Set the value */
            setText(myValue);

            /* return this as the component */
            return this;
        }
    }

    /**
     * Data Cell Editor.
     */
    private class DataCellEditor
            extends AbstractCellEditor
            implements TableCellEditor {
        /**
         * Serial Id.
         */
        private static final long serialVersionUID = 7142514324836088035L;

        /**
         * Are we in the middle of stopping edit?
         */
        private final JTextField theEditor;

        /**
         * Number format.
         */
        private final NumberFormat myFormatter = NumberFormat.getInstance();

        /**
         * The signal.
         */
        private transient ArduinoSignal theSignal;

        /**
         * The edited value.
         */
        private Number theValue;

        /**
         * Are we in the middle of stopping edit?
         */
        private boolean stoppingEdit;

        /**
         * Constructor.
         */
        DataCellEditor() {
            theEditor = new JTextField();
            theEditor.setHorizontalAlignment(SwingConstants.CENTER);
            theEditor.addFocusListener(new FocusListener() {
                @Override
                public void focusLost(final FocusEvent pE) {
                    /* Do nothing */
                }
                @Override
                public void focusGained(final FocusEvent pE) {
                    theEditor.selectAll();
                }
            });
        }

        @Override
        public Number getCellEditorValue() {
            return theValue;
        }

        @Override
        public Component getTableCellEditorComponent(final JTable pTable,
                                                     final Object pValue,
                                                     final boolean pSelected,
                                                     final int pRow,
                                                     final int pCol) {
            /* Determine the active signal */
            final int myRow = pTable.convertRowIndexToModel(pRow);
            theSignal = theSignals.get(myRow);

            /* Format the value */
            String myValue = null;
            if (pValue instanceof Double) {
                /* Find out how many decimals we have */
                final int myNumDecimals = theSignal.getFactor().getNumDecimals();
                myFormatter.setMaximumFractionDigits(myNumDecimals);
                myFormatter.setMinimumFractionDigits(myNumDecimals);
                myValue = myFormatter.format(((Number) pValue).doubleValue());
            } else if (pValue instanceof Long) {
                myValue = pValue.toString();
            }

            /* Set field value and start edit */
            theEditor.setText(myValue);
            theEditor.setBorder(BORDER_NORMAL);

            /* Return the field */
            return theEditor;
        }

        @Override
        public boolean stopCellEditing() {
            /* If we are already stopping edit */
            if (stoppingEdit) {
                return true;
            }

            /* Parse the data */
            stoppingEdit = true;
            try {
                theValue = myFormatter.parse(theEditor.getText());

                /* Catch parse exceptions */
            } catch (ParseException e) {
                /* Return status and set error border */
                stoppingEdit = false;
                theEditor.setBorder(BORDER_ERROR);
                return false;
            }

            /* Make sure that the value is the correct dataType */
            if (theValue instanceof Long && theSignal.isFloat()) {
                theValue = theValue.doubleValue();
            }
            if (theValue instanceof Double && !theSignal.isFloat()) {
                theValue = theValue.longValue();
            }

            /* If there is no error */
            boolean bComplete = false;
            if (checkValueForSignal(theSignal, theValue)) {
                /* Pass call onwards */
                bComplete = super.stopCellEditing();

                /* else set error border */
            } else {
                theEditor.setBorder(BORDER_ERROR);
            }

            /* Return status */
            stoppingEdit = false;
            return bComplete;
        }

        /**
         * Check value for signal.
         *
         * @param pSignal the signal
         * @param pValue  the value
         * @return valid true/false
         */
        private boolean checkValueForSignal(final ArduinoSignal pSignal,
                                            final Number pValue) {
            /* If we are unbounded, just return OK */
            final ArduinoSignalRange myRange = pSignal.getRange();
            if (myRange.unBounded()) {
                return true;
            }

            /* Check the value */
            return pValue instanceof Double
                   ? pValue.doubleValue() >= myRange.getMinimum().doubleValue()
                           && pValue.doubleValue() <= myRange.getMaximum().doubleValue()
                   : pValue.longValue() >= myRange.getMinimum().longValue()
                           && pValue.longValue() <= myRange.getMaximum().longValue();
        }
    }

    /**
     * RowFilter class.
     */
    private class MultiFilter extends RowFilter<ArduinoSignalModel, Integer> {
        @Override
        public boolean include(final Entry<? extends ArduinoSignalModel, ? extends Integer> entry) {
            /* Always show if this message has no multiplex */
            final ArduinoSignal myMulti = theMessage.getMultiplexSignal();
            if (myMulti == null) {
                return true;
            }

            /* Access the signal and show if it is non-multiplex or matches the multiplex */
            final ArduinoSignal mySignal = theSignals.get(entry.getIdentifier());
            final Long myMId = mySignal.getMultiplexId();
            return myMId.equals(ArduinoSignal.MULTI_NONE)
                    || myMId.equals(theValues.get(myMulti));
        }
    }
}