001package jmri.implementation;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.beans.*;
006import java.util.ArrayList;
007import java.util.List;
008
009import javax.annotation.CheckForNull;
010import javax.annotation.Nonnull;
011
012import jmri.*;
013
014import jmri.jmrit.display.EditorManager;
015import jmri.jmrit.display.layoutEditor.ConnectivityUtil; // normally these would be rolloed
016import jmri.jmrit.display.layoutEditor.HitPointType;     // up into jmri.jmrit.display.layoutEditor.*
017import jmri.jmrit.display.layoutEditor.LayoutBlock;      // but during the LE migration it's
018import jmri.jmrit.display.layoutEditor.LayoutBlockManager; // useful to be able to see
019import jmri.jmrit.display.layoutEditor.LayoutEditor;     // what specific classe are used.
020import jmri.jmrit.display.layoutEditor.LayoutSlip;
021import jmri.jmrit.display.layoutEditor.LayoutTurnout;
022import jmri.jmrit.display.layoutEditor.LevelXing;
023import jmri.jmrit.display.layoutEditor.PositionablePoint;
024import jmri.jmrit.display.layoutEditor.TrackNode;
025import jmri.jmrit.display.layoutEditor.TrackSegment;
026
027import jmri.util.NonNullArrayList;
028
029/**
030 * Sections represent a group of one or more connected Blocks that may be
031 * allocated to a train traveling in a given direction.
032 * <p>
033 * A Block may be in multiple Sections. All Blocks contained in a given section
034 * must be unique. Blocks are kept in order--the first block is connected to the
035 * second, the second is connected to the third, etc.
036 * <p>
037 * A Block in a Section must be connected to the Block before it (if there is
038 * one) and to the Block after it (if there is one), but may not be connected to
039 * any other Block in the Section. This restriction is enforced when a Section
040 * is created, and checked when a Section is loaded from disk.
041 * <p>
042 * A Section has a "direction" defined by the sequence in which Blocks are added
043 * to the Section. A train may run through a Section in either the forward
044 * direction (from first block to last block) or reverse direction (from last
045 * block to first block).
046 * <p>
047 * A Section has one or more EntryPoints. Each EntryPoint is a Path of one of
048 * the Blocks in the Section that defines a connection to a Block outside of the
049 * Section. EntryPoints are grouped into two lists: "forwardEntryPoints" - entry
050 * through which will result in a train traveling in the "forward" direction
051 * "reverseEntryPoints" - entry through which will result in a train traveling
052 * in the "reverse" direction Note that "forwardEntryPoints" are also reverse
053 * exit points, and vice versa.
054 * <p>
055 * A Section has one of the following states" FREE - available for allocation by
056 * a dispatcher FORWARD - allocated for travel in the forward direction REVERSE
057 * - allocated for travel in the reverse direction
058 * <p>
059 * A Section has an occupancy. A Section is OCCUPIED if any of its Blocks is
060 * OCCUPIED. A Section is UNOCCUPIED if all of its Blocks are UNOCCUPIED
061 * <p>
062 * A Section of may be allocated to only one train at a time, even if the trains
063 * are travelling in the same direction. If a Section has sufficient space for
064 * multiple trains travelling in the same direction it should be broken up into
065 * multiple Sections so the trains can follow each other through the original
066 * Section.
067 * <p>
068 * A Section may not contain any reverse loops. The track that is reversed in a
069 * reverse loop must be in a separate Section.
070 * <p>
071 * Each Section optionally carries two direction sensors, one for the forward
072 * direction and one for the reverse direction. These sensors force signals for
073 * travel in their respective directions to "RED" when they are active. When the
074 * Section is free, both the sensors are Active. These internal sensors follow
075 * the state of the Section, permitting signals to function normally in the
076 * direction of allocation.
077 * <p>
078 * Each Section optionally carries two stopping sensors, one for the forward
079 * direction and one for the reverse direction. These sensors change to active
080 * when a train traversing the Section triggers its sensing device. Stopping
081 * sensors are physical layout sensors, and may be either point sensors or
082 * occupancy sensors for short blocks at the end of the Section. A stopping
083 * sensor is used during automatic running to stop a train that has reached the
084 * end of its allocated Section. This is needed, for example, to allow a train
085 * to enter a passing siding and clear the track behind it. When not running
086 * automatically, these sensors may be used to light panel lights to notify the
087 * dispatcher that the train has reached the end of the Section.
088 * <p>
089 * This Section implementation provides for delayed initialization of blocks and
090 * direction sensors to be independent of order of items in panel files.
091 *
092 * @author Dave Duchamp Copyright (C) 2008,2010
093 */
094public class DefaultSection extends AbstractNamedBean implements Section {
095
096    private static final NamedBean.DisplayOptions USERSYS = NamedBean.DisplayOptions.USERNAME_SYSTEMNAME;
097
098    public DefaultSection(String systemName, String userName) {
099        super(systemName, userName);
100    }
101
102    public DefaultSection(String systemName) {
103        super(systemName);
104    }
105
106    /**
107     * Persistent instance variables (saved between runs)
108     */
109    private String mForwardBlockingSensorName = "";
110    private String mReverseBlockingSensorName = "";
111    private String mForwardStoppingSensorName = "";
112    private String mReverseStoppingSensorName = "";
113    private final List<Block> mBlockEntries = new NonNullArrayList<>();
114    private final List<EntryPoint> mForwardEntryPoints = new NonNullArrayList<>();
115    private final List<EntryPoint> mReverseEntryPoints = new NonNullArrayList<>();
116
117    /**
118     * Operational instance variables (not saved between runs).
119     */
120    private int mState = FREE;
121    private int mOccupancy = UNOCCUPIED;
122    private boolean mOccupancyInitialized = false;
123    private Block mFirstBlock = null;
124    private Block mLastBlock = null;
125
126    private NamedBeanHandle<Sensor> mForwardBlockingNamedSensor = null;
127    private NamedBeanHandle<Sensor> mReverseBlockingNamedSensor = null;
128    private NamedBeanHandle<Sensor> mForwardStoppingNamedSensor = null;
129    private NamedBeanHandle<Sensor> mReverseStoppingNamedSensor = null;
130
131    private final List<PropertyChangeListener> mBlockListeners = new ArrayList<>();
132    protected NamedBeanHandleManager nbhm = InstanceManager.getDefault(NamedBeanHandleManager.class);
133
134    /**
135     * Get the state of the Section.
136     * UNKNOWN, FORWARD, REVERSE, FREE
137     *
138     * @return the section state
139     */
140    @Override
141    public int getState() {
142        return mState;
143    }
144
145    /**
146     * Set the state of the Section.
147     * FREE, FORWARD or REVERSE.
148     * <br>
149     * UNKNOWN state not accepted here.
150     * @param state the state to set
151     */
152    @Override
153    public void setState(int state) {
154        if ((state == Section.FREE) || (state == Section.FORWARD) || (state == Section.REVERSE)) {
155            int old = mState;
156            mState = state;
157            firePropertyChange(PROPERTY_STATE, old, mState);
158            // update the forward/reverse blocking sensors as needed
159            Sensor fbSensor = getForwardBlockingSensor();
160            Sensor rbSensor = getReverseBlockingSensor();
161
162            switch (state) {
163                case FORWARD:
164                    try {
165                        if ((fbSensor != null) && (fbSensor.getState() != Sensor.INACTIVE)) {
166                            fbSensor.setState(Sensor.INACTIVE);
167                        }
168                        if ((rbSensor != null) && (rbSensor.getState() != Sensor.ACTIVE)) {
169                            rbSensor.setKnownState(Sensor.ACTIVE);
170                        }
171                    } catch (JmriException reason) {
172                        log.error("Exception when setting Sensors for Section {}", getDisplayName(USERSYS));
173                    }
174                    break;
175                case REVERSE:
176                    try {
177                        if ((rbSensor != null) && (rbSensor.getState() != Sensor.INACTIVE)) {
178                            rbSensor.setKnownState(Sensor.INACTIVE);
179                        }
180                        if ((fbSensor != null) && (fbSensor.getState() != Sensor.ACTIVE)) {
181                            fbSensor.setKnownState(Sensor.ACTIVE);
182                        }
183                    } catch (JmriException reason) {
184                        log.error("Exception when setting Sensors for Section {}", getDisplayName(USERSYS));
185                    }
186                    break;
187                case FREE:
188                    try {
189                        if ((fbSensor != null) && (fbSensor.getState() != Sensor.ACTIVE)) {
190                            fbSensor.setKnownState(Sensor.ACTIVE);
191                        }
192                        if ((rbSensor != null) && (rbSensor.getState() != Sensor.ACTIVE)) {
193                            rbSensor.setKnownState(Sensor.ACTIVE);
194                        }
195                    } catch (JmriException reason) {
196                        log.error("Exception when setting Sensors for Section {}", getDisplayName(USERSYS));
197                    }
198                    break;
199                default:
200                    break;
201            }
202        } else {
203            log.error("Attempt to set state of Section {} to illegal value - {}", getDisplayName(USERSYS), state);
204        }
205    }
206
207    /**
208     * Get the occupancy of a Section.
209     *
210     * @return {@link #OCCUPIED}, {@link #UNOCCUPIED}, or the state of the first
211     *         block that is neither occupied or unoccupied
212     */
213    @Override
214    public int getOccupancy() {
215        if (mOccupancyInitialized) {
216            return mOccupancy;
217        }
218        // initialize occupancy
219        mOccupancy = UNOCCUPIED;
220        for (Block block : mBlockEntries) {
221            if (block.getState() == OCCUPIED) {
222                mOccupancy = OCCUPIED;
223            } else if (block.getState() != UNOCCUPIED) {
224                log.warn("Occupancy of block {} is not OCCUPIED or UNOCCUPIED in Section - {}",
225                        block.getDisplayName(USERSYS), getDisplayName(USERSYS));
226                return (block.getState());
227            }
228        }
229        mOccupancyInitialized = true;
230        return mOccupancy;
231    }
232
233    private void setOccupancy(int occupancy) {
234        int old = mOccupancy;
235        mOccupancy = occupancy;
236        firePropertyChange(PROPERTY_OCCUPANCY, old, mOccupancy);
237    }
238
239    @Override
240    public String getForwardBlockingSensorName() {
241        if (mForwardBlockingNamedSensor != null) {
242            return mForwardBlockingNamedSensor.getName();
243        }
244        return mForwardBlockingSensorName;
245    }
246
247    @Override
248    public Sensor getForwardBlockingSensor() {
249        if (mForwardBlockingNamedSensor != null) {
250            return mForwardBlockingNamedSensor.getBean();
251        }
252        if ((mForwardBlockingSensorName != null)
253                && (!mForwardBlockingSensorName.isEmpty())) {
254            Sensor s = InstanceManager.sensorManagerInstance().
255                    getSensor(mForwardBlockingSensorName);
256            if (s == null) {
257                log.error("Missing FB Sensor - {} - when initializing Section - {}",
258                        mForwardBlockingSensorName, getDisplayName(USERSYS));
259                return null;
260            }
261            mForwardBlockingNamedSensor = nbhm.getNamedBeanHandle(mForwardBlockingSensorName, s);
262            return s;
263        }
264        return null;
265    }
266
267    @Override
268    public Sensor setForwardBlockingSensorName(String forwardSensor) {
269        if ((forwardSensor == null) || (forwardSensor.length() <= 0)) {
270            mForwardBlockingSensorName = "";
271            mForwardBlockingNamedSensor = null;
272            return null;
273        }
274        tempSensorName = forwardSensor;
275        Sensor s = validateSensor();
276        if (s == null) {
277            // sensor name not correct or not in sensor table
278            log.error("Sensor name - {} invalid when setting forward sensor in Section {}",
279                    forwardSensor, getDisplayName(USERSYS));
280            return null;
281        }
282        mForwardBlockingNamedSensor = nbhm.getNamedBeanHandle(tempSensorName, s);
283        mForwardBlockingSensorName = tempSensorName;
284        return s;
285    }
286
287    @Override
288    public void delayedSetForwardBlockingSensorName(String forwardSensor) {
289        mForwardBlockingSensorName = forwardSensor;
290    }
291
292    @Override
293    public String getReverseBlockingSensorName() {
294        if (mReverseBlockingNamedSensor != null) {
295            return mReverseBlockingNamedSensor.getName();
296        }
297        return mReverseBlockingSensorName;
298    }
299
300    @Override
301    public Sensor setReverseBlockingSensorName(String reverseSensor) {
302        if ((reverseSensor == null) || (reverseSensor.length() <= 0)) {
303            mReverseBlockingNamedSensor = null;
304            mReverseBlockingSensorName = "";
305            return null;
306        }
307        tempSensorName = reverseSensor;
308        Sensor s = validateSensor();
309        if (s == null) {
310            // sensor name not correct or not in sensor table
311            log.error("Sensor name - {} invalid when setting reverse sensor in Section {}",
312                    reverseSensor, getDisplayName(USERSYS));
313            return null;
314        }
315        mReverseBlockingNamedSensor = nbhm.getNamedBeanHandle(tempSensorName, s);
316        mReverseBlockingSensorName = tempSensorName;
317        return s;
318    }
319
320    @Override
321    public void delayedSetReverseBlockingSensorName(String reverseSensor) {
322        mReverseBlockingSensorName = reverseSensor;
323    }
324
325    @Override
326    public Sensor getReverseBlockingSensor() {
327        if (mReverseBlockingNamedSensor != null) {
328            return mReverseBlockingNamedSensor.getBean();
329        }
330        if ((mReverseBlockingSensorName != null)
331                && (!mReverseBlockingSensorName.isEmpty())) {
332            Sensor s = InstanceManager.sensorManagerInstance().
333                    getSensor(mReverseBlockingSensorName);
334            if (s == null) {
335                log.error("Missing Sensor - {} - when initializing Section - {}",
336                        mReverseBlockingSensorName, getDisplayName(USERSYS));
337                return null;
338            }
339            mReverseBlockingNamedSensor = nbhm.getNamedBeanHandle(mReverseBlockingSensorName, s);
340            return s;
341        }
342        return null;
343    }
344
345    @Override
346    public Block getLastBlock() {
347        return mLastBlock;
348    }
349
350    private String tempSensorName = "";
351
352    @CheckForNull
353    private Sensor validateSensor() {
354        // check if anything entered
355        if (tempSensorName.length() < 1) {
356            // no sensor specified
357            return null;
358        }
359        // get the sensor corresponding to this name
360        Sensor s = InstanceManager.sensorManagerInstance().getSensor(tempSensorName);
361        if (s == null) {
362            return null;
363        }
364        if (!tempSensorName.equals(s.getUserName()) && s.getUserName() != null) {
365            tempSensorName = s.getUserName();
366        }
367        return s;
368    }
369
370    @Override
371    public String getForwardStoppingSensorName() {
372        if (mForwardStoppingNamedSensor != null) {
373            return mForwardStoppingNamedSensor.getName();
374        }
375        return mForwardStoppingSensorName;
376    }
377
378    @Override
379    @CheckForNull
380    public Sensor getForwardStoppingSensor() {
381        if (mForwardStoppingNamedSensor != null) {
382            return mForwardStoppingNamedSensor.getBean();
383        }
384        if ((mForwardStoppingSensorName != null)
385                && (!mForwardStoppingSensorName.isEmpty())) {
386            Sensor s = InstanceManager.sensorManagerInstance().
387                    getSensor(mForwardStoppingSensorName);
388            if (s == null) {
389                log.error("Missing Sensor - {} - when initializing Section - {}",
390                        mForwardStoppingSensorName, getDisplayName(USERSYS));
391                return null;
392            }
393            mForwardStoppingNamedSensor = nbhm.getNamedBeanHandle(mForwardStoppingSensorName, s);
394            return s;
395        }
396        return null;
397    }
398
399    @Override
400    public Sensor setForwardStoppingSensorName(String forwardSensor) {
401        if ((forwardSensor == null) || (forwardSensor.length() <= 0)) {
402            mForwardStoppingNamedSensor = null;
403            mForwardStoppingSensorName = "";
404            return null;
405        }
406        tempSensorName = forwardSensor;
407        Sensor s = validateSensor();
408        if (s == null) {
409            // sensor name not correct or not in sensor table
410            log.error("Sensor name - {} invalid when setting forward sensor in Section {}",
411                    forwardSensor, getDisplayName(USERSYS));
412            return null;
413        }
414        mForwardStoppingNamedSensor = nbhm.getNamedBeanHandle(tempSensorName, s);
415        mForwardStoppingSensorName = tempSensorName;
416        return s;
417    }
418
419    @Override
420    public void delayedSetForwardStoppingSensorName(String forwardSensor) {
421        mForwardStoppingSensorName = forwardSensor;
422    }
423
424    @Override
425    public String getReverseStoppingSensorName() {
426        if (mReverseStoppingNamedSensor != null) {
427            return mReverseStoppingNamedSensor.getName();
428        }
429        return mReverseStoppingSensorName;
430    }
431
432    @Override
433    @CheckForNull
434    public Sensor setReverseStoppingSensorName(String reverseSensor) {
435        if ((reverseSensor == null) || (reverseSensor.length() <= 0)) {
436            mReverseStoppingNamedSensor = null;
437            mReverseStoppingSensorName = "";
438            return null;
439        }
440        tempSensorName = reverseSensor;
441        Sensor s = validateSensor();
442        if (s == null) {
443            // sensor name not correct or not in sensor table
444            log.error("Sensor name - {} invalid when setting reverse sensor in Section {}",
445                    reverseSensor, getDisplayName(USERSYS));
446            return null;
447        }
448        mReverseStoppingNamedSensor = nbhm.getNamedBeanHandle(tempSensorName, s);
449        mReverseStoppingSensorName = tempSensorName;
450        return s;
451    }
452
453    @Override
454    public void delayedSetReverseStoppingSensorName(String reverseSensor) {
455        mReverseStoppingSensorName = reverseSensor;
456    }
457
458    @Override
459    @CheckForNull
460    public Sensor getReverseStoppingSensor() {
461        if (mReverseStoppingNamedSensor != null) {
462            return mReverseStoppingNamedSensor.getBean();
463        }
464        if ((mReverseStoppingSensorName != null)
465                && (!mReverseStoppingSensorName.isEmpty())) {
466            Sensor s = InstanceManager.sensorManagerInstance().
467                    getSensor(mReverseStoppingSensorName);
468            if (s == null) {
469                log.error("Missing Sensor - {}  - when initializing Section - {}",
470                        mReverseStoppingSensorName, getDisplayName(USERSYS));
471                return null;
472            }
473            mReverseStoppingNamedSensor = nbhm.getNamedBeanHandle(mReverseStoppingSensorName, s);
474            return s;
475        }
476        return null;
477    }
478
479    /**
480     * Add a Block to the Section. Block and sequence number must be unique
481     * within the Section. Block sequence numbers are set automatically as
482     * blocks are added.
483     *
484     * @param b the block to add
485     * @return true if Block was added or false if Block does not connect to the
486     *         current Block, or the Block is not unique.
487     */
488    @Override
489    public boolean addBlock(Block b) {
490        // validate that this entry is unique, if not first.
491        if (mBlockEntries.isEmpty()) {
492            mFirstBlock = b;
493        } else {
494            // check that block is unique
495            for (Block block : mBlockEntries) {
496                if (block == b) {
497                    return false; // already present
498                }            // Note: connectivity to current block is assumed to have been checked
499            }
500        }
501
502        // a lot of this code searches for blocks by their user name.
503        // warn if there isn't one.
504        if (b.getUserName() == null) {
505            log.warn("Block {} does not have a user name, may not work correctly in Section {}",
506                    b.getDisplayName(USERSYS), getDisplayName(USERSYS));
507        }
508        // add Block to the Block list
509        mBlockEntries.add(b);
510        mLastBlock = b;
511        // check occupancy
512        if (b.getState() == OCCUPIED) {
513            if (mOccupancy != OCCUPIED) {
514                setOccupancy(OCCUPIED);
515            }
516        }
517        PropertyChangeListener listener = e -> handleBlockChange();
518
519        b.addPropertyChangeListener(listener);
520        mBlockListeners.add(listener);
521        return true;
522    }
523    private boolean initializationNeeded = false;
524    private final List<String> blockNameList = new ArrayList<>();
525
526    @Override
527    public void delayedAddBlock(String blockName) {
528        initializationNeeded = true;
529        blockNameList.add(blockName);
530    }
531
532    private void initializeBlocks() {
533        for (int i = 0; i < blockNameList.size(); i++) {
534            Block b = InstanceManager.getDefault(BlockManager.class).getBlock(blockNameList.get(i));
535            if (b == null) {
536                log.error("Missing Block - {} - when initializing Section - {}",
537                        blockNameList.get(i), getDisplayName(USERSYS));
538            } else {
539                if (mBlockEntries.isEmpty()) {
540                    mFirstBlock = b;
541                }
542                mBlockEntries.add(b);
543                mLastBlock = b;
544                PropertyChangeListener listener = e ->  handleBlockChange();
545                b.addPropertyChangeListener(listener);
546                mBlockListeners.add(listener);
547            }
548        }
549        initializationNeeded = false;
550    }
551
552    /**
553     * Handle change in occupancy of a Block in the Section.
554     */
555    private void handleBlockChange() {
556        int o = UNOCCUPIED;
557        for (Block block : mBlockEntries) {
558            if (block.getState() == OCCUPIED) {
559                o = OCCUPIED;
560                break;
561            }
562        }
563        if (mOccupancy != o) {
564            setOccupancy(o);
565        }
566    }
567
568    /**
569     * Get a list of blocks in this section
570     *
571     * @return a list of blocks
572     */
573    @Override
574    @Nonnull
575    public List<Block> getBlockList() {
576        if (initializationNeeded) {
577            initializeBlocks();
578        }
579        return new ArrayList<>(mBlockEntries);
580    }
581
582    /**
583     * Gets the number of Blocks in this Section
584     *
585     * @return the number of blocks
586     */
587    @Override
588    public int getNumBlocks() {
589        if (initializationNeeded) {
590            initializeBlocks();
591        }
592        return mBlockEntries.size();
593    }
594
595    /**
596     * Get the scale length of Section. Length of the Section is calculated by
597     * summing the lengths of all Blocks in the section. If all Block lengths
598     * have not been entered, length will not be correct.
599     *
600     * @param meters true to return length in meters, false to use feet
601     * @param scale  the scale; one of {@link jmri.Scale}
602     * @return the scale length
603     */
604    @Override
605    public float getLengthF(boolean meters, Scale scale) {
606        if (initializationNeeded) {
607            initializeBlocks();
608        }
609        float length = 0.0f;
610        for (Block block : mBlockEntries) {
611            length = length + block.getLengthMm();
612        }
613        length = length / (float) (scale.getScaleFactor());
614        if (meters) {
615            return (length * 0.001f);
616        }
617        return (length * 0.00328084f);
618    }
619
620    @Override
621    public int getLengthI(boolean meters, Scale scale) {
622        return ((int) ((getLengthF(meters, scale) + 0.5f)));
623    }
624
625    /**
626     * Gets the actual length of the Section without any scaling
627     *
628     * @return the real length in millimeters
629     */
630    @Override
631    public int getActualLength() {
632        if (initializationNeeded) {
633            initializeBlocks();
634        }
635        int len = 0;
636        for (Block b : mBlockEntries) {
637            len = len + ((int) b.getLengthMm());
638        }
639        return len;
640    }
641
642    /**
643     * Get Block by its Sequence number in the Section.
644     *
645     * @param seqNumber the sequence number
646     * @return the block or null if the sequence number is invalid
647     */
648    @Override
649    @CheckForNull
650    public Block getBlockBySequenceNumber(int seqNumber) {
651        if (initializationNeeded) {
652            initializeBlocks();
653        }
654        if ((seqNumber < mBlockEntries.size()) && (seqNumber >= 0)) {
655            return mBlockEntries.get(seqNumber);
656        }
657        return null;
658    }
659
660    /**
661     * Get the sequence number of a Block.
662     *
663     * @param b the block to get the sequence of
664     * @return the sequence number of b or -1 if b is not in the Section
665     */
666    @Override
667    public int getBlockSequenceNumber(Block b) {
668        for (int i = 0; i < mBlockEntries.size(); i++) {
669            if (b == mBlockEntries.get(i)) {
670                return i;
671            }
672        }
673        return -1;
674    }
675
676    /**
677     * Remove all Blocks, Block Listeners, and Entry Points
678     */
679    @Override
680    public void removeAllBlocksFromSection() {
681        for (int i = mBlockEntries.size(); i > 0; i--) {
682            Block b = mBlockEntries.get(i - 1);
683            b.removePropertyChangeListener(mBlockListeners.get(i - 1));
684            mBlockListeners.remove(i - 1);
685            mBlockEntries.remove(i - 1);
686        }
687        for (int i = mForwardEntryPoints.size(); i > 0; i--) {
688            mForwardEntryPoints.remove(i - 1);
689        }
690        for (int i = mReverseEntryPoints.size(); i > 0; i--) {
691            mReverseEntryPoints.remove(i - 1);
692        }
693        initializationNeeded = false;
694    }
695    /**
696     * Gets Blocks in order. If state is FREE or FORWARD, returns Blocks in
697     * forward order. If state is REVERSE, returns Blocks in reverse order.
698     * First call getEntryBlock, then call getNextBlock until null is returned.
699     */
700    private int blockIndex = 0;  // index of last block returned
701
702    @Override
703    @CheckForNull
704    public Block getEntryBlock() {
705        if (initializationNeeded) {
706            initializeBlocks();
707        }
708        if (mBlockEntries.size() <= 0) {
709            return null;
710        }
711        if (mState == REVERSE) {
712            blockIndex = mBlockEntries.size();
713        } else {
714            blockIndex = 1;
715        }
716        return mBlockEntries.get(blockIndex - 1);
717    }
718
719    @Override
720    @CheckForNull
721    public Block getNextBlock() {
722        if (initializationNeeded) {
723            initializeBlocks();
724        }
725        if (mState == REVERSE) {
726            blockIndex--;
727        } else {
728            blockIndex++;
729        }
730        if ((blockIndex > mBlockEntries.size()) || (blockIndex <= 0)) {
731            return null;
732        }
733        return mBlockEntries.get(blockIndex - 1);
734    }
735
736    @Override
737    @CheckForNull
738    public Block getExitBlock() {
739        if (initializationNeeded) {
740            initializeBlocks();
741        }
742        if (mBlockEntries.size() <= 0) {
743            return null;
744        }
745        if (mState == REVERSE) {
746            blockIndex = 1;
747        } else {
748            blockIndex = mBlockEntries.size();
749        }
750        return mBlockEntries.get(blockIndex - 1);
751    }
752
753    @Override
754    public boolean containsBlock(Block b) {
755        for (Block block : mBlockEntries) {
756            if (b == block) {
757                return true;
758            }
759        }
760        return false;
761    }
762
763    @Override
764    public boolean connectsToBlock(Block b) {
765        if (mForwardEntryPoints.stream().anyMatch((ep) -> (ep.getFromBlock() == b))) {
766            return true;
767        }
768        return mReverseEntryPoints.stream().anyMatch((ep) -> (ep.getFromBlock() == b));
769    }
770
771    @Override
772    public String getBeginBlockName() {
773        if (initializationNeeded) {
774            initializeBlocks();
775        }
776        if (mFirstBlock == null) {
777            return "unknown";
778        }
779        return mFirstBlock.getDisplayName();
780    }
781
782    @Override
783    public String getEndBlockName() {
784        if (initializationNeeded) {
785            initializeBlocks();
786        }
787        if (mLastBlock == null) {
788            return "unknown";
789        }
790        return mLastBlock.getDisplayName();
791    }
792
793    @Override
794    public void addToForwardList(EntryPoint ep) {
795        if (ep != null) {
796            mForwardEntryPoints.add(ep);
797        }
798    }
799
800    @Override
801    public void addToReverseList(EntryPoint ep) {
802        if (ep != null) {
803            mReverseEntryPoints.add(ep);
804        }
805    }
806
807    @Override
808    public void removeEntryPoint(EntryPoint ep) {
809        for (int i = mForwardEntryPoints.size(); i > 0; i--) {
810            if (mForwardEntryPoints.get(i - 1) == ep) {
811                mForwardEntryPoints.remove(i - 1);
812            }
813        }
814        for (int i = mReverseEntryPoints.size(); i > 0; i--) {
815            if (mReverseEntryPoints.get(i - 1) == ep) {
816                mReverseEntryPoints.remove(i - 1);
817            }
818        }
819    }
820
821    @Nonnull
822    @Override
823    public List<EntryPoint> getForwardEntryPointList() {
824        return new ArrayList<>(this.mForwardEntryPoints);
825    }
826
827    @Nonnull
828    @Override
829    public List<EntryPoint> getReverseEntryPointList() {
830        return new ArrayList<>(this.mReverseEntryPoints);
831    }
832
833    @Nonnull
834    @Override
835    public List<EntryPoint> getEntryPointList() {
836        List<EntryPoint> list = new ArrayList<>(this.mForwardEntryPoints);
837        list.addAll(this.mReverseEntryPoints);
838        return list;
839    }
840
841    @Override
842    public boolean isForwardEntryPoint(EntryPoint ep) {
843        for (int i = 0; i < mForwardEntryPoints.size(); i++) {
844            if (ep == mForwardEntryPoints.get(i)) {
845                return true;
846            }
847        }
848        return false;
849    }
850
851    @Override
852    public boolean isReverseEntryPoint(EntryPoint ep) {
853        for (int i = 0; i < mReverseEntryPoints.size(); i++) {
854            if (ep == mReverseEntryPoints.get(i)) {
855                return true;
856            }
857        }
858        return false;
859    }
860
861    /**
862     * Get the EntryPoint for entry from the specified Section for travel in
863     * specified direction.
864     *
865     * @param s   the section
866     * @param dir the direction of travel; one of {@link #FORWARD} or
867     *            {@link #REVERSE}
868     * @return the entry point or null if not found
869     */
870    @Override
871    @CheckForNull
872    public EntryPoint getEntryPointFromSection(Section s, int dir) {
873        if (dir == FORWARD) {
874            for (EntryPoint ep : mForwardEntryPoints) {
875                if (s.containsBlock(ep.getFromBlock())) {
876                    return ep;
877                }
878            }
879        } else if (dir == REVERSE) {
880            for (EntryPoint ep : mReverseEntryPoints) {
881                if (s.containsBlock(ep.getFromBlock())) {
882                    return ep;
883                }
884            }
885        }
886        return null;
887    }
888
889    /**
890     * Get the EntryPoint for exit to specified Section for travel in the
891     * specified direction.
892     *
893     * @param s   the section
894     * @param dir the direction of travel; one of {@link #FORWARD} or
895     *            {@link #REVERSE}
896     * @return the entry point or null if not found
897     */
898    @Override
899    @CheckForNull
900    public EntryPoint getExitPointToSection(Section s, int dir) {
901        if (s == null) {
902            return null;
903        }
904        if (dir == REVERSE) {
905            for (EntryPoint ep : mForwardEntryPoints) {
906                if (s.containsBlock(ep.getFromBlock())) {
907                    return ep;
908                }
909            }
910        } else if (dir == FORWARD) {
911            for (EntryPoint ep : mReverseEntryPoints) {
912                if (s.containsBlock(ep.getFromBlock())) {
913                    return ep;
914                }
915            }
916        }
917        return null;
918    }
919
920    /**
921     * Get the EntryPoint for entry from the specified Block for travel in the
922     * specified direction.
923     *
924     * @param b   the block
925     * @param dir the direction of travel; one of {@link #FORWARD} or
926     *            {@link #REVERSE}
927     * @return the entry point or null if not found
928     */
929    @Override
930    @CheckForNull
931    public EntryPoint getEntryPointFromBlock(Block b, int dir) {
932        if (dir == FORWARD) {
933            for (EntryPoint ep : mForwardEntryPoints) {
934                if (b == ep.getFromBlock()) {
935                    return ep;
936                }
937            }
938        } else if (dir == REVERSE) {
939            for (EntryPoint ep : mReverseEntryPoints) {
940                if (b == ep.getFromBlock()) {
941                    return ep;
942                }
943            }
944        }
945        return null;
946    }
947
948    /**
949     * Get the EntryPoint for exit to the specified Block for travel in the
950     * specified direction.
951     *
952     * @param b   the block
953     * @param dir the direction of travel; one of {@link #FORWARD} or
954     *            {@link #REVERSE}
955     * @return the entry point or null if not found
956     */
957    @Override
958    @CheckForNull
959    public EntryPoint getExitPointToBlock(Block b, int dir) {
960        if (dir == REVERSE) {
961            for (EntryPoint ep : mForwardEntryPoints) {
962                if (b == ep.getFromBlock()) {
963                    return ep;
964                }
965            }
966        } else if (dir == FORWARD) {
967            for (EntryPoint ep : mReverseEntryPoints) {
968                if (b == ep.getFromBlock()) {
969                    return ep;
970                }
971            }
972        }
973        return null;
974    }
975
976    /**
977     * Returns EntryPoint.FORWARD if proceeding from the throat to the other end
978     * is movement in the forward direction. Returns EntryPoint.REVERSE if
979     * proceeding from the throat to the other end is movement in the reverse
980     * direction. Returns EntryPoint.UNKNOWN if cannot determine direction. This
981     * should only happen if blocks are not set up correctly--if all connections
982     * go to the same Block, or not all Blocks set. An error message is logged
983     * if EntryPoint.UNKNOWN is returned.
984     */
985    private int getDirectionStandardTurnout(LayoutTurnout t, ConnectivityUtil cUtil) {
986        LayoutBlock aBlock = ((TrackSegment) t.getConnectA()).getLayoutBlock();
987        LayoutBlock bBlock = ((TrackSegment) t.getConnectB()).getLayoutBlock();
988        LayoutBlock cBlock = ((TrackSegment) t.getConnectC()).getLayoutBlock();
989        if ((aBlock == null) || (bBlock == null) || (cBlock == null)) {
990            log.error("All blocks not assigned for track segments connecting to turnout - {}.",
991                    t.getTurnout().getDisplayName(USERSYS));
992            return EntryPoint.UNKNOWN;
993        }
994        Block exBlock = checkDualDirection(aBlock, bBlock, cBlock);
995        if ((exBlock != null) || ((aBlock == bBlock) && (aBlock == cBlock))) {
996            // using Entry Points directly will lead to a problem, try following track - first from A following B
997            int dir = EntryPoint.UNKNOWN;
998            Block tBlock = null;
999            TrackNode tn = new TrackNode(t, HitPointType.TURNOUT_A, (TrackSegment) t.getConnectA(),
1000                    false, Turnout.CLOSED);
1001            while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1002                tn = cUtil.getNextNode(tn, 0);
1003                tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock);
1004            }
1005            if (tBlock == null) {
1006                // try from A following C
1007                tn = new TrackNode(t, HitPointType.TURNOUT_A, (TrackSegment) t.getConnectA(),
1008                        false, Turnout.THROWN);
1009                while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1010                    tn = cUtil.getNextNode(tn, 0);
1011                    tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock);
1012                }
1013            }
1014            if (tBlock != null) {
1015                String userName = tBlock.getUserName();
1016                if (userName != null) {
1017                    LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1018                    if (lb != null) {
1019                        dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, lb);
1020                    }
1021                }
1022            }
1023            if (dir == EntryPoint.UNKNOWN) {
1024                // try from B following A
1025                tBlock = null;
1026                tn = new TrackNode(t, HitPointType.TURNOUT_B, (TrackSegment) t.getConnectB(),
1027                        false, Turnout.CLOSED);
1028                while ((tBlock == null) && (tn != null && (!tn.reachedEndOfTrack()))) {
1029                    tn = cUtil.getNextNode(tn, 0);
1030                    tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock);
1031                }
1032                if (tBlock != null) {
1033                    String userName = tBlock.getUserName();
1034                    if (userName != null) {
1035                        LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1036                        if (lb != null) {
1037                            dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, lb);
1038                        }
1039                    }
1040                }
1041            }
1042            if (dir == EntryPoint.UNKNOWN) {
1043                log.error("Block definition ambiguity - cannot determine direction of Turnout {} in Section {}",
1044                        t.getTurnout().getDisplayName(USERSYS), getDisplayName(USERSYS));
1045            }
1046            return dir;
1047        }
1048        if ((aBlock != bBlock) && containsBlock(aBlock.getBlock()) && containsBlock(bBlock.getBlock())) {
1049            // both blocks are different, but are in this Section
1050            if (getBlockSequenceNumber(aBlock.getBlock()) < getBlockSequenceNumber(bBlock.getBlock())) {
1051                return EntryPoint.FORWARD;
1052            } else {
1053                return EntryPoint.REVERSE;
1054            }
1055        } else if ((aBlock != cBlock) && containsBlock(aBlock.getBlock()) && containsBlock(cBlock.getBlock())) {
1056            // both blocks are different, but are in this Section
1057            if (getBlockSequenceNumber(aBlock.getBlock()) < getBlockSequenceNumber(cBlock.getBlock())) {
1058                return EntryPoint.FORWARD;
1059            } else {
1060                return EntryPoint.REVERSE;
1061            }
1062        }
1063        LayoutBlock tBlock = t.getLayoutBlock();
1064        if (tBlock == null) {
1065            log.error("Block not assigned for turnout {}", t.getTurnout().getDisplayName(USERSYS));
1066            return EntryPoint.UNKNOWN;
1067        }
1068        if (containsBlock(aBlock.getBlock()) && (!containsBlock(bBlock.getBlock()))) {
1069            // aBlock is in Section, bBlock is not
1070            int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, bBlock);
1071            if (dir != EntryPoint.UNKNOWN) {
1072                return dir;
1073            }
1074            if ((tBlock != bBlock) && (!containsBlock(tBlock.getBlock()))) {
1075                dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, tBlock);
1076                if (dir != EntryPoint.UNKNOWN) {
1077                    return dir;
1078                }
1079            }
1080        }
1081        if (containsBlock(aBlock.getBlock()) && (!containsBlock(cBlock.getBlock()))) {
1082            // aBlock is in Section, cBlock is not
1083            int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, cBlock);
1084            if (dir != EntryPoint.UNKNOWN) {
1085                return dir;
1086            }
1087            if ((tBlock != cBlock) && (!containsBlock(tBlock.getBlock()))) {
1088                dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, tBlock);
1089                if (dir != EntryPoint.UNKNOWN) {
1090                    return dir;
1091                }
1092            }
1093        }
1094        if ((containsBlock(bBlock.getBlock()) || containsBlock(cBlock.getBlock()))
1095                && (!containsBlock(aBlock.getBlock()))) {
1096            // bBlock or cBlock is in Section, aBlock is not
1097            int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, aBlock);
1098            if (dir != EntryPoint.UNKNOWN) {
1099                return dir;
1100            }
1101            if ((tBlock != aBlock) && (!containsBlock(tBlock.getBlock()))) {
1102                dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, tBlock);
1103                if (dir != EntryPoint.UNKNOWN) {
1104                    return dir;
1105                }
1106            }
1107        }
1108        if (!containsBlock(aBlock.getBlock()) && !containsBlock(bBlock.getBlock()) && !containsBlock(cBlock.getBlock()) && containsBlock(tBlock.getBlock())) {
1109            //is the turnout in a section of its own?
1110            int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, aBlock);
1111            return dir;
1112        }
1113
1114        // should never get here
1115        log.error("Unexpected error in getDirectionStandardTurnout when working with turnout {}",
1116                t.getTurnout().getDisplayName(USERSYS));
1117        return EntryPoint.UNKNOWN;
1118    }
1119
1120    /**
1121     * Returns EntryPoint.FORWARD if proceeding from A to B (or D to C) is
1122     * movement in the forward direction. Returns EntryPoint.REVERSE if
1123     * proceeding from A to B (or D to C) is movement in the reverse direction.
1124     * Returns EntryPoint.UNKNOWN if cannot determine direction. This should
1125     * only happen if blocks are not set up correctly--if all connections go to
1126     * the same Block, or not all Blocks set. An error message is logged if
1127     * EntryPoint.UNKNOWN is returned.
1128     */
1129    private int getDirectionXoverTurnout(LayoutTurnout t, ConnectivityUtil cUtil) {
1130        LayoutBlock aBlock = ((TrackSegment) t.getConnectA()).getLayoutBlock();
1131        LayoutBlock bBlock = ((TrackSegment) t.getConnectB()).getLayoutBlock();
1132        LayoutBlock cBlock = ((TrackSegment) t.getConnectC()).getLayoutBlock();
1133        LayoutBlock dBlock = ((TrackSegment) t.getConnectD()).getLayoutBlock();
1134        if ((aBlock == null) || (bBlock == null) || (cBlock == null) || (dBlock == null)) {
1135            log.error("All blocks not assigned for track segments connecting to crossover turnout - {}.",
1136                    t.getTurnout().getDisplayName(USERSYS));
1137            return EntryPoint.UNKNOWN;
1138        }
1139        if ((aBlock == bBlock) && (aBlock == cBlock) && (aBlock == dBlock)) {
1140            log.error("Block setup problem - All track segments connecting to crossover turnout - {} are assigned to the same Block.",
1141                    t.getTurnout().getDisplayName(USERSYS));
1142            return EntryPoint.UNKNOWN;
1143        }
1144        if ((containsBlock(aBlock.getBlock())) || (containsBlock(bBlock.getBlock()))) {
1145            LayoutBlock exBlock = null;
1146            if (aBlock == bBlock) {
1147                if ((t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) && (cBlock == dBlock)) {
1148                    exBlock = cBlock;
1149                }
1150            }
1151            if (exBlock != null) {
1152                // set direction by tracking from a or b
1153                int dir = EntryPoint.UNKNOWN;
1154                Block tBlock = null;
1155                TrackNode tn = new TrackNode(t, HitPointType.TURNOUT_A, (TrackSegment) t.getConnectA(),
1156                        false, Turnout.CLOSED);
1157                while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1158                    tn = cUtil.getNextNode(tn, 0);
1159                    tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1160                }
1161                if (tBlock != null) {
1162                    String userName = tBlock.getUserName();
1163                    if (userName != null) {
1164                        LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1165                        if (lb != null) {
1166                            dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, lb);
1167                        }
1168                    }
1169                } else { // no tBlock found on leg A
1170                    tn = new TrackNode(t, HitPointType.TURNOUT_B, (TrackSegment) t.getConnectB(),
1171                            false, Turnout.CLOSED);
1172                    while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1173                        tn = cUtil.getNextNode(tn, 0);
1174                        tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1175                    }
1176                    if (tBlock != null) {
1177                        String userName = tBlock.getUserName();
1178                        if (userName != null) {
1179                            LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1180                            if (lb != null) {
1181                                dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, lb);
1182                            }
1183                        }
1184                    }
1185                }
1186                if (dir == EntryPoint.UNKNOWN) {
1187                    log.error("Block definition ambiguity - cannot determine direction of crossover Turnout {} in Section {}",
1188                            t.getTurnout().getDisplayName(USERSYS), getDisplayName(USERSYS));
1189                }
1190                return dir;
1191            }
1192            if ((aBlock != bBlock) && containsBlock(aBlock.getBlock()) && containsBlock(bBlock.getBlock())) {
1193                if (getBlockSequenceNumber(aBlock.getBlock()) < getBlockSequenceNumber(bBlock.getBlock())) {
1194                    return EntryPoint.FORWARD;
1195                } else {
1196                    return EntryPoint.REVERSE;
1197                }
1198            }
1199            if (containsBlock(aBlock.getBlock()) && (!containsBlock(bBlock.getBlock()))) {
1200                int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, bBlock);
1201                if (dir != EntryPoint.UNKNOWN) {
1202                    return dir;
1203                }
1204            }
1205            if (containsBlock(bBlock.getBlock()) && (!containsBlock(aBlock.getBlock()))) {
1206                int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, aBlock);
1207                if (dir != EntryPoint.UNKNOWN) {
1208                    return dir;
1209                }
1210            }
1211            if ((t.getTurnoutType() != LayoutTurnout.TurnoutType.LH_XOVER) && containsBlock(aBlock.getBlock())
1212                    && (!containsBlock(cBlock.getBlock()))) {
1213                int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, cBlock);
1214                if (dir != EntryPoint.UNKNOWN) {
1215                    return dir;
1216                }
1217            }
1218            if ((t.getTurnoutType() != LayoutTurnout.TurnoutType.RH_XOVER) && containsBlock(bBlock.getBlock())
1219                    && (!containsBlock(dBlock.getBlock()))) {
1220                int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, dBlock);
1221                if (dir != EntryPoint.UNKNOWN) {
1222                    return dir;
1223                }
1224            }
1225        }
1226        if ((containsBlock(dBlock.getBlock())) || (containsBlock(cBlock.getBlock()))) {
1227            LayoutBlock exBlock = null;
1228            if (dBlock == cBlock) {
1229                if ((t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) && (bBlock == aBlock)) {
1230                    exBlock = aBlock;
1231                }
1232            }
1233            if (exBlock != null) {
1234                // set direction by tracking from c or d
1235                int dir = EntryPoint.UNKNOWN;
1236                Block tBlock = null;
1237                TrackNode tn = new TrackNode(t, HitPointType.TURNOUT_D, (TrackSegment) t.getConnectD(),
1238                        false, Turnout.CLOSED);
1239                while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1240                    tn = cUtil.getNextNode(tn, 0);
1241                    tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1242                }
1243                if (tBlock != null) {
1244                    String userName = tBlock.getUserName();
1245                    if (userName != null) {
1246                        LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1247                        if (lb != null) {
1248                            dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, lb);
1249                        }
1250                    }
1251                } else {
1252                    tn = new TrackNode(t, HitPointType.TURNOUT_C, (TrackSegment) t.getConnectC(),
1253                            false, Turnout.CLOSED);
1254                    while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1255                        tn = cUtil.getNextNode(tn, 0);
1256                        tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1257                    }
1258                    if (tBlock != null) {
1259                        String userName = tBlock.getUserName();
1260                        if (userName != null) {
1261                            LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1262                            if (lb != null) {
1263                                dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, lb);
1264                            }
1265                        }
1266                    }
1267                }
1268                if (dir == EntryPoint.UNKNOWN) {
1269                    log.error("Block definition ambiguity - cannot determine direction of crossover Turnout {} in Section {}.",
1270                            t.getTurnout().getDisplayName(USERSYS), getDisplayName(USERSYS));
1271                }
1272                return dir;
1273            }
1274            if ((dBlock != cBlock) && containsBlock(dBlock.getBlock()) && containsBlock(cBlock.getBlock())) {
1275                if (getBlockSequenceNumber(dBlock.getBlock()) < getBlockSequenceNumber(cBlock.getBlock())) {
1276                    return EntryPoint.FORWARD;
1277                } else {
1278                    return EntryPoint.REVERSE;
1279                }
1280            }
1281            if (containsBlock(dBlock.getBlock()) && (!containsBlock(cBlock.getBlock()))) {
1282                int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, cBlock);
1283                if (dir != EntryPoint.UNKNOWN) {
1284                    return dir;
1285                }
1286            }
1287            if (containsBlock(cBlock.getBlock()) && (!containsBlock(dBlock.getBlock()))) {
1288                int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, dBlock);
1289                if (dir != EntryPoint.UNKNOWN) {
1290                    return dir;
1291                }
1292            }
1293            if ((t.getTurnoutType() != LayoutTurnout.TurnoutType.RH_XOVER) && containsBlock(dBlock.getBlock())
1294                    && (!containsBlock(bBlock.getBlock()))) {
1295                int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, bBlock);
1296                if (dir != EntryPoint.UNKNOWN) {
1297                    return dir;
1298                }
1299            }
1300            if ((t.getTurnoutType() != LayoutTurnout.TurnoutType.LH_XOVER) && containsBlock(cBlock.getBlock())
1301                    && (!containsBlock(aBlock.getBlock()))) {
1302                int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, aBlock);
1303                if (dir != EntryPoint.UNKNOWN) {
1304                    return dir;
1305                }
1306            }
1307        }
1308        log.error("Entry point checks failed - cannot determine direction of crossover Turnout {} in Section {}.",
1309                t.getTurnout().getDisplayName(USERSYS), getDisplayName(USERSYS));
1310        return EntryPoint.UNKNOWN;
1311    }
1312
1313    /**
1314     * Returns EntryPoint.FORWARD if proceeding from A to C or D (or B to D or
1315     * C) is movement in the forward direction. Returns EntryPoint.REVERSE if
1316     * proceeding from C or D to A (or D or C to B) is movement in the reverse
1317     * direction. Returns EntryPoint.UNKNOWN if cannot determine direction. This
1318     * should only happen if blocks are not set up correctly--if all connections
1319     * go to the same Block, or not all Blocks set. An error message is logged
1320     * if EntryPoint.UNKNOWN is returned.
1321     *
1322     * @param t Actually of type LayoutSlip, this is the track segment to check.
1323     */
1324    private int getDirectionSlip(LayoutTurnout t, ConnectivityUtil cUtil) {
1325        LayoutBlock aBlock = ((TrackSegment) t.getConnectA()).getLayoutBlock();
1326        LayoutBlock bBlock = ((TrackSegment) t.getConnectB()).getLayoutBlock();
1327        LayoutBlock cBlock = ((TrackSegment) t.getConnectC()).getLayoutBlock();
1328        LayoutBlock dBlock = ((TrackSegment) t.getConnectD()).getLayoutBlock();
1329        if ((aBlock == null) || (bBlock == null) || (cBlock == null) || (dBlock == null)) {
1330            log.error("All blocks not assigned for track segments connecting to crossover turnout - {}.",
1331                    t.getTurnout().getDisplayName(USERSYS));
1332            return EntryPoint.UNKNOWN;
1333        }
1334        if ((aBlock == bBlock) && (aBlock == cBlock) && (aBlock == dBlock)) {
1335            log.error("Block setup problem - All track segments connecting to crossover turnout - {} are assigned to the same Block.",
1336                    t.getTurnout().getDisplayName(USERSYS));
1337            return EntryPoint.UNKNOWN;
1338        }
1339        if ((containsBlock(aBlock.getBlock())) || (containsBlock(cBlock.getBlock()))) {
1340            LayoutBlock exBlock = null;
1341            if (aBlock == cBlock) {
1342                if ((t.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) && (bBlock == dBlock)) {
1343                    exBlock = bBlock;
1344                }
1345            }
1346            if (exBlock != null) {
1347                // set direction by tracking from a or b
1348                int dir = EntryPoint.UNKNOWN;
1349                Block tBlock = null;
1350                TrackNode tn = new TrackNode(t, HitPointType.SLIP_A, (TrackSegment) t.getConnectA(),
1351                        false, LayoutTurnout.STATE_AC);
1352                while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1353                    tn = cUtil.getNextNode(tn, 0);
1354                    tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1355                }
1356                if (tBlock != null) {
1357                    String userName = tBlock.getUserName();
1358                    if (userName != null) {
1359                        LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1360                        if (lb != null) {
1361                            dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, lb);
1362                        }
1363                    }
1364                } else {
1365                    tn = new TrackNode(t, HitPointType.SLIP_C, (TrackSegment) t.getConnectC(),
1366                            false, LayoutTurnout.STATE_AC);
1367                    while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1368                        tn = cUtil.getNextNode(tn, 0);
1369                        tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1370                    }
1371                    if (tBlock != null) {
1372                        String userName = tBlock.getUserName();
1373                        if (userName != null) {
1374                            LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1375                            if (lb != null) {
1376                                dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, lb);
1377                            }
1378                        }
1379                    }
1380                }
1381                if (dir == EntryPoint.UNKNOWN) {
1382                    log.error("Block definition ambiguity - cannot determine direction of crossover slip {} in Section {}.",
1383                            t.getTurnout().getDisplayName(USERSYS), getDisplayName(USERSYS));
1384                }
1385                return dir;
1386            }
1387            if ((aBlock != cBlock) && containsBlock(aBlock.getBlock()) && containsBlock(cBlock.getBlock())) {
1388                if (getBlockSequenceNumber(aBlock.getBlock()) < getBlockSequenceNumber(cBlock.getBlock())) {
1389                    return EntryPoint.FORWARD;
1390                } else {
1391                    return EntryPoint.REVERSE;
1392                }
1393            }
1394            if (containsBlock(aBlock.getBlock()) && (!containsBlock(cBlock.getBlock()))) {
1395                int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, cBlock);
1396                if (dir != EntryPoint.UNKNOWN) {
1397                    return dir;
1398                }
1399            }
1400            if (containsBlock(cBlock.getBlock()) && (!containsBlock(aBlock.getBlock()))) {
1401                int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, aBlock);
1402                if (dir != EntryPoint.UNKNOWN) {
1403                    return dir;
1404                }
1405            }
1406            int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, dBlock);
1407            if (dir != EntryPoint.UNKNOWN) {
1408                return dir;
1409            }
1410        }
1411
1412        if ((containsBlock(dBlock.getBlock())) || (containsBlock(bBlock.getBlock()))) {
1413            LayoutBlock exBlock = null;
1414            if (dBlock == bBlock) {
1415                if ((t.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) && (cBlock == aBlock)) {
1416                    exBlock = aBlock;
1417                }
1418            }
1419            if (exBlock != null) {
1420                // set direction by tracking from c or d
1421                int dir = EntryPoint.UNKNOWN;
1422                Block tBlock = null;
1423                TrackNode tn = new TrackNode(t, HitPointType.SLIP_D, (TrackSegment) t.getConnectD(),
1424                        false, LayoutTurnout.STATE_BD);
1425                while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1426                    tn = cUtil.getNextNode(tn, 0);
1427                    tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1428                }
1429                if (tBlock != null) {
1430                    String userName = tBlock.getUserName();
1431                    if (userName != null) {
1432                        LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1433                        if (lb != null) {
1434                            dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, lb);
1435                        }
1436                    }
1437                } else {
1438                    tn = new TrackNode(t, HitPointType.TURNOUT_B, (TrackSegment) t.getConnectB(),
1439                            false, LayoutTurnout.STATE_BD);
1440                    while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1441                        tn = cUtil.getNextNode(tn, 0);
1442                        tBlock = cUtil.getExitBlockForTrackNode(tn, exBlock.getBlock());
1443                    }
1444                    if (tBlock != null) {
1445                        String userName = tBlock.getUserName();
1446                        if (userName != null) {
1447                            LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1448                            if (lb != null) {
1449                                dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, lb);
1450                            }
1451                        }
1452                    }
1453                }
1454                if (dir == EntryPoint.UNKNOWN) {
1455                    log.error("Block definition ambiguity - cannot determine direction of slip {} in Section {}.",
1456                            t.getTurnout().getDisplayName(USERSYS), getDisplayName(USERSYS));
1457                }
1458                return dir;
1459            }
1460            if ((dBlock != bBlock) && containsBlock(dBlock.getBlock()) && containsBlock(bBlock.getBlock())) {
1461                if (getBlockSequenceNumber(dBlock.getBlock()) < getBlockSequenceNumber(bBlock.getBlock())) {
1462                    return EntryPoint.FORWARD;
1463                } else {
1464                    return EntryPoint.REVERSE;
1465                }
1466            }
1467            if (containsBlock(dBlock.getBlock()) && (!containsBlock(bBlock.getBlock()))) {
1468                int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, bBlock);
1469                if (dir != EntryPoint.UNKNOWN) {
1470                    return dir;
1471                }
1472            }
1473            if (containsBlock(bBlock.getBlock()) && (!containsBlock(dBlock.getBlock()))) {
1474                int dir = checkLists(mForwardEntryPoints, mReverseEntryPoints, dBlock);
1475                if (dir != EntryPoint.UNKNOWN) {
1476                    return dir;
1477                }
1478            }
1479            if (t.getTurnoutType() == LayoutSlip.TurnoutType.DOUBLE_SLIP) {
1480                int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, aBlock);
1481                if (dir != EntryPoint.UNKNOWN) {
1482                    return dir;
1483                }
1484            }
1485        }
1486        //If all else fails the slip must be in a block of its own so we shall work it out from there.
1487        if (t.getLayoutBlock() != aBlock) {
1488            //Block is not the same as that connected to A
1489            int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, aBlock);
1490            if (dir != EntryPoint.UNKNOWN) {
1491                return dir;
1492            }
1493        }
1494        if (t.getLayoutBlock() != bBlock) {
1495            //Block is not the same as that connected to B
1496            int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, bBlock);
1497            if (dir != EntryPoint.UNKNOWN) {
1498                return dir;
1499            }
1500        }
1501        if (t.getLayoutBlock() != cBlock) {
1502            //Block is not the same as that connected to C
1503            int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, cBlock);
1504            if (dir != EntryPoint.UNKNOWN) {
1505                return dir;
1506            }
1507        }
1508        if (t.getLayoutBlock() != dBlock) {
1509            //Block is not the same as that connected to D
1510            int dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, dBlock);
1511            if (dir != EntryPoint.UNKNOWN) {
1512                return dir;
1513            }
1514        }
1515        return EntryPoint.UNKNOWN;
1516    }
1517
1518    private boolean placeSensorInCrossover(String b1Name, String b2Name, String c1Name, String c2Name,
1519            int direction, ConnectivityUtil cUtil) {
1520        SignalHead b1Head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(b1Name);
1521        SignalHead b2Head = null;
1522        SignalHead c1Head = null;
1523        SignalHead c2Head = null;
1524        boolean success = true;
1525        if ((b2Name != null) && (!b2Name.isEmpty())) {
1526            b2Head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(b2Name);
1527        }
1528        if ((c1Name != null) && (!c1Name.isEmpty())) {
1529            c1Head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(c1Name);
1530        }
1531        if ((c2Name != null) && (!c2Name.isEmpty())) {
1532            c2Head = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(c2Name);
1533        }
1534        if (b2Head != null) {
1535            if (!checkDirectionSensor(b1Head, direction, ConnectivityUtil.OVERALL, cUtil)) {
1536                success = false;
1537            }
1538        } else {
1539            if (!checkDirectionSensor(b1Head, direction, ConnectivityUtil.CONTINUING, cUtil)) {
1540                success = false;
1541            }
1542        }
1543        if (c2Head != null) {
1544            if (!checkDirectionSensor(c2Head, direction, ConnectivityUtil.OVERALL, cUtil)) {
1545                success = false;
1546            }
1547        } else if (c1Head != null) {
1548            if (!checkDirectionSensor(c1Head, direction, ConnectivityUtil.DIVERGING, cUtil)) {
1549                success = false;
1550            }
1551        }
1552        return success;
1553    }
1554
1555    private int checkLists(List<EntryPoint> forwardList, List<EntryPoint> reverseList, LayoutBlock lBlock) {
1556        for (int i = 0; i < forwardList.size(); i++) {
1557            if (forwardList.get(i).getFromBlock() == lBlock.getBlock()) {
1558                return EntryPoint.FORWARD;
1559            }
1560        }
1561        for (int i = 0; i < reverseList.size(); i++) {
1562            if (reverseList.get(i).getFromBlock() == lBlock.getBlock()) {
1563                return EntryPoint.REVERSE;
1564            }
1565        }
1566        return EntryPoint.UNKNOWN;
1567    }
1568
1569    @CheckForNull
1570    private Block checkDualDirection(LayoutBlock aBlock, LayoutBlock bBlock, LayoutBlock cBlock) {
1571        for (int i = 0; i < mForwardEntryPoints.size(); i++) {
1572            Block b = mForwardEntryPoints.get(i).getFromBlock();
1573            for (int j = 0; j < mReverseEntryPoints.size(); j++) {
1574                if (mReverseEntryPoints.get(j).getFromBlock() == b) {
1575                    // possible dual direction
1576                    if (aBlock.getBlock() == b) {
1577                        return b;
1578                    } else if (bBlock.getBlock() == b) {
1579                        return b;
1580                    } else if ((cBlock.getBlock() == b) && (aBlock == bBlock)) {
1581                        return b;
1582                    }
1583                }
1584            }
1585        }
1586        return null;
1587    }
1588
1589    /**
1590     * Returns the direction for proceeding from LayoutBlock b to LayoutBlock a.
1591     * LayoutBlock a must be in the Section. LayoutBlock b may be in this
1592     * Section or may be an Entry Point to the Section.
1593     */
1594    private int getDirectionForBlocks(LayoutBlock a, LayoutBlock b) {
1595        if (containsBlock(b.getBlock())) {
1596            // both blocks are within this Section
1597            if (getBlockSequenceNumber(a.getBlock()) > getBlockSequenceNumber(b.getBlock())) {
1598                return EntryPoint.FORWARD;
1599            } else {
1600                return EntryPoint.REVERSE;
1601            }
1602        }
1603        // bBlock must be an entry point
1604        for (int i = 0; i < mForwardEntryPoints.size(); i++) {
1605            if (mForwardEntryPoints.get(i).getFromBlock() == b.getBlock()) {
1606                return EntryPoint.FORWARD;
1607            }
1608        }
1609        for (int j = 0; j < mReverseEntryPoints.size(); j++) {
1610            if (mReverseEntryPoints.get(j).getFromBlock() == b.getBlock()) {
1611                return EntryPoint.REVERSE;
1612            }
1613        }
1614        // should never get here
1615        log.error("Unexpected error in getDirectionForBlocks when working with LevelCrossing in Section {}",
1616                getDisplayName(USERSYS));
1617        return EntryPoint.UNKNOWN;
1618    }
1619
1620    /**
1621     * @return 'true' if successfully checked direction sensor by follow
1622     *         connectivity from specified track node; 'false' if an error
1623     *         occurred
1624     */
1625    private boolean setDirectionSensorByConnectivity(TrackNode tNode, TrackNode altNode, SignalHead sh,
1626            Block cBlock, ConnectivityUtil cUtil) {
1627        boolean successful = false;
1628        TrackNode tn = tNode;
1629        if ((tn != null) && (sh != null)) {
1630            Block tBlock = null;
1631            LayoutBlock lb;
1632            int dir = EntryPoint.UNKNOWN;
1633            while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1634                tn = cUtil.getNextNode(tn, 0);
1635                tBlock = (tn == null) ? null : cUtil.getExitBlockForTrackNode(tn, null);
1636            }
1637            if (tBlock != null) {
1638                String userName = tBlock.getUserName();
1639                if (userName != null) {
1640                    lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1641                    if (lb != null) {
1642                        dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, lb);
1643                    }
1644                }
1645            } else {
1646                tn = altNode;
1647                while ((tBlock == null) && (tn != null) && (!tn.reachedEndOfTrack())) {
1648                    tn = cUtil.getNextNode(tn, 0);
1649                    tBlock = (tn == null) ? null : cUtil.getExitBlockForTrackNode(tn, null);
1650                }
1651                if (tBlock != null) {
1652                    String userName = tBlock.getUserName();
1653                    if (userName != null) {
1654                        lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
1655                        if (lb != null) {
1656                            dir = checkLists(mReverseEntryPoints, mForwardEntryPoints, lb);
1657                            if (dir == EntryPoint.REVERSE) {
1658                                dir = EntryPoint.FORWARD;
1659                            } else if (dir == EntryPoint.FORWARD) {
1660                                dir = EntryPoint.REVERSE;
1661                            }
1662                        }
1663                    }
1664                }
1665            }
1666            if (dir != EntryPoint.UNKNOWN) {
1667                if (checkDirectionSensor(sh, dir, ConnectivityUtil.OVERALL, cUtil)) {
1668                    successful = true;
1669                }
1670            } else {
1671                log.error("Trouble following track in Block {} in Section {}.",
1672                        cBlock.getDisplayName(USERSYS), getDisplayName(USERSYS));
1673            }
1674        }
1675        return successful;
1676    }
1677
1678    /**
1679     * Place direction sensors in SSL for all Signal Heads in this Section if
1680     * the Sensors are not already present in the SSL.
1681     * <p>
1682     * Only anchor point block boundaries that have assigned signals are
1683     * considered. Only turnouts that have assigned signals are considered. Only
1684     * level crossings that have assigned signals are considered. Turnouts and
1685     * anchor points without signals are counted, and reported in warning
1686     * messages during this procedure, if there are any missing signals.
1687     * <p>
1688     * If this method has trouble, an error message is placed in the log
1689     * describing the trouble.
1690     *
1691     * @return the number or errors placing sensors; 1 is returned if no direction sensor is defined for this section
1692     */
1693    @Override
1694    public int placeDirectionSensors() {
1695        int missingSignalsBB = 0;
1696        int missingSignalsTurnouts = 0;
1697        int missingSignalsLevelXings = 0;
1698        int errorCount = 0;
1699
1700        var editorManager = InstanceManager.getDefault(EditorManager.class);
1701        if (editorManager.getAll(LayoutEditor.class).isEmpty()) {
1702            log.error("No Layout Editor panels on call to 'placeDirectionSensors'");
1703            return 1;
1704        }
1705
1706        if (initializationNeeded) {
1707            initializeBlocks();
1708        }
1709        if ((mForwardBlockingSensorName == null) || (mForwardBlockingSensorName.isEmpty())
1710                || (mReverseBlockingSensorName == null) || (mReverseBlockingSensorName.isEmpty())) {
1711            log.error("Missing direction sensor in Section {}", getDisplayName(USERSYS));
1712            return 1;
1713        }
1714        LayoutBlockManager layoutBlockManager = InstanceManager.getDefault(LayoutBlockManager.class);
1715        SignalHeadManager signalHeadManager = InstanceManager.getDefault(SignalHeadManager.class);
1716        LayoutEditor panel;
1717        ConnectivityUtil cUtil;
1718        LayoutBlock lBlock;
1719        for (Block cBlock : mBlockEntries) {
1720            String userName = cBlock.getUserName();
1721            if (userName == null) {
1722                log.error("No user name for block '{}' in 'placeDirectionSensors'", cBlock);
1723                continue;
1724            }
1725
1726            lBlock = layoutBlockManager.getByUserName(userName);
1727            if (lBlock == null) {
1728                log.error("No layout block for block '{}' in 'placeDirectionSensors'", cBlock.getDisplayName());
1729                continue;
1730            }
1731
1732            // get the panel and cutil for this Block
1733            panel = lBlock.getMaxConnectedPanel();
1734            if (panel == null) {
1735                log.error("Unable to get a panel for '{}' in 'placeDirectionSensors'", cBlock.getDisplayName());
1736                continue;
1737            }
1738            cUtil = new ConnectivityUtil(panel);
1739
1740            List<PositionablePoint> anchorList = cUtil.getAnchorBoundariesThisBlock(cBlock);
1741            for (int j = 0; j < anchorList.size(); j++) {
1742                PositionablePoint p = anchorList.get(j);
1743                if ((!p.getEastBoundSignal().isEmpty()) && (!p.getWestBoundSignal().isEmpty())) {
1744                    // have a signalled block boundary
1745                    SignalHead sh = cUtil.getSignalHeadAtAnchor(p, cBlock, false);
1746                    if (sh == null) {
1747                        log.warn("Unexpected missing signal head at boundary of Block {}", cBlock.getDisplayName(USERSYS));
1748                        errorCount++;
1749                    } else {
1750                        int direction = cUtil.getDirectionFromAnchor(mForwardEntryPoints,
1751                                mReverseEntryPoints, p);
1752                        if (direction == EntryPoint.UNKNOWN) {
1753                            // anchor is at a Block boundary within the Section
1754                            sh = cUtil.getSignalHeadAtAnchor(p, cBlock, true);
1755                            Block otherBlock = ((p.getConnect1()).getLayoutBlock()).getBlock();
1756                            if (otherBlock == cBlock) {
1757                                otherBlock = ((p.getConnect2()).getLayoutBlock()).getBlock();
1758                            }
1759                            if (getBlockSequenceNumber(cBlock) < getBlockSequenceNumber(otherBlock)) {
1760                                direction = EntryPoint.FORWARD;
1761                            } else {
1762                                direction = EntryPoint.REVERSE;
1763                            }
1764                        }
1765                        if (!checkDirectionSensor(sh, direction, ConnectivityUtil.OVERALL, cUtil)) {
1766                            errorCount++;
1767                        }
1768                    }
1769                } else {
1770                    errorCount++;
1771                    missingSignalsBB++;
1772                }
1773            }
1774            List<LevelXing> xingList = cUtil.getLevelCrossingsThisBlock(cBlock);
1775            for (int k = 0; k < xingList.size(); k++) {
1776                LevelXing x = xingList.get(k);
1777                LayoutBlock alBlock = ((TrackSegment) x.getConnectA()).getLayoutBlock();
1778                LayoutBlock blBlock = ((TrackSegment) x.getConnectB()).getLayoutBlock();
1779                LayoutBlock clBlock = ((TrackSegment) x.getConnectC()).getLayoutBlock();
1780                LayoutBlock dlBlock = ((TrackSegment) x.getConnectD()).getLayoutBlock();
1781                if (cUtil.isInternalLevelXingAC(x, cBlock)) {
1782                    // have an internal AC level crossing - is it signaled?
1783                    if (!x.getSignalAName().isEmpty() || (!x.getSignalCName().isEmpty())) {
1784                        // have a signaled AC level crossing internal to this block
1785                        if (!x.getSignalAName().isEmpty()) {
1786                            // there is a signal at A in the level crossing
1787                            TrackNode tn = new TrackNode(x, HitPointType.LEVEL_XING_A,
1788                                    (TrackSegment) x.getConnectA(), false, 0);
1789                            TrackNode altNode = new TrackNode(x, HitPointType.LEVEL_XING_C,
1790                                    (TrackSegment) x.getConnectC(), false, 0);
1791                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalAName());
1792                            if (!setDirectionSensorByConnectivity(tn, altNode, sh, cBlock, cUtil)) {
1793                                errorCount++;
1794                            }
1795                        }
1796                        if (!x.getSignalCName().isEmpty()) {
1797                            // there is a signal at C in the level crossing
1798                            TrackNode tn = new TrackNode(x, HitPointType.LEVEL_XING_C,
1799                                    (TrackSegment) x.getConnectC(), false, 0);
1800                            TrackNode altNode = new TrackNode(x, HitPointType.LEVEL_XING_A,
1801                                    (TrackSegment) x.getConnectA(), false, 0);
1802                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalCName());
1803                            if (!setDirectionSensorByConnectivity(tn, altNode, sh, cBlock, cUtil)) {
1804                                errorCount++;
1805                            }
1806                        }
1807                    }
1808                } else if (alBlock == lBlock) {
1809                    // have a level crossing with AC spanning a block boundary, with A in this Block
1810                    int direction = getDirectionForBlocks(alBlock, clBlock);
1811                    if (direction != EntryPoint.UNKNOWN) {
1812                        if (!x.getSignalCName().isEmpty()) {
1813                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalCName());
1814                            if (!checkDirectionSensor(sh, direction, ConnectivityUtil.OVERALL, cUtil)) {
1815                                errorCount++;
1816                            }
1817                        }
1818                    } else {
1819                        errorCount++;
1820                    }
1821                } else if (clBlock == lBlock) {
1822                    // have a level crossing with AC spanning a block boundary, with C in this Block
1823                    int direction = getDirectionForBlocks(clBlock, alBlock);
1824                    if (direction != EntryPoint.UNKNOWN) {
1825                        if (!x.getSignalAName().isEmpty()) {
1826                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalAName());
1827                            if (!checkDirectionSensor(sh, direction, ConnectivityUtil.OVERALL, cUtil)) {
1828                                errorCount++;
1829                            }
1830                        }
1831                    } else {
1832                        errorCount++;
1833                    }
1834                }
1835                if (cUtil.isInternalLevelXingBD(x, cBlock)) {
1836                    // have an internal BD level crossing - is it signaled?
1837                    if ((!x.getSignalBName().isEmpty()) || (!x.getSignalDName().isEmpty())) {
1838                        // have a signaled BD level crossing internal to this block
1839                        if (!x.getSignalBName().isEmpty()) {
1840                            // there is a signal at B in the level crossing
1841                            TrackNode tn = new TrackNode(x, HitPointType.LEVEL_XING_B,
1842                                    (TrackSegment) x.getConnectB(), false, 0);
1843                            TrackNode altNode = new TrackNode(x, HitPointType.LEVEL_XING_D,
1844                                    (TrackSegment) x.getConnectD(), false, 0);
1845                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalBName());
1846                            if (!setDirectionSensorByConnectivity(tn, altNode, sh, cBlock, cUtil)) {
1847                                errorCount++;
1848                            }
1849                        }
1850                        if (!x.getSignalDName().isEmpty()) {
1851                            // there is a signal at C in the level crossing
1852                            TrackNode tn = new TrackNode(x, HitPointType.LEVEL_XING_D,
1853                                    (TrackSegment) x.getConnectD(), false, 0);
1854                            TrackNode altNode = new TrackNode(x, HitPointType.LEVEL_XING_B,
1855                                    (TrackSegment) x.getConnectB(), false, 0);
1856                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalDName());
1857                            if (!setDirectionSensorByConnectivity(tn, altNode, sh, cBlock, cUtil)) {
1858                                errorCount++;
1859                            }
1860                        }
1861                    }
1862                } else if (blBlock == lBlock) {
1863                    // have a level crossing with BD spanning a block boundary, with B in this Block
1864                    int direction = getDirectionForBlocks(blBlock, dlBlock);
1865                    if (direction != EntryPoint.UNKNOWN) {
1866                        if (!x.getSignalDName().isEmpty()) {
1867                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalDName());
1868                            if (!checkDirectionSensor(sh, direction, ConnectivityUtil.OVERALL, cUtil)) {
1869                                errorCount++;
1870                            }
1871                        }
1872                    } else {
1873                        errorCount++;
1874                    }
1875                } else if (dlBlock == lBlock) {
1876                    // have a level crossing with BD spanning a block boundary, with D in this Block
1877                    int direction = getDirectionForBlocks(dlBlock, blBlock);
1878                    if (direction != EntryPoint.UNKNOWN) {
1879                        if (!x.getSignalBName().isEmpty()) {
1880                            SignalHead sh = signalHeadManager.getSignalHead(x.getSignalBName());
1881                            if (!checkDirectionSensor(sh, direction, ConnectivityUtil.OVERALL, cUtil)) {
1882                                errorCount++;
1883                            }
1884                        }
1885                    } else {
1886                        errorCount++;
1887                    }
1888                }
1889            }
1890            List<LayoutTurnout> turnoutList = cUtil.getLayoutTurnoutsThisBlock(cBlock);
1891            for (int m = 0; m < turnoutList.size(); m++) {
1892                LayoutTurnout t = turnoutList.get(m);
1893                if (cUtil.layoutTurnoutHasRequiredSignals(t)) {
1894                    // have a signalled turnout
1895                    if ((t.getLinkType() == LayoutTurnout.LinkType.NO_LINK)
1896                            && ((t.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT)
1897                            || (t.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT)
1898                            || (t.getTurnoutType() == LayoutTurnout.TurnoutType.WYE_TURNOUT))) {
1899                        // standard turnout - nothing special
1900                        // Note: direction is for proceeding from the throat to either other track
1901                        int direction = getDirectionStandardTurnout(t, cUtil);
1902                        int altDirection = EntryPoint.FORWARD;
1903                        if (direction == EntryPoint.FORWARD) {
1904                            altDirection = EntryPoint.REVERSE;
1905                        }
1906                        if (direction == EntryPoint.UNKNOWN) {
1907                            errorCount++;
1908                        } else {
1909                            SignalHead aHead = signalHeadManager.getSignalHead(t.getSignalA1Name());
1910                            SignalHead a2Head = null;
1911                            String a2Name = t.getSignalA2Name(); // returns "" for empty name, never null
1912                            if (!a2Name.isEmpty()) {
1913                                a2Head = signalHeadManager.getSignalHead(a2Name);
1914                            }
1915                            SignalHead bHead = signalHeadManager.getSignalHead(t.getSignalB1Name());
1916                            SignalHead cHead = signalHeadManager.getSignalHead(t.getSignalC1Name());
1917                            if (t.getLayoutBlock().getBlock() == cBlock) {
1918                                // turnout is in this block, set direction sensors on all signal heads
1919                                // Note: need allocation to traverse this turnout
1920                                if (!checkDirectionSensor(aHead, direction,
1921                                        ConnectivityUtil.OVERALL, cUtil)) {
1922                                    errorCount++;
1923                                }
1924                                if (a2Head != null) {
1925                                    if (!checkDirectionSensor(a2Head, direction,
1926                                            ConnectivityUtil.OVERALL, cUtil)) {
1927                                        errorCount++;
1928                                    }
1929                                }
1930                                if (!checkDirectionSensor(bHead, altDirection,
1931                                        ConnectivityUtil.OVERALL, cUtil)) {
1932                                    errorCount++;
1933                                }
1934                                if (!checkDirectionSensor(cHead, altDirection,
1935                                        ConnectivityUtil.OVERALL, cUtil)) {
1936                                    errorCount++;
1937                                }
1938                            } else {
1939                                if (((TrackSegment) t.getConnectA()).getLayoutBlock().getBlock() == cBlock) {
1940                                    // throat Track Segment is in this Block
1941                                    if (!checkDirectionSensor(bHead, altDirection,
1942                                            ConnectivityUtil.OVERALL, cUtil)) {
1943                                        errorCount++;
1944                                    }
1945                                    if (!checkDirectionSensor(cHead, altDirection,
1946                                            ConnectivityUtil.OVERALL, cUtil)) {
1947                                        errorCount++;
1948                                    }
1949                                } else if (((t.getContinuingSense() == Turnout.CLOSED)
1950                                        && (((TrackSegment) t.getConnectB()).getLayoutBlock().getBlock() == cBlock))
1951                                        || ((t.getContinuingSense() == Turnout.THROWN)
1952                                        && (((TrackSegment) t.getConnectC()).getLayoutBlock().getBlock() == cBlock))) {
1953                                    // continuing track segment is in this block, normal continuing sense - or -
1954                                    //  diverging track segment is in this block, reverse continuing sense.
1955                                    if (a2Head == null) {
1956                                        // single head at throat
1957                                        if (!checkDirectionSensor(aHead, direction,
1958                                                ConnectivityUtil.CONTINUING, cUtil)) {
1959                                            errorCount++;
1960                                        }
1961                                    } else {
1962                                        // two heads at throat
1963                                        if (!checkDirectionSensor(aHead, direction,
1964                                                ConnectivityUtil.OVERALL, cUtil)) {
1965                                            errorCount++;
1966                                        }
1967                                    }
1968                                    if (!checkDirectionSensor(bHead, altDirection,
1969                                            ConnectivityUtil.OVERALL, cUtil)) {
1970                                        errorCount++;
1971                                    }
1972                                } else if (((t.getContinuingSense() == Turnout.CLOSED)
1973                                        && (((TrackSegment) t.getConnectC()).getLayoutBlock().getBlock() == cBlock))
1974                                        || ((t.getContinuingSense() == Turnout.THROWN)
1975                                        && (((TrackSegment) t.getConnectB()).getLayoutBlock().getBlock() == cBlock))) {
1976                                    // diverging track segment is in this block, normal continuing sense - or -
1977                                    //  continuing track segment is in this block, reverse continuing sense.
1978                                    if (a2Head == null) {
1979                                        // single head at throat
1980                                        if (!checkDirectionSensor(aHead, direction,
1981                                                ConnectivityUtil.DIVERGING, cUtil)) {
1982                                            errorCount++;
1983                                        }
1984                                    } else {
1985                                        // two heads at throat
1986                                        if (!checkDirectionSensor(a2Head, direction,
1987                                                ConnectivityUtil.OVERALL, cUtil)) {
1988                                            errorCount++;
1989                                        }
1990                                    }
1991                                    if (!checkDirectionSensor(cHead, altDirection,
1992                                            ConnectivityUtil.OVERALL, cUtil)) {
1993                                        errorCount++;
1994                                    }
1995                                }
1996                            }
1997                        }
1998                    } else if (t.getLinkType() != LayoutTurnout.LinkType.NO_LINK) {
1999                        // special linked turnout
2000                        LayoutTurnout tLinked = getLayoutTurnoutFromTurnoutName(t.getLinkedTurnoutName(), panel);
2001                        if (tLinked == null) {
2002                            log.error("null Layout Turnout linked to turnout {}", t.getTurnout().getDisplayName(USERSYS));
2003                        } else if (t.getLinkType() == LayoutTurnout.LinkType.THROAT_TO_THROAT) {
2004                            SignalHead b1Head = signalHeadManager.getSignalHead(t.getSignalB1Name());
2005                            SignalHead b2Head = null;
2006                            String hName = t.getSignalB2Name(); // returns "" for empty name, never null
2007                            if (!hName.isEmpty()) {
2008                                b2Head = signalHeadManager.getSignalHead(hName);
2009                            }
2010                            SignalHead c1Head = signalHeadManager.getSignalHead(t.getSignalC1Name());
2011                            SignalHead c2Head = null;
2012                            hName = t.getSignalC2Name(); // returns "" for empty name, never null
2013                            if (!hName.isEmpty()) {
2014                                c2Head = signalHeadManager.getSignalHead(hName);
2015                            }
2016                            int direction = getDirectionStandardTurnout(t, cUtil);
2017                            int altDirection = EntryPoint.FORWARD;
2018                            if (direction == EntryPoint.FORWARD) {
2019                                altDirection = EntryPoint.REVERSE;
2020                            }
2021                            if (direction != EntryPoint.UNKNOWN) {
2022                                if (t.getLayoutBlock().getBlock() == cBlock) {
2023                                    // turnout is in this block, set direction sensors on all signal heads
2024                                    // Note: need allocation to traverse this turnout
2025                                    if (!checkDirectionSensor(b1Head, altDirection,
2026                                            ConnectivityUtil.OVERALL, cUtil)) {
2027                                        errorCount++;
2028                                    }
2029                                    if (b2Head != null) {
2030                                        if (!checkDirectionSensor(b2Head, altDirection,
2031                                                ConnectivityUtil.OVERALL, cUtil)) {
2032                                            errorCount++;
2033                                        }
2034                                    }
2035                                    if (!checkDirectionSensor(c1Head, altDirection,
2036                                            ConnectivityUtil.OVERALL, cUtil)) {
2037                                        errorCount++;
2038                                    }
2039                                    if (c2Head != null) {
2040                                        if (!checkDirectionSensor(c2Head, altDirection,
2041                                                ConnectivityUtil.OVERALL, cUtil)) {
2042                                            errorCount++;
2043                                        }
2044                                    }
2045                                } else {
2046                                    // turnout is not in this block, switch to heads of linked turnout
2047                                    b1Head = signalHeadManager.getSignalHead(tLinked.getSignalB1Name());
2048                                    hName = tLinked.getSignalB2Name(); // returns "" for empty name, never null
2049                                    b2Head = null;
2050                                    if (!hName.isEmpty()) {
2051                                        b2Head = signalHeadManager.getSignalHead(hName);
2052                                    }
2053                                    c1Head = signalHeadManager.getSignalHead(tLinked.getSignalC1Name());
2054                                    c2Head = null;
2055                                    hName = tLinked.getSignalC2Name(); // returns "" for empty name, never null
2056                                    if (!hName.isEmpty()) {
2057                                        c2Head = signalHeadManager.getSignalHead(hName);
2058                                    }
2059                                    if (((t.getContinuingSense() == Turnout.CLOSED)
2060                                            && (((TrackSegment) t.getConnectB()).getLayoutBlock().getBlock() == cBlock))
2061                                            || ((t.getContinuingSense() == Turnout.THROWN)
2062                                            && (((TrackSegment) t.getConnectC()).getLayoutBlock().getBlock() == cBlock))) {
2063                                        // continuing track segment is in this block
2064                                        if (b2Head != null) {
2065                                            if (!checkDirectionSensor(b1Head, direction,
2066                                                    ConnectivityUtil.OVERALL, cUtil)) {
2067                                                errorCount++;
2068                                            }
2069                                        } else {
2070                                            if (!checkDirectionSensor(b1Head, direction,
2071                                                    ConnectivityUtil.CONTINUING, cUtil)) {
2072                                                errorCount++;
2073                                            }
2074                                        }
2075                                        if (c2Head != null) {
2076                                            if (!checkDirectionSensor(c1Head, direction,
2077                                                    ConnectivityUtil.OVERALL, cUtil)) {
2078                                                errorCount++;
2079                                            }
2080                                        } else {
2081                                            if (!checkDirectionSensor(c1Head, direction,
2082                                                    ConnectivityUtil.CONTINUING, cUtil)) {
2083                                                errorCount++;
2084                                            }
2085                                        }
2086                                    } else if (((t.getContinuingSense() == Turnout.CLOSED)
2087                                            && (((TrackSegment) t.getConnectC()).getLayoutBlock().getBlock() == cBlock))
2088                                            || ((t.getContinuingSense() == Turnout.THROWN)
2089                                            && (((TrackSegment) t.getConnectB()).getLayoutBlock().getBlock() == cBlock))) {
2090                                        // diverging track segment is in this block
2091                                        if (b2Head != null) {
2092                                            if (!checkDirectionSensor(b2Head, direction,
2093                                                    ConnectivityUtil.OVERALL, cUtil)) {
2094                                                errorCount++;
2095                                            }
2096                                        } else {
2097                                            if (!checkDirectionSensor(b1Head, direction,
2098                                                    ConnectivityUtil.DIVERGING, cUtil)) {
2099                                                errorCount++;
2100                                            }
2101                                        }
2102                                        if (c2Head != null) {
2103                                            if (!checkDirectionSensor(c2Head, direction,
2104                                                    ConnectivityUtil.OVERALL, cUtil)) {
2105                                                errorCount++;
2106                                            }
2107                                        } else {
2108                                            if (!checkDirectionSensor(c1Head, direction,
2109                                                    ConnectivityUtil.DIVERGING, cUtil)) {
2110                                                errorCount++;
2111                                            }
2112                                        }
2113                                    }
2114                                }
2115                            }
2116                        } else if (t.getLinkType() == LayoutTurnout.LinkType.FIRST_3_WAY) {
2117                            SignalHead a1Head = signalHeadManager.getSignalHead(t.getSignalA1Name());
2118                            SignalHead a2Head = null;
2119                            String hName = t.getSignalA2Name();
2120                            if (!hName.isEmpty()) {
2121                                a2Head = signalHeadManager.getSignalHead(hName);
2122                            }
2123                            SignalHead a3Head = null;
2124                            hName = t.getSignalA3Name(); // returns "" for empty name, never null
2125                            if (!hName.isEmpty()) {
2126                                a3Head = signalHeadManager.getSignalHead(hName);
2127                            }
2128                            SignalHead cHead = signalHeadManager.getSignalHead(t.getSignalC1Name());
2129                            int direction = getDirectionStandardTurnout(t, cUtil);
2130                            int altDirection = EntryPoint.FORWARD;
2131                            if (direction == EntryPoint.FORWARD) {
2132                                altDirection = EntryPoint.REVERSE;
2133                            }
2134                            if (direction != EntryPoint.UNKNOWN) {
2135                                if (t.getLayoutBlock().getBlock() == cBlock) {
2136                                    // turnout is in this block, set direction sensors on all signal heads
2137                                    // Note: need allocation to traverse this turnout
2138                                    if (!checkDirectionSensor(a1Head, direction,
2139                                            ConnectivityUtil.OVERALL, cUtil)) {
2140                                        errorCount++;
2141                                    }
2142                                    if ((a2Head != null) && (a3Head != null)) {
2143                                        if (!checkDirectionSensor(a2Head, direction,
2144                                                ConnectivityUtil.OVERALL, cUtil)) {
2145                                            errorCount++;
2146                                        }
2147                                        if (!checkDirectionSensor(a3Head, direction,
2148                                                ConnectivityUtil.OVERALL, cUtil)) {
2149                                            errorCount++;
2150                                        }
2151                                    }
2152                                    if (!checkDirectionSensor(cHead, altDirection,
2153                                            ConnectivityUtil.OVERALL, cUtil)) {
2154                                        errorCount++;
2155                                    }
2156                                } else {
2157                                    // turnout is not in this block
2158                                    if (((TrackSegment) t.getConnectA()).getLayoutBlock().getBlock() == cBlock) {
2159                                        // throat Track Segment is in this Block
2160                                        if (!checkDirectionSensor(cHead, altDirection,
2161                                                ConnectivityUtil.OVERALL, cUtil)) {
2162                                            errorCount++;
2163                                        }
2164                                    } else if (((TrackSegment) t.getConnectC()).getLayoutBlock().getBlock() == cBlock) {
2165                                        // diverging track segment is in this Block
2166                                        if (a2Head != null) {
2167                                            if (!checkDirectionSensor(a2Head, direction,
2168                                                    ConnectivityUtil.OVERALL, cUtil)) {
2169                                                errorCount++;
2170                                            }
2171                                        } else {
2172                                            if (!checkDirectionSensor(a1Head, direction,
2173                                                    ConnectivityUtil.DIVERGING, cUtil)) {
2174                                                errorCount++;
2175                                            }
2176                                        }
2177                                    }
2178                                }
2179                            }
2180                        } else if (t.getLinkType() == LayoutTurnout.LinkType.SECOND_3_WAY) {
2181                            SignalHead bHead = signalHeadManager.getSignalHead(t.getSignalB1Name());
2182                            SignalHead cHead = signalHeadManager.getSignalHead(t.getSignalC1Name());
2183                            SignalHead a1Head = signalHeadManager.getSignalHead(tLinked.getSignalA1Name());
2184                            SignalHead a3Head = null;
2185                            String hName = tLinked.getSignalA3Name();
2186                            if (!hName.isEmpty()) {
2187                                a3Head = signalHeadManager.getSignalHead(hName);
2188                            }
2189                            int direction = getDirectionStandardTurnout(t, cUtil);
2190                            int altDirection = EntryPoint.FORWARD;
2191                            if (direction == EntryPoint.FORWARD) {
2192                                altDirection = EntryPoint.REVERSE;
2193                            }
2194                            if (direction != EntryPoint.UNKNOWN) {
2195                                if (t.getLayoutBlock().getBlock() == cBlock) {
2196                                    // turnout is in this block, set direction sensors on b and c signal heads
2197                                    // Note: need allocation to traverse this turnout
2198                                    if (!checkDirectionSensor(bHead, altDirection,
2199                                            ConnectivityUtil.OVERALL, cUtil)) {
2200                                        errorCount++;
2201                                    }
2202                                    if (!checkDirectionSensor(cHead, altDirection,
2203                                            ConnectivityUtil.OVERALL, cUtil)) {
2204                                        errorCount++;
2205                                    }
2206                                }
2207                                if (((TrackSegment) t.getConnectC()).getLayoutBlock().getBlock() == cBlock) {
2208                                    // diverging track segment is in this Block
2209                                    if (a3Head != null) {
2210                                        if (!checkDirectionSensor(a3Head, direction,
2211                                                ConnectivityUtil.OVERALL, cUtil)) {
2212                                            errorCount++;
2213                                        }
2214                                    } else {
2215                                        log.warn("Turnout {} - SSL for head {} cannot handle direction sensor for second diverging track.",
2216                                                tLinked.getTurnout().getDisplayName(USERSYS),( a1Head==null ? "Unknown" : a1Head.getDisplayName(USERSYS)));
2217                                        errorCount++;
2218                                    }
2219                                } else if (((TrackSegment) t.getConnectB()).getLayoutBlock().getBlock() == cBlock) {
2220                                    // continuing track segment is in this Block
2221                                    if (a3Head != null) {
2222                                        if (!checkDirectionSensor(a1Head, direction,
2223                                                ConnectivityUtil.OVERALL, cUtil)) {
2224                                            errorCount++;
2225                                        }
2226                                    } else {
2227                                        if (!checkDirectionSensor(a1Head, direction,
2228                                                ConnectivityUtil.CONTINUING, cUtil)) {
2229                                            errorCount++;
2230                                        }
2231                                    }
2232                                }
2233                            }
2234                        }
2235                    } else if ((t.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)
2236                            || (t.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER)
2237                            || (t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)) {
2238                        // crossover turnout
2239                        // Note: direction is for proceeding from A to B (or D to C)
2240                        int direction = getDirectionXoverTurnout(t, cUtil);
2241                        int altDirection = EntryPoint.FORWARD;
2242                        if (direction == EntryPoint.FORWARD) {
2243                            altDirection = EntryPoint.REVERSE;
2244                        }
2245                        if (direction == EntryPoint.UNKNOWN) {
2246                            errorCount++;
2247                        } else {
2248                            if (((TrackSegment) t.getConnectA()).getLayoutBlock().getBlock() == cBlock) {
2249                                if ((t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
2250                                        || (t.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)) {
2251                                    if (!placeSensorInCrossover(t.getSignalB1Name(), t.getSignalB2Name(),
2252                                            t.getSignalC1Name(), t.getSignalC2Name(), altDirection, cUtil)) {
2253                                        errorCount++;
2254                                    }
2255                                } else {
2256                                    if (!placeSensorInCrossover(t.getSignalB1Name(), t.getSignalB2Name(),
2257                                            null, null, altDirection, cUtil)) {
2258                                        errorCount++;
2259                                    }
2260                                }
2261                            }
2262                            if (((TrackSegment) t.getConnectB()).getLayoutBlock().getBlock() == cBlock) {
2263                                if ((t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
2264                                        || (t.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER)) {
2265                                    if (!placeSensorInCrossover(t.getSignalA1Name(), t.getSignalA2Name(),
2266                                            t.getSignalD1Name(), t.getSignalD2Name(), direction, cUtil)) {
2267                                        errorCount++;
2268                                    }
2269                                } else {
2270                                    if (!placeSensorInCrossover(t.getSignalA1Name(), t.getSignalA2Name(),
2271                                            null, null, direction, cUtil)) {
2272                                        errorCount++;
2273                                    }
2274                                }
2275                            }
2276                            if (((TrackSegment) t.getConnectC()).getLayoutBlock().getBlock() == cBlock) {
2277                                if ((t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
2278                                        || (t.getTurnoutType() == LayoutTurnout.TurnoutType.RH_XOVER)) {
2279                                    if (!placeSensorInCrossover(t.getSignalD1Name(), t.getSignalD2Name(),
2280                                            t.getSignalA1Name(), t.getSignalA2Name(), direction, cUtil)) {
2281                                        errorCount++;
2282                                    }
2283                                } else {
2284                                    if (!placeSensorInCrossover(t.getSignalD1Name(), t.getSignalD2Name(),
2285                                            null, null, direction, cUtil)) {
2286                                        errorCount++;
2287                                    }
2288                                }
2289                            }
2290                            if (((TrackSegment) t.getConnectD()).getLayoutBlock().getBlock() == cBlock) {
2291                                if ((t.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER)
2292                                        || (t.getTurnoutType() == LayoutTurnout.TurnoutType.LH_XOVER)) {
2293                                    if (!placeSensorInCrossover(t.getSignalC1Name(), t.getSignalC2Name(),
2294                                            t.getSignalB1Name(), t.getSignalB2Name(), altDirection, cUtil)) {
2295                                        errorCount++;
2296                                    }
2297                                } else {
2298                                    if (!placeSensorInCrossover(t.getSignalC1Name(), t.getSignalC2Name(),
2299                                            null, null, altDirection, cUtil)) {
2300                                        errorCount++;
2301                                    }
2302                                }
2303                            }
2304                        }
2305                    } else if (t.isTurnoutTypeSlip()) {
2306                        int direction = getDirectionSlip(t, cUtil);
2307                        int altDirection = EntryPoint.FORWARD;
2308                        if (direction == EntryPoint.FORWARD) {
2309                            altDirection = EntryPoint.REVERSE;
2310                        }
2311                        if (direction == EntryPoint.UNKNOWN) {
2312                            errorCount++;
2313                        } else {
2314                            if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalA1Name()), altDirection, ConnectivityUtil.OVERALL, cUtil)) {
2315                                errorCount++;
2316                            }
2317                            if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalA2Name()), altDirection, ConnectivityUtil.OVERALL, cUtil)) {
2318                                errorCount++;
2319                            }
2320                            if (t.getTurnoutType() == LayoutSlip.TurnoutType.SINGLE_SLIP) {
2321                                if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalB1Name()), altDirection, ConnectivityUtil.OVERALL, cUtil)) {
2322                                    errorCount++;
2323                                }
2324                            } else {
2325                                if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalB1Name()), altDirection, ConnectivityUtil.OVERALL, cUtil)) {
2326                                    errorCount++;
2327                                }
2328                                if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalB2Name()), altDirection, ConnectivityUtil.OVERALL, cUtil)) {
2329                                    errorCount++;
2330                                }
2331                            }
2332                            if (t.getTurnoutType() == LayoutSlip.TurnoutType.SINGLE_SLIP) {
2333                                if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalC1Name()), direction, ConnectivityUtil.OVERALL, cUtil)) {
2334                                    errorCount++;
2335                                }
2336                            } else {
2337                                if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalC1Name()), direction, ConnectivityUtil.OVERALL, cUtil)) {
2338                                    errorCount++;
2339                                }
2340                                if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalC2Name()), direction, ConnectivityUtil.OVERALL, cUtil)) {
2341                                    errorCount++;
2342                                }
2343                            }
2344                            if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalD1Name()), direction, ConnectivityUtil.OVERALL, cUtil)) {
2345                                errorCount++;
2346                            }
2347                            if (!checkDirectionSensor(signalHeadManager.getSignalHead(t.getSignalD2Name()), direction, ConnectivityUtil.OVERALL, cUtil)) {
2348                                errorCount++;
2349                            }
2350                        }
2351                    } else {
2352                        log.error("Unknown turnout type for turnout {} in Section {}.",
2353                                t.getTurnout().getDisplayName(USERSYS), getDisplayName(USERSYS));
2354                        errorCount++;
2355                    }
2356                } else {
2357                    // signal heads missing in turnout
2358                    missingSignalsTurnouts++;
2359                }
2360            }
2361        }
2362        // set up missing signal head message, if any
2363        if ((missingSignalsBB + missingSignalsTurnouts + missingSignalsLevelXings) > 0) {
2364            String s = "";
2365            if (missingSignalsBB > 0) {
2366                s = ", " + (missingSignalsBB) + " anchor point signal heads missing";
2367            }
2368            if (missingSignalsTurnouts > 0) {
2369                s = ", " + (missingSignalsTurnouts) + " turnouts missing signals";
2370            }
2371            if (missingSignalsLevelXings > 0) {
2372                s = ", " + (missingSignalsLevelXings) + " level crossings missing signals";
2373            }
2374            log.warn("Section - {} {}",getDisplayName(USERSYS),s);
2375        }
2376
2377        return errorCount;
2378    }
2379
2380    private boolean checkDirectionSensor(SignalHead sh, int direction, int where,
2381            ConnectivityUtil cUtil) {
2382        String sensorName = "";
2383        if (direction == EntryPoint.FORWARD) {
2384            sensorName = getForwardBlockingSensorName();
2385        } else if (direction == EntryPoint.REVERSE) {
2386            sensorName = getReverseBlockingSensorName();
2387        }
2388        return (cUtil.addSensorToSignalHeadLogic(sensorName, sh, where));
2389    }
2390
2391    private LayoutTurnout getLayoutTurnoutFromTurnoutName(String turnoutName, LayoutEditor panel) {
2392        Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(turnoutName);
2393        if (t == null) {
2394            return null;
2395        }
2396        for (LayoutTurnout lt : panel.getLayoutTurnouts()) {
2397            if (lt.getTurnout() == t) {
2398                return lt;
2399            }
2400        }
2401        return null;
2402    }
2403
2404    @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "was previously marked with @SuppressWarnings, reason unknown")
2405    private List<EntryPoint> getListOfForwardBlockEntryPoints(Block b) {
2406        if (initializationNeeded) {
2407            initializeBlocks();
2408        }
2409        List<EntryPoint> a = new ArrayList<>();
2410        for (int i = 0; i < mForwardEntryPoints.size(); i++) {
2411            if (b == (mForwardEntryPoints.get(i)).getBlock()) {
2412                a.add(mForwardEntryPoints.get(i));
2413            }
2414        }
2415        return a;
2416    }
2417
2418    /**
2419     * Validate the Section. This checks block connectivity, warns of redundant
2420     * EntryPoints, and otherwise checks internal consistency of the Section. An
2421     * appropriate error message is logged if a problem is found. This method
2422     * assumes that Block Paths are correctly initialized.
2423     *
2424     * @return an error description or empty string if there are no errors
2425     */
2426    @Override
2427    public String validate() {
2428        if (initializationNeeded) {
2429            initializeBlocks();
2430        }
2431
2432        // validate Paths and Bean Settings if a Layout Editor panel is available
2433        for (int i = 0; i < (mBlockEntries.size() - 1); i++) {
2434            Block test = getBlockBySequenceNumber(i);
2435            if (test == null){
2436                log.error("Block {} not found in Block Entries. Paths not checked.",i );
2437                break;
2438            }
2439            String userName = test.getUserName();
2440            if (userName != null) {
2441                LayoutBlock lBlock = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
2442                if (lBlock == null) {
2443                    log.error("Layout Block {} not found. Paths not checked.", userName);
2444                } else {
2445                    lBlock.updatePaths();
2446                }
2447            }
2448        }
2449
2450        // check connectivity between internal blocks
2451        if (mBlockEntries.size() > 1) {
2452            for (int i = 0; i < (mBlockEntries.size() - 1); i++) {
2453                Block thisBlock = getBlockBySequenceNumber(i);
2454                Block nextBlock = getBlockBySequenceNumber(i + 1);
2455                if ( thisBlock == null || nextBlock == null ) {
2456                        return "Sequential blocks " + i + " " + thisBlock + " or "
2457                            + i+1 + " " + nextBlock + " are empty in Block List.";
2458                    }
2459                if (!connected(thisBlock, nextBlock)) {
2460                    String s = "Sequential Blocks - " + thisBlock.getDisplayName(USERSYS)
2461                            + ", " + nextBlock.getDisplayName(USERSYS)
2462                            + " - are not connected in Section " + getDisplayName(USERSYS) + ".";
2463                    return s;
2464                }
2465                if (!connected(nextBlock, thisBlock)) {
2466                    String s = "Sequential Blocks - " + thisBlock.getDisplayName(USERSYS)
2467                            + ", " + nextBlock.getDisplayName(USERSYS)
2468                            + " - Paths are not consistent - Section " + getDisplayName(USERSYS) + ".";
2469                    return s;
2470                }
2471            }
2472        }
2473        // validate entry points
2474        if ((mForwardEntryPoints.isEmpty()) && (mReverseEntryPoints.isEmpty())) {
2475            return "Section " + getDisplayName(USERSYS) + "has no Entry Points.";
2476        }
2477        if (!mForwardEntryPoints.isEmpty()) {
2478            for (int i = 0; i < mForwardEntryPoints.size(); i++) {
2479                EntryPoint ep = mForwardEntryPoints.get(i);
2480                if (!containsBlock(ep.getBlock())) {
2481                    String s = "Entry Point Block, " + ep.getBlock().getDisplayName(USERSYS)
2482                            + ", is not a Block in Section " + getDisplayName(USERSYS) + ".";
2483                    return s;
2484                }
2485                if (!connectsToBlock(ep.getFromBlock())) {
2486                    String s = "Entry Point From Block, " + ep.getBlock().getDisplayName(USERSYS)
2487                            + ", is not connected to a Block in Section " + getDisplayName(USERSYS) + ".";
2488                    return s;
2489                }
2490                if (!ep.isForwardType()) {
2491                    String s = "Direction of FORWARD Entry Point From Block "
2492                            + ep.getFromBlock().getDisplayName(USERSYS) + " to Section "
2493                            + getDisplayName(USERSYS) + " is incorrectly set.";
2494                    return s;
2495                }
2496                if (!connected(ep.getBlock(), ep.getFromBlock())) {
2497                    String s = "Entry Point Blocks, " + ep.getBlock().getDisplayName(USERSYS)
2498                            + " and " + ep.getFromBlock().getDisplayName(USERSYS)
2499                            + ", are not connected in Section " + getDisplayName(USERSYS) + ".";
2500                    return s;
2501                }
2502            }
2503        }
2504        if (!mReverseEntryPoints.isEmpty()) {
2505            for (int i = 0; i < mReverseEntryPoints.size(); i++) {
2506                EntryPoint ep = mReverseEntryPoints.get(i);
2507                if (!containsBlock(ep.getBlock())) {
2508                    String s = "Entry Point Block, " + ep.getBlock().getDisplayName(USERSYS)
2509                            + ", is not a Block in Section " + getDisplayName(USERSYS) + ".";
2510                    return s;
2511                }
2512                if (!connectsToBlock(ep.getFromBlock())) {
2513                    String s = "Entry Point From Block, " + ep.getBlock().getDisplayName(USERSYS)
2514                            + ", is not connected to a Block in Section " + getDisplayName(USERSYS) + ".";
2515                    return s;
2516                }
2517                if (!ep.isReverseType()) {
2518                    String s = "Direction of REVERSE Entry Point From Block "
2519                            + ep.getFromBlock().getDisplayName(USERSYS) + " to Section "
2520                            + getDisplayName(USERSYS) + " is incorrectly set.";
2521                    return s;
2522                }
2523                if (!connected(ep.getBlock(), ep.getFromBlock())) {
2524                    String s = "Entry Point Blocks, " + ep.getBlock().getDisplayName(USERSYS)
2525                            + " and " + ep.getFromBlock().getDisplayName(USERSYS)
2526                            + ", are not connected in Section " + getDisplayName(USERSYS) + ".";
2527                    return s;
2528                }
2529            }
2530        }
2531        return "";
2532    }
2533
2534    private boolean connected(Block b1, Block b2) {
2535        if ((b1 != null) && (b2 != null)) {
2536            List<Path> paths = b1.getPaths();
2537            for (int i = 0; i < paths.size(); i++) {
2538                if (paths.get(i).getBlock() == b2) {
2539                    return true;
2540                }
2541            }
2542        }
2543        return false;
2544    }
2545
2546    /**
2547     * Set/reset the display to use alternate color for unoccupied blocks in
2548     * this section. If Layout Editor panel is not present, Layout Blocks will
2549     * not be present, and nothing will be set.
2550     *
2551     * @param set true to use alternate unoccupied color; false otherwise
2552     */
2553    @Override
2554    public void setAlternateColor(boolean set) {
2555        for (Block b : mBlockEntries) {
2556            String userName = b.getUserName();
2557            if (userName != null) {
2558                LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
2559                if (lb != null) {
2560                    lb.setUseExtraColor(set);
2561                }
2562            }
2563        }
2564    }
2565
2566    /**
2567     * Set/reset the display to use alternate color for unoccupied blocks in
2568     * this Section. If the Section already contains an active block, then the
2569     * alternative color will be set from the active block, if no active block
2570     * is found or we are clearing the alternative color then all the blocks in
2571     * the Section will be set. If Layout Editor panel is not present, Layout
2572     * Blocks will not be present, and nothing will be set.
2573     *
2574     * @param set true to use alternate unoccupied color; false otherwise
2575     */
2576    @Override
2577    public void setAlternateColorFromActiveBlock(boolean set) {
2578        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
2579        boolean beenSet = false;
2580        if (!set || getState() == FREE || getState() == UNKNOWN) {
2581            setAlternateColor(set);
2582        } else if (getState() == FORWARD) {
2583            for (Block b : mBlockEntries) {
2584                if (b.getState() == Block.OCCUPIED) {
2585                    beenSet = true;
2586                }
2587                if (beenSet) {
2588                    String userName = b.getUserName();
2589                    if (userName != null) {
2590                        LayoutBlock lb = lbm.getByUserName(userName);
2591                        if (lb != null) {
2592                            lb.setUseExtraColor(set);
2593                        }
2594                    }
2595                }
2596            }
2597        } else if (getState() == REVERSE) {
2598            for (Block b : mBlockEntries) {
2599                if (b.getState() == Block.OCCUPIED) {
2600                    beenSet = true;
2601                }
2602                if (beenSet) {
2603                    String userName = b.getUserName();
2604                    if (userName != null) {
2605                        LayoutBlock lb = lbm.getByUserName(userName);
2606                        if (lb != null) {
2607                            lb.setUseExtraColor(set);
2608                        }
2609                    }
2610                }
2611            }
2612        }
2613        if (!beenSet) {
2614            setAlternateColor(set);
2615        }
2616    }
2617
2618    /**
2619     * Set the block values for blocks in this Section.
2620     *
2621     * @param name the value to set all blocks to
2622     */
2623    @Override
2624    public void setNameInBlocks(String name) {
2625        for (Block b : mBlockEntries) {
2626            b.setValue(name);
2627        }
2628    }
2629
2630    /**
2631     * Set the block values for blocks in this Section.
2632     *
2633     * @param value the name to set block values to
2634     */
2635    @Override
2636    public void setNameInBlocks(Object value) {
2637        for (Block b : mBlockEntries) {
2638            b.setValue(value);
2639        }
2640    }
2641
2642    @Override
2643    public void setNameFromActiveBlock(Object value) {
2644        boolean beenSet = false;
2645        if (value == null || getState() == FREE || getState() == UNKNOWN) {
2646            setNameInBlocks(value);
2647        } else if (getState() == FORWARD) {
2648            for (Block b : mBlockEntries) {
2649                if (b.getState() == Block.OCCUPIED) {
2650                    beenSet = true;
2651                }
2652                if (beenSet) {
2653                    b.setValue(value);
2654                }
2655            }
2656        } else if (getState() == REVERSE) {
2657            for (Block b : mBlockEntries) {
2658                if (b.getState() == Block.OCCUPIED) {
2659                    beenSet = true;
2660                }
2661                if (beenSet) {
2662                    b.setValue(value);
2663                }
2664            }
2665        }
2666        if (!beenSet) {
2667            setNameInBlocks(value);
2668        }
2669    }
2670
2671    /**
2672     * Clear the block values for blocks in this Section.
2673     */
2674    @Override
2675    public void clearNameInUnoccupiedBlocks() {
2676        for (Block b : mBlockEntries) {
2677            if (b.getState() == Block.UNOCCUPIED) {
2678                b.setValue(null);
2679            }
2680        }
2681    }
2682
2683    /**
2684     * Suppress the update of a memory variable when a block goes to unoccupied,
2685     * so the text set above doesn't get wiped out.
2686     *
2687     * @param set true to suppress the update; false otherwise
2688     */
2689    @Override
2690    public void suppressNameUpdate(boolean set) {
2691        for (Block b : mBlockEntries) {
2692            String userName = b.getUserName();
2693            if (userName != null) {
2694                LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(userName);
2695                if (lb != null) {
2696                    lb.setSuppressNameUpdate(set);
2697                }
2698            }
2699        }
2700    }
2701
2702    private SectionType sectionType = USERDEFINED;
2703
2704    /**
2705     * Set Section Type.
2706     * <ul>
2707     * <li>USERDEFINED - Default Save all the information.
2708     * <li>SIGNALMASTLOGIC - Save only the name, blocks will be added by the SignalMast logic.
2709     * <li>DYNAMICADHOC - Created on an as required basis, not to be saved.
2710     * </ul>
2711     * @param type constant of section type.
2712     */
2713    @Override
2714    public void setSectionType(SectionType type) {
2715        sectionType = type;
2716    }
2717
2718    /**
2719     * Get Section Type.
2720     * Defaults to USERDEFINED.
2721     * @return constant of section type.
2722     */
2723    @Override
2724    public SectionType getSectionType() {
2725        return sectionType;
2726    }
2727
2728    @Override
2729    public String getBeanType() {
2730        return Bundle.getMessage("BeanNameSection");
2731    }
2732
2733    @Override
2734    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
2735        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
2736            NamedBean nb = (NamedBean) evt.getOldValue();
2737            if (nb instanceof Sensor) {
2738                if (nb.equals(getForwardBlockingSensor())) {
2739                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
2740                    throw new PropertyVetoException(Bundle.getMessage("VetoBlockingSensor", nb.getBeanType(), Bundle.getMessage("Forward"), Bundle.getMessage("Blocking"), getDisplayName()), e); // NOI18N
2741                }
2742                if (nb.equals(getForwardStoppingSensor())) {
2743                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
2744                    throw new PropertyVetoException(Bundle.getMessage("VetoBlockingSensor", nb.getBeanType(), Bundle.getMessage("Forward"), Bundle.getMessage("Stopping"), getDisplayName()), e);
2745                }
2746                if (nb.equals(getReverseBlockingSensor())) {
2747                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
2748                    throw new PropertyVetoException(Bundle.getMessage("VetoBlockingSensor", nb.getBeanType(), Bundle.getMessage("Reverse"), Bundle.getMessage("Blocking"), getDisplayName()), e);
2749                }
2750                if (nb.equals(getReverseStoppingSensor())) {
2751                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
2752                    throw new PropertyVetoException(Bundle.getMessage("VetoBlockingSensor", nb.getBeanType(), Bundle.getMessage("Reverse"), Bundle.getMessage("Stopping"), getDisplayName()), e);
2753                }
2754            }
2755            if (nb instanceof Block) {
2756                Block check = (Block)nb;
2757                if (getBlockList().contains(check)) {
2758                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
2759                    throw new PropertyVetoException(Bundle.getMessage("VetoBlockInSection", getDisplayName()), e);
2760                }
2761            }
2762        }
2763        // "DoDelete" case, if needed, should be handled here.
2764    }
2765
2766    @Override
2767    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
2768        List<NamedBeanUsageReport> report = new ArrayList<>();
2769        if (bean != null) {
2770            getBlockList().forEach((block) -> {
2771                if (bean.equals(block)) {
2772                    report.add(new NamedBeanUsageReport("SectionBlock"));
2773                }
2774            });
2775            if (bean.equals(getForwardBlockingSensor())) {
2776                report.add(new NamedBeanUsageReport("SectionSensorForwardBlocking"));
2777            }
2778            if (bean.equals(getForwardStoppingSensor())) {
2779                report.add(new NamedBeanUsageReport("SectionSensorForwardStopping"));
2780            }
2781            if (bean.equals(getReverseBlockingSensor())) {
2782                report.add(new NamedBeanUsageReport("SectionSensorReverseBlocking"));
2783            }
2784            if (bean.equals(getReverseStoppingSensor())) {
2785                report.add(new NamedBeanUsageReport("SectionSensorReverseStopping"));
2786            }
2787        }
2788        return report;
2789    }
2790
2791    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultSection.class);
2792
2793}