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