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