001package jmri.jmrit.display.layoutEditor;
002
003import java.text.MessageFormat;
004import java.util.*;
005
006import javax.annotation.*;
007
008import jmri.*;
009import jmri.jmrit.signalling.SignallingGuiTools;  // temporary
010
011
012/**
013 * PositionablePoint is a Point defining a node in the Track that can be dragged
014 * around the inside of the enclosing LayoutEditor panel using a right-drag
015 * (drag with meta key).
016 * <p>
017 * Three types of Positionable Point are supported: Anchor - point on track -
018 * two track connections End Bumper - end of track point - one track connection
019 * Edge Connector - This is used to link track segments between two different
020 * panels
021 * <p>
022 * Note that a PositionablePoint exists for specifying connectivity and drawing
023 * position only. The Track Segments connected to a PositionablePoint may belong
024 * to the same block or to different blocks. Since each Track Segment may only
025 * belong to one block, a PositionablePoint may function as a Block Boundary.
026 * <p>
027 * As an Edge Connector, this is a semi-transparent connection to a remote
028 * TrackSeqment via another Edge Connector object.
029 * <p>
030 * Signal names are saved here at a Block Boundary anchor point by the tool Set
031 * Signals at Block Boundary. PositionablePoint does nothing with these signal
032 * head names; it only serves as a place to store them.
033 * <p>
034 * Arrows and bumpers are visual presentation aspects handled in the View.
035 *
036 * @author Dave Duchamp Copyright (c) 2004-2007
037 * @author Bob Jacobsen Copyright (2) 2014, 2020
038 * @author George Warner Copyright (c) 2017-2019
039 */
040public class PositionablePoint extends LayoutTrack {
041
042    // defined constants
043    public enum PointType {
044        NONE,
045        ANCHOR,        // 1
046        END_BUMPER,    // 2
047        EDGE_CONNECTOR // 3
048    }
049
050    // operational instance variables (not saved between sessions)
051    // persistent instances variables (saved between sessions)
052    private PointType type = PointType.NONE;
053    private TrackSegment connect1 = null;
054    private TrackSegment connect2 = null;
055
056    protected NamedBeanHandle<SignalHead> signalEastHeadNamed = null; // signal head for east (south) bound trains
057    protected NamedBeanHandle<SignalHead> signalWestHeadNamed = null; // signal head for west (north) bound trains
058
059    private NamedBeanHandle<SignalMast> eastBoundSignalMastNamed = null;
060    private NamedBeanHandle<SignalMast> westBoundSignalMastNamed = null;
061    /* We use a namedbeanhandle for the sensors, even though we only store the name here,
062    this is so that we can keep up with moves and changes of userNames */
063    private NamedBeanHandle<Sensor> eastBoundSensorNamed = null;
064    private NamedBeanHandle<Sensor> westBoundSensorNamed = null;
065
066    public PositionablePoint(String id, PointType t, LayoutEditor models) {
067        super(id, models);
068
069        if ((t == PointType.ANCHOR) || (t == PointType.END_BUMPER) || (t == PointType.EDGE_CONNECTOR)) {
070            type = t;
071        } else {
072            log.error("Illegal type of PositionablePoint - {}", t);
073            type = PointType.ANCHOR;
074        }
075    }
076
077    // this should only be used for debugging...
078    @Override
079    public String toString() {
080        String result = "PositionalablePoint";
081        switch (type) {
082            case ANCHOR: {
083                result = "Anchor";
084                break;
085            }
086            case END_BUMPER: {
087                result = "End Bumper";
088                break;
089            }
090            case EDGE_CONNECTOR: {
091                result = "Edge Connector";
092                break;
093            }
094            default: {
095                result = "Unknown type (" + type + ")";
096                break;
097            }
098        }
099        return result + " '" + getName() + "'";
100    }
101
102    /**
103     * Get the point type.
104     * @return point type, i.e. ANCHOR, END_BUMPER, EDGE_CONNECTOR
105     */
106    public PointType getType() {
107        return type;
108    }
109
110    public void setType(PointType newType) {
111        if (type != newType) {
112            switch (newType) {
113                default:
114                case ANCHOR: {
115                    setTypeAnchor();
116                    break;
117                }
118                case END_BUMPER: {
119                    setTypeEndBumper();
120                    break;
121                }
122                case EDGE_CONNECTOR: {
123                    setTypeEdgeConnector();
124                    break;
125                }
126            }
127
128            log.debug("temporary - repaint was removed here, needs to be rescoped");
129            // models.repaint();
130
131        }
132    }
133
134    void setTypeAnchor() { // temporary: make private once decoupled
135        setIdent(models.getFinder().uniqueName("A", 1));
136        type = PointType.ANCHOR;
137        if (connect1 != null) {
138            if (connect1.getConnect1() == PositionablePoint.this) {
139                log.info("Elided handling of connect1 in setTypeAnchor");
140                //connect1.setArrowEndStart(false);   // temporary - is this being done in the view?
141                //connect1.setBumperEndStart(false);   // temporary - is this being done in the view?
142            }
143            if (connect1.getConnect2() == PositionablePoint.this) {
144                log.info("Elided handling of connect1 in setTypeAnchor");
145                //connect1.setArrowEndStop(false);   // temporary - is this being done in the view?
146                //connect1.setBumperEndStop(false);   // temporary - is this being done in the view?
147            }
148        }
149        if (connect2 != null) {
150            if (connect2.getConnect1() == PositionablePoint.this) {
151                log.info("Elided handling of connect2 in setTypeAnchor");
152                //connect2.setArrowEndStart(false);   // temporary - is this being done in the view?
153                //connect2.setBumperEndStart(false);   // temporary - is this being done in the view?
154            }
155            if (connect2.getConnect2() == PositionablePoint.this) {
156                log.info("Elided handling of connect2 in setTypeAnchor");
157                //connect2.setArrowEndStop(false);   // temporary - is this being done in the view?
158                //connect2.setBumperEndStop(false);   // temporary - is this being done in the view?
159            }
160        }
161    }
162
163    void setTypeEndBumper() { // temporary: make private once decoupled
164        setIdent(models.getFinder().uniqueName("EB", 1));
165        type = PointType.END_BUMPER;
166        if (connect1 != null) {
167            if (connect1.getConnect1() == PositionablePoint.this) {
168                log.info("Elided handling of connect1 in setTypeEndBumper");
169                //connect1.setArrowEndStart(false);   // temporary - is this being done in the view?
170                //connect1.setBumperEndStart(true);   // temporary - is this being done in the view?
171            }
172            if (connect1.getConnect2() == PositionablePoint.this) {
173                log.info("Elided handling of connect2 in setTypeEndBumper");
174                //connect1.setArrowEndStop(false);   // temporary - is this being done in the view?
175                //connect1.setBumperEndStop(true);   // temporary - is this being done in the view?
176            }
177        }
178    }
179
180    void setTypeEdgeConnector() { // temporary: make private once decoupled
181        setIdent(models.getFinder().uniqueName("EC", 1));
182        type = PointType.EDGE_CONNECTOR;
183        if (connect1 != null) {
184            if (connect1.getConnect1() == PositionablePoint.this) {
185                log.info("Elided handling of connect1 in setTypeEdgeConnector");
186                //connect1.setBumperEndStart(false);   // temporary - is this being done in the view?
187            }
188            if (connect1.getConnect2() == PositionablePoint.this) {
189                log.info("Elided handling of connect2 in setTypeEdgeConnector");
190                //connect1.setBumperEndStop(false);   // temporary - is this being done in the view?
191            }
192        }
193    }
194
195    /**
196     * Provide the destination TrackSegment of the 1st connection.
197     * @return destination track segment
198     */
199    public TrackSegment getConnect1() {
200        return connect1;
201    }
202
203    public void setConnect1(TrackSegment trk) { connect1 = trk; }
204
205    /**
206     * Provide the destination TrackSegment of the 2nd connection.
207     * When this is an EDGE CONNECTOR, it looks through the linked point (if any)
208     * to the far-end track connection.
209     * @return destination track segment
210     */
211    public TrackSegment getConnect2() {
212        if (type == PointType.EDGE_CONNECTOR && getLinkedPoint() != null) {
213            return getLinkedPoint().getConnect1();
214        }
215        return connect2;
216    }
217
218    /**
219     * Provide the destination TrackSegment of the 2nd connection
220     * without doing the look-through present in {@link #getConnect2()}
221     * @return destination track segment
222     */
223    public TrackSegment getConnect2Actual() {
224        return connect2;
225    }
226
227    public void setConnect2Actual(TrackSegment trk) { connect2 = trk; }
228
229
230    private PositionablePoint linkedPoint;
231
232    public String getLinkedEditorName() {
233        if (getLinkedEditor() != null) {
234            return getLinkedEditor().getLayoutName();
235        }
236        return "";
237    }
238
239    public PositionablePoint getLinkedPoint() {
240        return linkedPoint;
241    }
242
243    public String getLinkedPointId() {
244        if (linkedPoint != null) {
245            return linkedPoint.getId();
246        }
247        return "";
248    }
249
250    public void setLinkedPoint(PositionablePoint p) {
251        if (p == linkedPoint) {
252            return;
253        }
254        if (linkedPoint != null) {
255            PositionablePoint oldLinkedPoint = linkedPoint;
256            linkedPoint = null;
257            if (oldLinkedPoint.getLinkedPoint() != null) {
258                oldLinkedPoint.setLinkedPoint(null);
259            }
260            if (oldLinkedPoint.getConnect1() != null) {
261                TrackSegment ts = oldLinkedPoint.getConnect1();
262                oldLinkedPoint.getLayoutEditor().getLEAuxTools().setBlockConnectivityChanged();
263                ts.updateBlockInfo();
264
265                log.info("temporary - repaint was removed here, needs to be rescoped");
266                // oldLinkedPoint.getLayoutEditor().repaint();
267
268            }
269            if (getConnect1() != null) {
270                models.getLEAuxTools().setBlockConnectivityChanged();
271                getConnect1().updateBlockInfo();
272
273                log.info("temporary - repaint was removed here, needs to be rescoped");
274                // models.repaint();
275            }
276        }
277        linkedPoint = p;
278        if (p != null) {
279            p.setLinkedPoint(this);
280            if (getConnect1() != null) {
281                models.getLEAuxTools().setBlockConnectivityChanged();
282                getConnect1().updateBlockInfo();
283
284                log.info("temporary - repaint was removed here, needs to be rescoped");
285                // models.repaint();
286            }
287        }
288    }
289
290    @CheckReturnValue
291    public LayoutEditor getLinkedEditor() {
292        if (getLinkedPoint() != null) {
293            return getLinkedPoint().getLayoutEditor();
294        }
295        return null;
296    }
297
298    @CheckReturnValue
299    protected LayoutEditor getLayoutEditor() {
300        return models;
301    }
302
303    @CheckReturnValue
304    @Nonnull
305    public String getEastBoundSignal() {
306        SignalHead h = getEastBoundSignalHead();
307        if (h != null) {
308            return h.getDisplayName();
309        }
310        return "";
311    }
312
313    @CheckForNull
314    @CheckReturnValue
315    public SignalHead getEastBoundSignalHead() {
316        if (getType() == PointType.EDGE_CONNECTOR) {
317            int dir = getConnect1Dir();
318            if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) {
319                if (signalEastHeadNamed != null) {
320                    return signalEastHeadNamed.getBean();
321                }
322                return null;
323            } else if (getLinkedPoint() != null) {
324                // Do some checks to find where the connection is here.
325                int linkDir = getLinkedPoint().getConnect1Dir();
326                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
327                    return getLinkedPoint().getEastBoundSignalHead();
328                }
329            }
330        }
331
332        if (signalEastHeadNamed != null) {
333            return signalEastHeadNamed.getBean();
334        }
335        return null;
336    }
337
338    public void setEastBoundSignal(String signalName) {
339        if (getType() == PointType.EDGE_CONNECTOR) {
340            int dir = getConnect1Dir();
341            if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) {
342                setEastBoundSignalName(signalName);
343            } else if (getLinkedPoint() != null) {
344                int linkDir = getLinkedPoint().getConnect1Dir();
345                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
346                    getLinkedPoint().setEastBoundSignal(signalName);
347                } else {
348                    setEastBoundSignalName(signalName);
349                }
350            } else {
351                setEastBoundSignalName(signalName);
352            }
353        } else {
354            setEastBoundSignalName(signalName);
355        }
356    }
357
358    private void setEastBoundSignalName(@CheckForNull String signalHead) {
359        if (signalHead == null || signalHead.isEmpty()) {
360            signalEastHeadNamed = null;
361            return;
362        }
363
364        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
365        if (head != null) {
366            signalEastHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
367        } else {
368            signalEastHeadNamed = null;
369        }
370    }
371
372    @CheckReturnValue
373    @Nonnull
374    public String getWestBoundSignal() {
375        SignalHead h = getWestBoundSignalHead();
376        if (h != null) {
377            return h.getDisplayName();
378        }
379        return "";
380    }
381
382    @CheckForNull
383    @CheckReturnValue
384    public SignalHead getWestBoundSignalHead() {
385        if (getType() == PointType.EDGE_CONNECTOR) {
386            int dir = getConnect1Dir();
387            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
388                if (signalWestHeadNamed != null) {
389                    return signalWestHeadNamed.getBean();
390                }
391                return null;
392            } else if (getLinkedPoint() != null) {
393                // Do some checks to find where the connection is here.
394                int linkDir = getLinkedPoint().getConnect1Dir();
395                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
396                    return getLinkedPoint().getWestBoundSignalHead();
397                }
398            }
399        }
400
401        if (signalWestHeadNamed != null) {
402            return signalWestHeadNamed.getBean();
403        }
404        return null;
405    }
406
407    public void setWestBoundSignal(String signalName) {
408        if (getType() == PointType.EDGE_CONNECTOR) {
409            int dir = getConnect1Dir();
410            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
411                setWestBoundSignalName(signalName);
412            } else if (getLinkedPoint() != null) {
413                int linkDir = getLinkedPoint().getConnect1Dir();
414                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
415                    getLinkedPoint().setWestBoundSignal(signalName);
416                } else {
417                    setWestBoundSignalName(signalName);
418                }
419            } else {
420                setWestBoundSignalName(signalName);
421            }
422        } else {
423            setWestBoundSignalName(signalName);
424        }
425    }
426
427    private void setWestBoundSignalName(@CheckForNull String signalHead) {
428        if (signalHead == null || signalHead.isEmpty()) {
429            signalWestHeadNamed = null;
430            return;
431        }
432
433        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
434        if (head != null) {
435            signalWestHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
436        } else {
437            signalWestHeadNamed = null;
438        }
439    }
440
441    @CheckReturnValue
442    @Nonnull
443    public String getEastBoundSensorName() {
444        if (eastBoundSensorNamed != null) {
445            return eastBoundSensorNamed.getName();
446        }
447        return "";
448    }
449
450    @CheckReturnValue
451    public Sensor getEastBoundSensor() {
452        if (eastBoundSensorNamed != null) {
453            return eastBoundSensorNamed.getBean();
454        }
455        return null;
456    }
457
458    public void setEastBoundSensor(String sensorName) {
459        if (sensorName == null || sensorName.isEmpty()) {
460            eastBoundSensorNamed = null;
461            return;
462        }
463
464        try {
465            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
466            eastBoundSensorNamed = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
467        } catch (IllegalArgumentException ex) {
468            eastBoundSensorNamed = null;
469        }
470    }
471
472    @CheckReturnValue
473    @Nonnull
474    public String getWestBoundSensorName() {
475        if (westBoundSensorNamed != null) {
476            return westBoundSensorNamed.getName();
477        }
478        return "";
479    }
480
481    @CheckReturnValue
482    public Sensor getWestBoundSensor() {
483        if (westBoundSensorNamed != null) {
484            return westBoundSensorNamed.getBean();
485        }
486        return null;
487    }
488
489    public void setWestBoundSensor(String sensorName) {
490        if (sensorName == null || sensorName.isEmpty()) {
491            westBoundSensorNamed = null;
492            return;
493        }
494        try {
495            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
496            westBoundSensorNamed = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
497        } catch (IllegalArgumentException ex) {
498            westBoundSensorNamed = null;
499        }
500    }
501
502    @CheckReturnValue
503    @Nonnull
504    public String getEastBoundSignalMastName() {
505        if (getEastBoundSignalMastNamed() != null) {
506            return getEastBoundSignalMastNamed().getName();
507        }
508        return "";
509    }
510
511    @CheckReturnValue
512    public SignalMast getEastBoundSignalMast() {
513        if (getEastBoundSignalMastNamed() != null) {
514            return getEastBoundSignalMastNamed().getBean();
515        }
516        return null;
517    }
518
519    @CheckReturnValue
520    public NamedBeanHandle<SignalMast> getEastBoundSignalMastNamed() {
521        if (getType() == PointType.EDGE_CONNECTOR) {
522            int dir = getConnect1Dir();
523            if (dir == Path.SOUTH || dir == Path.EAST || dir == Path.SOUTH_EAST) {
524                return eastBoundSignalMastNamed;
525            } else if (getLinkedPoint() != null) {
526                int linkDir = getLinkedPoint().getConnect1Dir();
527                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
528                    return getLinkedPoint().getEastBoundSignalMastNamed();
529                }
530            }
531        }
532        return eastBoundSignalMastNamed;
533    }
534
535    public void setEastBoundSignalMast(String signalMast) {
536        SignalMast mast = null;
537        if (signalMast != null && !signalMast.isEmpty()) {
538            mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
539            if (mast == null) {
540                log.error("{}.setEastBoundSignalMast({}); Unable to find Signal Mast",
541                        getName(), signalMast);
542                return;
543            }
544        } else {
545            eastBoundSignalMastNamed = null;
546            return;
547        }
548        if (getType() == PointType.EDGE_CONNECTOR) {
549            int dir = getConnect1Dir();
550            if (dir == Path.EAST || dir == Path.SOUTH || dir == Path.SOUTH_EAST) {
551                eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
552            } else if (getLinkedPoint() != null) {
553                int linkDir = getLinkedPoint().getConnect1Dir();
554                if (linkDir == Path.SOUTH || linkDir == Path.EAST || linkDir == Path.SOUTH_EAST) {
555                    getLinkedPoint().setEastBoundSignalMast(signalMast);
556                } else {
557                    eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
558                }
559            } else {
560                eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
561            }
562        } else {
563            eastBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
564        }
565    }
566
567    @CheckReturnValue
568    @Nonnull
569    public String getWestBoundSignalMastName() {
570        if (getWestBoundSignalMastNamed() != null) {
571            return getWestBoundSignalMastNamed().getName();
572        }
573        return "";
574    }
575
576    @CheckReturnValue
577    public SignalMast getWestBoundSignalMast() {
578        if (getWestBoundSignalMastNamed() != null) {
579            return getWestBoundSignalMastNamed().getBean();
580        }
581        return null;
582    }
583
584    @CheckReturnValue
585    public NamedBeanHandle<SignalMast> getWestBoundSignalMastNamed() {
586        if (getType() == PointType.EDGE_CONNECTOR) {
587            int dir = getConnect1Dir();
588            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
589                return westBoundSignalMastNamed;
590            } else if (getLinkedPoint() != null) {
591                int linkDir = getLinkedPoint().getConnect1Dir();
592                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
593                    return getLinkedPoint().getWestBoundSignalMastNamed();
594                }
595            }
596        }
597        return westBoundSignalMastNamed;
598    }
599
600    public void setWestBoundSignalMast(String signalMast) {
601        SignalMast mast = null;
602        if (signalMast != null && !signalMast.isEmpty()) {
603            mast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signalMast);
604            if (mast == null) {
605                log.error("{}.setWestBoundSignalMast({}); Unable to find Signal Mast",
606                        getName(), signalMast);
607                return;
608            }
609        } else {
610            westBoundSignalMastNamed = null;
611            return;
612        }
613        if (getType() == PointType.EDGE_CONNECTOR) {
614            int dir = getConnect1Dir();
615            if (dir == Path.WEST || dir == Path.NORTH || dir == Path.NORTH_WEST) {
616                westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
617            } else if (getLinkedPoint() != null) {
618                int linkDir = getLinkedPoint().getConnect1Dir();
619                if (linkDir == Path.WEST || linkDir == Path.NORTH || linkDir == Path.NORTH_WEST) {
620                    getLinkedPoint().setWestBoundSignalMast(signalMast);
621                } else {
622                    westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
623                }
624            } else {
625                westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
626            }
627        } else {
628            westBoundSignalMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
629        }
630    }
631
632    public void removeBeanReference(jmri.NamedBean nb) {
633        if (nb == null) {
634            return;
635        }
636        if (nb instanceof SignalMast) {
637            if (nb.equals(getWestBoundSignalMast())) {
638                setWestBoundSignalMast(null);
639            } else if (nb.equals(getEastBoundSignalMast())) {
640                setEastBoundSignalMast(null);
641            }
642        } else if (nb instanceof Sensor) {
643            if (nb.equals(getWestBoundSensor())) {
644                setWestBoundSignalMast(null);
645            } else if (nb.equals(getEastBoundSensor())) {
646                setEastBoundSignalMast(null);
647            }
648        } else if (nb instanceof jmri.SignalHead) {
649            if (nb.equals(getWestBoundSignalHead())) {
650                setWestBoundSignal(null);
651            }
652            if (nb.equals(getEastBoundSignalHead())) {
653                setEastBoundSignal(null);
654            }
655
656        }
657    }
658
659    // initialization instance variables (used when loading a LayoutEditor)
660    public String trackSegment1Name = "";
661    public String trackSegment2Name = "";
662
663    /**
664     * Initialization method The above variables are initialized by
665     * PositionablePointXml, then the following method is called after the
666     * entire LayoutEditor is loaded to set the specific TrackSegment objects.
667     */
668    @Override
669    public void setObjects(LayoutEditor p) {
670        if (type == PointType.EDGE_CONNECTOR) {
671            connect1 = p.getFinder().findTrackSegmentByName(trackSegment1Name);
672            if (getConnect2() != null && getLinkedEditor() != null) {
673                //now that we have a connection we can fire off a change
674                TrackSegment ts = getConnect2();
675                getLinkedEditor().getLEAuxTools().setBlockConnectivityChanged();
676                ts.updateBlockInfo();
677            }
678        } else {
679            connect1 = p.getFinder().findTrackSegmentByName(trackSegment1Name);
680            connect2 = p.getFinder().findTrackSegmentByName(trackSegment2Name);
681        }
682        log.trace("PositionablePoint:setObjects {}: {} and {} {}", trackSegment1Name, connect1, trackSegment1Name, connect1);
683    }
684
685    /**
686     * setup a connection to a track
687     *
688     * @param track the track we want to connect to
689     * @return true if successful
690     */
691    public boolean setTrackConnection(@Nonnull TrackSegment track) {
692        return replaceTrackConnection(null, track);
693    }
694
695    /**
696     * remove a connection to a track
697     *
698     * @param track the track we want to disconnect from
699     * @return true if successful
700     */
701    public boolean removeTrackConnection(@Nonnull TrackSegment track) {
702        return replaceTrackConnection(track, null);
703    }
704
705    /**
706     * replace old track connection with new track connection
707     *
708     * @param oldTrack the old track connection
709     * @param newTrack the new track connection
710     * @return true if successful
711     */
712    public boolean replaceTrackConnection(@CheckForNull TrackSegment oldTrack, @CheckForNull TrackSegment newTrack) {
713        boolean result = false; // assume failure (pessimist!)
714        // trying to replace old track with null?
715        if (newTrack == null) {
716            // (yes) remove old connection
717            if (oldTrack != null) {
718                result = true;  // assume success (optimist!)
719                if (connect1 == oldTrack) {
720                    connect1 = null;        // disconnect connect1
721                    reCheckBlockBoundary();
722                    removeLinkedPoint();
723                    connect1 = connect2;    // Move connect2 to connect1
724                    connect2 = null;        // disconnect connect2
725                } else if (connect2 == oldTrack) {
726                    connect2 = null;
727                    reCheckBlockBoundary();
728                } else {
729                    result = false; // didn't find old connection
730                }
731            } else {
732                result = false; // can't replace null with null
733            }
734            if (!result) {
735                log.error("{}.replaceTrackConnection({}, {}); Attempt to remove non-existant track connection",
736                        getName(), (oldTrack == null) ? "null" : oldTrack.getName(), "null");
737            }
738        } else // already connected to newTrack?
739        if ((connect1 != newTrack) && (connect2 != newTrack)) {
740            // (no) find a connection we can connect to
741            result = true;  // assume success (optimist!)
742            if (connect1 == oldTrack) {
743                connect1 = newTrack;
744            } else if ((type == PointType.ANCHOR) && (connect2 == oldTrack)) {
745                connect2 = newTrack;
746                if (connect1.getLayoutBlock() == connect2.getLayoutBlock()) {
747                    westBoundSignalMastNamed = null;
748                    eastBoundSignalMastNamed = null;
749                    setWestBoundSensor("");
750                    setEastBoundSensor("");
751                }
752            } else {
753                log.error("{}.replaceTrackConnection({}, {}); Attempt to assign more than allowed number of connections",
754                        getName(), (oldTrack == null) ? "null" : oldTrack.getName(), newTrack.getName());
755                result = false;
756            }
757        } else {
758            log.warn("{}.replaceTrackConnection({}, {}); Already connected",
759                    getName(), (oldTrack == null) ? "null" : oldTrack.getName(), newTrack.getName());
760            result = false;
761        }
762        return result;
763    }
764
765    void removeSML(SignalMast signalMast) {
766        if (signalMast == null) {
767            return;
768        }
769        if (jmri.InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled() && InstanceManager.getDefault(jmri.SignalMastLogicManager.class).isSignalMastUsed(signalMast)) {
770            SignallingGuiTools.removeSignalMastLogic(null, signalMast);
771        }
772    }
773
774    /**
775     * {@inheritDoc}
776     */
777    @Override
778    public boolean canRemove() {
779        List<String> itemList = new ArrayList<>();
780        // A has two track segments, EB has one, EC has one plus optional link
781
782        TrackSegment ts1 = getConnect1();
783        TrackSegment ts2 = getConnect2();
784
785        if (ts1 != null) {
786            itemList.addAll(getSegmentReferences(ts1));
787        }
788        if (ts2 != null) {
789            for (String item : getSegmentReferences(ts2)) {
790                // Do not add duplicates
791                if (!itemList.contains(item)) {
792                    itemList.add(item);
793                }
794            }
795        }
796
797        if (!itemList.isEmpty()) {
798            String typeName = "";
799            switch (type) {
800                case ANCHOR:
801                    typeName = "Anchor";  // NOI18N
802                    break;
803                case END_BUMPER:
804                    typeName = "EndBumper";  // NOI18N
805                    break;
806                case EDGE_CONNECTOR:
807                    typeName = "EdgeConnector";  // NOI18N
808                    break;
809                default:
810                    typeName = "Unknown type (" + type + ")";  // NOI18N
811                    break;
812            }
813            models.displayRemoveWarning(this, itemList, typeName);
814        }
815        return itemList.isEmpty();
816    }
817
818    /**
819     * Build a list of sensors, signal heads, and signal masts attached to a
820     * connection point.
821     *
822     * @param ts The track segment to be checked.
823     * @return a list of bean reference names.
824     */
825    public ArrayList<String> getSegmentReferences(TrackSegment ts) {
826        ArrayList<String> items = new ArrayList<>();
827
828        HitPointType type1 = ts.getType1();
829        LayoutTrack conn1 = ts.getConnect1();
830        items.addAll(ts.getPointReferences(type1, conn1));
831
832        HitPointType type2 = ts.getType2();
833        LayoutTrack conn2 = ts.getConnect2();
834        items.addAll(ts.getPointReferences(type2, conn2));
835
836        return items;
837    }
838
839    void removeLinkedPoint() {
840        if (type == PointType.EDGE_CONNECTOR && getLinkedPoint() != null) {
841            if (getConnect2() != null && getLinkedEditor() != null) {
842                //as we have removed the point, need to force the update on the remote end.
843                LayoutEditor oldLinkedEditor = getLinkedEditor();
844                TrackSegment ts = getConnect2();
845                getLinkedPoint().setLinkedPoint(null);
846
847                log.info("temporary - repaint was removed here, needs to be rescoped");
848                // oldLinkedEditor.repaint();
849
850                oldLinkedEditor.getLEAuxTools().setBlockConnectivityChanged();
851                ts.updateBlockInfo();
852            }
853            linkedPoint = null;
854        }
855    }
856
857    /**
858     * Removes this object from display and persistence
859     */
860    public void remove() {  // temporary public instead of private for migration
861        // remove from persistence by flagging inactive
862        active = false;
863    }
864
865    private boolean active = true;
866
867    /**
868     * "active" means that the object is still displayed, and should be stored.
869     * @return true if active
870     */
871    protected boolean isActive() {
872        return active;
873    }
874
875    protected int getConnect1Dir() {
876        int result = Path.NONE;
877
878        TrackSegment ts1 = getConnect1();
879        if (ts1 != null) {
880            if (ts1.getConnect1() == this) {
881                result = models.computeDirectionFromCenter(this, ts1.getConnect2(), ts1.getType2());
882            } else {
883                result = models.computeDirectionFromCenter(this, ts1.getConnect1(), ts1.getType1());
884            }
885        }
886        return result;
887    }
888
889    /**
890     * {@inheritDoc}
891     */
892    @Override
893    public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
894        LayoutTrack result = null;
895        if (connectionType == HitPointType.POS_POINT) {
896            result = getConnect1();
897            if (null == result) {
898                result = getConnect2();
899            }
900        } else {
901            String errString = MessageFormat.format("{0}.getConnection({1}); Invalid Connection Type",
902                    getName(), connectionType); //I18IN
903            log.error("will throw {}", errString);
904            throw new jmri.JmriException(errString);
905        }
906        return result;
907    }
908
909    /**
910     * {@inheritDoc}
911     */
912    @Override
913    public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException {
914        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
915            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); unexpected type",
916                    getName(), connectionType, (o == null) ? "null" : o.getName(), type); //I18IN
917            log.error("will throw {}", errString); //I18IN
918            throw new jmri.JmriException(errString);
919        }
920        if (connectionType != HitPointType.POS_POINT) {
921            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid Connection Type",
922                    getName(), connectionType, (o == null) ? "null" : o.getName(), type); //I18IN
923            log.error("will throw {}", errString); //I18IN
924            throw new jmri.JmriException(errString);
925        }
926    }
927
928    /**
929     * return true if this connection type is disconnected
930     *
931     * @param connectionType the connection type to test
932     * @return true if the connection for this connection type is free
933     */
934    @Override
935    public boolean isDisconnected(HitPointType connectionType) {
936        boolean result = false;
937        if (connectionType == HitPointType.POS_POINT) {
938            result = ((getConnect1() == null) || (getConnect2() == null));
939        } else {
940            log.error("{}.isDisconnected({}); Invalid Connection Type",
941                    getName(), connectionType); //I18IN
942        }
943        return result;
944    }
945
946    @Override
947    public boolean isMainline() {
948        boolean result = false; // assume failure (pessimist!)
949        if (getConnect1() != null) {
950            result = getConnect1().isMainline();
951        }
952        if (getType() == PointType.ANCHOR) {
953            if (getConnect2() != null) {
954                result |= getConnect2().isMainline();
955            }
956        }
957        return result;
958    }
959
960    /**
961     * {@inheritDoc}
962     */
963    @Override
964    public void reCheckBlockBoundary() {
965        if (type == PointType.END_BUMPER) {
966            return;
967        }
968        if (getConnect1() == null && getConnect2() == null) {
969            //This is no longer a block boundary, therefore will remove signal masts and sensors if present
970            if (westBoundSignalMastNamed != null) {
971                removeSML(getWestBoundSignalMast());
972            }
973            if (eastBoundSignalMastNamed != null) {
974                removeSML(getEastBoundSignalMast());
975            }
976            westBoundSignalMastNamed = null;
977            eastBoundSignalMastNamed = null;
978            setWestBoundSensor("");
979            setEastBoundSensor("");
980            //TODO: May want to look at a method to remove the assigned mast
981            //from the panel and potentially any SignalMast logics generated
982        } else if (getConnect1() == null || getConnect2() == null) {
983            //could still be in the process of rebuilding the point details
984        } else if (getConnect1().getLayoutBlock() == getConnect2().getLayoutBlock()) {
985            //We are no longer a block bounardy
986            if (westBoundSignalMastNamed != null) {
987                removeSML(getWestBoundSignalMast());
988            }
989            if (eastBoundSignalMastNamed != null) {
990                removeSML(getEastBoundSignalMast());
991            }
992            westBoundSignalMastNamed = null;
993            eastBoundSignalMastNamed = null;
994            setWestBoundSensor("");
995            setEastBoundSensor("");
996            //TODO: May want to look at a method to remove the assigned mast
997            //from the panel and potentially any SignalMast logics generated
998        }
999    }   // reCheckBlockBoundary
1000
1001    /**
1002     * {@inheritDoc}
1003     */
1004     //
1005     // This uses a getCoords() call, and having that at this level
1006     // has to be temporary
1007     //
1008    @Override
1009    protected List<LayoutConnectivity> getLayoutConnectivity() {
1010        List<LayoutConnectivity> results = new ArrayList<>();
1011        LayoutConnectivity lc = null;
1012        LayoutBlock blk1 = null, blk2 = null;
1013        TrackSegment ts1 = getConnect1();
1014
1015        if (getType() == PointType.ANCHOR) {
1016            TrackSegment ts2 = getConnect2();
1017            if ((ts1 != null) && (ts2 != null)) {
1018                blk1 = ts1.getLayoutBlock();
1019                blk2 = ts2.getLayoutBlock();
1020                if ((blk1 != null) && (blk2 != null) && (blk1 != blk2)) {
1021                    // this is a block boundary, create a LayoutConnectivity
1022                    log.debug("Block boundary (''{}''<->''{}'') found at {}", blk1, blk2, this);
1023                    lc = new LayoutConnectivity(blk1, blk2);
1024
1025                    // determine direction from block 1 to block 2
1026                    lc.setDirection(
1027                        models.computeDirection(
1028                            ts1.getConnect1() == this ? ts1.getConnect2()   : ts1.getConnect1(),
1029                            ts1.getConnect1() == this ? ts1.getType2()      : ts1.getType1(),
1030
1031                            ts2.getConnect1() == this ? ts2.getConnect2() : ts2.getConnect1(),
1032                            ts2.getConnect1() == this ? ts2.getType2() : ts2.getType1()
1033                        )
1034                    );
1035
1036                    // save Connections
1037                    lc.setConnections(ts1, ts2, HitPointType.TRACK, this);
1038                    results.add(lc);
1039                }
1040            }
1041        } else if (getType() == PointType.EDGE_CONNECTOR) {
1042            TrackSegment ts2 = null;
1043            if (getLinkedPoint() != null) {
1044                ts2 = getLinkedPoint().getConnect1();
1045            }
1046            if ((ts1 != null) && (ts2 != null)) {
1047                blk1 = ts1.getLayoutBlock();
1048                blk2 = ts2.getLayoutBlock();
1049                if ((blk1 != null) && (blk2 != null) && (blk1 != blk2)) {
1050                    // this is a block boundary, create a LayoutConnectivity
1051                    log.debug("Block boundary (''{}''<->''{}'') found at {}", blk1, blk2, this);
1052                    lc = new LayoutConnectivity(blk1, blk2);
1053
1054                    // determine direction from block 1 to block 2
1055                    int result;
1056
1057                    if (ts1.getConnect1() == this) {
1058                        result = models.computeDirectionToCenter(ts1.getConnect2(), ts1.getType2(), this);
1059                    } else {
1060                        result = models.computeDirectionToCenter(ts1.getConnect1(), ts1.getType1(), this);
1061                    }
1062
1063                    //Need to find a way to compute the direction for this for a split over the panel
1064                    //In this instance work out the direction of the first track relative to the positionable poin.
1065                    lc.setDirection(result);
1066                    // save Connections
1067                    lc.setConnections(ts1, ts2, HitPointType.TRACK, this);
1068                    results.add(lc);
1069                }
1070            }
1071        }
1072        return results;
1073    }   // getLayoutConnectivity()
1074
1075    /**
1076     * {@inheritDoc}
1077     */
1078    @Override
1079    public List<HitPointType> checkForFreeConnections() {
1080        List<HitPointType> result = new ArrayList<>();
1081
1082        if ((getConnect1() == null)
1083                || ((getType() == PointType.ANCHOR) && (getConnect2() == null))) {
1084            result.add(HitPointType.POS_POINT);
1085        }
1086        return result;
1087    }
1088
1089    /**
1090     * {@inheritDoc}
1091     */
1092    @Override
1093    public boolean checkForUnAssignedBlocks() {
1094        // Positionable Points don't have blocks so...
1095        // nothing to see here... move along...
1096        return true;
1097    }
1098
1099    /**
1100     * {@inheritDoc}
1101     */
1102    @Override
1103    public void checkForNonContiguousBlocks(
1104            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
1105        /*
1106        * For each (non-null) blocks of this track do:
1107        * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
1108        * #2) If this track is not in one of the TrackNameSets for this block
1109        * #3) add a new set (with this block/track) to
1110        *     blockNamesToTrackNameSetMap and
1111        * #4) check all the connections in this
1112        *     block (by calling the 2nd method below)
1113        * <p>
1114        *     Basically, we're maintaining contiguous track sets for each block found
1115        *     (in blockNamesToTrackNameSetMap)
1116         */
1117        //check the 1st connection points block
1118        TrackSegment ts1 = getConnect1();
1119        String blk1 = null;
1120        List<Set<String>> TrackNameSets = null;
1121        Set<String> TrackNameSet = null;    // assume not found (pessimist!)
1122
1123        // this should never be null... but just in case...
1124        if (ts1 != null) {
1125            blk1 = ts1.getBlockName();
1126            if (!blk1.isEmpty()) {
1127                TrackNameSets = blockNamesToTrackNameSetsMap.get(blk1);
1128                if (TrackNameSets != null) { // (#1)
1129                    for (Set<String> checkTrackNameSet : TrackNameSets) {
1130                        if (checkTrackNameSet.contains(getName())) { // (#2)
1131                            TrackNameSet = checkTrackNameSet;
1132                            break;
1133                        }
1134                    }
1135                } else {    // (#3)
1136                    log.debug("*New block (''{}'') trackNameSets", blk1);
1137                    TrackNameSets = new ArrayList<>();
1138                    blockNamesToTrackNameSetsMap.put(blk1, TrackNameSets);
1139                }
1140                if (TrackNameSet == null) {
1141                    TrackNameSet = new LinkedHashSet<>();
1142                    log.debug("*    Add track ''{}'' to trackNameSet for block ''{}''", getName(), blk1);
1143                    TrackNameSet.add(getName());
1144                    TrackNameSets.add(TrackNameSet);
1145                }
1146                if (connect1 != null) { // (#4)
1147                    connect1.collectContiguousTracksNamesInBlockNamed(blk1, TrackNameSet);
1148                }
1149            }
1150        }
1151
1152        if (getType() == PointType.ANCHOR) {
1153            //check the 2nd connection points block
1154            TrackSegment ts2 = getConnect2();
1155            // this should never be null... but just in case...
1156            if (ts2 != null) {
1157                String blk2 = ts2.getBlockName();
1158                if (!blk2.isEmpty()) {
1159                    TrackNameSet = null;    // assume not found (pessimist!)
1160                    TrackNameSets = blockNamesToTrackNameSetsMap.get(blk2);
1161                    if (TrackNameSets != null) { // (#1)
1162                        for (Set<String> checkTrackNameSet : TrackNameSets) {
1163                            if (checkTrackNameSet.contains(getName())) { // (#2)
1164                                TrackNameSet = checkTrackNameSet;
1165                                break;
1166                            }
1167                        }
1168                    } else {    // (#3)
1169                        log.debug("*New block (''{}'') trackNameSets", blk2);
1170                        TrackNameSets = new ArrayList<>();
1171                        blockNamesToTrackNameSetsMap.put(blk2, TrackNameSets);
1172                    }
1173                    if (TrackNameSet == null) {
1174                        TrackNameSet = new LinkedHashSet<>();
1175                        log.debug("*    Add track ''{}'' to TrackNameSet for block ''{}''", getName(), blk2);
1176                        TrackNameSets.add(TrackNameSet);
1177                        TrackNameSet.add(getName());
1178                    }
1179                    if (connect2 != null) { // (#4)
1180                        connect2.collectContiguousTracksNamesInBlockNamed(blk2, TrackNameSet);
1181                    }
1182                }
1183            }
1184        }
1185    } // collectContiguousTracksNamesInBlockNamed
1186
1187    /**
1188     * {@inheritDoc}
1189     */
1190    @Override
1191    public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName,
1192            @Nonnull Set<String> TrackNameSet) {
1193        if (!TrackNameSet.contains(getName())) {
1194            TrackSegment ts1 = getConnect1();
1195            // this should never be null... but just in case...
1196            if (ts1 != null) {
1197                String blk1 = ts1.getBlockName();
1198                // is this the blockName we're looking for?
1199                if (blk1.equals(blockName)) {
1200                    // if we are added to the TrackNameSet
1201                    if (TrackNameSet.add(getName())) {
1202                        log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
1203                    }
1204                    // this should never be null... but just in case...
1205                    if (connect1 != null) {
1206                        connect1.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1207                    }
1208                }
1209            }
1210            if (getType() == PointType.ANCHOR) {
1211                TrackSegment ts2 = getConnect2();
1212                // this should never be null... but just in case...
1213                if (ts2 != null) {
1214                    String blk2 = ts2.getBlockName();
1215                    // is this the blockName we're looking for?
1216                    if (blk2.equals(blockName)) {
1217                        // if we are added to the TrackNameSet
1218                        if (TrackNameSet.add(getName())) {
1219                            log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
1220                        }
1221                        // this should never be null... but just in case...
1222                        if (connect2 != null) {
1223                            // it's time to play... flood your neighbour!
1224                            connect2.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1225                        }
1226                    }
1227                }
1228            }
1229        }
1230    }
1231
1232    /**
1233     * {@inheritDoc}
1234     */
1235    @Override
1236    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
1237        // positionable points don't have blocks...
1238        // nothing to see here, move along...
1239    }
1240
1241    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PositionablePoint.class);
1242
1243}