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 the route between the two points represented by the Destination Point name.
418     *
419     * @since 4.11.1
420     * @param nxPair The system or user name of the destination point.
421     */
422    public void setSingleSegmentRoute(String nxPair) {
423        DestinationPoints dp = getNamedBean(nxPair);
424        if (dp != null) {
425            String destUUID = dp.getUniqueId();
426            nxpair.forEach((pd, src) -> {
427                for (String srcUUID : src.getDestinationUniqueId()) {
428                    if (destUUID.equals(srcUUID)) {
429                        log.debug("Found the correct source: src = {}, dest = {}",
430                                pd.getSensor().getDisplayName(), dp.getDestPoint().getSensor().getDisplayName());
431                        setMultiPointRoute(pd, dp.getDestPoint());
432                        return;
433                    }
434                }
435            });
436        }
437    }
438
439    public void setMultiPointRoute(PointDetails requestpd, LayoutEditor panel) {
440        for (PointDetails pd : pointDetails) {
441            if (pd != requestpd) {
442                if (pd.getNXState() == NXBUTTONSELECTED) {
443                    setMultiPointRoute(pd, requestpd);
444                    return;
445                }
446            }
447        }
448    }
449
450    private void setMultiPointRoute(PointDetails fromPd, PointDetails toPd) {
451        boolean cleardown = false;
452        if (fromPd.isRouteFromPointSet() && toPd.isRouteToPointSet()) {
453            cleardown = true;
454        }
455        for (LayoutBlock pro : fromPd.getProtecting()) {
456            try {
457                jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class);
458                LayoutBlock toProt = null;
459                if (!toPd.getProtecting().isEmpty()) {
460                    toProt = toPd.getProtecting().get(0);
461                }
462                boolean result = lbm.getLayoutBlockConnectivityTools().checkValidDest(fromPd.getFacing(), pro, toPd.getFacing(), toProt, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR);
463                if (result) {
464                    List<LayoutBlock> blkList = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(fromPd.getFacing(), toPd.getFacing(), pro, cleardown, LayoutBlockConnectivityTools.Routing.NONE);
465                    if (!blkList.isEmpty()) {
466                        if (log.isDebugEnabled()) {
467                            for (LayoutBlock blk : blkList) {
468                                log.debug("blk = {}", blk.getDisplayName());
469                            }
470                        }
471                        List<jmri.NamedBean> beanList = lbm.getLayoutBlockConnectivityTools().getBeansInPath(blkList, null, jmri.Sensor.class);
472                        PointDetails fromPoint = fromPd;
473                        refCounter++;
474                        if (!beanList.isEmpty()) {
475                            if (log.isDebugEnabled()) {
476                                for (NamedBean xnb : beanList) {
477                                    log.debug("xnb = {}", xnb.getDisplayName());
478                                }
479                            }
480                            for (int i = 1; i < beanList.size(); i++) {
481                                NamedBean nb = beanList.get(i);
482                                PointDetails cur = getPointDetails(nb, fromPd.getPanel());
483                                Source s = nxpair.get(fromPoint);
484                                if (s != null) {
485                                    routesToSet.add(new SourceToDest(s, s.getDestForPoint(cur), false, refCounter));
486                                }
487                                fromPoint = cur;
488                            }
489                        }
490                        Source s = nxpair.get(fromPoint);
491                        if (s != null) {
492                            if (s.getDestForPoint(toPd) != null) {
493                                routesToSet.add(new SourceToDest(s, s.getDestForPoint(toPd), false, refCounter));
494                            }
495                        }
496                        processRoutesToSet();
497                        return;
498                    }
499                }
500            } catch (jmri.JmriException e) {
501                //Can be considered normal if route is blocked
502            }
503        }
504        fromPd.setNXButtonState(NXBUTTONINACTIVE);
505        toPd.setNXButtonState(NXBUTTONINACTIVE);
506    }
507
508    int refCounter = 0;
509
510    /**
511     * List holding SourceToDest sets of routes between two points.
512     */
513    List<SourceToDest> routesToSet = new ArrayList<>();
514
515    /**
516     * Class to store NX sets consisting of a source point, a destination point,
517     * a direction and a reference.
518     */
519    static class SourceToDest {
520
521        Source s = null;
522        DestinationPoints dp = null;
523        boolean direction = false;
524        int ref = -1;
525
526        /**
527         * Constructor for a SourceToDest element.
528         *
529         * @param s   a source point
530         * @param dp  a destination point
531         * @param dir a direction
532         * @param ref Integer used as reference
533         */
534        SourceToDest(Source s, DestinationPoints dp, boolean dir, int ref) {
535            this.s = s;
536            this.dp = dp;
537            this.direction = dir;
538            this.ref = ref;
539        }
540    }
541
542    int currentDealing = 0;
543
544    /**
545     * Activate each SourceToDest set in routesToSet
546     */
547    synchronized void processRoutesToSet() {
548        if (log.isDebugEnabled()) {
549            for (SourceToDest sd : routesToSet) {
550                String dpName = (sd.dp == null) ? "- null -" : sd.dp.getDestPoint().getSensor().getDisplayName();
551                log.debug("processRoutesToSet: {} -- {} -- {}", sd.s.getPoint().getSensor().getDisplayName(), dpName, sd.ref);
552            }
553        }
554
555        if (routesToSet.isEmpty()) {
556            return;
557        }
558        Source s = routesToSet.get(0).s;
559        DestinationPoints dp = routesToSet.get(0).dp;
560        boolean dir = routesToSet.get(0).direction;
561        currentDealing = routesToSet.get(0).ref;
562        routesToSet.remove(0);
563
564        dp.addPropertyChangeListener(propertyDestinationListener);
565        s.activeBean(dp, dir);
566    }
567
568    /**
569     * Remove remaining SourceToDest sets in routesToSet
570     */
571    synchronized void removeRemainingRoute() {
572        List<SourceToDest> toRemove = new ArrayList<>();
573        for (SourceToDest rts : routesToSet) {
574            if (rts.ref == currentDealing) {
575                toRemove.add(rts);
576                rts.dp.getDestPoint().setNXButtonState(NXBUTTONINACTIVE);
577            }
578        }
579        for (SourceToDest rts : toRemove) {
580            routesToSet.remove(rts);
581        }
582    }
583
584    protected PropertyChangeListener propertyDestinationListener = new PropertyChangeListener() {
585        @Override
586        public void propertyChange(PropertyChangeEvent e) {
587            ((DestinationPoints) e.getSource()).removePropertyChangeListener(this);
588            if (e.getPropertyName().equals("active")) {
589                processRoutesToSet();
590            } else if (e.getPropertyName().equals("stacked") || e.getPropertyName().equals("failed") || e.getPropertyName().equals("noChange")) {  // NOI18N
591                removeRemainingRoute();
592            }
593        }
594    };
595
596    List<Object> destinationList = new ArrayList<>();
597
598    // Need to sort out the presentation of the name here rather than using the point ID.
599    // This is used for the creation and display of information in the table.
600    // The presentation of the name might have to be done at the table level.
601    public List<Object> getNxSource(LayoutEditor panel) {
602        List<Object> source = new ArrayList<>();
603        destinationList = new ArrayList<>();
604
605        for (Entry<PointDetails, Source> e : nxpair.entrySet()) {
606            PointDetails key = e.getKey();
607            LayoutEditor pan = key.getPanel();
608            if (pan == panel) {
609                List<PointDetails> dest = nxpair.get(key).getDestinationPoints();
610                for (int i = 0; i < dest.size(); i++) {
611                    destinationList.add(dest.get(i).getRefObject());
612                    source.add(key.getRefObject());
613                }
614            }
615        }
616        return source;
617    }
618
619    public List<Object> getNxDestination() {
620        return destinationList;
621    }
622
623    public List<LayoutEditor> getSourcePanelList() {
624        List<LayoutEditor> list = new ArrayList<>();
625
626        for (Entry<PointDetails, Source> e : nxpair.entrySet()) {
627            PointDetails key = e.getKey();
628            LayoutEditor pan = key.getPanel();
629            if (!list.contains(pan)) {
630                list.add(pan);
631            }
632        }
633        return list;
634    }
635
636    /**
637     * Return a point if it already exists, or create a new one if not.
638     */
639    private PointDetails providePoint(LayoutBlock source, List<LayoutBlock> protecting, LayoutEditor panel) {
640        PointDetails sourcePoint = getPointDetails(source, protecting, panel);
641        if (sourcePoint == null) {
642            sourcePoint = new PointDetails(source, protecting);
643            sourcePoint.setPanel(panel);
644        }
645        return sourcePoint;
646    }
647
648    /**
649     * @since 4.17.4
650     */
651    @Override
652    public void propertyChange(PropertyChangeEvent evt) {
653        firePropertyChange("active", evt.getOldValue(), evt.getNewValue());
654    }
655
656
657    public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel) {
658        addNXDestination(source, destination, panel, null);
659    }
660
661    /**
662     * @since 4.17.4
663     * Register in Property Change Listener.
664     * @param source the source bean.
665     * @param destination the destination bean.
666     * @param panel the layout editor panel.
667     * @param id the points details id.
668     */
669    public void addNXDestination(NamedBean source, NamedBean destination, LayoutEditor panel, String id) {
670        if (source == null) {
671            log.error("no source Object provided");  // NOI18N
672            return;
673        }
674        if (destination == null) {
675            log.error("no destination Object provided");  // NOI18N
676            return;
677        }
678        PointDetails sourcePoint = providePoint(source, panel);
679        if (sourcePoint == null) {
680            log.error("source point for {} not created addNXDes", source.getDisplayName());  // NOI18N
681            return;
682        }
683
684        sourcePoint.setPanel(panel);
685        sourcePoint.setRefObject(source);
686        PointDetails destPoint = providePoint(destination, panel);
687        if (destPoint != null) {
688            destPoint.setPanel(panel);
689            destPoint.setRefObject(destination);
690            destPoint.getSignal();
691            if (!nxpair.containsKey(sourcePoint)) {
692                Source sp = new Source(sourcePoint);
693                nxpair.put(sourcePoint, sp);
694                sp.removePropertyChangeListener(this);
695                sp.addPropertyChangeListener(this);
696            }
697            nxpair.get(sourcePoint).addDestination(destPoint, id);
698        }
699
700        firePropertyChange("length", null, null);  // NOI18N
701    }
702
703    public List<Object> getDestinationList(Object obj, LayoutEditor panel) {
704        List<Object> list = new ArrayList<>();
705        if (nxpair.containsKey(getPointDetails(obj, panel))) {
706            List<PointDetails> from = nxpair.get(getPointDetails(obj, panel)).getDestinationPoints();
707            for (int i = 0; i < from.size(); i++) {
708                list.add(from.get(i).getRefObject());
709            }
710        }
711        return list;
712    }
713
714    public void removeNXSensor(Sensor sensor) {
715        log.info("panel maintenance has resulting in the request to remove a sensor: {}", sensor.getDisplayName());
716    }
717
718    // ============ NX Pair Delete Methods ============
719    // The request will be for all NX Pairs containing a sensor or
720    // a specific entry and exit sensor pair.
721
722    /**
723     * Entry point to delete all of the NX pairs for a specific sensor.
724     * 1) Build a list of affected NX pairs.
725     * 2) Check for Conditional references.
726     * 3) If no references, do the delete process with user approval.
727     * @since 4.11.2
728     * @param sensor The sensor whose pairs should be deleted.
729     * @return true if the delete was successful. False if prevented by
730     * Conditional references or user choice.
731     */
732    public boolean deleteNxPair(NamedBean sensor) {
733        if (sensor == null) {
734            log.error("deleteNxPair: sensor is null");  // NOI18N
735            return false;
736        }
737        createDeletePairList(sensor);
738        if (checkNxPairs()) {
739            // No Conditional references.
740            if (confirmDeletePairs()) {
741                deleteNxPairs();
742                return true;
743            }
744        }
745        return false;
746    }
747
748    /**
749     * Entry point to delete a specific NX pair.
750     *
751     * @since 4.11.2
752     * @param entrySensor The sensor that acts as the entry point.
753     * @param exitSensor The sensor that acts as the exit point.
754     * @param panel The layout editor panel that contains the entry sensor.
755     * @return true if the delete was successful. False if there are Conditional references.
756     */
757    public boolean deleteNxPair(NamedBean entrySensor, NamedBean exitSensor, LayoutEditor panel) {
758        if (entrySensor == null || exitSensor == null || panel == null) {
759            log.error("deleteNxPair: One or more null inputs");  // NOI18N
760            return false;
761        }
762        deletePairList.clear();
763        deletePairList.add(new DeletePair(entrySensor, exitSensor, panel));
764        if (checkNxPairs()) {
765            // No Conditional references.
766            deleteNxPairs();  // Delete with no prompt
767            return true;
768        }
769        return false;
770    }
771
772    /**
773     * Find Logix Conditionals that have Variables or Actions for the affected NX Pairs
774     * If any are found, display a dialog box listing the Conditionals and return false.
775     * <p>
776     * @since 4.11.2
777     * @return true if there are no references.
778     */
779    private boolean checkNxPairs() {
780        jmri.LogixManager mgr = InstanceManager.getDefault(jmri.LogixManager.class);
781        List<String> conditionalReferences = new ArrayList<>();
782        for (DeletePair dPair : deletePairList) {
783            if (dPair.dp == null) {
784                continue;
785            }
786            for (jmri.Logix lgx : mgr.getNamedBeanSet()) {
787                for (int i = 0; i < lgx.getNumConditionals(); i++) {
788                    String cdlName = lgx.getConditionalByNumberOrder(i);
789                    jmri.implementation.DefaultConditional cdl = (jmri.implementation.DefaultConditional) lgx.getConditional(cdlName);
790                    String cdlUserName = cdl.getUserName();
791                    if (cdlUserName == null) {
792                        cdlUserName = "";
793                    }
794                    for (jmri.ConditionalVariable var : cdl.getStateVariableList()) {
795                        if (var.getBean() == dPair.dp) {
796                            String refName = (cdlUserName.equals("")) ? cdlName : cdlName + "  ( " + cdlUserName + " )";
797                            if (!conditionalReferences.contains(refName)) {
798                                conditionalReferences.add(refName);
799                            }
800                        }
801                    }
802                    for (jmri.ConditionalAction act : cdl.getActionList()) {
803                        if (act.getBean() == dPair.dp) {
804                            String refName = (cdlUserName.equals("")) ? cdlName : cdlName + "  ( " + cdlUserName + " )";
805                            if (!conditionalReferences.contains(refName)) {
806                                conditionalReferences.add(refName);
807                            }
808                        }
809                    }
810                }
811            }
812        }
813        if (conditionalReferences.isEmpty()) {
814            return true;
815        }
816
817        conditionalReferences.sort(null);
818        StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteReferences"));
819        for (String ref : conditionalReferences) {
820            msg.append("\n    " + ref);  // NOI18N
821        }
822        JmriJOptionPane.showMessageDialog(null,
823                msg.toString(),
824                Bundle.getMessage("WarningTitle"),  // NOI18N
825                JmriJOptionPane.WARNING_MESSAGE);
826
827        return false;
828    }
829
830    /**
831     * Display a list of pending deletes and ask for confirmation.
832     * @since 4.11.2
833     * @return true if deletion confirmation is Yes.
834     */
835    private boolean confirmDeletePairs() {
836        if (!deletePairList.isEmpty()) {
837            StringBuilder msg = new StringBuilder(Bundle.getMessage("DeletePairs"));  // NOI18N
838            for (DeletePair dPair : deletePairList) {
839                if (dPair.dp != null) {
840                    msg.append("\n    ").append(dPair.dp.getDisplayName());  // NOI18N
841                }
842            }
843            msg.append("\n").append(Bundle.getMessage("DeleteContinue"));  // NOI18N
844            int resp = JmriJOptionPane.showConfirmDialog(null,
845                    msg.toString(),
846                    Bundle.getMessage("WarningTitle"),  // NOI18N
847                    JmriJOptionPane.YES_NO_OPTION,
848                    JmriJOptionPane.QUESTION_MESSAGE);
849            if (resp != JmriJOptionPane.YES_OPTION ) {
850                return false;
851            }
852        }
853        return true;
854    }
855
856    /**
857     * Delete the pairs in the delete pair list.
858     * @since 4.11.2
859     * @since 4.17.4
860     * Remove from Change Listener.
861     */
862    private void deleteNxPairs() {
863        for (DeletePair dp : deletePairList) {
864            PointDetails sourcePoint = getPointDetails(dp.src, dp.pnl);
865            PointDetails destPoint = getPointDetails(dp.dest, dp.pnl);
866            nxpair.get(sourcePoint).removeDestination(destPoint);
867            firePropertyChange("length", null, null);  // NOI18N
868            if (nxpair.get(sourcePoint).getDestinationPoints().isEmpty()) {
869                nxpair.get(sourcePoint).removePropertyChangeListener(this);
870                nxpair.remove(sourcePoint);
871            }
872        }
873    }
874
875    /**
876     * List of NX pairs that are scheduled for deletion.
877     * @since 4.11.2
878     */
879    List<DeletePair> deletePairList = new ArrayList<>();
880
881    /**
882     * Class to store NX pair components.
883     * @since 4.11.2
884     */
885    class DeletePair {
886        NamedBean src = null;
887        NamedBean dest = null;
888        LayoutEditor pnl = null;
889        DestinationPoints dp = null;
890
891        /**
892         * Constructor for a DeletePair row.
893         *
894         * @param src  Source sensor bean
895         * @param dest Ddestination sensor bean
896         * @param pnl  The LayoutEditor panel for the source bean
897         */
898        DeletePair(NamedBean src, NamedBean dest, LayoutEditor pnl) {
899            this.src = src;
900            this.dest = dest;
901            this.pnl = pnl;
902
903            // Get the actual destination point, if any.
904            PointDetails sourcePoint = getPointDetails(src, pnl);
905            PointDetails destPoint = getPointDetails(dest, pnl);
906            if (sourcePoint != null && destPoint != null) {
907                if (nxpair.containsKey(sourcePoint)) {
908                    this.dp = nxpair.get(sourcePoint).getDestForPoint(destPoint);
909                }
910            }
911        }
912    }
913
914    /**
915     * Rebuild the delete pair list based on the supplied sensor.
916     * Find all of the NX pairs that use this sensor as either a source or
917     * destination.  They will be candidates for deletion.
918     *
919     * @since 4.11.2
920     * @param sensor The sensor being deleted,
921     */
922    void createDeletePairList(NamedBean sensor) {
923        deletePairList.clear();
924        nxpair.forEach((pdSrc, src) -> {
925            Sensor sBean = pdSrc.getSensor();
926            LayoutEditor sPanel = pdSrc.getPanel();
927            for (PointDetails pdDest : src.getDestinationPoints()) {
928                Sensor dBean = pdDest.getSensor();
929                if (sensor == sBean || sensor == dBean) {
930                    log.debug("Delete pair: {} to {}, panel = {}",  // NOI18N
931                            sBean.getDisplayName(), dBean.getDisplayName(), sPanel.getLayoutName());
932                    deletePairList.add(new DeletePair(sBean, dBean, sPanel));
933                }
934            }
935        });
936    }
937
938    // ============ End NX Pair Delete Methods ============
939
940    /**
941     * Create a list of sensors that have the layout block as either
942     * facing or protecting.
943     * Called by {@link jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTrackEditor#hasNxSensorPairs}.
944     * @since 4.11.2
945     * @param layoutBlock The layout block to be checked.
946     * @return the a list of sensors affected by the layout block or an empty list.
947     */
948    public List<String> layoutBlockSensors(@Nonnull LayoutBlock layoutBlock) {
949        log.debug("layoutBlockSensors: {}", layoutBlock.getDisplayName());
950        List<String> blockSensors = new ArrayList<>();
951        nxpair.forEach((pdSrc, src) -> {
952            Sensor sBean = pdSrc.getSensor();
953            for (LayoutBlock sProtect : pdSrc.getProtecting()) {
954                if (layoutBlock == pdSrc.getFacing() || layoutBlock == sProtect) {
955                    log.debug("  Source = '{}', Facing = '{}', Protecting = '{}'         ",
956                            sBean.getDisplayName(), pdSrc.getFacing().getDisplayName(), sProtect.getDisplayName());
957                    blockSensors.add(sBean.getDisplayName());
958                }
959            }
960
961            for (PointDetails pdDest : src.getDestinationPoints()) {
962                Sensor dBean = pdDest.getSensor();
963                for (LayoutBlock dProtect : pdDest.getProtecting()) {
964                    if (layoutBlock == pdDest.getFacing() || layoutBlock == dProtect) {
965                        log.debug("    Destination = '{}', Facing = '{}', Protecting = '{}'     ",
966                                dBean.getDisplayName(), pdDest.getFacing().getDisplayName(), dProtect.getDisplayName());
967                        blockSensors.add(dBean.getDisplayName());
968                    }
969                }
970            }
971        });
972        return blockSensors;
973    }
974
975    public boolean isDestinationValid(Object source, Object dest, LayoutEditor panel) {
976        if (nxpair.containsKey(getPointDetails(source, panel))) {
977            return nxpair.get(getPointDetails(source, panel)).isDestinationValid(getPointDetails(dest, panel));
978        }
979        return false;
980    }
981
982    public boolean isUniDirection(Object source, LayoutEditor panel, Object dest) {
983        if (nxpair.containsKey(getPointDetails(source, panel))) {
984            return nxpair.get(getPointDetails(source, panel)).getUniDirection(dest, panel);
985        }
986        return false;
987    }
988
989    public void setUniDirection(Object source, LayoutEditor panel, Object dest, boolean set) {
990        if (nxpair.containsKey(getPointDetails(source, panel))) {
991            nxpair.get(getPointDetails(source, panel)).setUniDirection(dest, panel, set);
992        }
993    }
994
995    public boolean canBeBiDirectional(Object source, LayoutEditor panel, Object dest) {
996        if (nxpair.containsKey(getPointDetails(source, panel))) {
997            return nxpair.get(getPointDetails(source, panel)).canBeBiDirection(dest, panel);
998        }
999        return false;
1000    }
1001
1002    public boolean isEnabled(Object source, LayoutEditor panel, Object dest) {
1003        if (nxpair.containsKey(getPointDetails(source, panel))) {
1004            return nxpair.get(getPointDetails(source, panel)).isEnabled(dest, panel);
1005        }
1006        return false;
1007    }
1008
1009    public void setEnabled(Object source, LayoutEditor panel, Object dest, boolean set) {
1010        if (nxpair.containsKey(getPointDetails(source, panel))) {
1011            nxpair.get(getPointDetails(source, panel)).setEnabled(dest, panel, set);
1012        }
1013    }
1014
1015    public void setEntryExitType(Object source, LayoutEditor panel, Object dest, int set) {
1016        if (nxpair.containsKey(getPointDetails(source, panel))) {
1017            nxpair.get(getPointDetails(source, panel)).setEntryExitType(dest, panel, set);
1018        }
1019    }
1020
1021    public int getEntryExitType(Object source, LayoutEditor panel, Object dest) {
1022        if (nxpair.containsKey(getPointDetails(source, panel))) {
1023            return nxpair.get(getPointDetails(source, panel)).getEntryExitType(dest, panel);
1024        }
1025        return 0x00;
1026    }
1027
1028    public String getUniqueId(Object source, LayoutEditor panel, Object dest) {
1029        if (nxpair.containsKey(getPointDetails(source, panel))) {
1030            return nxpair.get(getPointDetails(source, panel)).getUniqueId(dest, panel);
1031        }
1032        return null;
1033    }
1034
1035    public List<String> getEntryExitList() {
1036        List<String> destlist = new ArrayList<>();
1037        for (Source e : nxpair.values()) {
1038            destlist.addAll(e.getDestinationUniqueId());
1039        }
1040        return destlist;
1041    }
1042
1043    // protecting helps us to determine which direction we are going.
1044    // validateOnly flag is used, if all we are doing is simply checking to see if the source/destpoints are valid
1045    // when creating the pairs in the user GUI
1046    public boolean isPathActive(Object sourceObj, Object destObj, LayoutEditor panel) {
1047        PointDetails pd = getPointDetails(sourceObj, panel);
1048        if (nxpair.containsKey(pd)) {
1049            Source source = nxpair.get(pd);
1050            return source.isRouteActive(getPointDetails(destObj, panel));
1051        }
1052        return false;
1053    }
1054
1055    public void cancelInterlock(Object source, LayoutEditor panel, Object dest) {
1056        if (nxpair.containsKey(getPointDetails(source, panel))) {
1057            nxpair.get(getPointDetails(source, panel)).cancelInterlock(dest, panel);
1058        }
1059
1060    }
1061
1062    jmri.SignalMastLogicManager smlm = InstanceManager.getDefault(jmri.SignalMastLogicManager.class);
1063
1064    public final static int CANCELROUTE = 0;
1065    public final static int CLEARROUTE = 1;
1066    public final static int EXITROUTE = 2;
1067    public final static int STACKROUTE = 4;
1068
1069   /**
1070     * Return a point from a given LE Panel.
1071     *
1072     * @param obj The point object
1073     * @param panel The Layout Editor panel on which the point was placed
1074     * @return the point object, null if the point is not found
1075     */
1076    public PointDetails getPointDetails(Object obj, LayoutEditor panel) {
1077        for (int i = 0; i < pointDetails.size(); i++) {
1078            if ((pointDetails.get(i).getRefObject() == obj)) {
1079                return pointDetails.get(i);
1080
1081            }
1082        }
1083        return null;
1084    }
1085
1086    /**
1087     * Return either an existing point stored in pointDetails, or create a new one as required.
1088     *
1089     * @param source The Layout Block functioning as the source (origin)
1090     * @param destination A (list of) Layout Blocks functioning as destinations
1091     * @param panel The Layout Editor panel on which the point is to be placed
1092     * @return the point object
1093     */
1094    PointDetails getPointDetails(LayoutBlock source, List<LayoutBlock> destination, LayoutEditor panel) {
1095        PointDetails newPoint = new PointDetails(source, destination);
1096        newPoint.setPanel(panel);
1097        for (int i = 0; i < pointDetails.size(); i++) {
1098            if (pointDetails.get(i).equals(newPoint)) {
1099                return pointDetails.get(i);
1100            }
1101        }
1102        //Not found so will add
1103        pointDetails.add(newPoint);
1104        return newPoint;
1105    }
1106
1107    //No point can have multiple copies of what is the same thing.
1108    static List<PointDetails> pointDetails = new ArrayList<PointDetails>();
1109
1110    /**
1111     * Get the name of a destinationPoint on a LE Panel.
1112     *
1113     * @param obj the point object
1114     * @param panel The Layout Editor panel on which it is expected to be placed
1115     * @return the name of the point
1116     */
1117    public String getPointAsString(NamedBean obj, LayoutEditor panel) {
1118        if (obj == null) {
1119            return "null";  // NOI18N
1120        }
1121        PointDetails valid = getPointDetails(obj, panel);  //was just plain getPoint
1122        if (valid != null) {
1123            return valid.getDisplayName();
1124        }
1125        return "empty";  // NOI18N
1126    }
1127
1128    List<StackDetails> stackList = new ArrayList<>();
1129
1130    /**
1131     * If a route is requested but is currently blocked, ask user
1132     * if it should be added to stackList.
1133     *
1134     * @param dp DestinationPoints object
1135     * @param reverse true for a reversed running direction, mostly false
1136     */
1137    synchronized public void stackNXRoute(DestinationPoints dp, boolean reverse) {
1138        if (isRouteStacked(dp, reverse)) {
1139            return;
1140        }
1141        stackList.add(new StackDetails(dp, reverse));
1142        checkTimer.start();
1143        if (stackPanel == null) {
1144            stackPanel = new StackNXPanel();
1145        }
1146        if (stackDialog == null) {
1147            stackDialog = new JDialog();
1148            stackDialog.setTitle(Bundle.getMessage("WindowTitleStackRoutes"));  // NOI18N
1149            stackDialog.add(stackPanel);
1150        }
1151        stackPanel.updateGUI();
1152
1153        stackDialog.pack();
1154        stackDialog.setModal(false);
1155        stackDialog.setVisible(true);
1156    }
1157
1158    StackNXPanel stackPanel = null;
1159    JDialog stackDialog = null;
1160
1161    /**
1162     * Get a list of all stacked routes from stackList.
1163     *
1164     * @return an List containing destinationPoint elements
1165     */
1166    public List<DestinationPoints> getStackedInterlocks() {
1167        List<DestinationPoints> dpList = new ArrayList<>();
1168        for (StackDetails st : stackList) {
1169            dpList.add(st.getDestinationPoint());
1170        }
1171        return dpList;
1172    }
1173
1174    /**
1175     * Query if a stacked route is in stackList.
1176     *
1177     * @param dp DestinationPoints object
1178     * @param reverse true for a reversed running direction, mostly false
1179     * @return true if dp is in stackList
1180     */
1181    public boolean isRouteStacked(DestinationPoints dp, boolean reverse) {
1182        Iterator<StackDetails> iter = stackList.iterator();
1183        while (iter.hasNext()) {
1184            StackDetails st = iter.next();
1185            if (st.getDestinationPoint() == dp && st.getReverse() == reverse) {
1186                return true;
1187            }
1188        }
1189        return false;
1190    }
1191
1192    /**
1193     * Remove a stacked route from stackList.
1194     *
1195     * @param dp DestinationPoints object
1196     * @param reverse true for a reversed running direction, mostly false
1197     */
1198    synchronized public void cancelStackedRoute(DestinationPoints dp, boolean reverse) {
1199        Iterator<StackDetails> iter = stackList.iterator();
1200        while (iter.hasNext()) {
1201            StackDetails st = iter.next();
1202            if (st.getDestinationPoint() == dp && st.getReverse() == reverse) {
1203                iter.remove();
1204            }
1205        }
1206        stackPanel.updateGUI();
1207        if (stackList.isEmpty()) {
1208            stackDialog.setVisible(false);
1209            checkTimer.stop();
1210        }
1211    }
1212
1213    /**
1214     * Class to collect (stack) routes when they are requested but blocked.
1215     */
1216    static class StackDetails {
1217
1218        DestinationPoints dp;
1219        boolean reverse;
1220
1221        StackDetails(DestinationPoints dp, boolean reverse) {
1222            this.dp = dp;
1223            this.reverse = reverse;
1224        }
1225
1226        boolean getReverse() {
1227            return reverse;
1228        }
1229
1230        DestinationPoints getDestinationPoint() {
1231            return dp;
1232        }
1233    }
1234
1235    javax.swing.Timer checkTimer = new javax.swing.Timer(10000, (java.awt.event.ActionEvent e) -> {
1236        checkRoute();
1237    });
1238
1239    /**
1240     * Step through stackList and activate the first stacked route in line
1241     * if it is no longer blocked.
1242     */
1243    synchronized void checkRoute() {
1244        checkTimer.stop();
1245        StackDetails[] tmp = new StackDetails[stackList.size()];
1246        stackList.toArray(tmp);
1247
1248        for (StackDetails st : tmp) {
1249            if (!st.getDestinationPoint().isActive()) {
1250                // If the route is not already active, then check.
1251                // If the route does get set, then the setting process will remove the route from the stack.
1252                st.getDestinationPoint().setInterlockRoute(st.getReverse());
1253            }
1254        }
1255
1256        if (!stackList.isEmpty()) {
1257            checkTimer.start();
1258        } else {
1259            stackDialog.setVisible(false);
1260        }
1261    }
1262
1263    public void removePropertyChangeListener(PropertyChangeListener list, NamedBean obj, LayoutEditor panel) {
1264        if (obj == null) {
1265            return;
1266        }
1267        PointDetails valid = getPointDetails(obj, panel);
1268        if (valid != null) {
1269            valid.removePropertyChangeListener(list);
1270        }
1271    }
1272
1273    boolean runWhenStabilised = false;
1274    LayoutEditor toUseWhenStable;
1275    int interlockTypeToUseWhenStable;
1276
1277    /**
1278     * Discover all possible valid source and destination Signal Mast Logic pairs
1279     * on all Layout Editor panels.
1280     *
1281     * @param editor The Layout Editor panel
1282     * @param interlockType Integer value representing the type of interlocking, one of
1283     *                      SETUPTURNOUTSONLY, SETUPSIGNALMASTLOGIC or FULLINTERLOCK
1284     * @throws JmriException when an error occurs during discovery
1285     */
1286    public void automaticallyDiscoverEntryExitPairs(LayoutEditor editor, int interlockType) throws JmriException {
1287        //This is almost a duplicate of that in the DefaultSignalMastLogicManager
1288        runWhenStabilised = false;
1289        jmri.jmrit.display.layoutEditor.LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class);
1290        if (!lbm.isAdvancedRoutingEnabled()) {
1291            throw new JmriException("advanced routing not enabled");  // NOI18N
1292        }
1293        if (!lbm.routingStablised()) {
1294            runWhenStabilised = true;
1295            toUseWhenStable = editor;
1296            interlockTypeToUseWhenStable = interlockType;
1297            log.debug("Layout block routing has not yet stabilised, discovery will happen once it has");  // NOI18N
1298            return;
1299        }
1300        HashMap<NamedBean, List<NamedBean>> validPaths = lbm.getLayoutBlockConnectivityTools().
1301                discoverValidBeanPairs(null, Sensor.class, LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR);
1302        EntryExitPairs eep = this;
1303        for (Entry<NamedBean, List<NamedBean>> entry : validPaths.entrySet()) {
1304            NamedBean key = entry.getKey();
1305            List<NamedBean> validDestMast = validPaths.get(key);
1306            if (validDestMast.size() > 0) {
1307                eep.addNXSourcePoint(key, editor);
1308                for (int i = 0; i < validDestMast.size(); i++) {
1309                    if (!eep.isDestinationValid(key, validDestMast.get(i), editor)) {
1310                        eep.addNXDestination(key, validDestMast.get(i), editor);
1311                        eep.setEntryExitType(key, editor, validDestMast.get(i), interlockType);
1312                    }
1313                }
1314            }
1315        }
1316
1317        firePropertyChange("autoGenerateComplete", null, null);  // NOI18N
1318    }
1319
1320    protected PropertyChangeListener propertyBlockManagerListener = new PropertyChangeListener() {
1321        @Override
1322        public void propertyChange(PropertyChangeEvent e) {
1323            if (e.getPropertyName().equals("topology")) {  // NOI18N
1324                //boolean newValue = new Boolean.parseBoolean(String.valueOf(e.getNewValue()));
1325                boolean newValue = (Boolean) e.getNewValue();
1326                if (newValue) {
1327                    if (runWhenStabilised) {
1328                        try {
1329                            automaticallyDiscoverEntryExitPairs(toUseWhenStable, interlockTypeToUseWhenStable);
1330                        } catch (JmriException je) {
1331                            //Considered normal if routing not enabled
1332                        }
1333                    }
1334                }
1335            }
1336        }
1337    };
1338
1339    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
1340
1341    }
1342
1343    @Override
1344    public void deleteBean(@Nonnull DestinationPoints bean, @Nonnull String property) throws PropertyVetoException {
1345
1346    }
1347
1348    @Override
1349    @Nonnull
1350    public String getBeanTypeHandled(boolean plural) {
1351        return Bundle.getMessage(plural ? "BeanNameTransits" : "BeanNameTransit");  // NOI18N
1352    }
1353
1354    /**
1355     * {@inheritDoc}
1356     */
1357    @Override
1358    public Class<DestinationPoints> getNamedBeanClass() {
1359        return DestinationPoints.class;
1360    }
1361
1362    /**
1363     * {@inheritDoc}
1364     */
1365    @Override
1366    @OverridingMethodsMustInvokeSuper
1367    public void setPropertyChangesSilenced(@Nonnull String propertyName, boolean silenced) {
1368        if (!"beans".equals(propertyName)) {
1369            throw new IllegalArgumentException("Property " + propertyName + " cannot be silenced.");
1370        }
1371        silencedProperties.put(propertyName, silenced);
1372        if (propertyName.equals("beans") && !silenced) {
1373            fireIndexedPropertyChange("beans", getNamedBeanSet().size(), null, null);
1374        }
1375    }
1376
1377    /** {@inheritDoc} */
1378    @Override
1379    public void addDataListener(ManagerDataListener<DestinationPoints> e) {
1380        if (e != null) listeners.add(e);
1381    }
1382
1383    /** {@inheritDoc} */
1384    @Override
1385    public void removeDataListener(ManagerDataListener<DestinationPoints> e) {
1386        if (e != null) listeners.remove(e);
1387    }
1388
1389    final List<ManagerDataListener<DestinationPoints>> listeners = new ArrayList<>();
1390
1391    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EntryExitPairs.class);
1392
1393}