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