001package jmri.jmrit.entryexit;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.Hashtable;
006import java.util.List;
007import java.util.Map.Entry;
008import jmri.InstanceManager;
009import jmri.NamedBean;
010import jmri.Sensor;
011import jmri.SignalHead;
012import jmri.SignalMast;
013import jmri.jmrit.display.EditorManager;
014import jmri.jmrit.display.SensorIcon;
015import jmri.jmrit.display.layoutEditor.LayoutBlock;
016import jmri.jmrit.display.layoutEditor.LayoutEditor;
017import jmri.jmrit.display.layoutEditor.LayoutSlip;
018import jmri.jmrit.display.layoutEditor.LayoutTurnout;
019import jmri.jmrit.display.layoutEditor.LevelXing;
020import jmri.jmrit.display.layoutEditor.PositionablePoint;
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024public class PointDetails {
025
026    //May want to look at putting a listener on the refLoc to listen to updates to blocks, signals and sensors attached to it
027    LayoutEditor panel = null;
028    LayoutBlock facing;
029    List<LayoutBlock> protectingBlocks;
030    private NamedBean refObj;
031    private Object refLoc;
032    private Sensor sensor;
033    private SignalMast signalmast;
034    private SignalHead signalhead;
035
036    static int nxButtonTimeout = 10;
037
038    Source sourceRoute;
039    transient Hashtable<DestinationPoints, Source> destinations = new Hashtable<>(5);
040
041    public PointDetails(LayoutBlock facing, List<LayoutBlock> protecting) {
042        this.facing = facing;
043        this.protectingBlocks = protecting;
044    }
045
046    public LayoutBlock getFacing() {
047        return facing;
048    }
049
050    public List<LayoutBlock> getProtecting() {
051        return protectingBlocks;
052    }
053
054    //This might be better off a ref to the source pointdetail.
055    boolean routeToSet = false;
056
057    void setRouteTo(boolean boo) {
058        routeToSet = boo;
059    }
060
061    boolean routeFromSet = false;
062
063    void setRouteFrom(boolean boo) {
064        routeFromSet = boo;
065    }
066
067    public void setPanel(LayoutEditor panel) {
068        this.panel = panel;
069        // find the panel that actually contains this sensor, default to the supplied panel
070        for (LayoutEditor layout : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
071            for (SensorIcon si : layout.getSensorList()) {
072                if (sensor == si.getNamedBean()) {
073                    this.panel = layout;
074                    return;
075                }
076            }
077        }
078    }
079
080    void setSensor(Sensor sen) {
081        if (sensor == sen) {
082            return;
083        }
084        if (sensor != null) {
085            sensor.removePropertyChangeListener(nxButtonListener);
086        }
087        sensor = sen;
088        if (sensor != null) {
089            sensor.addPropertyChangeListener(nxButtonListener);
090        }
091    }
092
093    void addSensorList() {
094        sensor.addPropertyChangeListener(nxButtonListener);
095    }
096
097    void removeSensorList() {
098        sensor.removePropertyChangeListener(nxButtonListener);
099    }
100
101    //Sensor getSensor() { return sensor; }
102    protected PropertyChangeListener nxButtonListener = new PropertyChangeListener() {
103        //First off if we were inactive, and now active
104        @Override
105        public void propertyChange(PropertyChangeEvent e) {
106            nxButtonStateChange(e);
107        }
108    };
109
110    private void nxButtonStateChange(PropertyChangeEvent e) {
111        if (!e.getPropertyName().equals("KnownState")) {  // NOI18N
112            return;
113        }
114        int now = ((Integer) e.getNewValue());
115        int old = ((Integer) e.getOldValue());
116
117        if ((old == Sensor.UNKNOWN) || (old == Sensor.INCONSISTENT)) {
118            setButtonState(EntryExitPairs.NXBUTTONINACTIVE);
119            return;
120        }
121        int changesWhenClicked = getButtonChanges();
122        if (InstanceManager.getDefault(EntryExitPairs.class).holdButtonClick(() -> handleHeldClick(e, changesWhenClicked))) {
123            // The click is handled when the route being set is complete. The sensor is not
124            // written now, so that a hardware button only sees the click itself.
125            return;
126        }
127
128        DestinationPoints destPoint = null;
129
130        for (Entry<DestinationPoints, Source> dp : destinations.entrySet()) {
131            // This point might be a destination in zero or more NX pairs.
132            // If the source point is active, then this is a single point route.
133            destPoint = dp.getKey();
134            if (destPoint.isEnabled() && dp.getValue().getPoint().getNXState() == EntryExitPairs.NXBUTTONSELECTED) {
135                // This destination point has an active source point
136                setButtonState(EntryExitPairs.NXBUTTONSELECTED);
137                // Call activeBean directly
138                destPoint.activeBean(false);
139                log.debug("[nxButtonStateChange] Single point route selected");
140                return;
141            }
142        }
143
144        if (sourceRoute != null) {
145            if (now == Sensor.ACTIVE && getNXState() == EntryExitPairs.NXBUTTONINACTIVE) {
146                setButtonState(EntryExitPairs.NXBUTTONSELECTED);
147                for (Entry<PointDetails, DestinationPoints> en : sourceRoute.pointToDest.entrySet()) {
148                    //Sensor sen = getSensorFromPoint(en.getKey().getPoint());
149                    //Set a time out on the source sensor, so that if its state hasn't been changed, then we will clear it out.
150                    if (en.getValue().isEnabled() && !en.getValue().getUniDirection()) {
151                        if (en.getKey().getNXState() == EntryExitPairs.NXBUTTONSELECTED) {
152                            // Call activeBean via the Source object when bi-directional
153                            sourceRoute.activeBean(en.getValue(), true);
154                        }
155                    }
156                }
157            } else if (now == Sensor.INACTIVE && getNXState() == EntryExitPairs.NXBUTTONSELECTED) {
158                //sensor inactive, nxbutton state was selected, going to set back to inactive - ie user cancelled button
159                setButtonState(EntryExitPairs.NXBUTTONINACTIVE);
160            } else if (now == Sensor.INACTIVE && getNXState() == EntryExitPairs.NXBUTTONACTIVE) {
161                //Sensor gone inactive, while nxbutton was selected - potential start of user either clear route or setting another
162                setButtonState(EntryExitPairs.NXBUTTONSELECTED);
163                for (Entry<PointDetails, DestinationPoints> en : sourceRoute.pointToDest.entrySet()) {
164                    //Sensor sen = getSensorFromPoint(en.getKey().getPoint());
165                    //Set a time out on the source sensor, so that if its state hasn't been changed, then we will clear it out.
166                    if (en.getValue().isEnabled() && !en.getValue().getUniDirection()) {
167                        if (en.getKey().getNXState() == EntryExitPairs.NXBUTTONSELECTED) {
168                            // Call activeBean via the Source object when bi-directional
169                            sourceRoute.activeBean(en.getValue(), false);
170                        }
171                    }
172                }
173            }
174        } else if (destPoint != null) {
175            //Button set as a destination but has no source, it has had a change in state
176            if (now == Sensor.ACTIVE) {
177                //State now is Active will set flashing
178                setButtonState(EntryExitPairs.NXBUTTONSELECTED);
179            } else if (getNXState() == EntryExitPairs.NXBUTTONACTIVE) {
180                //Sensor gone inactive while it was previosly active
181                setButtonState(EntryExitPairs.NXBUTTONSELECTED);
182            } else if (getNXState() == EntryExitPairs.NXBUTTONSELECTED) {
183                //Sensor gone inactive while it was previously selected therefore will cancel
184                setButtonState(EntryExitPairs.NXBUTTONINACTIVE);
185            }
186        }
187
188        log.debug("[nxButtonStateChange] Initial button or possible multiple point route");
189        jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).setMultiPointRoute(this, panel);
190    }
191
192    /**
193     * Number of changes NX has made to the state of this button or to its
194     * sensor. A held click compares it to tell if the route changed the button.
195     */
196    private int buttonChanges = 0;
197
198    private synchronized int getButtonChanges() {
199        return buttonChanges;
200    }
201
202    private synchronized void buttonChanged() {
203        buttonChanges++;
204    }
205
206    /**
207     * Handle a click on the button that was held back while a route was being
208     * set.
209     * <p>
210     * If nothing has changed the button since the click, the click is handled
211     * as it happened, without writing the sensor again. If the route changed
212     * the button, the click is handled as if it happened now: like a click, it
213     * changes the sensor to its other state. This is how the click was handled
214     * when the panel waited for the route to be set.
215     *
216     * @param e                  the sensor change of the click
217     * @param changesWhenClicked the number of button changes when the click was
218     *                           held back
219     */
220    private void handleHeldClick(PropertyChangeEvent e, int changesWhenClicked) {
221        Sensor s = getSensor();
222        if (s == null) {
223            return;
224        }
225        if (getButtonChanges() == changesWhenClicked && e.getNewValue().equals(s.getKnownState())) {
226            nxButtonStateChange(e);
227            return;
228        }
229        try {
230            s.setKnownState(s.getKnownState() == Sensor.ACTIVE ? Sensor.INACTIVE : Sensor.ACTIVE);
231        } catch (jmri.JmriException ex) {
232            log.error("Could not set Sensor known state.", ex);
233        }
234    }
235
236    void setSignalMast(SignalMast mast) {
237        signalmast = mast;
238    }
239
240    void setSource(Source src) {
241        if (sourceRoute == src) {
242            return;
243        }
244        sourceRoute = src;
245    }
246
247    void setDestination(DestinationPoints srcdp, Source src) {
248        if (!destinations.containsKey(srcdp)) {
249            destinations.put(srcdp, src);
250        }
251    }
252
253    void removeDestination(DestinationPoints srcdp) {
254        destinations.remove(srcdp);
255        if (sourceRoute == null && destinations.isEmpty()) {
256            stopFlashSensor();
257            sensor.removePropertyChangeListener(nxButtonListener);
258            setSensor(null);
259        }
260    }
261
262    void removeSource(Source src) {
263        sourceRoute = null;
264        if (destinations.isEmpty()) {
265            stopFlashSensor();
266            setSensor(null);
267        }
268    }
269
270    private int nxButtonState = EntryExitPairs.NXBUTTONINACTIVE;
271
272    void setButtonState(int state) {
273        setNXButtonState(state);
274    }
275
276    void setNXState(int state) {
277        if (state == nxButtonState) {
278            return;
279        }
280        if (state == EntryExitPairs.NXBUTTONSELECTED) {
281            nxButtonTimeOut();
282            flashSensor();
283        } else {
284            cancelNXButtonTimeOut();
285            stopFlashSensor();
286        }
287        nxButtonState = state;
288        buttonChanged();
289    }
290
291    public int getNXState() {
292        return nxButtonState;
293    }
294
295    SignalMast getSignalMast() {
296        return signalmast;
297    }
298
299    void setSignalHead(SignalHead head) {
300        signalhead = head;
301    }
302
303    SignalHead getSignalHead() {
304        return signalhead;
305    }
306
307    public LayoutEditor getPanel() {
308        return panel;
309    }
310
311    public void setRefObject(NamedBean refObs) {
312        for (LayoutEditor pnl : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) {
313            if (refLoc == null) {
314                setRefObjectByPanel(refObs, pnl);
315            }
316        }
317    }
318
319    public void setRefObjectByPanel(NamedBean refObs, LayoutEditor pnl) {
320        refObj = refObs;
321        if (pnl != null && refObj != null) {
322            if (refObj instanceof SignalMast || refObj instanceof Sensor) {
323                //String mast = ((SignalMast)refObj).getUserName();
324                refLoc = pnl.getFinder().findPositionablePointByEastBoundBean(refObj);
325                if (refLoc == null) {
326                    refLoc = pnl.getFinder().findPositionablePointByWestBoundBean(refObj);
327                }
328                if (refLoc == null) {
329                    refLoc = pnl.getFinder().findLayoutTurnoutByBean(refObj);
330                }
331                if (refLoc == null) {
332                    refLoc = pnl.getFinder().findLevelXingByBean(refObj);
333                }
334                if (refLoc == null) {
335                    refLoc = pnl.getFinder().findLayoutSlipByBean(refObj);
336                }
337                if (refObj instanceof Sensor) {
338                    setSensor((Sensor) refObj);
339                }
340            } else if (refObj instanceof SignalHead) {
341                String signal = refObj.getDisplayName();
342                refLoc = pnl.getFinder().findPositionablePointByEastBoundSignal(signal);
343                if (refLoc == null) {
344                    refLoc = pnl.getFinder().findPositionablePointByWestBoundSignal(signal);
345                }
346            }
347        }
348//        if (refLoc != null) {
349//            if (refLoc instanceof PositionablePoint) {
350//                //((PositionablePoint)refLoc).addPropertyChangeListener(this);
351//            } else if (refLoc instanceof LayoutTurnout) {  //<== this includes LayoutSlips
352//                //((LayoutTurnout)refLoc).addPropertyChangeListener(this);
353//            } else if (refLoc instanceof LevelXing) {
354//                //((LevelXing)refLoc).addPropertyChangeListener(this);
355//            }
356//        }
357        //With this set ref we can probably add a listener to it, so that we can detect when a change to the point details takes place
358    }
359
360    public NamedBean getRefObject() {
361        return refObj;
362    }
363
364    public Object getRefLocation() {
365        return refLoc;
366    }
367
368    //LayoutEditor getLayoutEditor() { return panel; }
369    public boolean isRouteToPointSet() {
370        return routeToSet;
371    }
372
373    public boolean isRouteFromPointSet() {
374        return routeFromSet;
375    }
376
377    public String getDisplayName() {
378        if (sensor != null) {
379            String description = sensor.getDisplayName();
380            if (signalmast != null) {
381                description = description + " (" + signalmast.getDisplayName() + ")";
382            }
383            return description;
384        }
385
386        if (refObj instanceof SignalMast) {
387            return refObj.getDisplayName();
388        } else if (refObj instanceof Sensor) {
389            return refObj.getDisplayName();
390        } else if (refObj instanceof SignalHead) {
391            return refObj.getDisplayName();
392        }
393        return "no display name";  // NOI18N
394    }
395
396    transient Thread nxButtonTimeOutThr;
397
398    void nxButtonTimeOut() {
399        if ((nxButtonTimeOutThr != null) && (nxButtonTimeOutThr.isAlive())) {
400            return;
401        }
402        extendedtime = true;
403        class ButtonTimeOut implements Runnable {
404
405            ButtonTimeOut() {
406            }
407
408            @Override
409            public void run() {
410                try {
411                    //Stage one default timer for the button if no other button has been pressed
412                    Thread.sleep(nxButtonTimeout * 1000L);
413                    //Stage two if an extended time out has been requested
414                    if (extendedtime) {
415                        Thread.sleep(60000);  //timeout after a minute waiting for the sml to set.
416                    }
417                } catch (InterruptedException ex) {
418                    log.debug("[nxButtonTimeOut] Flash timer cancelled");  // NOI18N
419                }
420                setNXButtonState(EntryExitPairs.NXBUTTONINACTIVE);
421            }
422        }
423        ButtonTimeOut t = new ButtonTimeOut();
424        nxButtonTimeOutThr = jmri.util.ThreadingUtil.newThread(t, "NX Button Timeout " + getSensor().getDisplayName());  // NOI18N
425
426        nxButtonTimeOutThr.start();
427    }
428
429    void cancelNXButtonTimeOut() {
430        if (nxButtonTimeOutThr != null) {
431            nxButtonTimeOutThr.interrupt();
432        }
433
434    }
435
436    boolean extendedtime = false;
437
438    public void flashSensor() {
439        for (SensorIcon si : getPanel().getSensorList()) {
440            if (si.getSensor() == getSensor()) {
441                si.flashSensor(2, Sensor.ACTIVE, Sensor.INACTIVE);
442            }
443        }
444    }
445
446    public void stopFlashSensor() {
447        for (SensorIcon si : getPanel().getSensorList()) {
448            if (si.getSensor() == getSensor()) {
449                si.stopFlash();
450            }
451        }
452    }
453
454    synchronized public void setNXButtonState(int state) {
455        if (getSensor() == null) {
456            return;
457        }
458        if (state == EntryExitPairs.NXBUTTONINACTIVE) {
459            //If a route is set to or from out point then we need to leave/set the sensor to ACTIVE
460            if (isRouteToPointSet()) {
461                state = EntryExitPairs.NXBUTTONACTIVE;
462            } else if (isRouteFromPointSet()) {
463                state = EntryExitPairs.NXBUTTONACTIVE;
464            }
465        }
466        setNXState(state);
467        int sensorState;
468        switch (state) {
469            case EntryExitPairs.NXBUTTONINACTIVE:
470                sensorState = Sensor.INACTIVE;
471                break;
472            case EntryExitPairs.NXBUTTONACTIVE:
473                sensorState = Sensor.ACTIVE;
474                break;
475            case EntryExitPairs.NXBUTTONSELECTED:
476                sensorState = Sensor.ACTIVE;
477                break;
478            default:
479                sensorState = Sensor.UNKNOWN;
480                break;
481        }
482
483        //Might need to clear listeners at the stage and then reapply them after.
484        if (getSensor().getKnownState() != sensorState) {
485            buttonChanged();
486            removeSensorList();
487            try {
488                getSensor().setKnownState(sensorState);
489            } catch (jmri.JmriException ex) {
490                log.error("Could not set Sensor known state.", ex);
491            }
492            addSensorList();
493        }
494    }
495
496    /**
497     * @since 4.17.6
498     * Making the source object available for scripting in Jython.
499     * @return the sensor.
500     */
501    public Sensor getSensor() {
502        if (getRefObject() == null) {
503            return null;
504        }
505        if ((getPanel() != null) && (!getPanel().isEditable()) && (sensor != null)) {
506            return sensor;
507        }
508
509        if (getRefObject() instanceof Sensor) {
510            setSensor((Sensor) getRefObject());
511            return (Sensor) getRefObject();
512        }
513        Object objLoc = getRefLocation();
514        Object objRef = getRefObject();
515        SignalHead head = null;
516        SignalMast mast = null;
517        String username = "";
518        String systemname = "";
519        Sensor foundSensor = null;
520        if (objRef instanceof SignalMast) {
521            mast = (SignalMast) objRef;
522        }
523        if (objRef instanceof SignalHead) {
524            head = (SignalHead) objRef;
525            username = head.getUserName();
526            systemname = head.getSystemName();
527        }
528        jmri.SensorManager sm = InstanceManager.sensorManagerInstance();
529        if (objLoc instanceof PositionablePoint) {
530            PositionablePoint p = (PositionablePoint) objLoc;
531            if (mast != null) {
532                if (p.getEastBoundSignalMast() == objRef) {
533                    foundSensor = p.getEastBoundSensor();
534                } else if (p.getWestBoundSignalMast() == objRef) {
535                    foundSensor = p.getWestBoundSensor();
536                }
537            } else if (head != null) {
538                if ((p.getEastBoundSignal().equals(username))
539                        || p.getEastBoundSignal().equals(systemname)) {
540                    foundSensor = p.getEastBoundSensor();
541                } else if ((p.getWestBoundSignal().equals(username))
542                        || p.getWestBoundSignal().equals(systemname)) {
543                    foundSensor = p.getWestBoundSensor();
544                }
545            }
546        } else if (objLoc instanceof LayoutSlip) {
547            LayoutSlip sl = (LayoutSlip) objLoc;
548            if (mast != null) {
549                if (sl.getSignalAMast() == objRef) {
550                    foundSensor = sl.getSensorA();
551                } else if (sl.getSignalBMast() == objRef) {
552                    foundSensor = sl.getSensorB();
553                } else if (sl.getSignalCMast() == objRef) {
554                    foundSensor = sl.getSensorC();
555                } else if (sl.getSignalDMast() == objRef) {
556                    foundSensor = sl.getSensorD();
557                }
558            }
559            if (head != null) {
560                if ((sl.getSignalA1Name().equals(username)) || (sl.getSignalA1Name().equals(systemname))) {
561                    foundSensor = sm.getSensor(sl.getSensorAName());
562                } else if ((sl.getSignalB1Name().equals(username)) || (sl.getSignalB1Name().equals(systemname))) {
563                    foundSensor = sm.getSensor(sl.getSensorBName());
564                } else if ((sl.getSignalC1Name().equals(username)) || (sl.getSignalC1Name().equals(systemname))) {
565                    foundSensor = sm.getSensor(sl.getSensorCName());
566                } else if ((sl.getSignalD1Name().equals(username)) || (sl.getSignalD1Name().equals(systemname))) {
567                    foundSensor = sm.getSensor(sl.getSensorDName());
568                }
569            }
570        } else //note: you have to do this after LayoutSlip
571        // because LayoutSlip extends LayoutTurnout
572        // (So a LayoutSlip would be an instance of LayoutTurnout.)
573        if (objLoc instanceof LayoutTurnout) {  //<== this includes LayoutSlips
574            LayoutTurnout t = (LayoutTurnout) objLoc;
575            if (mast != null) {
576                if (t.getSignalAMast() == objRef) {
577                    foundSensor = t.getSensorA();
578                } else if (t.getSignalBMast() == objRef) {
579                    foundSensor = t.getSensorB();
580                } else if (t.getSignalCMast() == objRef) {
581                    foundSensor = t.getSensorC();
582                } else if (t.getSignalDMast() == objRef) {
583                    foundSensor = t.getSensorD();
584                }
585            }
586            if (head != null) {
587                if ((t.getSignalA1Name().equals(username)) || (t.getSignalA1Name().equals(systemname))) {
588                    foundSensor = t.getSensorA();
589                } else if ((t.getSignalA2Name().equals(username)) || (t.getSignalA2Name().equals(systemname))) {
590                    foundSensor = t.getSensorA();
591                } else if ((t.getSignalA3Name().equals(username)) || (t.getSignalA3Name().equals(systemname))) {
592                    foundSensor = t.getSensorA();
593                } else if ((t.getSignalB1Name().equals(username)) || (t.getSignalB1Name().equals(systemname))) {
594                    foundSensor = t.getSensorB();
595                } else if ((t.getSignalB2Name().equals(username)) || (t.getSignalB2Name().equals(systemname))) {
596                    foundSensor = t.getSensorB();
597                } else if ((t.getSignalC1Name().equals(username)) || (t.getSignalC1Name().equals(systemname))) {
598                    foundSensor = t.getSensorC();
599                } else if ((t.getSignalC2Name().equals(username)) || (t.getSignalC2Name().equals(systemname))) {
600                    foundSensor = t.getSensorC();
601                } else if ((t.getSignalD1Name().equals(username)) || (t.getSignalD1Name().equals(systemname))) {
602                    foundSensor = t.getSensorD();
603                } else if ((t.getSignalD2Name().equals(username)) || (t.getSignalD2Name().equals(systemname))) {
604                    foundSensor = t.getSensorD();
605                }
606            }
607        } else if (objLoc instanceof LevelXing) {
608            LevelXing x = (LevelXing) objLoc;
609            if (mast != null) {
610                if (x.getSignalAMast() == objRef) {
611                    foundSensor = x.getSensorA();
612                } else if (x.getSignalBMast() == objRef) {
613                    foundSensor = x.getSensorB();
614                } else if (x.getSignalCMast() == objRef) {
615                    foundSensor = x.getSensorC();
616                } else if (x.getSignalDMast() == objRef) {
617                    foundSensor = x.getSensorD();
618                }
619            }
620            if (head != null) {
621                if ((x.getSignalAName().equals(username)) || (x.getSignalAName().equals(systemname))) {
622                    foundSensor = x.getSensorA();
623                } else if ((x.getSignalBName().equals(username)) || (x.getSignalBName().equals(systemname))) {
624                    foundSensor = x.getSensorB();
625                } else if ((x.getSignalCName().equals(username)) || (x.getSignalCName().equals(systemname))) {
626                    foundSensor = x.getSensorC();
627                } else if ((x.getSignalDName().equals(username)) || (x.getSignalDName().equals(systemname))) {
628                    foundSensor = x.getSensorD();
629                }
630            }
631        }
632        setSensor(foundSensor);
633        return foundSensor;
634    }
635
636    NamedBean getSignal() {
637        if ((getPanel() != null) && (!getPanel().isEditable()) && (getSignalMast() != null)) {
638            return getSignalMast();
639        }
640        if ((getPanel() != null) && (!getPanel().isEditable()) && (getSignalHead() != null)) {
641            return getSignalHead();
642        }
643        jmri.SignalHeadManager sh = InstanceManager.getDefault(jmri.SignalHeadManager.class);
644        NamedBean signal = null;
645
646        if (getRefObject() == null) {
647            log.error("Signal not found at point");  // NOI18N
648            return null;
649        } else if (getRefObject() instanceof SignalMast) {
650            signal = getRefObject();
651            setSignalMast(((SignalMast) getRefObject()));
652            return signal;
653        } else if (getRefObject() instanceof SignalHead) {
654            signal = getRefObject();
655            setSignalHead(((SignalHead) getRefObject()));
656            return signal;
657        }
658
659        Sensor sen = (Sensor) getRefObject();
660        log.debug("  Looking at sensor '{}' on panel '{}' at '{}'",
661                sen.getDisplayName(), getPanel().getLayoutName(), getRefLocation());
662        if (getRefLocation() instanceof PositionablePoint) {
663            PositionablePoint p = (PositionablePoint) getRefLocation();
664            if (p.getEastBoundSensor() == sen) {
665                if (p.getEastBoundSignalMast() != null) {
666                    signal = p.getEastBoundSignalMast();
667                } else if (!p.getEastBoundSignal().isEmpty()) {
668                    signal = sh.getSignalHead(p.getEastBoundSignal());
669                }
670            } else if (p.getWestBoundSensor() == sen) {
671                if (p.getWestBoundSignalMast() != null) {
672                    signal = p.getWestBoundSignalMast();
673                } else if (!p.getWestBoundSignal().isEmpty()) {
674                    signal = sh.getSignalHead(p.getWestBoundSignal());
675                }
676            }
677        } else if (getRefLocation() instanceof LayoutSlip) {
678            LayoutSlip t = (LayoutSlip) getRefLocation();
679            if (t.getSensorA() == sen) {
680                if (t.getSignalAMast() != null) {
681                    signal = t.getSignalAMast();
682                } else if (!t.getSignalA1Name().isEmpty()) {
683                    signal = sh.getSignalHead(t.getSignalA1Name());
684                }
685            } else if (t.getSensorB() == sen) {
686                if (t.getSignalBMast() != null) {
687                    signal = t.getSignalBMast();
688                } else if (!t.getSignalB1Name().isEmpty()) {
689                    signal = sh.getSignalHead(t.getSignalB1Name());
690                }
691            } else if (t.getSensorC() == sen) {
692                if (t.getSignalCMast() != null) {
693                    signal = t.getSignalCMast();
694                } else if (!t.getSignalC1Name().isEmpty()) {
695                    signal = sh.getSignalHead(t.getSignalC1Name());
696                }
697            } else if (t.getSensorD() == sen) {
698                if (t.getSignalDMast() != null) {
699                    signal = t.getSignalDMast();
700                } else if (!t.getSignalD1Name().isEmpty()) {
701                    signal = sh.getSignalHead(t.getSignalD1Name());
702                }
703            }
704        } else //note: you have to do this after LayoutSlip
705        // because LayoutSlip extends LayoutTurnout
706        // (So a LayoutSlip would be an instance of LayoutTurnout.)
707        if (getRefLocation() instanceof LayoutTurnout) {  //<== this includes LayoutSlips
708            LayoutTurnout t = (LayoutTurnout) getRefLocation();
709            if (t.getSensorA() == sen) {
710                if (t.getSignalAMast() != null) {
711                    signal = t.getSignalAMast();
712                } else if (!t.getSignalA1Name().isEmpty()) {
713                    signal = sh.getSignalHead(t.getSignalA1Name());
714                }
715            } else if (t.getSensorB() == sen) {
716                if (t.getSignalBMast() != null) {
717                    signal = t.getSignalBMast();
718                } else if (!t.getSignalB1Name().isEmpty()) {
719                    signal = sh.getSignalHead(t.getSignalB1Name());
720                }
721            } else if (t.getSensorC() == sen) {
722                if (t.getSignalCMast() != null) {
723                    signal = t.getSignalCMast();
724                } else if (!t.getSignalC1Name().isEmpty()) {
725                    signal = sh.getSignalHead(t.getSignalC1Name());
726                }
727            } else if (t.getSensorD() == sen) {
728                if (t.getSignalDMast() != null) {
729                    signal = t.getSignalDMast();
730                } else if (!t.getSignalD1Name().isEmpty()) {
731                    signal = sh.getSignalHead(t.getSignalD1Name());
732                }
733            }
734        } else if (getRefLocation() instanceof LevelXing) {
735            LevelXing x = (LevelXing) getRefLocation();
736            if (x.getSensorA() == sen) {
737                if (x.getSignalAMast() != null) {
738                    signal = x.getSignalAMast();
739                } else if (!x.getSignalAName().isEmpty()) {
740                    signal = sh.getSignalHead(x.getSignalAName());
741                }
742            } else if (x.getSensorB() == sen) {
743                if (x.getSignalBMast() != null) {
744                    signal = x.getSignalBMast();
745                } else if (!x.getSignalBName().isEmpty()) {
746                    signal = sh.getSignalHead(x.getSignalBName());
747                }
748            } else if (x.getSensorC() == sen) {
749                if (x.getSignalCMast() != null) {
750                    signal = x.getSignalCMast();
751                } else if (!x.getSignalCName().isEmpty()) {
752                    signal = sh.getSignalHead(x.getSignalCName());
753                }
754            } else if (x.getSensorD() == sen) {
755                if (x.getSignalDMast() != null) {
756                    signal = x.getSignalDMast();
757                } else if (!x.getSignalDName().isEmpty()) {
758                    signal = sh.getSignalHead(x.getSignalDName());
759                }
760            }
761        }
762
763        if (signal instanceof SignalMast) {
764            setSignalMast(((SignalMast) signal));
765        } else if (signal instanceof SignalHead) {
766            setSignalHead(((SignalHead) signal));
767        }
768        return signal;
769    }
770
771    @Override
772    public boolean equals(Object obj) {
773        if (obj == this) {
774            return true;
775        }
776        if (obj == null) {
777            return false;
778        }
779
780        if (!(getClass() == obj.getClass())) {
781            return false;
782        } else {
783            PointDetails tmp = (PointDetails) obj;
784            if (tmp.getFacing() != this.facing) {
785                return false;
786            }
787            if (!tmp.getProtecting().equals(this.protectingBlocks)) {
788                return false;
789            }
790            if (tmp.getPanel() != this.panel) {
791                return false;
792            }
793        }
794        return true;
795    }
796
797    @Override
798    public int hashCode() {
799        int hash = 7;
800        hash = 37 * hash + (this.panel != null ? this.panel.hashCode() : 0);
801        hash = 37 * hash + (this.facing != null ? this.facing.hashCode() : 0);
802        hash = 37 * hash + (this.protectingBlocks != null ? this.protectingBlocks.hashCode() : 0);
803        return hash;
804    }
805
806    java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this);
807
808    public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
809        pcs.addPropertyChangeListener(l);
810    }
811
812    public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
813        pcs.removePropertyChangeListener(l);
814    }
815
816    protected void firePropertyChange(String p, Object old, Object n) {
817        pcs.firePropertyChange(p, old, n);
818    }
819
820    private static final Logger log = LoggerFactory.getLogger(PointDetails.class);
821}