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