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