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