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