001package jmri.jmrit.display.layoutEditor;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.text.MessageFormat;
006import java.util.*;
007
008import javax.annotation.Nonnull;
009
010import jmri.*;
011import jmri.jmrit.signalling.SignallingGuiTools;
012
013/**
014 * A LevelXing is two track segment on a layout that cross at an angle.
015 * <p>
016 * A LevelXing has four connection points, designated A, B, C, and D. At the
017 * crossing, A-C and B-D are straight segments. A train proceeds through the
018 * crossing on either of these segments.
019 * <br>
020 * <pre>
021 *    A   D
022 *    \\ //
023 *      X
024 *    // \\
025 *    B   C
026 * </pre>
027 * <br>
028 * Each straight segment carries Block information. A-C and B-D may be in the
029 * same or different Layout Blocks.
030 * <p>
031 * For drawing purposes, each LevelXing carries a center point and displacements
032 * for A and B. The displacements for C = - the displacement for A, and the
033 * displacement for D = - the displacement for B. The center point and these
034 * displacements may be adjusted by the user when in edit mode.
035 * <p>
036 * When LevelXings are first created, there are no connections. Block
037 * information and connections are added when available.
038 * <p>
039 * Signal Head names are saved here to keep track of where signals are.
040 * LevelXing only serves as a storage place for signal head names. The names are
041 * placed here by Set Signals at Level Crossing in Tools menu.
042 *
043 * @author Dave Duchamp Copyright (c) 2004-2007
044 * @author George Warner Copyright (c) 2017-2019
045 */
046public class LevelXing extends LayoutTrack {
047
048    /**
049     * Constructor.
050     * @param id ID string.
051     * @param models the main layout editor.
052     */
053    public LevelXing(String id, LayoutEditor models) {
054        super(id, models);
055    }
056
057    // defined constants
058    // operational instance variables (not saved between sessions)
059    private NamedBeanHandle<LayoutBlock> namedLayoutBlockAC = null;
060    private NamedBeanHandle<LayoutBlock> namedLayoutBlockBD = null;
061
062    protected NamedBeanHandle<SignalHead> signalAHeadNamed = null; // signal at A track junction
063    protected NamedBeanHandle<SignalHead> signalBHeadNamed = null; // signal at B track junction
064    protected NamedBeanHandle<SignalHead> signalCHeadNamed = null; // signal at C track junction
065    protected NamedBeanHandle<SignalHead> signalDHeadNamed = null; // signal at D track junction
066
067    protected NamedBeanHandle<SignalMast> signalAMastNamed = null; // signal at A track junction
068    protected NamedBeanHandle<SignalMast> signalBMastNamed = null; // signal at B track junction
069    protected NamedBeanHandle<SignalMast> signalCMastNamed = null; // signal at C track junction
070    protected NamedBeanHandle<SignalMast> signalDMastNamed = null; // signal at D track junction
071
072    private NamedBeanHandle<Sensor> sensorANamed = null; // sensor at A track junction
073    private NamedBeanHandle<Sensor> sensorBNamed = null; // sensor at B track junction
074    private NamedBeanHandle<Sensor> sensorCNamed = null; // sensor at C track junction
075    private NamedBeanHandle<Sensor> sensorDNamed = null; // sensor at D track junction
076
077    private LayoutTrack connectA = null;
078    private LayoutTrack connectB = null;
079    private LayoutTrack connectC = null;
080    private LayoutTrack connectD = null;
081
082    public enum Geometry {
083        POINTA, POINTB, POINTC, POINTD
084    }
085
086    // temporary reference to the Editor that will eventually be part of View
087    //private final jmri.jmrit.display.models.LayoutEditorDialogs.LevelXingEditor editor;
088
089    // this should only be used for debugging
090    @Override
091    public String toString() {
092        return "LevelXing " + getName();
093    }
094
095    /*
096    * Accessor methods
097     */
098    @Nonnull
099    public String getBlockNameAC() {
100        String result = null;
101        if (namedLayoutBlockAC != null) {
102            result = namedLayoutBlockAC.getName();
103        }
104        return ((result == null) ? "" : result);
105    }
106
107    @Nonnull
108    public String getBlockNameBD() {
109        String result = getBlockNameAC();
110        if (namedLayoutBlockBD != null) {
111            result = namedLayoutBlockBD.getName();
112        }
113        return result;
114    }
115
116    public SignalHead getSignalHead(Geometry loc) {
117        NamedBeanHandle<SignalHead> namedBean = null;
118        switch (loc) {
119            case POINTA:
120                namedBean = signalAHeadNamed;
121                break;
122            case POINTB:
123                namedBean = signalBHeadNamed;
124                break;
125            case POINTC:
126                namedBean = signalCHeadNamed;
127                break;
128            case POINTD:
129                namedBean = signalDHeadNamed;
130                break;
131            default:
132                log.warn("{}.getSignalHead({})", getName(), loc);
133                break;
134        }
135        if (namedBean != null) {
136            return namedBean.getBean();
137        }
138        return null;
139    }
140
141    public SignalMast getSignalMast(Geometry loc) {
142        NamedBeanHandle<SignalMast> namedBean = null;
143        switch (loc) {
144            case POINTA:
145                namedBean = signalAMastNamed;
146                break;
147            case POINTB:
148                namedBean = signalBMastNamed;
149                break;
150            case POINTC:
151                namedBean = signalCMastNamed;
152                break;
153            case POINTD:
154                namedBean = signalDMastNamed;
155                break;
156            default:
157                log.warn("{}.getSignalMast({})", getName(), loc);
158                break;
159        }
160        if (namedBean != null) {
161            return namedBean.getBean();
162        }
163        return null;
164    }
165
166    public Sensor getSensor(Geometry loc) {
167        NamedBeanHandle<Sensor> namedBean = null;
168        switch (loc) {
169            case POINTA:
170                namedBean = sensorANamed;
171                break;
172            case POINTB:
173                namedBean = sensorBNamed;
174                break;
175            case POINTC:
176                namedBean = sensorCNamed;
177                break;
178            case POINTD:
179                namedBean = sensorDNamed;
180                break;
181            default:
182                log.warn("{}.getSensor({})", getName(), loc);
183                break;
184        }
185        if (namedBean != null) {
186            return namedBean.getBean();
187        }
188        return null;
189    }
190
191    @Nonnull
192    public String getSignalAName() {
193        if (signalAHeadNamed != null) {
194            return signalAHeadNamed.getName();
195        }
196        return "";
197    }
198
199    public void setSignalAName(String signalHead) {
200        if (signalHead == null || signalHead.isEmpty()) {
201            signalAHeadNamed = null;
202            return;
203        }
204
205        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
206        if (head != null) {
207            signalAHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
208        } else {
209            signalAHeadNamed = null;
210        }
211    }
212
213    @Nonnull
214    public String getSignalBName() {
215        if (signalBHeadNamed != null) {
216            return signalBHeadNamed.getName();
217        }
218        return "";
219    }
220
221    public void setSignalBName(String signalHead) {
222        if (signalHead == null || signalHead.isEmpty()) {
223            signalBHeadNamed = null;
224            return;
225        }
226
227        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
228        if (head != null) {
229            signalBHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
230        } else {
231            signalBHeadNamed = null;
232        }
233    }
234
235    @Nonnull
236    public String getSignalCName() {
237        if (signalCHeadNamed != null) {
238            return signalCHeadNamed.getName();
239        }
240        return "";
241    }
242
243    public void setSignalCName(String signalHead) {
244        if (signalHead == null || signalHead.isEmpty()) {
245            signalCHeadNamed = null;
246            return;
247        }
248
249        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
250        if (head != null) {
251            signalCHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
252        } else {
253            signalCHeadNamed = null;
254        }
255    }
256
257    @Nonnull
258    public String getSignalDName() {
259        if (signalDHeadNamed != null) {
260            return signalDHeadNamed.getName();
261        }
262        return "";
263    }
264
265    public void setSignalDName(String signalHead) {
266        if (signalHead == null || signalHead.isEmpty()) {
267            signalDHeadNamed = null;
268            return;
269        }
270
271        SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signalHead);
272        if (head != null) {
273            signalDHeadNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalHead, head);
274        } else {
275            signalDHeadNamed = null;
276        }
277    }
278
279    public void removeBeanReference(jmri.NamedBean nb) {
280        if (nb == null) {
281            return;
282        }
283        if (nb instanceof SignalMast) {
284            if (nb.equals(getSignalAMast())) {
285                setSignalAMast(null);
286                return;
287            }
288            if (nb.equals(getSignalBMast())) {
289                setSignalBMast(null);
290                return;
291            }
292            if (nb.equals(getSignalCMast())) {
293                setSignalCMast(null);
294                return;
295            }
296            if (nb.equals(getSignalDMast())) {
297                setSignalDMast(null);
298                return;
299            }
300        }
301        if (nb instanceof Sensor) {
302            if (nb.equals(getSensorA())) {
303                setSensorAName(null);
304                return;
305            }
306            if (nb.equals(getSensorB())) {
307                setSensorBName(null);
308                return;
309            }
310            if (nb.equals(getSensorC())) {
311                setSensorCName(null);
312                return;
313            }
314            if (nb.equals(getSensorD())) {
315                setSensorDName(null);
316                return;
317            }
318        }
319        if (nb instanceof SignalHead) {
320            if (nb.equals(getSignalHead(Geometry.POINTA))) {
321                setSignalAName(null);
322                return;
323            }
324            if (nb.equals(getSignalHead(Geometry.POINTB))) {
325                setSignalBName(null);
326                return;
327            }
328            if (nb.equals(getSignalHead(Geometry.POINTC))) {
329                setSignalCName(null);
330                return;
331            }
332            if (nb.equals(getSignalHead(Geometry.POINTD))) {
333                setSignalDName(null);
334            }
335        }
336    }
337
338    public String getSignalAMastName() {
339        if (signalAMastNamed != null) {
340            return signalAMastNamed.getName();
341        }
342        return "";
343    }
344
345    public SignalMast getSignalAMast() {
346        if (signalAMastNamed != null) {
347            return signalAMastNamed.getBean();
348        }
349        return null;
350    }
351
352    public void setSignalAMast(String signalMast) {
353        if (signalMast == null || signalMast.isEmpty()) {
354            signalAMastNamed = null;
355            return;
356        }
357
358        try {
359            SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(signalMast);
360            signalAMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
361        } catch (IllegalArgumentException ex) {
362            signalAMastNamed = null;
363        }
364    }
365
366    public String getSignalBMastName() {
367        if (signalBMastNamed != null) {
368            return signalBMastNamed.getName();
369        }
370        return "";
371    }
372
373    public SignalMast getSignalBMast() {
374        if (signalBMastNamed != null) {
375            return signalBMastNamed.getBean();
376        }
377        return null;
378    }
379
380    public void setSignalBMast(String signalMast) {
381        if (signalMast == null || signalMast.isEmpty()) {
382            signalBMastNamed = null;
383            return;
384        }
385
386        try {
387            SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(signalMast);
388            signalBMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
389        } catch (IllegalArgumentException ex) {
390            signalBMastNamed = null;
391        }
392    }
393
394    public String getSignalCMastName() {
395        if (signalCMastNamed != null) {
396            return signalCMastNamed.getName();
397        }
398        return "";
399    }
400
401    public SignalMast getSignalCMast() {
402        if (signalCMastNamed != null) {
403            return signalCMastNamed.getBean();
404        }
405        return null;
406    }
407
408    public void setSignalCMast(String signalMast) {
409        if (signalMast == null || signalMast.isEmpty()) {
410            signalCMastNamed = null;
411            return;
412        }
413
414        try {
415            SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(signalMast);
416            signalCMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
417        } catch (IllegalArgumentException ex) {
418            signalCMastNamed = null;
419        }
420    }
421
422    public String getSignalDMastName() {
423        if (signalDMastNamed != null) {
424            return signalDMastNamed.getName();
425        }
426        return "";
427    }
428
429    public SignalMast getSignalDMast() {
430        if (signalDMastNamed != null) {
431            return signalDMastNamed.getBean();
432        }
433        return null;
434    }
435
436    public void setSignalDMast(String signalMast) {
437        if (signalMast == null || signalMast.isEmpty()) {
438            signalDMastNamed = null;
439            return;
440        }
441
442        try {
443            SignalMast mast = InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(signalMast);
444            signalDMastNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(signalMast, mast);
445        } catch (IllegalArgumentException ex) {
446            signalDMastNamed = null;
447        }
448    }
449
450    public String getSensorAName() {
451        if (sensorANamed != null) {
452            return sensorANamed.getName();
453        }
454        return "";
455    }
456
457    public Sensor getSensorA() {
458        if (sensorANamed != null) {
459            return sensorANamed.getBean();
460        }
461        return null;
462    }
463
464    public void setSensorAName(String sensorName) {
465        if (sensorName == null || sensorName.isEmpty()) {
466            sensorANamed = null;
467            return;
468        }
469
470        try {
471            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
472            sensorANamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
473        } catch (IllegalArgumentException ex) {
474            sensorANamed = null;
475        }
476    }
477
478    public String getSensorBName() {
479        if (sensorBNamed != null) {
480            return sensorBNamed.getName();
481        }
482        return "";
483    }
484
485    public Sensor getSensorB() {
486        if (sensorBNamed != null) {
487            return sensorBNamed.getBean();
488        }
489        return null;
490    }
491
492    public void setSensorBName(String sensorName) {
493        if (sensorName == null || sensorName.isEmpty()) {
494            sensorBNamed = null;
495            return;
496        }
497
498        try {
499            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
500            sensorBNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
501        } catch (IllegalArgumentException ex) {
502            sensorBNamed = null;
503        }
504    }
505
506    public String getSensorCName() {
507        if (sensorCNamed != null) {
508            return sensorCNamed.getName();
509        }
510        return "";
511    }
512
513    public Sensor getSensorC() {
514        if (sensorCNamed != null) {
515            return sensorCNamed.getBean();
516        }
517        return null;
518    }
519
520    public void setSensorCName(String sensorName) {
521        if (sensorName == null || sensorName.isEmpty()) {
522            sensorCNamed = null;
523            return;
524        }
525
526        try {
527            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
528            sensorCNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
529        } catch (IllegalArgumentException ex) {
530            sensorCNamed = null;
531        }
532    }
533
534    public String getSensorDName() {
535        if (sensorDNamed != null) {
536            return sensorDNamed.getName();
537        }
538        return "";
539    }
540
541    public Sensor getSensorD() {
542        if (sensorDNamed != null) {
543            return sensorDNamed.getBean();
544        }
545        return null;
546    }
547
548    public void setSensorDName(String sensorName) {
549        if (sensorName == null || sensorName.isEmpty()) {
550            sensorDNamed = null;
551            return;
552        }
553
554        try {
555            Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
556            sensorDNamed = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor);
557        } catch (IllegalArgumentException ex) {
558            sensorDNamed = null;
559        }
560    }
561
562    /**
563     * {@inheritDoc}
564     */
565    @Override
566    public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
567        switch (connectionType) {
568            case LEVEL_XING_A:
569                return connectA;
570            case LEVEL_XING_B:
571                return connectB;
572            case LEVEL_XING_C:
573                return connectC;
574            case LEVEL_XING_D:
575                return connectD;
576            default:
577                break;
578        }
579        String errstring = MessageFormat.format("{0}.getConnection({1}); invalid connection type", getName(), connectionType); //I18IN
580        log.error("will throw {}", errstring);
581        throw new jmri.JmriException(errstring);
582    }
583
584    /**
585     * {@inheritDoc}
586     */
587    @Override
588    public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException {
589        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
590            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); invalid type",
591                    getName(), connectionType, (o == null) ? "null" : o.getName(), type);
592            log.error("will throw {}", errString);
593            throw new jmri.JmriException(errString);
594        }
595        switch (connectionType) {
596            case LEVEL_XING_A:
597                connectA = o;
598                break;
599            case LEVEL_XING_B:
600                connectB = o;
601                break;
602            case LEVEL_XING_C:
603                connectC = o;
604                break;
605            case LEVEL_XING_D:
606                connectD = o;
607                break;
608            default:
609                String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); invalid connection type",
610                        getName(), connectionType, (o == null) ? "null" : o.getName(), type);
611                log.error("will throw {}", errString);
612                throw new jmri.JmriException(errString);
613        }
614    }
615
616    public LayoutTrack getConnectA() {
617        return connectA;
618    }
619
620    public LayoutTrack getConnectB() {
621        return connectB;
622    }
623
624    public LayoutTrack getConnectC() {
625        return connectC;
626    }
627
628    public LayoutTrack getConnectD() {
629        return connectD;
630    }
631
632    public void setConnectA(LayoutTrack o, HitPointType type) {
633        connectA = o;
634        if ((connectA != null) && (type != HitPointType.TRACK)) {
635            log.error("{}.setConnectA(({}, {}); invalid type",
636                    getName(), o.getName(), type);
637        }
638    }
639
640    public void setConnectB(LayoutTrack o, HitPointType type) {
641        connectB = o;
642        if ((connectB != null) && (type != HitPointType.TRACK)) {
643            log.error("{}.setConnectB(({}, {}); invalid type",
644                    getName(), o.getName(), type);
645        }
646    }
647
648    public void setConnectC(LayoutTrack o, HitPointType type) {
649        connectC = o;
650        if ((connectC != null) && (type != HitPointType.TRACK)) {
651            log.error("{}.setConnectC(({}, {}); invalid type",
652                    getName(), o.getName(), type);
653        }
654    }
655
656    public void setConnectD(LayoutTrack o, HitPointType type) {
657        connectD = o;
658        if ((connectD != null) && (type != HitPointType.TRACK)) {
659            log.error("{}.setConnectD(({}, {}); invalid type",
660                    getName(), o.getName(), type);
661        }
662    }
663
664    public LayoutBlock getLayoutBlockAC() {
665        return (namedLayoutBlockAC != null) ? namedLayoutBlockAC.getBean() : null;
666    }
667
668    public LayoutBlock getLayoutBlockBD() {
669        return (namedLayoutBlockBD != null) ? namedLayoutBlockBD.getBean() : getLayoutBlockAC();
670    }
671
672    /**
673     * Add Layout Blocks.
674     * @param newLayoutBlock the layout block to add.
675     */
676    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Null is accepted as a valid value")  // temporary, as added in error
677    public void setLayoutBlockAC(LayoutBlock newLayoutBlock) {
678        LayoutBlock blockAC = getLayoutBlockAC();
679        LayoutBlock blockBD = getLayoutBlockBD();
680        if (blockAC != newLayoutBlock) {
681            // block 1 has changed, if old block exists, decrement use
682            if ((blockAC != null) && (blockAC != blockBD)) {
683                blockAC.decrementUse();
684            }
685            blockAC = newLayoutBlock;
686            if (newLayoutBlock != null) {
687                namedLayoutBlockAC = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(newLayoutBlock.getUserName(), newLayoutBlock);
688            } else {
689                namedLayoutBlockAC = null;
690            }
691
692            // decrement use if block was previously counted
693            if ((blockAC != null) && (blockAC == blockBD)) {
694                blockAC.decrementUse();
695            }
696        }
697    }
698
699    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Null is accepted as a valid value") // temporary, as added in error
700    public void setLayoutBlockBD(LayoutBlock newLayoutBlock) {
701        LayoutBlock blockAC = getLayoutBlockAC();
702        LayoutBlock blockBD = getLayoutBlockBD();
703        if (blockBD != newLayoutBlock) {
704            // block 1 has changed, if old block exists, decrement use
705            if ((blockBD != null) && (blockBD != blockAC)) {
706                blockBD.decrementUse();
707            }
708            blockBD = newLayoutBlock;
709            if (newLayoutBlock != null) {
710                namedLayoutBlockBD = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(newLayoutBlock.getUserName(), newLayoutBlock);
711            } else {
712                namedLayoutBlockBD = null;
713            }
714            // decrement use if block was previously counted
715            if ((blockBD != null) && (blockBD == blockAC)) {
716                blockBD.decrementUse();
717            }
718        }
719
720    }
721
722    public void updateBlockInfo() {
723        LayoutBlock blockAC = getLayoutBlockAC();
724        LayoutBlock blockBD = getLayoutBlockBD();
725        LayoutBlock b1 = null;
726        LayoutBlock b2 = null;
727        if (blockAC != null) {
728            blockAC.updatePaths();
729        }
730        if (connectA != null) {
731            b1 = ((TrackSegment) connectA).getLayoutBlock();
732            if ((b1 != null) && (b1 != blockAC)) {
733                b1.updatePaths();
734            }
735        }
736        if (connectC != null) {
737            b2 = ((TrackSegment) connectC).getLayoutBlock();
738            if ((b2 != null) && (b2 != blockAC) && (b2 != b1)) {
739                b2.updatePaths();
740            }
741        }
742        if (blockBD != null) {
743            blockBD.updatePaths();
744        }
745        if (connectB != null) {
746            b1 = ((TrackSegment) connectB).getLayoutBlock();
747            if ((b1 != null) && (b1 != blockBD)) {
748                b1.updatePaths();
749            }
750        }
751        if (connectD != null) {
752            b2 = ((TrackSegment) connectD).getLayoutBlock();
753            if ((b2 != null) && (b2 != blockBD) && (b2 != b1)) {
754                b2.updatePaths();
755            }
756        }
757        reCheckBlockBoundary();
758    }
759
760    void removeSML(SignalMast signalMast) {
761        if (signalMast == null) {
762            return;
763        }
764        if (jmri.InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled() && InstanceManager.getDefault(jmri.SignalMastLogicManager.class).isSignalMastUsed(signalMast)) {
765            SignallingGuiTools.removeSignalMastLogic(null, signalMast);
766        }
767    }
768
769    /**
770     * Test if mainline track or not.
771     *
772     * @return true if either connecting track segment is mainline; Defaults to
773     *         not mainline if connecting track segments are missing
774     */
775    public boolean isMainlineAC() {
776        if (((connectA != null) && (((TrackSegment) connectA).isMainline()))
777                || ((connectC != null) && (((TrackSegment) connectC).isMainline()))) {
778            return true;
779        } else {
780            return false;
781        }
782    }
783
784    public boolean isMainlineBD() {
785        if (((connectB != null) && (((TrackSegment) connectB).isMainline()))
786                || ((connectD != null) && (((TrackSegment) connectD).isMainline()))) {
787            return true;
788        } else {
789            return false;
790        }
791    }
792
793    @Override
794    public boolean isMainline() {
795        return (isMainlineAC() || isMainlineBD());
796    }
797
798    // initialization instance variables (used when loading a LayoutEditor)
799    public String connectAName = "";
800    public String connectBName = "";
801    public String connectCName = "";
802    public String connectDName = "";
803
804    public String tLayoutBlockNameAC = "";
805    public String tLayoutBlockNameBD = "";
806
807    /**
808     * Initialization method The above variables are initialized by
809     * PositionablePointXml, then the following method is called after the
810     * entire LayoutEditor is loaded to set the specific TrackSegment objects.
811     */
812    @Override
813    public void setObjects(LayoutEditor p) {
814        connectA = p.getFinder().findTrackSegmentByName(connectAName);
815        connectB = p.getFinder().findTrackSegmentByName(connectBName);
816        connectC = p.getFinder().findTrackSegmentByName(connectCName);
817        connectD = p.getFinder().findTrackSegmentByName(connectDName);
818
819        LayoutBlock lb;
820        if (!tLayoutBlockNameAC.isEmpty()) {
821            lb = p.provideLayoutBlock(tLayoutBlockNameAC);
822            String userName = lb.getUserName();
823            if (userName != null) {
824                namedLayoutBlockAC = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
825                if (namedLayoutBlockBD != namedLayoutBlockAC) {
826                    lb.incrementUse();
827                }
828            } else {
829                log.error("LevelXing.setObjects(); bad blockname AC ''{}''", tLayoutBlockNameAC);
830                namedLayoutBlockAC = null;
831            }
832            tLayoutBlockNameAC = null; //release this memory
833        }
834
835        if (!tLayoutBlockNameBD.isEmpty()) {
836            lb = p.provideLayoutBlock(tLayoutBlockNameBD);
837            String userName = lb.getUserName();
838            if (userName != null) {
839                namedLayoutBlockBD = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(userName, lb);
840                if (namedLayoutBlockBD != namedLayoutBlockAC) {
841                    lb.incrementUse();
842                }
843            } else {
844                log.error("{}.setObjects(); bad blockname BD ''{}''", this, tLayoutBlockNameBD);
845                namedLayoutBlockBD = null;
846            }
847            tLayoutBlockNameBD = null; //release this memory
848        }
849    }
850
851    /**
852     * {@inheritDoc}
853     */
854    @Override
855    public boolean canRemove() {
856        ArrayList<String> beanReferences = getBeanReferences("All");  // NOI18N
857        if (!beanReferences.isEmpty()) {
858            models.displayRemoveWarning(this, beanReferences, "LevelCrossing");  // NOI18N
859        }
860        return beanReferences.isEmpty();
861    }
862
863    /**
864     * Build a list of sensors, signal heads, and signal masts attached to a
865     * level crossing point.
866     *
867     * @param pointName Specify the point (A-D) or all (All) points.
868     * @return a list of bean reference names.
869     */
870    public ArrayList<String> getBeanReferences(String pointName) {
871        ArrayList<String> references = new ArrayList<>();
872        if (pointName.equals("A") || pointName.equals("All")) {  // NOI18N
873            if (!getSignalAMastName().isEmpty()) {
874                references.add(getSignalAMastName());
875            }
876            if (!getSensorAName().isEmpty()) {
877                references.add(getSensorAName());
878            }
879            if (!getSignalAName().isEmpty()) {
880                references.add(getSignalAName());
881            }
882        }
883        if (pointName.equals("B") || pointName.equals("All")) {  // NOI18N
884            if (!getSignalBMastName().isEmpty()) {
885                references.add(getSignalBMastName());
886            }
887            if (!getSensorBName().isEmpty()) {
888                references.add(getSensorBName());
889            }
890            if (!getSignalBName().isEmpty()) {
891                references.add(getSignalBName());
892            }
893        }
894        if (pointName.equals("C") || pointName.equals("All")) {  // NOI18N
895            if (!getSignalCMastName().isEmpty()) {
896                references.add(getSignalCMastName());
897            }
898            if (!getSensorCName().isEmpty()) {
899                references.add(getSensorCName());
900            }
901            if (!getSignalCName().isEmpty()) {
902                references.add(getSignalCName());
903            }
904        }
905        if (pointName.equals("D") || pointName.equals("All")) {  // NOI18N
906            if (!getSignalDMastName().isEmpty()) {
907                references.add(getSignalDMastName());
908            }
909            if (!getSensorDName().isEmpty()) {
910                references.add(getSensorDName());
911            }
912            if (!getSignalDName().isEmpty()) {
913                references.add(getSignalDName());
914            }
915        }
916        return references;
917    }
918
919
920    public String[] getBlockBoundaries() {
921        final String[] boundaryBetween = new String[4];
922
923        String blockNameAC = getBlockNameAC();
924        String blockNameBD = getBlockNameBD();
925
926        LayoutBlock blockAC = getLayoutBlockAC();
927        LayoutBlock blockBD = getLayoutBlockAC();
928
929        if (!blockNameAC.isEmpty() && (blockAC != null)) {
930            if ((connectA instanceof TrackSegment) && (((TrackSegment) connectA).getLayoutBlock() != blockAC)) {
931                try {
932                    boundaryBetween[0] = (((TrackSegment) connectA).getLayoutBlock().getDisplayName() + " - " + blockAC.getDisplayName());
933                } catch (java.lang.NullPointerException e) {
934                    //Can be considered normal if tracksegement hasn't yet been allocated a block
935                    log.debug("TrackSegement at connection A doesn't contain a layout block");
936                }
937            }
938            if ((connectC instanceof TrackSegment) && (((TrackSegment) connectC).getLayoutBlock() != blockAC)) {
939                try {
940                    boundaryBetween[2] = (((TrackSegment) connectC).getLayoutBlock().getDisplayName() + " - " + blockAC.getDisplayName());
941                } catch (java.lang.NullPointerException e) {
942                    //Can be considered normal if tracksegement hasn't yet been allocated a block
943                    log.debug("TrackSegement at connection C doesn't contain a layout block");
944                }
945            }
946        }
947        if (!blockNameBD.isEmpty() && (blockBD != null)) {
948            if ((connectB instanceof TrackSegment) && (((TrackSegment) connectB).getLayoutBlock() != blockBD)) {
949                try {
950                    boundaryBetween[1] = (((TrackSegment) connectB).getLayoutBlock().getDisplayName() + " - " + blockBD.getDisplayName());
951                } catch (java.lang.NullPointerException e) {
952                    //Can be considered normal if tracksegement hasn't yet been allocated a block
953                    log.debug("TrackSegement at connection B doesn't contain a layout block");
954                }
955            }
956            if ((connectD instanceof TrackSegment) && (((TrackSegment) connectD).getLayoutBlock() != blockBD)) {
957                try {
958                    boundaryBetween[3] = (((TrackSegment) connectD).getLayoutBlock().getDisplayName() + " - " + blockBD.getDisplayName());
959                } catch (java.lang.NullPointerException e) {
960                    //Can be considered normal if tracksegement hasn't yet been allocated a block
961                    log.debug("TrackSegement at connection D doesn't contain a layout block");
962                }
963            }
964        }
965        return boundaryBetween;
966    }
967
968    /**
969     * Remove this object from display and persistance.
970     */
971    public void remove() {
972        // remove from persistance by flagging inactive
973        active = false;
974    }
975
976    boolean active = true;
977
978    /**
979     * Get if active.
980     * "active" means that the object is still displayed, and should be stored.
981     * @return true if still displayed, else false.
982     */
983    public boolean isActive() {
984        return active;
985    }
986
987    ArrayList<SignalMast> sml = new ArrayList<>();
988
989    public void addSignalMastLogic(SignalMast sm) {
990        if (sml.contains(sm)) {
991            return;
992        }
993        if (sml.isEmpty()) {
994            sml.add(sm);
995            return;
996        }
997        SignalMastLogic sl = InstanceManager.getDefault(jmri.SignalMastLogicManager.class).getSignalMastLogic(sm);
998        for (SignalMast signalMast : sml) {
999            SignalMastLogic s = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalMastLogic(signalMast);
1000            if (s != null) {
1001                s.setConflictingLogic(sm, this);
1002            }
1003            sl.setConflictingLogic(signalMast, this);
1004        }
1005        sml.add(sm);
1006    }
1007
1008    public void removeSignalMastLogic(SignalMast sm) {
1009        if (!sml.contains(sm)) {
1010            return;
1011        }
1012        sml.remove(sm);
1013        if (sml.isEmpty()) {
1014            return;
1015        }
1016        for (int i = 0; i < sml.size(); i++) {
1017            SignalMastLogic s = InstanceManager.getDefault(jmri.SignalMastLogicManager.class).getSignalMastLogic(sm);
1018            if (s != null) {
1019                s.removeConflictingLogic(sm, this);
1020            }
1021        }
1022    }
1023
1024    /*
1025    * {@inheritDoc}
1026     */
1027    @Override
1028    public void reCheckBlockBoundary() {
1029        // nothing to see here... move along...
1030    }
1031
1032    /*
1033    * {@inheritDoc}
1034     */
1035    @Override
1036    protected ArrayList<LayoutConnectivity> getLayoutConnectivity() {
1037        // nothing to see here... move along...
1038        return null;
1039    }
1040
1041    /**
1042     * {@inheritDoc}
1043     */
1044    @Override
1045    public List<HitPointType> checkForFreeConnections() {
1046        List<HitPointType> result = new ArrayList<>();
1047
1048        //check the A connection point
1049        if (getConnectA() == null) {
1050            result.add(HitPointType.LEVEL_XING_A);
1051        }
1052
1053        //check the B connection point
1054        if (getConnectB() == null) {
1055            result.add(HitPointType.LEVEL_XING_B);
1056        }
1057
1058        //check the C connection point
1059        if (getConnectC() == null) {
1060            result.add(HitPointType.LEVEL_XING_C);
1061        }
1062
1063        //check the D connection point
1064        if (getConnectD() == null) {
1065            result.add(HitPointType.LEVEL_XING_D);
1066        }
1067        return result;
1068    }
1069
1070    /**
1071     * {@inheritDoc}
1072     */
1073    @Override
1074    public boolean checkForUnAssignedBlocks() {
1075        return ((getLayoutBlockAC() != null) && (getLayoutBlockBD() != null));
1076    }
1077
1078    /**
1079     * {@inheritDoc}
1080     */
1081    @Override
1082    public void checkForNonContiguousBlocks(
1083            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
1084        /*
1085        * For each (non-null) blocks of this track do:
1086        * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
1087        * #2) If this track is already in the TrackNameSet for this block
1088        *     then return (done!)
1089        * #3) else add a new set (with this block/track) to
1090        *     blockNamesToTrackNameSetMap and check all the connections in this
1091        *     block (by calling the 2nd method below)
1092        * <p>
1093        *     Basically, we're maintaining contiguous track sets for each block found
1094        *     (in blockNamesToTrackNameSetMap)
1095         */
1096
1097        // We're only using a map here because it's convient to
1098        // use it to pair up blocks and connections
1099        Map<LayoutTrack, String> blocksAndTracksMap = new HashMap<>();
1100        if ((getLayoutBlockAC() != null) && (connectA != null)) {
1101            blocksAndTracksMap.put(connectA, getLayoutBlockAC().getDisplayName());
1102        }
1103        if ((getLayoutBlockAC() != null) && (connectC != null)) {
1104            blocksAndTracksMap.put(connectC, getLayoutBlockAC().getDisplayName());
1105        }
1106        if ((getLayoutBlockBD() != null) && (connectB != null)) {
1107            blocksAndTracksMap.put(connectB, getLayoutBlockBD().getDisplayName());
1108        }
1109        if ((getLayoutBlockBD() != null) && (connectD != null)) {
1110            blocksAndTracksMap.put(connectD, getLayoutBlockBD().getDisplayName());
1111        }
1112
1113        List<Set<String>> TrackNameSets = null;
1114        Set<String> TrackNameSet = null;
1115        for (Map.Entry<LayoutTrack, String> entry : blocksAndTracksMap.entrySet()) {
1116            LayoutTrack theConnect = entry.getKey();
1117            String theBlockName = entry.getValue();
1118
1119            TrackNameSet = null;    // assume not found (pessimist!)
1120            TrackNameSets = blockNamesToTrackNameSetsMap.get(theBlockName);
1121            if (TrackNameSets != null) { // (#1)
1122                for (Set<String> checkTrackNameSet : TrackNameSets) {
1123                    if (checkTrackNameSet.contains(getName())) { // (#2)
1124                        TrackNameSet = checkTrackNameSet;
1125                        break;
1126                    }
1127                }
1128            } else {    // (#3)
1129                log.debug("*New block ('{}') trackNameSets", theBlockName);
1130                TrackNameSets = new ArrayList<>();
1131                blockNamesToTrackNameSetsMap.put(theBlockName, TrackNameSets);
1132            }
1133            if (TrackNameSet == null) {
1134                TrackNameSet = new LinkedHashSet<>();
1135                TrackNameSets.add(TrackNameSet);
1136            }
1137            if (TrackNameSet.add(getName())) {
1138                log.debug("*    Add track ''{}'' to trackNameSet for block ''{}''", getName(), theBlockName);
1139            }
1140            theConnect.collectContiguousTracksNamesInBlockNamed(theBlockName, TrackNameSet);
1141        }
1142    }   // collectContiguousTracksNamesInBlockNamed
1143
1144    /**
1145     * {@inheritDoc}
1146     */
1147    @Override
1148    public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName,
1149            @Nonnull Set<String> TrackNameSet) {
1150        if (!TrackNameSet.contains(getName())) {
1151            // check all the matching blocks in this track and...
1152            //  #1) add us to TrackNameSet and...
1153            //  #2) flood them
1154            //check the AC blockName
1155            if (getBlockNameAC().equals(blockName)) {
1156                // if we are added to the TrackNameSet
1157                if (TrackNameSet.add(getName())) {
1158                    log.debug("*    Add track ''{}'for block ''{}''", getName(), blockName);
1159                }
1160                // it's time to play... flood your neighbours!
1161                if (connectA != null) {
1162                    connectA.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1163                }
1164                if (connectC != null) {
1165                    connectC.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1166                }
1167            }
1168            //check the BD blockName
1169            if (getBlockNameBD().equals(blockName)) {
1170                // if we are added to the TrackNameSet
1171                if (TrackNameSet.add(getName())) {
1172                    log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
1173                }
1174                // it's time to play... flood your neighbours!
1175                if (connectB != null) {
1176                    connectB.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1177                }
1178                if (connectD != null) {
1179                    connectD.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
1180                }
1181            }
1182        }
1183    }
1184
1185    /**
1186     * {@inheritDoc}
1187     */
1188    @Override
1189    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
1190        setLayoutBlockAC(layoutBlock);
1191        setLayoutBlockBD(layoutBlock);
1192    }
1193
1194    /**
1195     * {@inheritDoc}
1196     */
1197    @Override
1198    public String getTypeName() {
1199        return Bundle.getMessage("TypeName_LevelXing");
1200    }
1201
1202    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LevelXing.class);
1203}