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