001package jmri.jmrix.can.cbus;
002
003import java.util.EnumSet;
004
005import javax.annotation.CheckForNull;
006import javax.annotation.Nonnull;
007import javax.swing.JLabel;
008import javax.swing.tree.DefaultMutableTreeNode;
009
010import jmri.jmrix.AbstractMessage;
011import jmri.jmrix.can.cbus.swing.CbusFilterTreePane;
012
013
014/**
015 * Class to implement filtering of CBUS frames.
016 * Long event OPCs are not altered for a node number of 0
017 * @author Steve Young (C) 2018, 2020
018 */
019public class CbusFilter {
020
021    private final java.util.List<FilterHolder> list = new java.util.concurrent.CopyOnWriteArrayList<>();
022
023    private final CbusFilterTreePane filterTreePane;
024    private int _evMin = 0;
025    private int _evMax = 65535;
026    private int _ndMin = 0;
027    private int _ndMax = 65535;
028
029    private long filteredMessage = 0;
030    private long filteredReply = 0;
031    private long passedMessage = 0;
032    private long passedReply = 0;
033
034    public static final int CFMAXCATS =CbusFilterType.values().length;
035
036    public static final String ROOT_NODE_TEXT = Bundle.getMessage("FilterAllFrames");
037
038    /**
039     * Creates a new instance of CbusFilter
040     * @param filterPane The Instance Pane
041     */
042    public CbusFilter(CbusFilterTreePane filterPane) {
043        filterTreePane = filterPane;
044        getTree();
045    }
046
047    /**
048     * Filter CanMessage or CanReply.
049     *
050     * @param test Message to Test
051     * @return Filter number which failed, else -1
052     */
053    public int filter( @Nonnull AbstractMessage test) {
054
055        int nodeNum = CbusMessage.getNodeNumber(test);
056        if ( nodeNum > 0 ) {
057            int nodeFilter = checknode(nodeNum);
058            if ( nodeFilter > -1 ) {
059                incrementTotals(test, nodeFilter);
060                return nodeFilter;
061            }
062        }
063
064        for (CbusFilterType singleFilter : CbusFilterType.allFilters(test.getElement(0))) {
065            int toReturn = singleFilter.action(test,this);
066            if ( toReturn>-1){
067                incrementTotals(test, toReturn);
068                return toReturn;
069            }
070            else if ( toReturn==-2){ // Extended or RTR CAN Frame, No OPC's to filter.
071                break;
072            }
073        }
074        incrementTotals(test, -1);
075        return -1;
076    }
077
078    private void incrementTotals( @Nonnull AbstractMessage test, int result){
079        incrementCount(result);
080        if ( test instanceof jmri.jmrix.can.CanReply ) {
081            if ( result > -1 ) {
082                this.filteredReply++;
083            } else {
084                this.passedReply++;
085            }
086        } else {
087            if ( result > -1 ) {
088                this.filteredMessage++;
089            } else {
090                this.passedMessage++;
091            }
092        }
093    }
094
095    public void setFiltersByName( java.util.Set<String> activeFilters ) {
096        list.forEach( f -> f.setActive(activeFilters.contains(f.node.toString())));
097    }
098
099    public boolean isFilterActive(int filterNum) {
100        return list.get(filterNum).active;
101    }
102
103    /**
104     * Perform Node checks for a given node number.
105     * If a new Node Number is found, is added to the main Node List
106     * and a new filter created.
107     *
108     * @param node Node Number
109     * @return Filter number which failed, else -1
110     */
111    private int checknode(int node ) {
112
113        String nodeNum = String.valueOf(node);
114
115        FilterHolder fh = getFilterHolder(nodeNum);
116        if ( fh == null ) {
117
118            DefaultMutableTreeNode snode = new DefaultMutableTreeNode(nodeNum);
119            fh = new FilterHolder(snode, node);
120            list.add(fh);
121
122            DefaultMutableTreeNode ndnd = this.getTreeNode(CbusFilterType.CFNODE);
123            ndnd.add(snode);
124
125            if (filterTreePane !=null) {
126                filterTreePane.reset();
127            }
128        }
129
130        if ( list.get(CbusFilterType.CFNODE.ordinal()).active){
131            return CbusFilterType.CFNODE.ordinal();
132        }
133
134        if ( fh.active ){
135            return list.indexOf(fh);
136        }
137        return -1;
138    }
139
140    @CheckForNull
141    private FilterHolder getFilterHolder( @Nonnull String treeNodeName) {
142        for ( FilterHolder fh : list ) {
143            if ( treeNodeName.equals(fh.node.toString()) ) {
144                return fh;
145            }
146        }
147        return null;
148    }
149
150    /**
151     * Increment Filter count for a given filter ID.
152     * @param filternum Filter ID
153     */
154    private void incrementCount(int filternum ){
155        if ( filternum >= 0 ) {
156            FilterHolder node = list.get(filternum);
157            if ( node != null ) {
158                node.filterCount++;
159            }
160        }
161    }
162
163    /**
164     * Set a single Filter to pass or filter.
165     * @param id Filter ID
166     * @param trueorfalse true to filter, false to pass through.
167     */
168    public void setFilter(int id, boolean trueorfalse) {
169        list.get(id).setActive(trueorfalse);
170    }
171
172    /**
173     * Set the event or node min and max values.
174     *
175     * @param filter CFEVENTMIN, CFEVENTMAX, CFNODEMIN or CFNODEMAX
176     * @param val min or max value
177     */
178    public void setMinMax(@Nonnull CbusFilterType filter, int val){
179        switch (filter) {
180            case CFEVENTMIN:
181                _evMin = val;
182                break;
183            case CFEVENTMAX:
184                _evMax = val;
185                break;
186            case CFNODEMIN:
187                _ndMin = val;
188                break;
189            case CFNODEMAX:
190                _ndMax = val;
191                break;
192            default:
193                break;
194        }
195    }
196
197    public long getFilteredMessage(){
198        return filteredMessage;
199    }
200
201    public long getFilteredReply(){
202        return filteredReply;
203    }
204
205    public long getPassedMessage(){
206        return passedMessage;
207    }
208
209    public long getPassedReply(){
210        return passedReply;
211    }
212
213    /**
214     * Get Minimum Event Number.
215     * @return Minimum Event
216     */
217    public int getEvMin() {
218        return _evMin;
219    }
220
221    /**
222     * Get Maximum Event Number.
223     * @return Maximum Event
224     */
225    public int getEvMax() {
226        return _evMax;
227    }
228
229    /**
230     * Get Minimum Node Number.
231     * @return Minimum Node
232     */
233    public int getNdMin() {
234        return _ndMin;
235    }
236
237    /**
238     * Get Maximum Node Number.
239     * @return Maximum Node
240     */
241    public int getNdMax() {
242        return _ndMax;
243    }
244
245    private DefaultMutableTreeNode root;
246
247    public final synchronized DefaultMutableTreeNode getTree(){
248        if ( root == null ) {
249            root = new DefaultMutableTreeNode(ROOT_NODE_TEXT);
250            EnumSet.allOf(CbusFilterType.class).forEach( singleFilter -> {
251                DefaultMutableTreeNode snode = new DefaultMutableTreeNode(singleFilter.getName());
252                var category = singleFilter.getCategory();
253                if (  category == null ) {
254                    root.add(snode);
255                } else {
256                    getTreeNode(category).add(snode);
257                }
258                list.add( new FilterHolder(snode) );
259
260            });
261
262        }
263        return root;
264    }
265
266    @Nonnull
267    private DefaultMutableTreeNode getTreeNode( @Nonnull CbusFilterType type ) {
268        for ( FilterHolder f : list) {
269            if ( type.getName().equals(f.node.toString() ) ) {
270                return f.node;
271            }
272        }
273        throw new IllegalArgumentException("type Not found in list of nodes");
274    }
275
276    public void resetCounts() {
277        list.forEach( FilterHolder::resetCount);
278    }
279
280    @CheckForNull
281    public JLabel getNumberFilteredLabel( DefaultMutableTreeNode node ) {
282        FilterHolder f = getFilterHolder(node.toString());
283        if ( f != null ) {
284            return f.getCountLabel();
285        }
286        return null;
287    }
288
289    public int getNodeNumber( DefaultMutableTreeNode node ) {
290        FilterHolder f = getFilterHolder(node.toString());
291        if ( f != null ) {
292            return f.nodeNum;
293        }
294        return -1;
295    }
296
297    private static class FilterHolder {
298
299        final DefaultMutableTreeNode node;
300        boolean active;
301        int filterCount;
302        final int nodeNum;
303        private static final java.awt.Color COUNT_COLOUR = new java.awt.Color(139,0,0);
304
305        private FilterHolder(DefaultMutableTreeNode node) {
306            this.node = node;
307            nodeNum = -1;
308        }
309
310        private FilterHolder(DefaultMutableTreeNode node, int nodeNumber) {
311            this.node = node;
312            this.nodeNum = nodeNumber;
313        }
314
315        void resetCount() {
316            filterCount = 0;
317        }
318
319        JLabel getCountLabel() {
320            JLabel toRet = new JLabel( " " + filterCount + " ");
321            toRet.setForeground( COUNT_COLOUR );
322            return toRet;
323        }
324
325        void setActive( boolean newVal) {
326            active = newVal;
327        }
328
329    }
330
331    // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusFilter.class);
332
333}