001package jmri.implementation;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.List;
006import java.util.regex.Pattern;
007
008import javax.annotation.*;
009
010import jmri.*;
011import jmri.jmrit.beantable.LRouteTableAction;
012import jmri.jmrit.entryexit.EntryExitPairs;
013
014/**
015 * Class providing the basic logic of the Logix interface.
016 *
017 * @author Dave Duchamp Copyright (C) 2007
018 * @author Pete Cressman Copyright (C) 2009
019 */
020public class DefaultLogix extends AbstractNamedBean
021        implements Logix {
022
023    private final ConditionalManager conditionalManager;
024
025    public DefaultLogix(String systemName, String userName) {
026        this(systemName,userName,InstanceManager.getDefault(ConditionalManager.class));
027    }
028
029    public DefaultLogix(String systemName,String userName,ConditionalManager conditionalManager) {
030        super(systemName, userName);
031        this.conditionalManager = conditionalManager;
032    }
033
034    public DefaultLogix(String systemName) {
035        this(systemName,InstanceManager.getDefault(ConditionalManager.class));
036    }
037
038    public DefaultLogix(String systemName,ConditionalManager conditionalManager) {
039        super(systemName);
040        this.conditionalManager = conditionalManager;
041    }
042
043    @Override
044    @Nonnull
045    public String getBeanType() {
046        return Bundle.getMessage("BeanNameLogix");  // NOI18N
047    }
048
049    /**
050     * Persistant instance variables (saved between runs). Order is significant.
051     */
052    private final ArrayList<String> _conditionalSystemNames = new ArrayList<>();
053    private ArrayList<JmriSimplePropertyListener> _listeners = new ArrayList<>();
054
055    /**
056     * Maintain a list of conditional objects.  The key is the conditional system name
057     * @since 4.7.4
058     */
059    private final HashMap<String, Conditional> _conditionalMap = new HashMap<>();
060
061    /**
062     * Operational instance variables (not saved between runs)
063     */
064    private boolean mEnabled = true;
065
066    private boolean _isActivated = false;
067
068    private boolean _isGuiSet = false;
069
070    /**
071     * Get number of Conditionals for this Logix
072     */
073    @Override
074    public int getNumConditionals() {
075        return _conditionalSystemNames.size();
076    }
077
078    /**
079     * Move 'row' to 'nextInOrder' and shift all between 'row' and 'nextInOrder'
080     * up one position {@literal ( row > nextInOrder )}
081     */
082    @Override
083    public void swapConditional(int nextInOrder, int row) {
084        if (row <= nextInOrder) {
085            return;
086        }
087        String temp = _conditionalSystemNames.get(row);
088        for (int i = row; i > nextInOrder; i--) {
089            _conditionalSystemNames.set(i, _conditionalSystemNames.get(i - 1));
090        }
091        _conditionalSystemNames.set(nextInOrder, temp);
092    }
093
094    /**
095     * Returns the system name of the conditional that will calculate in the
096     * specified order. This is also the order the Conditional is listed in the
097     * Add/Edit Logix dialog. If 'order' is greater than the number of
098     * Conditionals for this Logix, null is returned.
099     *
100     * @param order  order in which the Conditional calculates.
101     */
102    @Override
103    @CheckForNull
104    public String getConditionalByNumberOrder(int order) {
105        try {
106            return _conditionalSystemNames.get(order);
107        } catch (java.lang.IndexOutOfBoundsException ioob) {
108            return null;
109        }
110    }
111
112    /**
113     * Add a Conditional to this Logix R
114     *
115     * @param systemName The Conditional system name
116     * @param order       the order this conditional should calculate in if
117     *                   order is negative, the conditional is added at the end
118     *                   of current group of conditionals
119     */
120    @Override
121    public void addConditional(String systemName, int order) {
122        _conditionalSystemNames.add(systemName);
123    }
124
125    /**
126     * Add a child Conditional to the parent Logix.
127     *
128     * @since 4.7.4
129     * @param systemName The system name for the Conditional object.
130     * @param conditional The Conditional object.
131     * @return true if the Conditional was added, false otherwise.
132     */
133    @Override
134    public boolean addConditional(String systemName, Conditional conditional) {
135        Conditional chkDuplicate = _conditionalMap.putIfAbsent(systemName, conditional);
136        if (chkDuplicate == null) {
137            return true;
138        }
139        log.error("Conditional '{}' has already been added to Logix '{}'", systemName, getSystemName());  // NOI18N
140        return false;
141    }
142
143    /**
144     * Get a Conditional belonging to this Logix.
145     *
146     * @since 4.7.4
147     * @param systemName The name of the Conditional object.
148     * @return the Conditional object or null if not found.
149     */
150    @Override
151    @CheckForNull
152    public Conditional getConditional(String systemName) {
153        return _conditionalMap.get(systemName);
154    }
155
156    /**
157     * Set enabled status. Enabled is a bound property All conditionals are set
158     * to UNKNOWN state and recalculated when the Logix is enabled, provided the
159     * Logix has been previously activated.
160     * <p>
161     * Enabling a Logix that has not been activated (for example one loaded
162     * disabled) activates it, so that a later deActivateLogix() removes its
163     * listeners again.
164     */
165    @Override
166    public void setEnabled(boolean state) {
167
168        boolean old = mEnabled;
169        mEnabled = state;
170        if (old != state) {
171            if (_isActivated || state) {
172                deActivateLogix();
173                activateLogix();
174            }
175            for (int i = _listeners.size() - 1; i >= 0; i--) {
176                _listeners.get(i).setEnabled(state);
177            }
178            firePropertyChange(PROPERTY_ENABLED, old, state);
179        }
180    }
181
182    /**
183     * Get enabled status
184     */
185    @Override
186    public boolean getEnabled() {
187        return mEnabled;
188    }
189
190    /**
191     * Delete a Conditional and remove it from this Logix
192     * <p>
193     * Note: Since each Logix must have at least one Conditional to do anything,
194     * the user is warned in Logix Table Action when the last Conditional is
195     * deleted.
196     *
197     * @param systemName The Conditional system name
198     * @return null if Conditional was successfully deleted or not present, otherwise
199     * returns a string array list of current usage that prevent deletion, used to present
200     * a warning dialog to the user
201     */
202    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS",
203    justification = "null returned is documented in each method to mean completed without problems")
204    @Override
205    public String[] deleteConditional(String systemName) {
206        if (_conditionalSystemNames.isEmpty()) {
207            return null;
208        }
209
210        // check other Logix(es) for use of this conditional (systemName) for use as a
211        // variable in one of their conditionals
212        ArrayList<String> checkReferences = conditionalManager.getWhereUsed(systemName);
213        if (checkReferences != null) {
214            Conditional c = getConditional(systemName);
215            String refName = checkReferences.get(0);
216            Logix x = conditionalManager.getParentLogix(refName);
217            if ( x == null ) {
218                log.error("Unable to get Parent Logix {} while deleting Conditional {}",
219                    refName, systemName);
220                return null;
221            }
222            Conditional cRef = x.getConditional(refName);
223            return new String[]{c.getUserName(), c.getSystemName(), cRef.getUserName(),
224                cRef.getSystemName(), x.getUserName(), x.getSystemName()};
225        }
226
227        // Confirm the presence of the Conditional object
228        Conditional c = conditionalManager.getBySystemName(systemName);
229        if (c == null) {
230            log.error("attempt to delete non-existing Conditional - {}", systemName);  // NOI18N
231            return null;
232        }
233
234        // Remove Conditional from this logix
235        if (!_conditionalSystemNames.remove(systemName)) {
236            log.error("attempt to delete Conditional not in Logix: {}", systemName);  // NOI18N
237            return null;
238        }
239
240        _conditionalMap.remove(systemName);
241        return null;
242    }
243
244    /**
245     * Calculate all Conditionals, triggering action if the user specified
246     * conditions are met, and the Logix is enabled.
247     */
248    @Override
249    public void calculateConditionals() {
250        for (String conditionalSystemName : _conditionalSystemNames) {
251            Conditional c = getConditional(conditionalSystemName);
252            if (c == null) {
253                log.error("Invalid conditional system name when calculating Logix - {}",
254                    conditionalSystemName);
255            } else {
256                // calculate without taking any action unless Logix is enabled
257                c.calculate(mEnabled, null);
258            }
259        }
260    }
261
262    /**
263     * Activate the Logix, starts Logix processing by connecting all inputs that
264     * are included the Conditionals in this Logix.
265     * <p>
266     * A Logix must be activated before it will calculate any of its
267     * Conditionals.
268     */
269    @Override
270    public void activateLogix() {
271        // if the Logix is already busy, simply return
272        if (_isActivated) {
273            return;
274        }
275        // set the state of all Conditionals to UNKNOWN
276        resetConditionals();
277        // assemble a list of needed listeners
278        assembleListenerList();
279        // create and attach the needed property change listeners
280        // start a minute Listener if needed
281        for (JmriSimplePropertyListener listener : _listeners) {
282            startListener(listener);
283        }
284        // mark this Logix as busy
285        _isActivated = true;
286        // calculate this Logix to set initial state of Conditionals
287        calculateConditionals();
288    }
289
290    private void resetConditionals() {
291        for (String conditionalSystemName : _conditionalSystemNames) {
292            Conditional conditional = getConditional(conditionalSystemName);
293            if (conditional != null) {
294                try {
295                    conditional.setState(NamedBean.UNKNOWN);
296                } catch (JmriException ignore) {
297                }
298            }
299        }
300    }
301
302    // Pattern to check for new style NX system name
303    static final Pattern NXUUID = Pattern.compile(
304        "^IN:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",   // NOI18N
305        Pattern.CASE_INSENSITIVE);
306
307    /**
308     * ConditionalVariables only have a single name field.  For user interface purposes
309     * a gui name is used for the referenced conditional user name.  This is not used
310     * for other object types.
311     * <p>
312     * In addition to setting the GUI name, any state variable references are changed to
313     * conditional system names.  This converts the XML system/user name field to the system name
314     * for conditional references.  It does not affect other objects such as sensors, turnouts, etc.
315     * <p>
316     * For Entry/Exit references, replace NX user names and old style NX UUID references
317     * with the new style "IN:" + UUID reference.  If the referenced NX does not exist,
318     * it will be removed from the Variable or Action list. (4.11.4)
319     * <p>
320     * Called by {@link jmri.managers.DefaultLogixManager#activateAllLogixs}
321     * @since 4.7.4
322     */
323    @Override
324    public void setGuiNames() {
325        if (_isGuiSet) {
326            return;
327        }
328        if ("SYS".equals(getSystemName())) {
329            _isGuiSet = true;
330            return;
331        }
332        for (String cName : _conditionalSystemNames) {
333            Conditional conditional = getConditional(cName);
334            if (conditional == null) {
335                // A Logix index entry exists without a corresponding conditional.  This
336                // should never happen.
337                log.error("setGuiNames: Missing conditional for Logix index entry, "
338                    + " Logix name = '{}', Conditional index name = '{}'",  // NOI18N
339                        getSystemName(), cName);
340                continue;
341            }
342            List<ConditionalVariable> varList = conditional.getCopyOfStateVariables();
343            boolean isDirty = false;
344            ArrayList<ConditionalVariable> badVariable = new ArrayList<>();
345            for (ConditionalVariable condVar : varList) {
346                // Find any Conditional State Variables
347                if (condVar.getType() == Conditional.Type.CONDITIONAL_TRUE
348                        || condVar.getType() == Conditional.Type.CONDITIONAL_FALSE) {
349                    // Get the referenced (target) conditonal -- The name can be either a system name or a user name
350                    Conditional cRef = conditionalManager.getConditional(condVar.getName());
351                    if (cRef != null) {
352                        // re-arrange names as needed
353                        condVar.setName(cRef.getSystemName()); // The state variable reference is now a conditional system name
354                        String uName = cRef.getUserName();
355                        if (uName == null || uName.isEmpty()) {
356                            condVar.setGuiName(cRef.getSystemName());
357                        } else {
358                            condVar.setGuiName(uName);
359                        }
360                        // Add the conditional reference to the where used map
361                        conditionalManager.addWhereUsed(condVar.getName(), cName);
362                        isDirty = true;
363                    } else {
364                        log.error("setGuiNames: For conditional '{}' in logix '{}', "
365                            + "the referenced conditional, '{}',  does not exist",
366                                cName, getSystemName(), condVar.getName());
367                    }
368                }
369
370                // Find any Entry/Exit State Variables
371                if (condVar.getType() == Conditional.Type.ENTRYEXIT_ACTIVE
372                        || condVar.getType() == Conditional.Type.ENTRYEXIT_INACTIVE) {
373                    if (!NXUUID.matcher(condVar.getName()).find()) {
374                        // Either a user name or an old style system name (plain UUID)
375                        jmri.jmrit.entryexit.DestinationPoints dp =
376                            InstanceManager.getDefault(EntryExitPairs.class).
377                                getNamedBean(condVar.getName());
378                        if (dp != null) {
379                            // Replace name with current system name
380                            condVar.setName(dp.getSystemName());
381                            isDirty = true;
382                        } else {
383                            log.error("setGuiNames: For conditional '{}' in logix '{}', "
384                                + "the referenced Entry Exit Pair, '{}',  does not exist",
385                                    cName, getSystemName(), condVar.getName());
386                            badVariable.add(condVar);
387                        }
388                    }
389                }
390            }
391            if (!badVariable.isEmpty()) {
392                isDirty = true;
393                badVariable.forEach(varList::remove);
394            }
395            if (isDirty) {
396                conditional.setStateVariables(varList);
397            }
398
399            List<ConditionalAction> actionList = conditional.getCopyOfActions();
400            isDirty = false;
401            ArrayList<ConditionalAction> badAction = new ArrayList<>();
402            for (ConditionalAction action : actionList) {
403                // Find any Entry/Exit Actions
404                if (action.getType() == Conditional.Action.SET_NXPAIR_ENABLED
405                    || action.getType() == Conditional.Action.SET_NXPAIR_DISABLED
406                        || action.getType() == Conditional.Action.SET_NXPAIR_SEGMENT) {
407                    if (!NXUUID.matcher(action.getDeviceName()).find()) {
408                        // Either a user name or an old style system name (plain UUID)
409                        jmri.jmrit.entryexit.DestinationPoints dp =
410                            InstanceManager.getDefault(EntryExitPairs.class).
411                                getNamedBean(action.getDeviceName());
412                        if (dp != null) {
413                            // Replace name with current system name
414                            action.setDeviceName(dp.getSystemName());
415                            isDirty = true;
416                        } else {
417                            log.error("setGuiNames: For conditional '{}' in logix '{}', "
418                                + "the referenced Entry Exit Pair, '{}',  does not exist",  // NOI18N
419                                    cName, getSystemName(), action.getDeviceName());
420                            badAction.add(action);
421                        }
422                    }
423                }
424            }
425            if (!badAction.isEmpty()) {
426                isDirty = true;
427                badAction.forEach(actionList::remove);
428            }
429            if (isDirty) {
430                conditional.setAction(actionList);
431            }
432        }
433        _isGuiSet = true;
434    }
435
436    /**
437     * Assemble a list of Listeners needed to activate this Logix.
438     */
439    private void assembleListenerList() {
440        // initialize by cleaning up
441        // start from end down to safely delete preventing concurrent modification ex
442        for (int i = _listeners.size() - 1; i >= 0; i--) {
443            removeListener(_listeners.get(i));
444        }
445        _listeners = new ArrayList<>();
446        // cycle thru Conditionals to find objects to listen to
447        for (int i = 0; i < _conditionalSystemNames.size(); i++) {
448            Conditional conditional = getConditional(_conditionalSystemNames.get(i));
449            if (conditional != null) {
450                List<ConditionalVariable> variableList = conditional.getCopyOfStateVariables();
451                for (ConditionalVariable variable : variableList) {
452                    // check if listening for a change has been suppressed
453                    int varListenerType = 0;
454                    String varName = variable.getName();
455                    NamedBeanHandle<?> namedBean = variable.getNamedBean();
456                    Conditional.Type varType = variable.getType();
457                    int signalAspect = -1;
458                    // Get Listener type from variable type
459                    switch (varType) {
460                        case SENSOR_ACTIVE:
461                        case SENSOR_INACTIVE:
462                            varListenerType = LISTENER_TYPE_SENSOR;
463                            break;
464                        case TURNOUT_THROWN:
465                        case TURNOUT_CLOSED:
466                            varListenerType = LISTENER_TYPE_TURNOUT;
467                            break;
468                        case CONDITIONAL_TRUE:
469                        case CONDITIONAL_FALSE:
470                            varListenerType = LISTENER_TYPE_CONDITIONAL;
471                            break;
472                        case LIGHT_ON:
473                        case LIGHT_OFF:
474                            varListenerType = LISTENER_TYPE_LIGHT;
475                            break;
476                        case MEMORY_EQUALS:
477                        case MEMORY_COMPARE:
478                        case MEMORY_EQUALS_INSENSITIVE:
479                        case MEMORY_COMPARE_INSENSITIVE:
480                            varListenerType = LISTENER_TYPE_MEMORY;
481                            break;
482                        case ROUTE_FREE:
483                        case ROUTE_OCCUPIED:
484                        case ROUTE_ALLOCATED:
485                        case ROUTE_SET:
486                        case TRAIN_RUNNING:
487                            varListenerType = LISTENER_TYPE_WARRANT;
488                            break;
489                        case FAST_CLOCK_RANGE:
490                            varListenerType = LISTENER_TYPE_FASTCLOCK;
491                            varName = "clock";  // NOI18N
492                            break;
493                        case SIGNAL_HEAD_RED:
494                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
495                            signalAspect = SignalHead.RED;
496                            break;
497                        case SIGNAL_HEAD_YELLOW:
498                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
499                            signalAspect = SignalHead.YELLOW;
500                            break;
501                        case SIGNAL_HEAD_GREEN:
502                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
503                            signalAspect = SignalHead.GREEN;
504                            break;
505                        case SIGNAL_HEAD_DARK:
506                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
507                            signalAspect = SignalHead.DARK;
508                            break;
509                        case SIGNAL_HEAD_LUNAR:
510                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
511                            signalAspect = SignalHead.LUNAR;
512                            break;
513                        case SIGNAL_HEAD_FLASHRED:
514                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
515                            signalAspect = SignalHead.FLASHRED;
516                            break;
517                        case SIGNAL_HEAD_FLASHYELLOW:
518                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
519                            signalAspect = SignalHead.FLASHYELLOW;
520                            break;
521                        case SIGNAL_HEAD_FLASHGREEN:
522                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
523                            signalAspect = SignalHead.FLASHGREEN;
524                            break;
525                        case SIGNAL_HEAD_FLASHLUNAR:
526                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
527                            signalAspect = SignalHead.FLASHLUNAR;
528                            break;
529                        case SIGNAL_HEAD_LIT:
530                        case SIGNAL_HEAD_HELD:
531                            varListenerType = LISTENER_TYPE_SIGNALHEAD;
532                            break;
533                        case SIGNAL_MAST_ASPECT_EQUALS:
534                        case SIGNAL_MAST_LIT:
535                        case SIGNAL_MAST_HELD:
536                            varListenerType = LISTENER_TYPE_SIGNALMAST;
537                            break;
538                        case BLOCK_STATUS_EQUALS:
539                            varListenerType = LISTENER_TYPE_OBLOCK;
540                            break;
541                        case ENTRYEXIT_ACTIVE:
542                        case ENTRYEXIT_INACTIVE:
543                            varListenerType = LISTENER_TYPE_ENTRYEXIT;
544                            break;
545                        default:
546                            if (!LRouteTableAction.getLogixInitializer().equals(varName)) {
547                                log.warn("Unhandled conditional variable type: {}", varType);  // NOI18N
548                            }
549                            break;
550                    }
551                    int positionOfListener = getPositionOfListener(varListenerType, varType, varName);
552                    // add to list if new
553                    JmriSimplePropertyListener listener;
554                    if (positionOfListener == -1) {
555                        switch (varListenerType) {
556                            case LISTENER_TYPE_SENSOR:
557                                listener = new JmriTwoStatePropertyListener(Sensor.PROPERTY_KNOWN_STATE,
558                                    LISTENER_TYPE_SENSOR, namedBean, varType, conditional);
559                                break;
560                            case LISTENER_TYPE_TURNOUT:
561                                listener = new JmriTwoStatePropertyListener(Turnout.PROPERTY_KNOWN_STATE,
562                                    LISTENER_TYPE_TURNOUT, namedBean, varType, conditional);
563                                break;
564                            case LISTENER_TYPE_CONDITIONAL:
565                                listener = new JmriTwoStatePropertyListener(Conditional.PROPERTY_KNOWN_STATE,
566                                    LISTENER_TYPE_CONDITIONAL, namedBean, varType, conditional);
567                                break;
568                            case LISTENER_TYPE_LIGHT:
569                                listener = new JmriTwoStatePropertyListener(Light.PROPERTY_KNOWN_STATE,
570                                    LISTENER_TYPE_LIGHT, namedBean, varType, conditional);
571                                break;
572                            case LISTENER_TYPE_MEMORY:
573                                listener = new JmriTwoStatePropertyListener(Memory.PROPERTY_VALUE,
574                                    LISTENER_TYPE_MEMORY, namedBean, varType, conditional);
575                                break;
576                            case LISTENER_TYPE_WARRANT:
577                                listener = new JmriSimplePropertyListener(null,
578                                    LISTENER_TYPE_WARRANT, namedBean, varType, conditional);
579                                break;
580                            case LISTENER_TYPE_FASTCLOCK:
581                                listener = new JmriClockPropertyListener(Timebase.PROPERTY_CHANGE_MINUTES,
582                                    LISTENER_TYPE_FASTCLOCK, varName, varType, conditional,
583                                        variable.getNum1(), variable.getNum2());
584                                break;
585                            case LISTENER_TYPE_SIGNALHEAD:
586                                if (signalAspect < 0) {
587                                    if (varType == Conditional.Type.SIGNAL_HEAD_LIT) {
588                                        listener = new JmriTwoStatePropertyListener(SignalHead.PROPERTY_LIT,
589                                            LISTENER_TYPE_SIGNALHEAD, namedBean, varType, conditional);
590                                    } else { // varType == Conditional.TYPE_SIGNAL_HEAD_HELD
591                                        listener = new JmriTwoStatePropertyListener(SignalHead.PROPERTY_HELD,
592                                            LISTENER_TYPE_SIGNALHEAD, namedBean, varType, conditional);
593                                    }
594                                } else {
595                                    listener = new JmriMultiStatePropertyListener(SignalHead.PROPERTY_APPEARANCE,
596                                        LISTENER_TYPE_SIGNALHEAD, namedBean, varType, conditional, signalAspect);
597                                }
598                                break;
599                            case LISTENER_TYPE_SIGNALMAST:
600                                switch (varType) {
601                                    case SIGNAL_MAST_LIT:
602                                        listener = new JmriTwoStatePropertyListener(SignalMast.PROPERTY_LIT,
603                                            LISTENER_TYPE_SIGNALMAST,  namedBean, varType, conditional);
604                                        break;
605                                    case SIGNAL_MAST_HELD:
606                                        listener = new JmriTwoStatePropertyListener(SignalMast.PROPERTY_HELD,
607                                            LISTENER_TYPE_SIGNALMAST, namedBean, varType, conditional);
608                                        break;
609                                    default:
610                                        listener = new JmriTwoStatePropertyListener(SignalMast.PROPERTY_ASPECT,
611                                            LISTENER_TYPE_SIGNALMAST, namedBean, varType, conditional);
612                                        break;
613                                }
614                                break;
615                            case LISTENER_TYPE_OBLOCK:
616                                listener = new JmriTwoStatePropertyListener(jmri.jmrit.logix.OBlock.PROPERTY_STATE,
617                                    LISTENER_TYPE_OBLOCK, namedBean, varType, conditional);
618                                break;
619                            case LISTENER_TYPE_ENTRYEXIT:
620                                listener = new JmriTwoStatePropertyListener(EntryExitPairs.PROPERTY_ACTIVE,
621                                    LISTENER_TYPE_ENTRYEXIT, namedBean, varType, conditional);
622                                break;
623                            default:
624                                if (!LRouteTableAction.getLogixInitializer().equals(varName)) {
625                                    log.error("Unknown (new) Variable Listener type= {}, for varName= {}, varType= {} in Conditional, {}",
626                                        varListenerType, varName, varType, _conditionalSystemNames.get(i));
627                                }
628                                continue;
629                        }
630                        _listeners.add(listener);
631                    } else {
632                        switch (varListenerType) {
633                            case LISTENER_TYPE_SENSOR:
634                            case LISTENER_TYPE_TURNOUT:
635                            case LISTENER_TYPE_CONDITIONAL:
636                            case LISTENER_TYPE_LIGHT:
637                            case LISTENER_TYPE_MEMORY:
638                            case LISTENER_TYPE_WARRANT:
639                            case LISTENER_TYPE_SIGNALMAST:
640                            case LISTENER_TYPE_OBLOCK:
641                            case LISTENER_TYPE_ENTRYEXIT:
642                                listener = _listeners.get(positionOfListener);
643                                listener.addConditional(conditional);
644                                break;
645                            case LISTENER_TYPE_FASTCLOCK:
646                                JmriClockPropertyListener cpl = (JmriClockPropertyListener) _listeners.get(positionOfListener);
647                                cpl.setRange(variable.getNum1(), variable.getNum2());
648                                cpl.addConditional(conditional);
649                                break;
650                            case LISTENER_TYPE_SIGNALHEAD:
651                                if (signalAspect < 0) {
652                                    listener = _listeners.get(positionOfListener);
653                                    listener.addConditional(conditional);
654                                } else {
655                                    JmriMultiStatePropertyListener mpl = (JmriMultiStatePropertyListener) _listeners.get(positionOfListener);
656                                    mpl.addConditional(conditional);
657                                    mpl.setState(signalAspect);
658                                }
659                                break;
660                            default:
661                                log.error("Unknown (old) Variable Listener type= {}, for varName= {}, varType= {} in Conditional, {}",
662                                        varListenerType, varName, varType, _conditionalSystemNames.get(i));
663                        }
664                    }
665                    // addition listeners needed for memory compare
666                    if (varType == Conditional.Type.MEMORY_COMPARE || varType == Conditional.Type.MEMORY_COMPARE_INSENSITIVE) {
667                        positionOfListener = getPositionOfListener(varListenerType, varType, variable.getDataString());
668                        if (positionOfListener == -1) {
669                            String name = variable.getDataString();
670                            try {
671                                Memory my = InstanceManager.memoryManagerInstance().provideMemory(name);
672                                NamedBeanHandle<?> nb = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, my);
673
674                                listener = new JmriTwoStatePropertyListener(Memory.PROPERTY_VALUE, LISTENER_TYPE_MEMORY,
675                                        nb, varType, conditional);
676                                _listeners.add(listener);
677                            } catch (IllegalArgumentException ex) {
678                                log.error("invalid memory name= \"{}\" in state variable", name);  // NOI18N
679                                break;
680                            }
681                        } else {
682                            listener = _listeners.get(positionOfListener);
683                            listener.addConditional(conditional);
684                        }
685                    }
686                }
687            } else {
688                log.error("invalid conditional system name in Logix \"{}\" "
689                        + "assembleListenerList DELETING {} from Conditional list.",
690                    getSystemName(), _conditionalSystemNames.get(i));
691                _conditionalSystemNames.remove(i);
692            }
693        }
694    }
695
696    private int getPositionOfListener(int varListenerType, Conditional.Type varType, String varName) {
697        // check if already in list
698        for (int j = 0; (j < _listeners.size()); j++) {
699            if (varListenerType == _listeners.get(j).getType()) {
700                if (varName.equals(_listeners.get(j).getDevName())) {
701                    if (varListenerType == LISTENER_TYPE_SIGNALHEAD) {
702                        if (varType == Conditional.Type.SIGNAL_HEAD_LIT
703                                || varType == Conditional.Type.SIGNAL_HEAD_HELD) {
704                            if (varType == _listeners.get(j).getVarType()) {
705                                return j;
706                            }
707                        } else if (SignalHead.PROPERTY_APPEARANCE.equals(_listeners.get(j).getPropertyName())) {
708                            // the Appearance Listener can handle all aspects
709                            return j;
710                        }
711                    } else {
712                        return j;
713                    }
714                }
715            }
716
717        }
718        return -1;
719    }
720
721    /* /**
722     * Assembles and returns a list of state variables that are used by
723     * conditionals of this Logix including the number of occurances of each
724     * variable that trigger a calculation, and the number of occurances where
725     * the triggering has been suppressed. The main use of this method is to
726     * return information that can be used to test for inconsistency in
727     * suppressing triggering of a calculation among multiple occurances of the
728     * same state variable. Caller provides an ArrayList of the variables to
729     * check and an empty Array list to return the counts for triggering or
730     * suppressing calculation. The first index is a count that the
731     * correspondeing variable triggers calculation and second is a count that
732     * the correspondeing variable suppresses Calculation. Note this method must
733     * not modify the supplied variable list in any way.
734     *
735     * public void getStateVariableList(ArrayList <ConditionalVariable> varList,
736     * ArrayList <int[]> triggerPair) { // initialize Conditional c = null;
737     * String testSystemName = ""; String testUserName = ""; String testVarName
738     * = ""; // cycle thru Conditionals to find state variables
739     * ConditionalManager cm = InstanceManager.getDefault(jmri.ConditionalManager.class); for
740     * (int i=0; i<_conditionalSystemNames.size(); i++) { c =
741     * cm.getBySystemName(_conditionalSystemNames.get(i)); if (c!=null) {
742     * ArrayList variableList = c.getCopyOfStateVariables(); for (int k = 0;
743     * k<variableList.size(); k++) { ConditionalVariable variable =
744     * (ConditionalVariable)variableList.get(k); testVarName =
745     * variable.getName(); testSystemName = ""; testUserName = ""; // initialize
746     * this state variable switch (variable.getType()) { case
747     * Conditional.TYPE_SENSOR_ACTIVE: case Conditional.TYPE_SENSOR_INACTIVE:
748     * Sensor s = InstanceManager.sensorManagerInstance().
749     * getSensor(testVarName); if (s!=null) { testSystemName =
750     * s.getSystemName(); testUserName = s.getUserName(); } break; case
751     * Conditional.TYPE_TURNOUT_THROWN: case Conditional.TYPE_TURNOUT_CLOSED:
752     * Turnout t = InstanceManager.turnoutManagerInstance().
753     * getTurnout(testVarName); if (t!=null) { testSystemName =
754     * t.getSystemName(); testUserName = t.getUserName(); } break; case
755     * Conditional.TYPE_CONDITIONAL_TRUE: case
756     * Conditional.TYPE_CONDITIONAL_FALSE: Conditional cx =
757     * InstanceManager.getDefault(jmri.ConditionalManager.class).
758     * getConditional(this,testVarName); if (cx==null) { cx =
759     * InstanceManager.getDefault(jmri.ConditionalManager.class).
760     * getBySystemName(testVarName); } if (cx!=null) { testSystemName =
761     * cx.getSystemName(); testUserName = cx.getUserName(); } break; case
762     * Conditional.TYPE_LIGHT_ON: case Conditional.TYPE_LIGHT_OFF: Light lgt =
763     * InstanceManager.lightManagerInstance(). getLight(testVarName); if
764     * (lgt!=null) { testSystemName = lgt.getSystemName(); testUserName =
765     * lgt.getUserName(); } break; case Conditional.TYPE_MEMORY_EQUALS: Memory m
766     * = InstanceManager.memoryManagerInstance(). getMemory(testVarName); if
767     * (m!=null) { testSystemName = m.getSystemName(); testUserName =
768     * m.getUserName(); } break; case Conditional.TYPE_SIGNAL_HEAD_RED: case
769     * Conditional.TYPE_SIGNAL_HEAD_YELLOW: case
770     * Conditional.TYPE_SIGNAL_HEAD_GREEN: case
771     * Conditional.TYPE_SIGNAL_HEAD_DARK: case
772     * Conditional.TYPE_SIGNAL_HEAD_FLASHRED: case
773     * Conditional.TYPE_SIGNAL_HEAD_FLASHYELLOW: case
774     * Conditional.TYPE_SIGNAL_HEAD_FLASHGREEN: SignalHead h =
775     * InstanceManager.getDefault(jmri.SignalHeadManager.class). getSignalHead(testVarName);
776     * if (h!=null) { testSystemName = h.getSystemName(); testUserName =
777     * h.getUserName(); } break; case Conditional.TYPE_SIGNAL_HEAD_LIT:
778     * SignalHead hx = InstanceManager.getDefault(jmri.SignalHeadManager.class).
779     * getSignalHead(testVarName); if (hx!=null) { testSystemName =
780     * hx.getSystemName(); testUserName = hx.getUserName(); } break; case
781     * Conditional.TYPE_SIGNAL_HEAD_HELD: SignalHead hy =
782     * InstanceManager.getDefault(jmri.SignalHeadManager.class). getSignalHead(testVarName);
783     * if (hy!=null) { testSystemName = hy.getSystemName(); testUserName =
784     * hy.getUserName(); } break; default: testSystemName = ""; } // check if
785     * this state variable is already in the list to be returned boolean inList
786     * = false; int indexOfRepeat = -1; if (testSystemName!="") { // getXXXXXX
787     * succeeded, process this state variable for (int j=0; j<varList.size();
788     * j++) { ConditionalVariable v = varList.get(j); if (
789     * v.getName().equals(testSystemName) || v.getName().equals(testUserName) )
790     * { inList = true; indexOfRepeat = j; break; } } // add to list if new and
791     * if there is room if ( inList ) { int[] trigs =
792     * triggerPair.get(indexOfRepeat); if ( variable.doCalculation() ) {
793     * trigs[0]++; } else { trigs[1]++;
794     *
795     * }
796     * }
797     * }
798     * }
799     * }
800     * else { log.error("invalid conditional system name in Logix
801     * getStateVariableList - "+ _conditionalSystemNames.get(i));
802     *
803     * }
804     * }
805     * } // getStateVariableList
806     */
807
808    /**
809     * Deactivate the Logix. This method disconnects the Logix from all input
810     * objects and stops it from being triggered to calculate.
811     * <p>
812     * A Logix must be deactivated before its Conditionals are changed.
813     */
814    @Override
815    public void deActivateLogix() {
816        if (_isActivated) {
817            // Logix is active, deactivate it and all listeners
818            _isActivated = false;
819            // remove listeners if there are any
820            for (int i = _listeners.size() - 1; i >= 0; i--) {
821                removeListener(_listeners.get(i));
822            }
823        }
824    }
825
826    /**
827     * Creates a listener of the required type and starts it
828     */
829    private void startListener(JmriSimplePropertyListener listener) {
830
831        if (listener.getType() == LISTENER_TYPE_FASTCLOCK) {
832            Timebase tb = InstanceManager.getDefault(Timebase.class);
833            tb.addMinuteChangeListener(listener);
834        } else {
835            NamedBeanHandle<?> namedBeanHandle = listener.getNamedBean();
836            if (namedBeanHandle == null) {
837                log.error("Bad name for {} '{}' when setting up Logix listener [ {} ]",
838                    getListenerTypeName(listener.getType()), listener.getDevName(), this.getSystemName());
839            } else {
840                NamedBean nb = namedBeanHandle.getBean();
841                nb.addPropertyChangeListener(listener, namedBeanHandle.getName(),
842                    "Logix " + getDisplayName());
843            }
844        }
845    }
846
847    /**
848     * Remove a listener of the required type
849     */
850    private void removeListener(JmriSimplePropertyListener listener) {
851        String typeName = null;
852        NamedBean nb;
853        NamedBeanHandle<?> namedBeanHandle;
854        try {
855            switch (listener.getType()) {
856                case LISTENER_TYPE_FASTCLOCK:
857                    Timebase tb = InstanceManager.getDefault(Timebase.class);
858                    tb.removeMinuteChangeListener(listener);
859                    return;
860                case LISTENER_TYPE_ENTRYEXIT:
861                    NamedBean ex = InstanceManager.getDefault(EntryExitPairs.class)
862                            .getNamedBean(listener.getDevName());
863                    if (ex == null) {
864                        typeName = "entryexit";  // NOI18N
865                        break;
866                    }
867                    ex.removePropertyChangeListener(listener);
868                    return;
869                default:
870                    namedBeanHandle = listener.getNamedBean();
871                    if (namedBeanHandle == null) {
872                        typeName = getListenerTypeName(listener.getType());
873                        break;
874                    }
875                    nb = namedBeanHandle.getBean();
876                    nb.removePropertyChangeListener(listener);
877                    return;
878            }
879        } catch (Exception ex) {
880            log.error("Bad name for listener on \"{}\": ", listener.getDevName(), ex);  // NOI18N
881        }
882        log.error("Bad name for {} listener on \"{}\" when removing", typeName, listener.getDevName());  // NOI18N
883    }
884
885    /**
886     * Get an I18N String of the Bean Listener type.
887     * @param listenerType the LISTENER_TYPE constant.
888     * @return I18N String of the Bean type, non-plural.
889     */
890    private static String getListenerTypeName(int listenerType) {
891        String msg;
892        switch (listenerType) {
893            case LISTENER_TYPE_SENSOR:
894                msg = InstanceManager.getDefault(SensorManager.class).getBeanTypeHandled();
895                break;
896            case LISTENER_TYPE_TURNOUT:
897                msg = InstanceManager.getDefault(TurnoutManager.class).getBeanTypeHandled();
898                break;
899            case LISTENER_TYPE_LIGHT:
900                msg = InstanceManager.getDefault(LightManager.class).getBeanTypeHandled();
901                break;
902            case LISTENER_TYPE_CONDITIONAL:
903                msg = InstanceManager.getDefault(ConditionalManager.class).getBeanTypeHandled();
904                break;
905            case LISTENER_TYPE_SIGNALHEAD:
906                msg = InstanceManager.getDefault(SignalHeadManager.class).getBeanTypeHandled();
907                break;
908            case LISTENER_TYPE_SIGNALMAST:
909                msg = InstanceManager.getDefault(SignalMastManager.class).getBeanTypeHandled();
910                break;
911            case LISTENER_TYPE_MEMORY:
912                msg = InstanceManager.getDefault(MemoryManager.class).getBeanTypeHandled();
913                break;
914            case LISTENER_TYPE_WARRANT:
915                msg = InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class).getBeanTypeHandled();
916                break;
917            case LISTENER_TYPE_OBLOCK:
918                msg = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getBeanTypeHandled();
919                break;
920            case LISTENER_TYPE_ENTRYEXIT:
921                msg = InstanceManager.getDefault(EntryExitPairs.class).getBeanTypeHandled();
922                break;
923            default:
924                msg = "unknown Listener type number: " + listenerType;  // NOI18N
925        }
926        return msg;
927    }
928
929    /* /**
930     * Assembles a list of state variables that both trigger the Logix, and are
931     * changed by it. Returns true if any such variables were found. Returns
932     * false otherwise. Can be called when Logix is enabled.
933     *
934     * public boolean checkLoopCondition() { loopGremlins = new
935     * ArrayList<String[]>(); if (!_isActivated) { // Prepare a list of all
936     * variables used in conditionals java.util.HashSet <ConditionalVariable>
937     * variableList = new java.util.HashSet<ConditionalVariable>();
938     * ConditionalManager cm = InstanceManager.getDefault(jmri.ConditionalManager.class); for
939     * (int i=0; i<_conditionalSystemNames.size(); i++) { Conditional c = null;
940     * c = cm.getBySystemName(_conditionalSystemNames.get(i)); if (c!=null) { //
941     * Not necesary to modify methods, equals and hashcode. Redundacy checked in
942     * addGremlin variableList.addAll(c.getCopyOfStateVariables()); } }
943     * java.util.HashSet <ConditionalVariable> variableList = new
944     * java.util.HashSet<ConditionalVariable>(); ConditionalVariable v = null;
945     * // check conditional action items Conditional c = null; for (int i=0;
946     * i<_conditionalSystemNames.size(); i++) { // get next conditional c =
947     * cm.getBySystemName(_conditionalSystemNames.get(i)); if (c!=null) {
948     * ArrayList <ConditionalAction> actionList = c.getCopyOfActions(); for (int
949     * j = 0; j < actionList.size(); j++) { ConditionalAction action =
950     * actionList.get(j); String sName = ""; String uName = ""; switch
951     * (action.getType()) { case Conditional.ACTION_NONE: break; case
952     * Conditional.ACTION_SET_TURNOUT: case Conditional.ACTION_DELAYED_TURNOUT:
953     * case Conditional.ACTION_RESET_DELAYED_TURNOUT: case
954     * Conditional.ACTION_CANCEL_TURNOUT_TIMERS: Turnout t =
955     * InstanceManager.turnoutManagerInstance().
956     * provideTurnout(action.getDeviceName()); if (t!=null) { sName =
957     * t.getSystemName(); uName = t.getUserName(); // check for action on the
958     * same turnout Iterator <ConditionalVariable>it= variableList.iterator();
959     * while(it.hasNext()) { v = it.next(); if (v.getType() ==
960     * Conditional.TYPE_TURNOUT_CLOSED || v.getType() ==
961     * Conditional.TYPE_TURNOUT_THROWN) { if ( (v.getName().equals(sName)) ||
962     * (v.getName().equals(uName)) ) { // possible conflict found
963     * addGremlin("Turnout", sName, uName); } } } } break; case
964     * Conditional.ACTION_SET_SIGNAL_APPEARANCE: case
965     * Conditional.ACTION_SET_SIGNAL_HELD: case
966     * Conditional.ACTION_CLEAR_SIGNAL_HELD: case
967     * Conditional.ACTION_SET_SIGNAL_DARK: case
968     * Conditional.ACTION_SET_SIGNAL_LIT: SignalHead h =
969     * InstanceManager.getDefault(jmri.SignalHeadManager.class).
970     * getSignalHead(action.getDeviceName()); if (h!=null) { sName =
971     * h.getSystemName(); uName = h.getUserName(); // check for action on the
972     * same signal head Iterator <ConditionalVariable>it=
973     * variableList.iterator(); while(it.hasNext()) { v = it.next(); if
974     * (v.getType() >= Conditional.TYPE_SIGNAL_HEAD_RED || v.getType() <=
975     * Conditional.TYPE_SIGNAL_HEAD_HELD) { if ( (v.getName().equals(sName)) ||
976     * (v.getName().equals(uName)) ) { // possible conflict found
977     * addGremlin("SignalHead", sName, uName); } } } } break; case
978     * Conditional.ACTION_SET_SENSOR: case Conditional.ACTION_DELAYED_SENSOR:
979     * case Conditional.ACTION_RESET_DELAYED_SENSOR: case
980     * Conditional.ACTION_CANCEL_SENSOR_TIMERS: Sensor s =
981     * InstanceManager.sensorManagerInstance().
982     * provideSensor(action.getDeviceName()); if (s!=null) { sName =
983     * s.getSystemName(); uName = s.getUserName(); // check for action on the
984     * same sensor Iterator <ConditionalVariable>it= variableList.iterator();
985     * while(it.hasNext()) { v = it.next(); if (v.getType() ==
986     * Conditional.TYPE_SENSOR_ACTIVE || v.getType() ==
987     * Conditional.TYPE_SENSOR_INACTIVE) {
988     *
989     * if ( (v.getName().equals(sName)) || (v.getName().equals(uName)) ) { //
990     * possible conflict found addGremlin("Sensor",sName, uName); } } } } break;
991     * case Conditional.ACTION_SET_LIGHT: case
992     * Conditional.ACTION_SET_LIGHT_TRANSITION_TIME: case
993     * Conditional.ACTION_SET_LIGHT_INTENSITY: Light lgt =
994     * InstanceManager.lightManagerInstance(). getLight(action.getDeviceName());
995     * if (lgt!=null) { sName = lgt.getSystemName(); uName = lgt.getUserName();
996     * // check for listener on the same light Iterator <ConditionalVariable>it=
997     * variableList.iterator(); while(it.hasNext()) { v = it.next(); if
998     * (v.getType() == Conditional.TYPE_LIGHT_ON || v.getType() ==
999     * Conditional.TYPE_LIGHT_OFF) { if ( (v.getName().equals(sName)) ||
1000     * (v.getName().equals(uName)) ) { // possible conflict found
1001     * addGremlin("Light", sName, uName); } } } } break; case
1002     * Conditional.ACTION_SET_MEMORY: case Conditional.ACTION_COPY_MEMORY:
1003     * Memory m = InstanceManager.memoryManagerInstance().
1004     * provideMemory(action.getDeviceName()); if (m!=null) { sName =
1005     * m.getSystemName(); uName = m.getUserName(); // check for variable on the
1006     * same memory Iterator <ConditionalVariable>it= variableList.iterator();
1007     * while(it.hasNext()) { v = it.next(); if (v.getType() ==
1008     * Conditional.TYPE_MEMORY_EQUALS) { if ( (v.getName().equals(sName)) ||
1009     * (v.getName().equals(uName)) ) { // possible conflict found
1010     * addGremlin("Memory", sName, uName); } } } } break; case
1011     * Conditional.ACTION_SET_FAST_CLOCK_TIME: case
1012     * Conditional.ACTION_START_FAST_CLOCK: case
1013     * Conditional.ACTION_STOP_FAST_CLOCK: Iterator <ConditionalVariable>it=
1014     * variableList.iterator(); while(it.hasNext()) { v = it.next(); if
1015     * (v.getType() == Conditional.TYPE_FAST_CLOCK_RANGE) {
1016     * addGremlin("FastClock", null, v.getName()); } } break; default: } } } } }
1017     * return (loopGremlins.size()>0); }
1018     *
1019     * private void addGremlin(String type, String sName, String uName) { //
1020     * check for redundancy String names = uName+ (sName == null ? "" : "
1021     * ("+sName+")"); for (int i=0; i<loopGremlins.size(); i++) { String[] str =
1022     * loopGremlins.get(i); if (str[0].equals(type) && str[1].equals(names)) {
1023     * return; } } String[] item = new String[2]; item[0] = type; item[1] =
1024     * names; loopGremlins.add(item); }
1025     *
1026     * ArrayList <String[]> loopGremlins = null;
1027     *
1028     * /**
1029     * Returns a string listing state variables that might result in a loop.
1030     * Returns an empty string if there are none, probably because
1031     * "checkLoopCondition" was not invoked before the call, or returned false.
1032     *
1033     * public ArrayList
1034     * <String[]> getLoopGremlins() {return(loopGremlins);}
1035     */
1036
1037    /**
1038     * Not needed for Logixs - included to complete implementation of the
1039     * NamedBean interface.
1040     */
1041    @Override
1042    public int getState() {
1043        log.warn("Unexpected call to getState in DefaultLogix.");  // NOI18N
1044        return UNKNOWN;
1045    }
1046
1047    /**
1048     * Not needed for Logixs - included to complete implementation of the
1049     * NamedBean interface.
1050     * @param state unused.
1051     */
1052    @Override
1053    public void setState(int state) {
1054        log.warn("Unexpected call to setState in DefaultLogix.");  // NOI18N
1055    }
1056
1057    @Override
1058    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
1059        if (Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) {
1060            NamedBean nb = (NamedBean) evt.getOldValue();
1061            for (JmriSimplePropertyListener listener : _listeners) {
1062                if (nb.equals(listener.getBean())) {
1063                    var e = new java.beans.PropertyChangeEvent(this, Manager.PROPERTY_DO_NOT_DELETE, null, null);
1064                    throw new java.beans.PropertyVetoException(
1065                        Bundle.getMessage("InUseLogixListener", nb.getBeanType(), getDisplayName()), e);   // NOI18N
1066                }
1067            }
1068
1069            String cName;
1070            Conditional c;
1071            for (String conditionalSystemName : _conditionalSystemNames) {
1072                cName = conditionalSystemName;
1073                c = conditionalManager.getBySystemName(cName);
1074                if (c != null) {
1075                    for (ConditionalAction ca : c.getCopyOfActions()) {
1076                        if (nb.equals(ca.getBean())) {
1077                            var e = new java.beans.PropertyChangeEvent(
1078                                this, Manager.PROPERTY_DO_NOT_DELETE, null, null);
1079                            throw new java.beans.PropertyVetoException(
1080                                Bundle.getMessage("InUseLogixAction", nb.getBeanType(), getDisplayName()), e); // NOI18N
1081                        }
1082                    }
1083                    for (ConditionalVariable v : c.getCopyOfStateVariables()) {
1084                        if (nb.equals(v.getBean()) || nb.equals(v.getNamedBeanData())) {
1085                            var e = new java.beans.PropertyChangeEvent(this,
1086                                Manager.PROPERTY_DO_NOT_DELETE, null, null);
1087                            throw new java.beans.PropertyVetoException( Bundle.getMessage("InUseLogixVariable",
1088                                nb.getBeanType(), getDisplayName()), e);
1089                        }
1090                    }
1091                }
1092            }
1093        }
1094    }
1095
1096    @Override
1097    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
1098        List<NamedBeanUsageReport> report = new ArrayList<>();
1099        if (bean != null) {
1100            for (int i = 0; i < getNumConditionals(); i++) {
1101                DefaultConditional cdl = (DefaultConditional) getConditional(getConditionalByNumberOrder(i));
1102                if ( cdl == null ) {
1103                    continue;
1104                }
1105                cdl.getStateVariableList().forEach( variable -> {
1106                    if (bean.equals(variable.getBean())) {
1107                        report.add(new NamedBeanUsageReport("ConditionalVariable", cdl, variable.toString()));
1108                    }
1109                    if (bean.equals(variable.getNamedBeanData())) {
1110                        report.add(new NamedBeanUsageReport("ConditionalVariableData", cdl, variable.toString()));
1111                    }
1112                });
1113                cdl.getActionList().forEach( action -> {
1114                    if (bean.equals(action.getBean())) {
1115                        boolean triggerType = cdl.getTriggerOnChange();
1116                        report.add(new NamedBeanUsageReport("ConditionalAction", cdl, action.description(triggerType)));
1117                    }
1118                });
1119            }
1120        }
1121        return report;
1122    }
1123
1124    /** {@inheritDoc} */
1125    @Override
1126    @OverridingMethodsMustInvokeSuper
1127    public void dispose() {
1128        // remove the listeners first, so a deleted Logix stops running
1129        deActivateLogix();
1130        super.dispose();
1131        for (int i = 0; i < getNumConditionals(); i++) {
1132            Conditional c = getConditional(getConditionalByNumberOrder(i));
1133            if ( c != null ) {
1134                c.dispose();
1135            }
1136        }
1137    }
1138
1139    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultLogix.class);
1140
1141}