001package jmri.jmrit.entryexit;
002
003import java.awt.Color;
004import java.awt.event.MouseAdapter;
005import java.awt.event.MouseEvent;
006import java.beans.PropertyChangeEvent;
007import java.beans.PropertyChangeListener;
008import java.beans.PropertyVetoException;
009import java.util.*;
010import java.util.Map.Entry;
011
012import javax.annotation.CheckReturnValue;
013import javax.annotation.Nonnull;
014import javax.annotation.OverridingMethodsMustInvokeSuper;
015import javax.swing.JDialog;
016import javax.swing.JOptionPane;
017import javax.swing.JPanel;
018
019import jmri.*;
020import jmri.beans.VetoableChangeSupport;
021import jmri.jmrit.display.EditorManager;
022import jmri.jmrit.display.layoutEditor.LayoutBlock;
023import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools;
024import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
025import jmri.jmrit.display.layoutEditor.LayoutEditor;
026import jmri.jmrix.internal.InternalSystemConnectionMemo;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * Implements an Entry Exit based method of setting turnouts, setting up signal
033 * logic and allocating blocks through a path based on the Layout Editor.
034 * <p>
035 * The route is based upon having a sensor assigned at a known location on the
036 * panel (set at the boundary of two different blocks) through to a sensor at a
037 * remote location on the same panel. Using the layout block routing, a path can
038 * then be set between the two sensors so long as one exists and no
039 * section of track is set occupied. If available an alternative route will be
040 * used when the direct path is occupied (blocked).
041 * <p>
042 * Initial implementation only handles the setting up of turnouts on a path.
043 *
044 * @author Kevin Dickerson Copyright (C) 2011
045 */
046public class EntryExitPairs extends VetoableChangeSupport implements Manager<DestinationPoints>, jmri.InstanceManagerAutoDefault,
047        PropertyChangeListener {
048
049    public LayoutBlockConnectivityTools.Metric routingMethod = LayoutBlockConnectivityTools.Metric.METRIC;
050
051    public final static int NXBUTTONSELECTED = 0x08;
052    public final static int NXBUTTONACTIVE = Sensor.ACTIVE;
053    public final static int NXBUTTONINACTIVE = Sensor.INACTIVE;
054    private final SystemConnectionMemo memo;
055    private final Map<String, Boolean> silencedProperties = new HashMap<>();
056
057    private int settingTimer = 2000;
058
059    public int getSettingTimer() {
060        return settingTimer;
061    }
062
063    public void setSettingTimer(int i) {
064        settingTimer = i;
065    }
066
067    private Color settingRouteColor = null;
068
069    public boolean useDifferentColorWhenSetting() {
070        return (settingRouteColor != null);
071    }
072
073    public Color getSettingRouteColor() {
074        return settingRouteColor;
075    }
076
077    public void setSettingRouteColor(Color col) {
078        settingRouteColor = col;
079    }
080
081    /**
082     * Constant value to represent that the entryExit will only set up the
083     * turnouts between two different points.
084     */
085    public final static int SETUPTURNOUTSONLY = 0x00;
086
087    /**
088     * Constant value to represent that the entryExit will set up the turnouts
089     * between two different points and configure the Signal Mast Logic to use
090     * the correct blocks.
091     */
092    public final static int SETUPSIGNALMASTLOGIC = 0x01;
093
094    /**
095     * Constant value to represent that the entryExit will do full interlocking.
096     * It will set the turnouts and "reserve" the blocks.
097     */
098    public final static int FULLINTERLOCK = 0x02;
099
100    boolean allocateToDispatcher = false;
101
102    public final static int PROMPTUSER = 0x00;
103    public final static int AUTOCLEAR = 0x01;
104    public final static int AUTOCANCEL = 0x02;
105    public final static int AUTOSTACK = 0x03;
106
107    public final static int OVERLAP_CANCEL = 0x01;
108    public final static int OVERLAP_STACK = 0x02;
109
110    int routeClearOption = PROMPTUSER;
111    int routeOverlapOption = PROMPTUSER;
112    String memoryOption = "";     // Optional memory variable to receive allocation messages
113    int memoryClearDelay = 0;     // Delay before clearing memory, 0 for clearing disabled
114
115    static JPanel glassPane = new JPanel();
116
117    /**
118     * Delay between issuing Turnout commands
119     */
120    public int turnoutSetDelay = 0;
121
122    /**
123     * Constructor for creating an EntryExitPairs object and create a transparent JPanel for it.
124     */
125    public EntryExitPairs() {
126        memo = InstanceManager.getDefault(InternalSystemConnectionMemo.class);
127        InstanceManager.getOptionalDefault(ConfigureManager.class).ifPresent(cm -> cm.registerUser(this));
128        InstanceManager.getDefault(LayoutBlockManager.class).addPropertyChangeListener(propertyBlockManagerListener);
129
130        glassPane.setOpaque(false);
131        glassPane.setLayout(null);
132        glassPane.addMouseListener(new MouseAdapter() {
133            @Override
134            public void mousePressed(MouseEvent e) {
135                e.consume();
136            }
137        });
138    }
139
140    public void setDispatcherIntegration(boolean boo) {
141        allocateToDispatcher = boo;
142    }
143
144    public boolean getDispatcherIntegration() {
145        return allocateToDispatcher;
146    }
147
148    /**
149     * Get the transparent JPanel for this EntryExitPairs.
150     * @return JPanel overlay
151     */
152    public JPanel getGlassPane() {
153        return glassPane;
154    }
155
156    HashMap<PointDetails, Source> nxpair = new HashMap<>();
157
158    public void addNXSourcePoint(LayoutBlock facing, List<LayoutBlock> protecting, NamedBean loc, LayoutEditor panel) {
159        PointDetails point = providePoint(facing, protecting, panel);
160        point.setRefObject(loc);
161    }
162
163    public void addNXSourcePoint(NamedBean source) {
164        PointDetails point = null;
165        for (LayoutEditor editor : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
166            point = providePoint(source, editor);
167        }
168        if (point == null) {
169            log.error("Unable to find a location on any panel for item {}", source.getDisplayName());  // NOI18N
170        }
171    }
172
173    public void addNXSourcePoint(NamedBean source, LayoutEditor panel) {
174        if (source == null) {
175            log.error("source bean supplied is null");  // NOI18N
176            return;
177        }
178        if (panel == null) {
179            log.error("panel supplied is null");  // NOI18N
180            return;
181        }
182        PointDetails point;
183        point = providePoint(source, panel);
184        if (point == null) {
185            log.error("Unable to find a location on the panel {} for item {}", panel.getLayoutName(), source.getDisplayName());  // NOI18N
186        }
187    }
188
189    public Object getEndPointLocation(NamedBean source, LayoutEditor panel) {
190        if (source == null) {
191            log.error("Source bean past is null");  // NOI18N
192            return null;
193        }
194        if (panel == null) {
195            log.error("panel passed is null");  // NOI18N
196            return null;
197        }
198        PointDetails sourcePoint = getPointDetails(source, panel);
199        if (sourcePoint == null) {
200            log.error("Point is not located");  // NOI18N
201            return null;
202        }
203        return sourcePoint.getRefLocation();
204    }
205
206    /** {@inheritDoc} */
207    @Override
208    public int getXMLOrder() {
209        return ENTRYEXIT;
210    }
211
212    /** {@inheritDoc} */
213    @Override
214    public DestinationPoints getBySystemName(String systemName) {
215        for (Source e : nxpair.values()) {
216            DestinationPoints pd = e.getByUniqueId(systemName);
217            if (pd != null) {
218                return pd;
219            }
220        }
221        return null;
222    }
223
224    /** {@inheritDoc} */
225    @Override
226    public DestinationPoints getByUserName(@Nonnull String userName) {
227        for (Source e : nxpair.values()) {
228            DestinationPoints pd = e.getByUserName(userName);
229            if (pd != null) {
230                return pd;
231            }
232        }
233        return null;
234    }
235
236    /** {@inheritDoc} */
237    @Override
238    public DestinationPoints getNamedBean(@Nonnull String name) {
239        DestinationPoints b = getByUserName(name);
240        if (b != null) {
241            return b;
242        }
243        return getBySystemName(name);
244    }
245
246    /** {@inheritDoc} */
247    @Nonnull
248    @Override
249    public SystemConnectionMemo getMemo() {
250        return memo;
251    }
252
253    /** {@inheritDoc} */
254    @Override
255    @Nonnull
256    public String getSystemPrefix() {
257        return memo.getSystemPrefix();
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public char typeLetter() {
263        throw new UnsupportedOperationException("Not supported yet.");  // NOI18N
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    @Nonnull
269    public String makeSystemName(@Nonnull String s) {
270        throw new UnsupportedOperationException("Not supported yet.");  // NOI18N
271    }
272
273    /** {@inheritDoc} */
274    @Override
275    @CheckReturnValue
276    public int getObjectCount() {
277        return getNamedBeanSet().size();
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    @Nonnull
283    @Deprecated  // will be removed when superclass method is removed due to @Override
284    public List<String> getSystemNameList() {
285        jmri.util.LoggingUtil.deprecationWarning(log, "getSystemNameList");
286        return getEntryExitList();
287    }
288
289    /**
290     * Implemented to support the Conditional combo box name list
291     * @since 4.9.3
292     * @return a list of Destination Point beans
293     */
294    @Override
295    @Nonnull
296    @Deprecated  // will be removed when superclass method is removed due to @Override
297    public List<DestinationPoints> getNamedBeanList() {
298        jmri.util.LoggingUtil.deprecationWarning(log, "getNamedBeanList");
299        List<DestinationPoints> beanList = new ArrayList<>();
300        for (Source e : nxpair.values()) {
301            List<String> uidList = e.getDestinationUniqueId();
302            for (String uid : uidList) {
303                beanList.add(e.getByUniqueId(uid));
304            }
305        }
306        return beanList;
307    }
308
309    /**
310     * Implemented to support the Conditional combo box name list
311     * @since 4.9.3
312     * @return a list of Destination Point beans
313     */
314    @Override
315    @Nonnull
316    public SortedSet<DestinationPoints> getNamedBeanSet() {
317        TreeSet<DestinationPoints> beanList = new TreeSet<>(new jmri.util.NamedBeanComparator<>());
318        for (Source e : nxpair.values()) {
319            List<String> uidList = e.getDestinationUniqueId();
320            for (String uid : uidList) {
321                beanList.add(e.getByUniqueId(uid));
322            }
323        }
324        return beanList;
325    }
326
327    /** {@inheritDoc} */
328    @Override
329    public void register(@Nonnull DestinationPoints n) {
330        throw new UnsupportedOperationException("Not supported yet.");  // NOI18N
331    }
332
333    /** {@inheritDoc} */
334    @Override
335    public void deregister(@Nonnull DestinationPoints n) {
336        throw new UnsupportedOperationException("Not supported yet.");  // NOI18N
337    }
338
339    public void setClearDownOption(int i) {
340        routeClearOption = i;
341    }
342
343    public int getClearDownOption() {
344        return routeClearOption;
345    }
346
347    public void setOverlapOption(int i) {
348        routeOverlapOption = i;
349    }
350
351    public int getOverlapOption() {
352        return routeOverlapOption;
353    }
354
355    public void setMemoryOption(String memoryName) {
356        memoryOption = memoryName;
357    }
358
359    public String getMemoryOption() {
360        return memoryOption;
361    }
362
363    public void setMemoryClearDelay(int secs) {
364        memoryClearDelay = secs;
365    }
366
367    public int getMemoryClearDelay() {
368        return memoryClearDelay;
369    }
370
371    /** {@inheritDoc} */
372    @Override
373    public void dispose() {
374    }
375
376    /**
377     * Generate the point details, given a known source and a
378     * Layout Editor panel.
379     *
380     * @param source Origin of movement
381     * @param panel  A Layout Editor panel
382     * @return A PointDetails object
383     */
384    public PointDetails providePoint(NamedBean source, LayoutEditor panel) {
385        PointDetails sourcePoint = getPointDetails(source, panel);
386        if (sourcePoint == null) {
387            LayoutBlock facing = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getFacingBlockByNamedBean(source, null);
388            List<LayoutBlock> protecting = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getProtectingBlocksByNamedBean(source, null);
389//             log.info("facing = {}, protecting = {}", facing, protecting);
390            if (facing == null && protecting.size() == 0) {
391                log.error("Unable to find facing and protecting blocks");  // NOI18N
392                return null;
393            }
394            sourcePoint = providePoint(facing, protecting, panel);
395            sourcePoint.setRefObject(source);
396        }
397        return sourcePoint;
398    }
399
400    /**
401     * Return a list of all source (origin) points on a given
402     * Layout Editor panel.
403     *
404     * @param panel  A Layout Editor panel
405     * @return A list of source objects
406     */
407    public List<Object> getSourceList(LayoutEditor panel) {
408        List<Object> list = new ArrayList<>();
409
410        for (Entry<PointDetails, Source> e : nxpair.entrySet()) {
411            Object obj = (e.getKey()).getRefObject();
412            LayoutEditor pan = (e.getKey()).getPanel();
413            if (pan == panel) {
414                if (!list.contains(obj)) {
415                    list.add(obj);
416                }
417            } // end while
418        }
419        return list;
420    }
421
422    public Source getSourceForPoint(PointDetails pd) {
423        return nxpair.get(pd);
424    }
425
426    public int getNxPairNumbers(LayoutEditor panel) {
427        int total = 0;
428        for (Entry<PointDetails, Source> e : nxpair.entrySet()) {
429            PointDetails key = e.getKey();
430            LayoutEditor pan = key.getPanel();
431            if (pan == panel) {
432                total = total + e.getValue().getNumberOfDestinations();
433            } // end while
434        }
435
436        return total;
437    }
438
439    /**
440     * Set the route between the two points represented by the Destination Point name.
441     *
442     * @since 4.11.1
443     * @param nxPair The system or user name of the destination point.
444     */
445    public void setSingleSegmentRoute(String nxPair) {
446        DestinationPoints dp = getNamedBean(nxPair);
447        if (dp != null) {
448            String destUUID = dp.getUniqueId();
449            nxpair.forEach((pd, src) -> {
450                for (String srcUUID : src.getDestinationUniqueId()) {
451                    if (destUUID.equals(srcUUID)) {
452                        log.debug("Found the correct source: src = {}, dest = {}",
453                                pd.getSensor().getDisplayName(), dp.getDestPoint().getSensor().getDisplayName());
454                        setMultiPointRoute(pd, dp.getDestPoint());
455                        return;
456                    }
457                }
458            });
459        }
460    }
461
462    public void setMultiPointRoute(PointDetails requestpd, LayoutEditor panel) {
463        for (PointDetails pd : pointDetails) {
464            if (pd != requestpd) {
465                if (pd.getNXState() == NXBUTTONSELECTED) {
466                    setMultiPointRoute(pd, requestpd);
467                    return;
468                }
469            }
470        }
471    }
472
473    private void setMultiPointRoute(PointDetails fromPd, PointDetails toPd) {
474        boolean cleardown = false;
475        if (fromPd.isRouteFromPointSet() && toPd.isRouteToPointSet()) {
476            cleardown = true;
477        }
478        for (LayoutBlock pro : fromPd.getProtecting()) {
479            try {
480                jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class);
481                LayoutBlock toProt = null;
482                if (!toPd.getProtecting().isEmpty()) {
483                    toProt = toPd.getProtecting().get(0);
484                }
485                boolean result = lbm.getLayoutBlockConnectivityTools().checkValidDest(fromPd.getFacing(), pro, toPd.getFacing(), toProt, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR);
486                if (result) {
487                    List<LayoutBlock> blkList = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(fromPd.getFacing(), toPd.getFacing(), pro, cleardown, LayoutBlockConnectivityTools.Routing.NONE);
488                    if (!blkList.isEmpty()) {
489                        if (log.isDebugEnabled()) {
490                            for (LayoutBlock blk : blkList) {
491                                log.debug("blk = {}", blk.getDisplayName());
492                            }
493                        }
494                        List<jmri.NamedBean> beanList = lbm.getLayoutBlockConnectivityTools().getBeansInPath(blkList, null, jmri.Sensor.class);
495                        PointDetails fromPoint = fromPd;
496                        refCounter++;
497                        if (!beanList.isEmpty()) {
498                            if (log.isDebugEnabled()) {
499                                for (NamedBean xnb : beanList) {
500                                    log.debug("xnb = {}", xnb.getDisplayName());
501                                }
502                            }
503                            for (int i = 1; i < beanList.size(); i++) {
504                                NamedBean nb = beanList.get(i);
505                                PointDetails cur = getPointDetails(nb, fromPd.getPanel());
506                                Source s = nxpair.get(fromPoint);
507                                if (s != null) {
508                                    routesToSet.add(new SourceToDest(s, s.getDestForPoint(cur), false, refCounter));
509                                }
510                                fromPoint = cur;
511                            }
512                        }
513                        Source s = nxpair.get(fromPoint);
514                        if (s != null) {
515                            if (s.getDestForPoint(toPd) != null) {
516                                routesToSet.add(new SourceToDest(s, s.getDestForPoint(toPd), false, refCounter));
517                            }
518                        }
519                        processRoutesToSet();
520                        return;
521                    }
522                }
523            } catch (jmri.JmriException e) {
524                //Can be considered normal if route is blocked
525            }
526        }
527        fromPd.setNXButtonState(NXBUTTONINACTIVE);
528        toPd.setNXButtonState(NXBUTTONINACTIVE);
529    }
530
531    int refCounter = 0;
532
533    /**
534     * List holding SourceToDest sets of routes between two points.
535     */
536    List<SourceToDest> routesToSet = new ArrayList<>();
537
538    /**
539     * Class to store NX sets consisting of a source point, a destination point,
540     * a direction and a reference.
541     */
542    static class SourceToDest {
543
544        Source s = null;
545        DestinationPoints dp = null;
546        boolean direction = false;
547        int ref = -1;
548
549        /**
550         * Constructor for a SourceToDest element.
551         *
552         * @param s   a source point
553         * @param dp  a destination point
554         * @param dir a direction
555         * @param ref Integer used as reference
556         */
557        SourceToDest(Source s, DestinationPoints dp, boolean dir, int ref) {
558            this.s = s;
559            this.dp = dp;
560            this.direction = dir;
561            this.ref = ref;
562        }
563    }
564
565    int currentDealing = 0;
566
567    /**
568     * Activate each SourceToDest set in routesToSet
569     */
570    synchronized void processRoutesToSet() {
571        if (log.isDebugEnabled()) {
572            for (SourceToDest sd : routesToSet) {
573                String dpName = (sd.dp == null) ? "- null -" : sd.dp.getDestPoint().getSensor().getDisplayName();
574                log.debug("processRoutesToSet: {} -- {} -- {}", sd.s.getPoint().getSensor().getDisplayName(), dpName, sd.ref);
575            }
576        }
577
578        if (routesToSet.isEmpty()) {
579            return;
580        }
581        Source s = routesToSet.get(0).s;
582        DestinationPoints dp = routesToSet.get(0).dp;
583        boolean dir = routesToSet.get(0).direction;
584        currentDealing = routesToSet.get(0).ref;
585        routesToSet.remove(0);
586
587        dp.addPropertyChangeListener(propertyDestinationListener);
588        s.activeBean(dp, dir);
589    }
590
591    /**
592     * Remove remaining SourceToDest sets in routesToSet
593     */
594    synchronized void removeRemainingRoute() {
595        List<SourceToDest> toRemove = new ArrayList<>();
596        for (SourceToDest rts : routesToSet) {
597            if (rts.ref == currentDealing) {
598                toRemove.add(rts);
599                rts.dp.getDestPoint().setNXButtonState(NXBUTTONINACTIVE);
600            }
601        }
602        for (SourceToDest rts : toRemove) {
603            routesToSet.remove(rts);
604        }
605    }
606
607    protected PropertyChangeListener propertyDestinationListener = new PropertyChangeListener() {
608        @Override
609        public void propertyChange(PropertyChangeEvent e) {
610            ((DestinationPoints) e.getSource()).removePropertyChangeListener(this);
611            if (e.getPropertyName().equals("active")) {
612                processRoutesToSet();
613            } else if (e.getPropertyName().equals("stacked") || e.getPropertyName().equals("failed") || e.getPropertyName().equals("noChange")) {  // NOI18N
614                removeRemainingRoute();
615            }
616        }
617    };
618
619    List<Object> destinationList = new ArrayList<>();
620
621    // Need to sort out the presentation of the name here rather than using the point ID.
622    // This is used for the creation and display of information in the table.
623    // The presentation of the name might have to be done at the table level.
624    public List<Object> getNxSource(LayoutEditor panel) {
625        List<Object> source = new ArrayList<>();
626        destinationList = new ArrayList<>();
627
628        for (Entry<PointDetails, Source> e : nxpair.entrySet()) {
629            PointDetails key = e.getKey();
630            LayoutEditor pan = key.getPanel();
631            if (pan == panel) {
632                List<PointDetails> dest = nxpair.get(key).getDestinationPoints();
633                for (int i = 0; i < dest.size(); i++) {
634                    destinationList.add(dest.get(i).getRefObject());
635                    source.add(key.getRefObject());
636                }
637            }
638        }
639        return source;
640    }
641
642    public List<Object> getNxDestination() {
643        return destinationList;
644    }
645
646    public List<LayoutEditor> getSourcePanelList() {
647        List<LayoutEditor> list = new ArrayList<>();
648
649        for (Entry<PointDetails, Source> e : nxpair.entrySet()) {
650            PointDetails key = e.getKey();
651            LayoutEditor pan = key.getPanel();
652            if (!list.contains(pan)) {
653                list.add(pan);
654            }
655        }
656        return list;
657    }
658
659    /**
660     * Return a point if it already exists, or create a new one if not.
661     */
662    private PointDetails providePoint(LayoutBlock source, List<LayoutBlock> protecting, LayoutEditor panel) {
663        PointDetails sourcePoint = getPointDetails(source, protecting, panel);
664        if (sourcePoint == null) {
665            sourcePoint = new PointDetails(source, protecting);
666            sourcePoint.setPanel(panel);
667        }
668        return sourcePoint;
669    }
670
671    /**
672     * @since 4.17.4
673     */
674    @Override
675    public void propertyChange(PropertyChangeEvent evt) {
676        firePropertyChange("active", evt.getOldValue(), evt.getNewValue());
677    }
678
679
680    public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel) {
681        addNXDestination(source, destination, panel, null);
682    }
683
684    /**
685     * @since 4.17.4
686     * Register in Property Change Listener.
687     * @param source the source bean.
688     * @param destination the destination bean.
689     * @param panel the layout editor panel.
690     * @param id the points details id.
691     */
692    public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel, String id) {
693        if (source == null) {
694            log.error("no source Object provided");  // NOI18N
695            return;
696        }
697        if (destination == null) {
698            log.error("no destination Object provided");  // NOI18N
699            return;
700        }
701        PointDetails sourcePoint = providePoint(source, panel);
702        if (sourcePoint == null) {
703            log.error("source point for {} not created addNXDes", source.getDisplayName());  // NOI18N
704            return;
705        }
706
707        sourcePoint.setPanel(panel);
708        sourcePoint.setRefObject(source);
709        PointDetails destPoint = providePoint(destination, panel);
710        if (destPoint != null) {
711            destPoint.setPanel(panel);
712            destPoint.setRefObject(destination);
713            destPoint.getSignal();
714            if (!nxpair.containsKey(sourcePoint)) {
715                Source sp = new Source(sourcePoint);
716                nxpair.put(sourcePoint, sp);
717                sp.removePropertyChangeListener(this);
718                sp.addPropertyChangeListener(this);
719            }
720            nxpair.get(sourcePoint).addDestination(destPoint, id);
721        }
722
723        firePropertyChange("length", null, null);  // NOI18N
724    }
725
726    public List<Object> getDestinationList(Object obj, LayoutEditor panel) {
727        List<Object> list = new ArrayList<>();
728        if (nxpair.containsKey(getPointDetails(obj, panel))) {
729            List<PointDetails> from = nxpair.get(getPointDetails(obj, panel)).getDestinationPoints();
730            for (int i = 0; i < from.size(); i++) {
731                list.add(from.get(i).getRefObject());
732            }
733        }
734        return list;
735    }
736
737    public void removeNXSensor(Sensor sensor) {
738        log.info("panel maintenance has resulting in the request to remove a sensor: {}", sensor.getDisplayName());
739    }
740
741    // ============ NX Pair Delete Methods ============
742    // The request will be for all NX Pairs containing a sensor or
743    // a specific entry and exit sensor pair.
744
745    /**
746     * Entry point to delete all of the NX pairs for a specific sensor.
747     * 1) Build a list of affected NX pairs.
748     * 2) Check for Conditional references.
749     * 3) If no references, do the delete process with user approval.
750     * <p>
751     * @since 4.11.2
752     * @param sensor The sensor whose pairs should be deleted.
753     * @return true if the delete was successful. False if prevented by
754     * Conditional references or user choice.
755     */
756    public boolean deleteNxPair(NamedBean sensor) {
757        if (sensor == null) {
758            log.error("deleteNxPair: sensor is null");  // NOI18N
759            return false;
760        }
761        createDeletePairList(sensor);
762        if (checkNxPairs()) {
763            // No Conditional references.
764            if (confirmDeletePairs()) {
765                deleteNxPairs();
766                return true;
767            }
768        }
769        return false;
770    }
771
772    /**
773     * Entry point to delete a specific NX pair.
774     *
775     * @since 4.11.2
776     * @param entrySensor The sensor that acts as the entry point.
777     * @param exitSensor The sensor that acts as the exit point.
778     * @param panel The layout editor panel that contains the entry sensor.
779     * @return true if the delete was successful. False if there are Conditional references.
780     */
781    public boolean deleteNxPair(NamedBean entrySensor, NamedBean exitSensor, LayoutEditor panel) {
782        if (entrySensor == null || exitSensor == null || panel == null) {
783            log.error("deleteNxPair: One or more null inputs");  // NOI18N
784            return false;
785        }
786        deletePairList.clear();
787        deletePairList.add(new DeletePair(entrySensor, exitSensor, panel));
788        if (checkNxPairs()) {
789            // No Conditional references.
790            deleteNxPairs();  // Delete with no prompt
791            return true;
792        }
793        return false;
794    }
795
796    /**
797     * Find Logix Conditionals that have Variables or Actions for the affected NX Pairs
798     * If any are found, display a dialog box listing the Conditionals and return false.
799     * <p>
800     * @since 4.11.2
801     * @return true if there are no references.
802     */
803    private boolean checkNxPairs() {
804        jmri.LogixManager mgr = InstanceManager.getDefault(jmri.LogixManager.class);
805        List<String> conditionalReferences = new ArrayList<>();
806        for (DeletePair dPair : deletePairList) {
807            if (dPair.dp == null) {
808                continue;
809            }
810            for (jmri.Logix lgx : mgr.getNamedBeanSet()) {
811                for (int i = 0; i < lgx.getNumConditionals(); i++) {
812                    String cdlName = lgx.getConditionalByNumberOrder(i);
813                    jmri.implementation.DefaultConditional cdl = (jmri.implementation.DefaultConditional) lgx.getConditional(cdlName);
814                    String cdlUserName = cdl.getUserName();
815                    if (cdlUserName == null) {
816                        cdlUserName = "";
817                    }
818                    for (jmri.ConditionalVariable var : cdl.getStateVariableList()) {
819                        if (var.getBean() == dPair.dp) {
820                            String refName = (cdlUserName.equals("")) ? cdlName : cdlName + "  ( " + cdlUserName + " )";
821                            if (!conditionalReferences.contains(refName)) {
822                                conditionalReferences.add(refName);
823                            }
824                        }
825                    }
826                    for (jmri.ConditionalAction act : cdl.getActionList()) {
827                        if (act.getBean() == dPair.dp) {
828                            String refName = (cdlUserName.equals("")) ? cdlName : cdlName + "  ( " + cdlUserName + " )";
829                            if (!conditionalReferences.contains(refName)) {
830                                conditionalReferences.add(refName);
831                            }
832                        }
833                    }
834                }
835            }
836        }
837        if (conditionalReferences.isEmpty()) {
838            return true;
839        }
840
841        conditionalReferences.sort(null);
842        StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteReferences"));
843        for (String ref : conditionalReferences) {
844            msg.append("\n    " + ref);  // NOI18N
845        }
846        JOptionPane.showMessageDialog(null,
847                msg.toString(),
848                Bundle.getMessage("WarningTitle"),  // NOI18N
849                JOptionPane.WARNING_MESSAGE);
850
851        return false;
852    }
853
854    /**
855     * Display a list of pending deletes and ask for confirmation.
856     * @since 4.11.2
857     * @return true if deletion confirmation is Yes.
858     */
859    private boolean confirmDeletePairs() {
860        if (deletePairList.size() > 0) {
861            StringBuilder msg = new StringBuilder(Bundle.getMessage("DeletePairs"));  // NOI18N
862            for (DeletePair dPair : deletePairList) {
863                if (dPair.dp != null) {
864                    msg.append("\n    " + dPair.dp.getDisplayName());  // NOI18N
865                }
866            }
867            msg.append("\n" + Bundle.getMessage("DeleteContinue"));  // NOI18N
868            int resp = JOptionPane.showConfirmDialog(null,
869                    msg.toString(),
870                    Bundle.getMessage("WarningTitle"),  // NOI18N
871                    JOptionPane.YES_NO_OPTION,
872                    JOptionPane.QUESTION_MESSAGE);
873            if (resp != 0) {
874                return false;
875            }
876        }
877        return true;
878    }
879
880    /**
881     * Delete the pairs in the delete pair list.
882     * @since 4.11.2
883     * @since 4.17.4
884     * Remove from Change Listener.
885     */
886    private void deleteNxPairs() {
887        for (DeletePair dp : deletePairList) {
888            PointDetails sourcePoint = getPointDetails(dp.src, dp.pnl);
889            PointDetails destPoint = getPointDetails(dp.dest, dp.pnl);
890            nxpair.get(sourcePoint).removeDestination(destPoint);
891            firePropertyChange("length", null, null);  // NOI18N
892            if (nxpair.get(sourcePoint).getDestinationPoints().isEmpty()) {
893                nxpair.get(sourcePoint).removePropertyChangeListener(this);
894                nxpair.remove(sourcePoint);
895            }
896        }
897    }
898
899    /**
900     * List of NX pairs that are scheduled for deletion.
901     * @since 4.11.2
902     */
903    List<DeletePair> deletePairList = new ArrayList<>();
904
905    /**
906     * Class to store NX pair components.
907     * @since 4.11.2
908     */
909    class DeletePair {
910        NamedBean src = null;
911        NamedBean dest = null;
912        LayoutEditor pnl = null;
913        DestinationPoints dp = null;
914
915        /**
916         * Constructor for a DeletePair row.
917         *
918         * @param src  Source sensor bean
919         * @param dest Ddestination sensor bean
920         * @param pnl  The LayoutEditor panel for the source bean
921         */
922        DeletePair(NamedBean src, NamedBean dest, LayoutEditor pnl) {
923            this.src = src;
924            this.dest = dest;
925            this.pnl = pnl;
926
927            // Get the actual destination point, if any.
928            PointDetails sourcePoint = getPointDetails(src, pnl);
929            PointDetails destPoint = getPointDetails(dest, pnl);
930            if (sourcePoint != null && destPoint != null) {
931                if (nxpair.containsKey(sourcePoint)) {
932                    this.dp = nxpair.get(sourcePoint).getDestForPoint(destPoint);
933                }
934            }
935        }
936    }
937
938    /**
939     * Rebuild the delete pair list based on the supplied sensor.
940     * Find all of the NX pairs that use this sensor as either a source or
941     * destination.  They will be candidates for deletion.
942     * <p>
943     * @since 4.11.2
944     * @param sensor The sensor being deleted,
945     */
946    void createDeletePairList(NamedBean sensor) {
947        deletePairList.clear();
948        nxpair.forEach((pdSrc, src) -> {
949            Sensor sBean = pdSrc.getSensor();
950            LayoutEditor sPanel = pdSrc.getPanel();
951            for (PointDetails pdDest : src.getDestinationPoints()) {
952                Sensor dBean = pdDest.getSensor();
953                if (sensor == sBean || sensor == dBean) {
954                    log.debug("Delete pair: {} to {}, panel = {}",  // NOI18N
955                            sBean.getDisplayName(), dBean.getDisplayName(), sPanel.getLayoutName());
956                    deletePairList.add(new DeletePair(sBean, dBean, sPanel));
957                }
958            }
959        });
960    }
961
962    // ============ End NX Pair Delete Methods ============
963
964    /**
965     * Create a list of sensors that have the layout block as either
966     * facing or protecting.
967     * Called by {@link jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTrackEditor#hasNxSensorPairs}.
968     * @since 4.11.2
969     * @param layoutBlock The layout block to be checked.
970     * @return the a list of sensors affected by the layout block or an empty list.
971     */
972    public List<String> layoutBlockSensors(@Nonnull LayoutBlock layoutBlock) {
973        log.debug("layoutBlockSensors: {}", layoutBlock.getDisplayName());
974        List<String> blockSensors = new ArrayList<>();
975        nxpair.forEach((pdSrc, src) -> {
976            Sensor sBean = pdSrc.getSensor();
977            for (LayoutBlock sProtect : pdSrc.getProtecting()) {
978                if (layoutBlock == pdSrc.getFacing() || layoutBlock == sProtect) {
979                    log.debug("  Source = '{}', Facing = '{}', Protecting = '{}'         ",
980                            sBean.getDisplayName(), pdSrc.getFacing().getDisplayName(), sProtect.getDisplayName());
981                    blockSensors.add(sBean.getDisplayName());
982                }
983            }
984
985            for (PointDetails pdDest : src.getDestinationPoints()) {
986                Sensor dBean = pdDest.getSensor();
987                for (LayoutBlock dProtect : pdDest.getProtecting()) {
988                    if (layoutBlock == pdDest.getFacing() || layoutBlock == dProtect) {
989                        log.debug("    Destination = '{}', Facing = '{}', Protecting = '{}'     ",
990                                dBean.getDisplayName(), pdDest.getFacing().getDisplayName(), dProtect.getDisplayName());
991                        blockSensors.add(dBean.getDisplayName());
992                    }
993                }
994            }
995        });
996        return blockSensors;
997    }
998
999    public boolean isDestinationValid(Object source, Object dest, LayoutEditor panel) {
1000        if (nxpair.containsKey(getPointDetails(source, panel))) {
1001            return nxpair.get(getPointDetails(source, panel)).isDestinationValid(getPointDetails(dest, panel));
1002        }
1003        return false;
1004    }
1005
1006    public boolean isUniDirection(Object source, LayoutEditor panel, Object dest) {
1007        if (nxpair.containsKey(getPointDetails(source, panel))) {
1008            return nxpair.get(getPointDetails(source, panel)).getUniDirection(dest, panel);
1009        }
1010        return false;
1011    }
1012
1013    public void setUniDirection(Object source, LayoutEditor panel, Object dest, boolean set) {
1014        if (nxpair.containsKey(getPointDetails(source, panel))) {
1015            nxpair.get(getPointDetails(source, panel)).setUniDirection(dest, panel, set);
1016        }
1017    }
1018
1019    public boolean canBeBiDirectional(Object source, LayoutEditor panel, Object dest) {
1020        if (nxpair.containsKey(getPointDetails(source, panel))) {
1021            return nxpair.get(getPointDetails(source, panel)).canBeBiDirection(dest, panel);
1022        }
1023        return false;
1024    }
1025
1026    public boolean isEnabled(Object source, LayoutEditor panel, Object dest) {
1027        if (nxpair.containsKey(getPointDetails(source, panel))) {
1028            return nxpair.get(getPointDetails(source, panel)).isEnabled(dest, panel);
1029        }
1030        return false;
1031    }
1032
1033    public void setEnabled(Object source, LayoutEditor panel, Object dest, boolean set) {
1034        if (nxpair.containsKey(getPointDetails(source, panel))) {
1035            nxpair.get(getPointDetails(source, panel)).setEnabled(dest, panel, set);
1036        }
1037    }
1038
1039    public void setEntryExitType(Object source, LayoutEditor panel, Object dest, int set) {
1040        if (nxpair.containsKey(getPointDetails(source, panel))) {
1041            nxpair.get(getPointDetails(source, panel)).setEntryExitType(dest, panel, set);
1042        }
1043    }
1044
1045    public int getEntryExitType(Object source, LayoutEditor panel, Object dest) {
1046        if (nxpair.containsKey(getPointDetails(source, panel))) {
1047            return nxpair.get(getPointDetails(source, panel)).getEntryExitType(dest, panel);
1048        }
1049        return 0x00;
1050    }
1051
1052    public String getUniqueId(Object source, LayoutEditor panel, Object dest) {
1053        if (nxpair.containsKey(getPointDetails(source, panel))) {
1054            return nxpair.get(getPointDetails(source, panel)).getUniqueId(dest, panel);
1055        }
1056        return null;
1057    }
1058
1059    public List<String> getEntryExitList() {
1060        List<String> destlist = new ArrayList<>();
1061        for (Source e : nxpair.values()) {
1062            destlist.addAll(e.getDestinationUniqueId());
1063        }
1064        return destlist;
1065    }
1066
1067    // protecting helps us to determine which direction we are going.
1068    // validateOnly flag is used, if all we are doing is simply checking to see if the source/destpoints are valid
1069    // when creating the pairs in the user GUI
1070    public boolean isPathActive(Object sourceObj, Object destObj, LayoutEditor panel) {
1071        PointDetails pd = getPointDetails(sourceObj, panel);
1072        if (nxpair.containsKey(pd)) {
1073            Source source = nxpair.get(pd);
1074            return source.isRouteActive(getPointDetails(destObj, panel));
1075        }
1076        return false;
1077    }
1078
1079    public void cancelInterlock(Object source, LayoutEditor panel, Object dest) {
1080        if (nxpair.containsKey(getPointDetails(source, panel))) {
1081            nxpair.get(getPointDetails(source, panel)).cancelInterlock(dest, panel);
1082        }
1083
1084    }
1085
1086    jmri.SignalMastLogicManager smlm = InstanceManager.getDefault(jmri.SignalMastLogicManager.class);
1087
1088    public final static int CANCELROUTE = 0;
1089    public final static int CLEARROUTE = 1;
1090    public final static int EXITROUTE = 2;
1091    public final static int STACKROUTE = 4;
1092
1093   /**
1094     * Return a point from a given LE Panel.
1095     *
1096     * @param obj The point object
1097     * @param panel The Layout Editor panel on which the point was placed
1098     * @return the point object, null if the point is not found
1099     */
1100    public PointDetails getPointDetails(Object obj, LayoutEditor panel) {
1101        for (int i = 0; i < pointDetails.size(); i++) {
1102            if ((pointDetails.get(i).getRefObject() == obj)) {
1103                return pointDetails.get(i);
1104
1105            }
1106        }
1107        return null;
1108    }
1109
1110    /**
1111     * Return either an existing point stored in pointDetails, or create a new one as required.
1112     *
1113     * @param source The Layout Block functioning as the source (origin)
1114     * @param destination A (list of) Layout Blocks functioning as destinations
1115     * @param panel The Layout Editor panel on which the point is to be placed
1116     * @return the point object
1117     */
1118    PointDetails getPointDetails(LayoutBlock source, List<LayoutBlock> destination, LayoutEditor panel) {
1119        PointDetails newPoint = new PointDetails(source, destination);
1120        newPoint.setPanel(panel);
1121        for (int i = 0; i < pointDetails.size(); i++) {
1122            if (pointDetails.get(i).equals(newPoint)) {
1123                return pointDetails.get(i);
1124            }
1125        }
1126        //Not found so will add
1127        pointDetails.add(newPoint);
1128        return newPoint;
1129    }
1130
1131    //No point can have multiple copies of what is the same thing.
1132    static List<PointDetails> pointDetails = new ArrayList<PointDetails>();
1133
1134    /**
1135     * Get the name of a destinationPoint on a LE Panel.
1136     *
1137     * @param obj the point object
1138     * @param panel The Layout Editor panel on which it is expected to be placed
1139     * @return the name of the point
1140     */
1141    public String getPointAsString(NamedBean obj, LayoutEditor panel) {
1142        if (obj == null) {
1143            return "null";  // NOI18N
1144        }
1145        PointDetails valid = getPointDetails(obj, panel);  //was just plain getPoint
1146        if (valid != null) {
1147            return valid.getDisplayName();
1148        }
1149        return "empty";  // NOI18N
1150    }
1151
1152    List<StackDetails> stackList = new ArrayList<>();
1153
1154    /**
1155     * If a route is requested but is currently blocked, ask user
1156     * if it should be added to stackList.
1157     *
1158     * @param dp DestinationPoints object
1159     * @param reverse true for a reversed running direction, mostly false
1160     */
1161    synchronized public void stackNXRoute(DestinationPoints dp, boolean reverse) {
1162        if (isRouteStacked(dp, reverse)) {
1163            return;
1164        }
1165        stackList.add(new StackDetails(dp, reverse));
1166        checkTimer.start();
1167        if (stackPanel == null) {
1168            stackPanel = new StackNXPanel();
1169        }
1170        if (stackDialog == null) {
1171            stackDialog = new JDialog();
1172            stackDialog.setTitle(Bundle.getMessage("WindowTitleStackRoutes"));  // NOI18N
1173            stackDialog.add(stackPanel);
1174        }
1175        stackPanel.updateGUI();
1176
1177        stackDialog.pack();
1178        stackDialog.setModal(false);
1179        stackDialog.setVisible(true);
1180    }
1181
1182    StackNXPanel stackPanel = null;
1183    JDialog stackDialog = null;
1184
1185    /**
1186     * Get a list of all stacked routes from stackList.
1187     *
1188     * @return an List containing destinationPoint elements
1189     */
1190    public List<DestinationPoints> getStackedInterlocks() {
1191        List<DestinationPoints> dpList = new ArrayList<>();
1192        for (StackDetails st : stackList) {
1193            dpList.add(st.getDestinationPoint());
1194        }
1195        return dpList;
1196    }
1197
1198    /**
1199     * Query if a stacked route is in stackList.
1200     *
1201     * @param dp DestinationPoints object
1202     * @param reverse true for a reversed running direction, mostly false
1203     * @return true if dp is in stackList
1204     */
1205    public boolean isRouteStacked(DestinationPoints dp, boolean reverse) {
1206        Iterator<StackDetails> iter = stackList.iterator();
1207        while (iter.hasNext()) {
1208            StackDetails st = iter.next();
1209            if (st.getDestinationPoint() == dp && st.getReverse() == reverse) {
1210                return true;
1211            }
1212        }
1213        return false;
1214    }
1215
1216    /**
1217     * Remove a stacked route from stackList.
1218     *
1219     * @param dp DestinationPoints object
1220     * @param reverse true for a reversed running direction, mostly false
1221     */
1222    synchronized public void cancelStackedRoute(DestinationPoints dp, boolean reverse) {
1223        Iterator<StackDetails> iter = stackList.iterator();
1224        while (iter.hasNext()) {
1225            StackDetails st = iter.next();
1226            if (st.getDestinationPoint() == dp && st.getReverse() == reverse) {
1227                iter.remove();
1228            }
1229        }
1230        stackPanel.updateGUI();
1231        if (stackList.isEmpty()) {
1232            stackDialog.setVisible(false);
1233            checkTimer.stop();
1234        }
1235    }
1236
1237    /**
1238     * Class to collect (stack) routes when they are requested but blocked.
1239     */
1240    static class StackDetails {
1241
1242        DestinationPoints dp;
1243        boolean reverse;
1244
1245        StackDetails(DestinationPoints dp, boolean reverse) {
1246            this.dp = dp;
1247            this.reverse = reverse;
1248        }
1249
1250        boolean getReverse() {
1251            return reverse;
1252        }
1253
1254        DestinationPoints getDestinationPoint() {
1255            return dp;
1256        }
1257    }
1258
1259    javax.swing.Timer checkTimer = new javax.swing.Timer(10000, (java.awt.event.ActionEvent e) -> {
1260        checkRoute();
1261    });
1262
1263    /**
1264     * Step through stackList and activate the first stacked route in line
1265     * if it is no longer blocked.
1266     */
1267    synchronized void checkRoute() {
1268        checkTimer.stop();
1269        StackDetails[] tmp = new StackDetails[stackList.size()];
1270        stackList.toArray(tmp);
1271
1272        for (StackDetails st : tmp) {
1273            if (!st.getDestinationPoint().isActive()) {
1274                // If the route is not already active, then check.
1275                // If the route does get set, then the setting process will remove the route from the stack.
1276                st.getDestinationPoint().setInterlockRoute(st.getReverse());
1277            }
1278        }
1279
1280        if (!stackList.isEmpty()) {
1281            checkTimer.start();
1282        } else {
1283            stackDialog.setVisible(false);
1284        }
1285    }
1286
1287    public void removePropertyChangeListener(PropertyChangeListener list, NamedBean obj, LayoutEditor panel) {
1288        if (obj == null) {
1289            return;
1290        }
1291        PointDetails valid = getPointDetails(obj, panel);
1292        if (valid != null) {
1293            valid.removePropertyChangeListener(list);
1294        }
1295    }
1296
1297    boolean runWhenStabilised = false;
1298    LayoutEditor toUseWhenStable;
1299    int interlockTypeToUseWhenStable;
1300
1301    /**
1302     * Discover all possible valid source and destination Signal Mast Logic pairs
1303     * on all Layout Editor panels.
1304     *
1305     * @param editor The Layout Editor panel
1306     * @param interlockType Integer value representing the type of interlocking, one of
1307     *                      SETUPTURNOUTSONLY, SETUPSIGNALMASTLOGIC or FULLINTERLOCK
1308     * @throws JmriException when an error occurs during discovery
1309     */
1310    public void automaticallyDiscoverEntryExitPairs(LayoutEditor editor, int interlockType) throws JmriException {
1311        //This is almost a duplicate of that in the DefaultSignalMastLogicManager
1312        runWhenStabilised = false;
1313        jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class);
1314        if (!lbm.isAdvancedRoutingEnabled()) {
1315            throw new JmriException("advanced routing not enabled");  // NOI18N
1316        }
1317        if (!lbm.routingStablised()) {
1318            runWhenStabilised = true;
1319            toUseWhenStable = editor;
1320            interlockTypeToUseWhenStable = interlockType;
1321            log.debug("Layout block routing has not yet stabilised, discovery will happen once it has");  // NOI18N
1322            return;
1323        }
1324        HashMap<NamedBean, List<NamedBean>> validPaths = lbm.getLayoutBlockConnectivityTools().
1325                discoverValidBeanPairs(null, Sensor.class, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR);
1326        EntryExitPairs eep = this;
1327        for (Entry<NamedBean, List<NamedBean>> entry : validPaths.entrySet()) {
1328            NamedBean key = entry.getKey();
1329            List<NamedBean> validDestMast = validPaths.get(key);
1330            if (validDestMast.size() > 0) {
1331                eep.addNXSourcePoint(key, editor);
1332                for (int i = 0; i < validDestMast.size(); i++) {
1333                    if (!eep.isDestinationValid(key, validDestMast.get(i), editor)) {
1334                        eep.addNXDestination(key, validDestMast.get(i), editor);
1335                        eep.setEntryExitType(key, editor, validDestMast.get(i), interlockType);
1336                    }
1337                }
1338            }
1339        }
1340
1341        firePropertyChange("autoGenerateComplete", null, null);  // NOI18N
1342    }
1343
1344    protected PropertyChangeListener propertyBlockManagerListener = new PropertyChangeListener() {
1345        @Override
1346        public void propertyChange(PropertyChangeEvent e) {
1347            if (e.getPropertyName().equals("topology")) {  // NOI18N
1348                //boolean newValue = new Boolean.parseBoolean(String.valueOf(e.getNewValue()));
1349                boolean newValue = (Boolean) e.getNewValue();
1350                if (newValue) {
1351                    if (runWhenStabilised) {
1352                        try {
1353                            automaticallyDiscoverEntryExitPairs(toUseWhenStable, interlockTypeToUseWhenStable);
1354                        } catch (JmriException je) {
1355                            //Considered normal if routing not enabled
1356                        }
1357                    }
1358                }
1359            }
1360        }
1361    };
1362
1363    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
1364
1365    }
1366
1367    @Override
1368    public void deleteBean(@Nonnull DestinationPoints bean, @Nonnull String property) throws PropertyVetoException {
1369
1370    }
1371
1372    @Override
1373    @Nonnull
1374    public String getBeanTypeHandled(boolean plural) {
1375        return Bundle.getMessage(plural ? "BeanNameTransits" : "BeanNameTransit");  // NOI18N
1376    }
1377
1378    /**
1379     * {@inheritDoc}
1380     */
1381    @Override
1382    public Class<DestinationPoints> getNamedBeanClass() {
1383        return DestinationPoints.class;
1384    }
1385
1386    /**
1387     * {@inheritDoc}
1388     */
1389    @Override
1390    @OverridingMethodsMustInvokeSuper
1391    public void setPropertyChangesSilenced(@Nonnull String propertyName, boolean silenced) {
1392        if (!"beans".equals(propertyName)) {
1393            throw new IllegalArgumentException("Property " + propertyName + " cannot be silenced.");
1394        }
1395        silencedProperties.put(propertyName, silenced);
1396        if (propertyName.equals("beans") && !silenced) {
1397            fireIndexedPropertyChange("beans", getNamedBeanSet().size(), null, null);
1398        }
1399    }
1400
1401    /** {@inheritDoc} */
1402    @Override
1403    @Deprecated
1404    @SuppressWarnings("deprecation")
1405    public void addDataListener(ManagerDataListener<DestinationPoints> e) {
1406        if (e != null) listeners.add(e);
1407    }
1408
1409    /** {@inheritDoc} */
1410    @Override
1411    @Deprecated
1412    @SuppressWarnings("deprecation")
1413    public void removeDataListener(ManagerDataListener<DestinationPoints> e) {
1414        if (e != null) listeners.remove(e);
1415    }
1416
1417    @SuppressWarnings("deprecation")
1418    final List<ManagerDataListener<DestinationPoints>> listeners = new ArrayList<>();
1419
1420    // initialize logging
1421    private final static Logger log = LoggerFactory.getLogger(EntryExitPairs.class);
1422
1423}