001package jmri.implementation; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.beans.PropertyVetoException; 006import java.beans.VetoableChangeListener; 007import java.util.*; 008 009import javax.annotation.CheckForNull; 010import javax.annotation.Nonnull; 011 012import jmri.*; 013import jmri.jmrit.display.EditorManager; 014import jmri.jmrit.display.layoutEditor.ConnectivityUtil; 015import jmri.jmrit.display.layoutEditor.LayoutBlock; 016import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools; 017import jmri.jmrit.display.layoutEditor.LayoutBlockManager; 018import jmri.jmrit.display.layoutEditor.LayoutEditor; 019import jmri.jmrit.display.layoutEditor.LayoutSlip; 020import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState; 021import jmri.jmrit.display.layoutEditor.LayoutTurnout; 022import jmri.jmrit.display.layoutEditor.LayoutTraverser; 023import jmri.jmrit.display.layoutEditor.LayoutTurntable; 024import jmri.jmrit.display.layoutEditor.LevelXing; 025import jmri.util.ThreadingUtil; 026 027/** 028 * Default implementation of {@link jmri.SignalMastLogic}. 029 * 030 * @author Kevin Dickerson Copyright (C) 2011 031 */ 032public class DefaultSignalMastLogic extends AbstractNamedBean implements SignalMastLogic, VetoableChangeListener { 033 034 SignalMast source; 035 SignalMast destination; 036 String stopAspect; 037 038 Hashtable<SignalMast, DestinationMast> destList = new Hashtable<>(); 039 LayoutEditor editor; 040 041 LayoutBlock facingBlock = null; 042 LayoutBlock remoteProtectingBlock = null; 043 044 boolean disposing = false; 045 046 /** 047 * Initialise a Signal Mast Logic for a given source Signal Mast. 048 * 049 * @param source The Signal Mast we are configuring an SML for 050 */ 051 public DefaultSignalMastLogic(@Nonnull SignalMast source) { 052 super(source.toString()); // default system name 053 this.source = source; 054 try { 055 this.stopAspect = source.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER); 056 this.source.addPropertyChangeListener(propertySourceMastListener); 057 if (source.getAspect() == null) { 058 source.setAspect(stopAspect); 059 } 060 } catch (Exception ex) { 061 log.error("Error while creating Signal Logic", ex); 062 } 063 } 064 065 // Most of the following methods will inherit Javadoc from SignalMastLogic.java 066 /** 067 * {@inheritDoc } 068 */ 069 @Override 070 public void setFacingBlock(LayoutBlock facing) { 071 facingBlock = facing; 072 } 073 074 /** 075 * {@inheritDoc } 076 */ 077 @Override 078 public LayoutBlock getFacingBlock() { 079 return facingBlock; 080 } 081 082 /** 083 * {@inheritDoc } 084 */ 085 @Override 086 public LayoutBlock getProtectingBlock(@Nonnull SignalMast dest) { 087 if (!destList.containsKey(dest)) { 088 return null; 089 } 090 return destList.get(dest).getProtectingBlock(); 091 } 092 093 /** 094 * {@inheritDoc } 095 */ 096 @Override 097 public SignalMast getSourceMast() { 098 return source; 099 } 100 101 /** 102 * {@inheritDoc } 103 */ 104 @Override 105 public void replaceSourceMast(SignalMast oldMast, SignalMast newMast) { 106 if (oldMast != source) { 107 // Old mast does not match new mast so will exit replace 108 return; 109 } 110 source.removePropertyChangeListener(propertySourceMastListener); 111 source = newMast; 112 stopAspect = source.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER); 113 source.addPropertyChangeListener(propertySourceMastListener); 114 if (source.getAspect() == null) { 115 source.setAspect(stopAspect); 116 } 117 getDestinationList().forEach(sm -> { 118 DestinationMast destMast = destList.get(sm); 119 if (destMast.getAssociatedSection() != null) { 120 String oldUserName = destMast.getAssociatedSection().getUserName(); 121 String newUserName = source.getDisplayName() + ":" + sm.getDisplayName(); 122 if (oldUserName != null) { 123 InstanceManager.getDefault(NamedBeanHandleManager.class) 124 .renameBean(oldUserName, newUserName, ((NamedBean) destMast.getAssociatedSection())); 125 } else { 126 log.warn("AssociatedSection oldUserName null for destination mast {}, skipped", 127 destMast.getDisplayName()); 128 } 129 } 130 }); 131 firePropertyChange(PROPERTY_UPDATED_SOURCE, oldMast, newMast); 132 } 133 134 /** 135 * {@inheritDoc } 136 */ 137 @Override 138 public void replaceDestinationMast(SignalMast oldMast, SignalMast newMast) { 139 if (!destList.containsKey(oldMast)) { 140 return; 141 } 142 DestinationMast destMast = destList.get(oldMast); 143 destMast.updateDestinationMast(newMast); 144 if (destination == oldMast) { 145 oldMast.removePropertyChangeListener(propertyDestinationMastListener); 146 newMast.addPropertyChangeListener(propertyDestinationMastListener); 147 destination = newMast; 148 setSignalAppearance(); 149 } 150 destList.remove(oldMast); 151 if (destMast.getAssociatedSection() != null) { 152 String oldUserName = destMast.getAssociatedSection().getUserName(); 153 String newUserName = source.getDisplayName() + ":" + newMast.getDisplayName(); 154 if (oldUserName != null) { 155 InstanceManager.getDefault(NamedBeanHandleManager.class) 156 .renameBean(oldUserName, newUserName, destMast.getAssociatedSection()); 157 } else { 158 log.warn("AssociatedSection oldUserName null for destination mast {}, skipped", 159 destMast.getDisplayName()); 160 } 161 } 162 destList.put(newMast, destMast); 163 firePropertyChange(PROPERTY_UPDATED_DESTINATION, oldMast, newMast); 164 } 165 166 /** 167 * {@inheritDoc } 168 */ 169 @Override 170 public void setDestinationMast(SignalMast dest) { 171 if (destList.containsKey(dest)) { 172 // if already present, not a change 173 log.debug("Destination mast '{}' was already defined in SML with this source mast", dest.getDisplayName()); 174 return; 175 } 176 int oldSize = destList.size(); 177 destList.put(dest, new DestinationMast(dest)); 178 //InstanceManager.getDefault(SignalMastLogicManager.class).addDestinationMastToLogic(this, dest); 179 firePropertyChange(PROPERTY_LENGTH, oldSize, destList.size()); 180 // make new dest mast appear in (update of) SignallingSourcePanel Table by having that table listen to PropertyChange Events from SML TODO 181 } 182 183 /** 184 * {@inheritDoc } 185 */ 186 @Override 187 public boolean isDestinationValid(SignalMast dest) { 188 if (dest == null) { 189 return false; 190 } 191 return destList.containsKey(dest); 192 } 193 194 /** 195 * {@inheritDoc } 196 */ 197 @Override 198 public List<SignalMast> getDestinationList() { 199 List<SignalMast> out = new ArrayList<>(); 200 Enumeration<SignalMast> en = destList.keys(); 201 while (en.hasMoreElements()) { 202 out.add(en.nextElement()); 203 } 204 return out; 205 } 206 207 /** 208 * {@inheritDoc } 209 */ 210 @Override 211 public String getComment(SignalMast dest) { 212 if (!destList.containsKey(dest)) { 213 return ""; 214 } 215 return destList.get(dest).getComment(); 216 } 217 218 /** 219 * {@inheritDoc } 220 */ 221 @Override 222 public void setComment(String comment, SignalMast dest) { 223 if (!destList.containsKey(dest)) { 224 return; 225 } 226 destList.get(dest).setComment(comment); 227 } 228 229 /** 230 * {@inheritDoc } 231 */ 232 @Override 233 public void setStore(int store, SignalMast destination) { 234 if (!destList.containsKey(destination)) { 235 return; 236 } 237 destList.get(destination).setStore(store); 238 } 239 240 /** 241 * {@inheritDoc } 242 */ 243 @Override 244 public int getStoreState(SignalMast destination) { 245 if (!destList.containsKey(destination)) { 246 return STORENONE; 247 } 248 return destList.get(destination).getStoreState(); 249 } 250 251 /** 252 * {@inheritDoc } 253 */ 254 @Override 255 public void setEnabled(SignalMast dest) { 256 if (!destList.containsKey(dest)) { 257 return; 258 } 259 destList.get(dest).setEnabled(); 260 } 261 262 /** 263 * {@inheritDoc } 264 */ 265 @Override 266 public void setDisabled(SignalMast dest) { 267 if (!destList.containsKey(dest)) { 268 return; 269 } 270 destList.get(dest).setDisabled(); 271 } 272 273 /** 274 * {@inheritDoc } 275 */ 276 @Override 277 public boolean isEnabled(SignalMast dest) { 278 if (!destList.containsKey(dest)) { 279 return false; 280 } 281 return destList.get(dest).isEnabled(); 282 } 283 284 /** 285 * {@inheritDoc } 286 */ 287 @Override 288 public boolean isActive(SignalMast dest) { 289 if (!destList.containsKey(dest)) { 290 return false; 291 } 292 return destList.get(dest).isActive(); 293 } 294 295 /** 296 * {@inheritDoc } 297 */ 298 @Override 299 public SignalMast getActiveDestination() { 300 for (SignalMast sm : getDestinationList()) { 301 if (destList.get(sm).isActive()) { 302 return sm; 303 } 304 } 305 return null; 306 } 307 308 /** 309 * {@inheritDoc } 310 */ 311 @Override 312 public boolean removeDestination(SignalMast dest) { 313 int oldSize = destList.size(); 314 if (destList.containsKey(dest)) { 315 //InstanceManager.getDefault(SignalMastLogicManager.class).removeDestinationMastToLogic(this, dest); 316 destList.get(dest).dispose(); 317 destList.remove(dest); 318 firePropertyChange(PROPERTY_LENGTH, oldSize, destList.size()); 319 } 320 return destList.isEmpty(); 321 } 322 323 /** 324 * {@inheritDoc } 325 */ 326 @Override 327 public void disableLayoutEditorUse() { 328 destList.values().forEach(dest -> { 329 try { 330 dest.useLayoutEditor(false); 331 } catch (JmriException e) { 332 log.error("Could not disable LayoutEditor ", e); 333 } 334 }); 335 } 336 337 /** 338 * {@inheritDoc } 339 */ 340 @Override 341 public void useLayoutEditor(boolean boo, SignalMast destination) throws JmriException { 342 if (!destList.containsKey(destination)) { 343 return; 344 } 345 if (boo) { 346 log.debug("Set use layout editor"); 347 Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class); 348 /*We don't care which layout editor panel the signalmast is on, just so long as 349 the routing is done via layout blocks*/ 350 // TODO: what is this? 351 log.debug("userLayoutEditor finds layout list size is {}", layout.size()); 352 for (LayoutEditor findeditor : layout) { 353 log.debug("layouteditor {}", findeditor.getLayoutName()); 354 if (facingBlock == null) { 355 facingBlock = InstanceManager.getDefault(LayoutBlockManager.class) 356 .getFacingBlockByMast(getSourceMast(), findeditor); 357 } 358 } 359 } 360 destList.get(destination).useLayoutEditor(boo); 361 } 362 363 /** 364 * Add direction sensors to SML 365 * 366 * @return number of errors 367 */ 368 @Override 369 public int setupDirectionSensors() { 370 // iterrate over the signal masts 371 int errorCount = 0; 372 for (SignalMast sm : getDestinationList()) { 373 String displayName = sm.getDisplayName(); 374 Section sec = getAssociatedSection(sm); 375 if (sec != null) { 376 Block thisFacingBlock; 377 Sensor fwd = sec.getForwardBlockingSensor(); 378 Sensor rev = sec.getReverseBlockingSensor(); 379 LayoutBlock lBlock = getFacingBlock(); 380 if (lBlock == null) { 381 try { 382 useLayoutEditor(true, sm); // force a refind 383 } catch (JmriException ex) { 384 continue; 385 } 386 } 387 if (lBlock != null) { 388 thisFacingBlock = lBlock.getBlock(); 389 EntryPoint fwdEntryPoint = sec.getEntryPointFromBlock(thisFacingBlock, Section.FORWARD); 390 EntryPoint revEntryPoint = sec.getEntryPointFromBlock(thisFacingBlock, Section.REVERSE); 391 log.debug("Mast[{}] Sec[{}] Fwd[{}] Rev [{}]", 392 displayName, sec, fwd, rev); 393 if (fwd != null && fwdEntryPoint != null) { 394 addSensor(fwd.getUserName(), Sensor.INACTIVE, sm); 395 log.debug("Mast[{}] Sec[{}] Fwd[{}] fwdEP[{}]", 396 displayName, sec, fwd, 397 fwdEntryPoint.getBlock().getUserName()); 398 399 } else if (rev != null && revEntryPoint != null) { 400 addSensor(rev.getUserName(), Sensor.INACTIVE, sm); 401 log.debug("Mast[{}] Sec[{}] Rev [{}] revEP[{}]", 402 displayName, sec, rev, 403 revEntryPoint.getBlock().getUserName()); 404 405 } else { 406 log.error("Mast[{}] Cannot Establish entry point to protected section", displayName); 407 errorCount += 1; 408 } 409 } else { 410 log.error("Mast[{}] No Facing Block", displayName); 411 errorCount += 1; 412 } 413 } else { 414 log.error("Mast[{}] No Associated Section", displayName); 415 errorCount += 1; 416 } 417 } 418 return errorCount; 419 } 420 421 /** 422 * {@inheritDoc } 423 */ 424 @Override 425 public void removeDirectionSensors() { 426 //TODO find aaway of easilty identifying the ones we added. 427 } 428 429 /** 430 * {@inheritDoc } 431 */ 432 @Override 433 public boolean useLayoutEditor(SignalMast destination) { 434 if (!destList.containsKey(destination)) { 435 return false; 436 } 437 return destList.get(destination).useLayoutEditor(); 438 } 439 440 /** 441 * {@inheritDoc } 442 */ 443 @Override 444 public void useLayoutEditorDetails(boolean turnouts, boolean blocks, SignalMast destination) throws JmriException { 445 if (!destList.containsKey(destination)) { 446 return; 447 } 448 destList.get(destination).useLayoutEditorDetails(turnouts, blocks); 449 } 450 451 /** 452 * {@inheritDoc } 453 */ 454 @Override 455 public boolean useLayoutEditorBlocks(SignalMast destination) { 456 if (!destList.containsKey(destination)) { 457 return false; 458 } 459 return destList.get(destination).useLayoutEditorBlocks(); 460 } 461 462 /** 463 * {@inheritDoc } 464 */ 465 @Override 466 public boolean useLayoutEditorTurnouts(SignalMast destination) { 467 if (!destList.containsKey(destination)) { 468 return false; 469 } 470 return destList.get(destination).useLayoutEditorTurnouts(); 471 } 472 473 /** 474 * {@inheritDoc } 475 */ 476 @Override 477 public Section getAssociatedSection(SignalMast destination) { 478 if (!destList.containsKey(destination)) { 479 return null; 480 } 481 return destList.get(destination).getAssociatedSection(); 482 } 483 484 /** 485 * {@inheritDoc } 486 */ 487 @Override 488 public void setAssociatedSection(Section sec, SignalMast destination) { 489 if (!destList.containsKey(destination)) { 490 return; 491 } 492 destList.get(destination).setAssociatedSection(sec); 493 } 494 495 /** 496 * {@inheritDoc } 497 */ 498 @Override 499 public boolean allowAutoMaticSignalMastGeneration(SignalMast destination) { 500 if (!destList.containsKey(destination)) { 501 return false; 502 } 503 return destList.get(destination).allowAutoSignalMastGen(); 504 } 505 506 /** 507 * {@inheritDoc } 508 */ 509 @Override 510 public void allowAutoMaticSignalMastGeneration(boolean allow, SignalMast destination) { 511 if (!destList.containsKey(destination)) { 512 return; 513 } 514 destList.get(destination).allowAutoSignalMastGen(allow); 515 } 516 517 /** 518 * {@inheritDoc } 519 */ 520 @Override 521 public void allowTurnoutLock(boolean lock, SignalMast destination) { 522 if (!destList.containsKey(destination)) { 523 return; 524 } 525 destList.get(destination).allowTurnoutLock(lock); 526 } 527 528 /** 529 * {@inheritDoc } 530 */ 531 @Override 532 public boolean isTurnoutLockAllowed(SignalMast destination) { 533 if (!destList.containsKey(destination)) { 534 return false; 535 } 536 return destList.get(destination).isTurnoutLockAllowed(); 537 } 538 539 /** 540 * {@inheritDoc } 541 */ 542 @Override 543 public void setTurnouts(Hashtable<NamedBeanHandle<Turnout>, Integer> turnouts, SignalMast destination) { 544 if (!destList.containsKey(destination)) { 545 return; 546 } 547 destList.get(destination).setTurnouts(turnouts); 548 } 549 550 /** 551 * {@inheritDoc } 552 */ 553 @Override 554 public void setAutoTurnouts(Hashtable<Turnout, Integer> turnouts, SignalMast destination) { 555 if (!destList.containsKey(destination)) { 556 return; 557 } 558 destList.get(destination).setAutoTurnouts(turnouts); 559 } 560 561 /** 562 * {@inheritDoc } 563 */ 564 @Override 565 public void setBlocks(Hashtable<Block, Integer> blocks, SignalMast destination) { 566 if (!destList.containsKey(destination)) { 567 return; 568 } 569 destList.get(destination).setBlocks(blocks); 570 } 571 572 /** 573 * {@inheritDoc } 574 */ 575 @Override 576 public void setAutoBlocks(LinkedHashMap<Block, Integer> blocks, SignalMast destination) { 577 if (!destList.containsKey(destination)) { 578 return; 579 } 580 destList.get(destination).setAutoBlocks(blocks); 581 } 582 583 /** 584 * {@inheritDoc } 585 */ 586 @Override 587 public void setMasts(Hashtable<SignalMast, String> masts, SignalMast destination) { 588 if (!destList.containsKey(destination)) { 589 return; 590 } 591 destList.get(destination).setMasts(masts); 592 } 593 594 /** 595 * {@inheritDoc } 596 */ 597 @Override 598 public void setAutoMasts(Hashtable<SignalMast, String> masts, SignalMast destination) { 599 if (!destList.containsKey(destination)) { 600 return; 601 } 602 destList.get(destination).setAutoMasts(masts, true); 603 } 604 605 /** 606 * {@inheritDoc } 607 */ 608 @Override 609 public void setSensors(Hashtable<NamedBeanHandle<Sensor>, Integer> sensors, SignalMast destination) { 610 if (!destList.containsKey(destination)) { 611 return; 612 } 613 destList.get(destination).setSensors(sensors); 614 } 615 616 /** 617 * {@inheritDoc } 618 */ 619 @Override 620 public void addSensor(String sensorName, int state, SignalMast destination) { 621 if (!destList.containsKey(destination)) { 622 return; 623 } 624 Sensor sen = InstanceManager.sensorManagerInstance().getSensor(sensorName); 625 if (sen != null) { 626 NamedBeanHandle<Sensor> namedSensor = InstanceManager.getDefault(NamedBeanHandleManager.class) 627 .getNamedBeanHandle(sensorName, sen); 628 destList.get(destination).addSensor(namedSensor, state); 629 } 630 } 631 632 /** 633 * {@inheritDoc } 634 */ 635 @Override 636 public void removeSensor(String sensorName, SignalMast destination) { 637 Sensor sen = InstanceManager.sensorManagerInstance().getSensor(sensorName); 638 removeSensor(sen, destination); 639 } 640 641 public void removeSensor(Sensor sen, SignalMast destination) { 642 if (!destList.containsKey(destination)) { 643 return; 644 } 645 if (sen != null) { 646 destList.get(destination).removeSensor(sen); 647 } 648 } 649 650 /** 651 * {@inheritDoc } 652 */ 653 @Override 654 public List<Block> getBlocks(SignalMast destination) { 655 if (!destList.containsKey(destination)) { 656 return new ArrayList<>(); 657 } 658 return destList.get(destination).getBlocks(); 659 } 660 661 /** 662 * {@inheritDoc } 663 */ 664 @Override 665 public List<Block> getAutoBlocks(SignalMast destination) { 666 if (!destList.containsKey(destination)) { 667 return new ArrayList<>(); 668 } 669 return destList.get(destination).getAutoBlocks(); 670 } 671 672 /** 673 * {@inheritDoc } 674 */ 675 @Override 676 public List<Block> getAutoBlocksBetweenMasts(SignalMast destination) { 677 if (!destList.containsKey(destination)) { 678 return new ArrayList<>(); 679 } 680 return destList.get(destination).getAutoBlocksBetweenMasts(); 681 } 682 683 /** 684 * {@inheritDoc } 685 */ 686 @Override 687 public List<Turnout> getTurnouts(SignalMast destination) { 688 if (!destList.containsKey(destination)) { 689 return new ArrayList<>(); 690 } 691 return destList.get(destination).getTurnouts(); 692 } 693 694 /** 695 * {@inheritDoc } 696 */ 697 @Override 698 public List<NamedBeanHandle<Turnout>> getNamedTurnouts(SignalMast destination) { 699 if (!destList.containsKey(destination)) { 700 return new ArrayList<>(); 701 } 702 return destList.get(destination).getNamedTurnouts(); 703 } 704 705 public void removeTurnout(Turnout turn, SignalMast destination) { 706 if (!destList.containsKey(destination)) { 707 return; 708 } 709 710 if (turn != null) { 711 destList.get(destination).removeTurnout(turn); 712 } 713 } 714 715 /** 716 * {@inheritDoc } 717 */ 718 @Override 719 public List<Turnout> getAutoTurnouts(SignalMast destination) { 720 if (!destList.containsKey(destination)) { 721 return new ArrayList<>(); 722 } 723 return destList.get(destination).getAutoTurnouts(); 724 } 725 726 /** 727 * {@inheritDoc } 728 */ 729 @Override 730 public List<Sensor> getSensors(SignalMast destination) { 731 if (!destList.containsKey(destination)) { 732 return new ArrayList<>(); 733 } 734 return destList.get(destination).getSensors(); 735 } 736 737 /** 738 * {@inheritDoc } 739 */ 740 @Override 741 public List<NamedBeanHandle<Sensor>> getNamedSensors(SignalMast destination) { 742 if (!destList.containsKey(destination)) { 743 return new ArrayList<>(); 744 } 745 return destList.get(destination).getNamedSensors(); 746 } 747 748 /** 749 * {@inheritDoc } 750 */ 751 @Override 752 public List<SignalMast> getSignalMasts(SignalMast destination) { 753 if (!destList.containsKey(destination)) { 754 return new ArrayList<>(); 755 } 756 return destList.get(destination).getSignalMasts(); 757 } 758 759 /** 760 * {@inheritDoc } 761 */ 762 @Override 763 public List<SignalMast> getAutoMasts(SignalMast destination) { 764 if (!destList.containsKey(destination)) { 765 return new ArrayList<>(); 766 } 767 return destList.get(destination).getAutoSignalMasts(); 768 } 769 770 /** 771 * {@inheritDoc } 772 */ 773 @Override 774 public void initialise() { 775 Enumeration<SignalMast> en = destList.keys(); 776 while (en.hasMoreElements()) { 777 destList.get(en.nextElement()).initialise(); 778 } 779 } 780 781 /** 782 * {@inheritDoc } 783 */ 784 @Override 785 public void initialise(SignalMast destination) { 786 if (disposing) { 787 return; 788 } 789 790 if (!destList.containsKey(destination)) { 791 return; 792 } 793 destList.get(destination).initialise(); 794 } 795 796 /** 797 * {@inheritDoc } 798 */ 799 @Override 800 public LinkedHashMap<Block, Integer> setupLayoutEditorTurnoutDetails(List<LayoutBlock> blks, SignalMast destination) { 801 if (disposing) { 802 return new LinkedHashMap<>(); 803 } 804 805 if (!destList.containsKey(destination)) { 806 return new LinkedHashMap<>(); 807 } 808 return destList.get(destination).setupLayoutEditorTurnoutDetails(blks); 809 } 810 811 /** 812 * {@inheritDoc } 813 */ 814 @Override 815 public void setupLayoutEditorDetails() { 816 if (disposing) { 817 return; 818 } 819 Enumeration<SignalMast> en = destList.keys(); 820 while (en.hasMoreElements()) { 821 try { 822 destList.get(en.nextElement()).setupLayoutEditorDetails(); 823 } catch (JmriException e) { 824 //Considered normal if no route is valid on a Layout Editor panel 825 } 826 } 827 } 828 829 /** 830 * Check if routes to the destination Signal Mast are clear. 831 * 832 * @return true if the path to the next signal is clear 833 */ 834 boolean checkStates() { 835 SignalMast oldActiveMast = destination; 836 SignalMast newActiveMast = null; 837 boolean pairChanged = false; 838 839 Enumeration<SignalMast> en = destList.keys(); 840 log.debug("checkStates enumerates over {} mast(s)", destList.size()); 841 while (en.hasMoreElements()) { 842 SignalMast key = en.nextElement(); 843 DestinationMast dm = destList.get(key); 844 log.debug(" Destination mast {}", key.getDisplayName()); 845 log.debug(" isEnabled: {}", dm.isEnabled()); 846 log.debug(" isActive: {}", dm.active); 847 848 if (dm.active != dm.reportedActive) { 849 dm.reportedActive = dm.active; 850 pairChanged = true; 851 } 852 if (newActiveMast == null && dm.isEnabled() && dm.isActive()) { 853 newActiveMast = key; 854 } 855 } 856 if (newActiveMast == oldActiveMast) { 857 // no change, so leave the listener, the turnout locks and the state as they are 858 if (pairChanged) { 859 // but let listeners such as the signalling tables show the pair that changed 860 firePropertyChange(PROPERTY_STATE, null, newActiveMast); 861 } 862 return newActiveMast != null; 863 } 864 865 if (oldActiveMast != null) { 866 log.debug("Remove listener from destination"); 867 oldActiveMast.removePropertyChangeListener(propertyDestinationMastListener); 868 if (destList.containsKey(oldActiveMast)) { 869 destList.get(oldActiveMast).clearTurnoutLock(); 870 } 871 } 872 destination = newActiveMast; 873 if (newActiveMast == null) { 874 firePropertyChange(PROPERTY_STATE, oldActiveMast, null); 875 return false; 876 } 877 log.debug(" Add listener to destination"); 878 newActiveMast.addPropertyChangeListener(propertyDestinationMastListener); 879 log.debug(" firePropertyChange: \"state\""); 880 firePropertyChange(PROPERTY_STATE, oldActiveMast, newActiveMast); 881 destList.get(newActiveMast).lockTurnouts(); 882 return true; 883 } 884 885 /** 886 * {@inheritDoc } 887 */ 888 @Override 889 public boolean areBlocksIncluded(List<Block> blks) { 890 Enumeration<SignalMast> en = destList.keys(); 891 while (en.hasMoreElements()) { 892 SignalMast dm = en.nextElement(); 893 boolean included; 894 for (int i = 0; i < blks.size(); i++) { 895 included = destList.get(dm).isBlockIncluded(blks.get(i)); 896 if (included) { 897 return true; 898 } 899 included = destList.get(dm).isAutoBlockIncluded(blks.get(i)); 900 if (included) { 901 return true; 902 } 903 } 904 } 905 return false; 906 } 907 908 /** 909 * {@inheritDoc } 910 */ 911 @Override 912 public int getBlockState(Block block, SignalMast destination) { 913 if (!destList.containsKey(destination)) { 914 return -1; 915 } 916 return destList.get(destination).getBlockState(block); 917 } 918 919 /** 920 * {@inheritDoc } 921 */ 922 @Override 923 public boolean isBlockIncluded(Block block, SignalMast destination) { 924 if (!destList.containsKey(destination)) { 925 return false; 926 } 927 return destList.get(destination).isBlockIncluded(block); 928 } 929 930 /** 931 * {@inheritDoc } 932 */ 933 @Override 934 public boolean isTurnoutIncluded(Turnout turnout, SignalMast destination) { 935 if (!destList.containsKey(destination)) { 936 return false; 937 } 938 return destList.get(destination).isTurnoutIncluded(turnout); 939 } 940 941 /** 942 * {@inheritDoc } 943 */ 944 @Override 945 public boolean isSensorIncluded(Sensor sensor, SignalMast destination) { 946 if (!destList.containsKey(destination)) { 947 return false; 948 } 949 return destList.get(destination).isSensorIncluded(sensor); 950 } 951 952 /** 953 * {@inheritDoc } 954 */ 955 @Override 956 public boolean isSignalMastIncluded(SignalMast signal, SignalMast destination) { 957 if (!destList.containsKey(destination)) { 958 return false; 959 } 960 return destList.get(destination).isSignalMastIncluded(signal); 961 } 962 963 /** 964 * {@inheritDoc } 965 */ 966 @Override 967 public int getAutoBlockState(Block block, SignalMast destination) { 968 if (!destList.containsKey(destination)) { 969 return -1; 970 } 971 return destList.get(destination).getAutoBlockState(block); 972 } 973 974 /** 975 * {@inheritDoc } 976 */ 977 @Override 978 public int getSensorState(Sensor sensor, SignalMast destination) { 979 if (!destList.containsKey(destination)) { 980 return -1; 981 } 982 return destList.get(destination).getSensorState(sensor); 983 } 984 985 /** 986 * {@inheritDoc } 987 */ 988 @Override 989 public int getTurnoutState(Turnout turnout, SignalMast destination) { 990 if (!destList.containsKey(destination)) { 991 return -1; 992 } 993 return destList.get(destination).getTurnoutState(turnout); 994 } 995 996 /** 997 * {@inheritDoc } 998 */ 999 @Override 1000 public int getAutoTurnoutState(Turnout turnout, SignalMast destination) { 1001 if (!destList.containsKey(destination)) { 1002 return -1; 1003 } 1004 return destList.get(destination).getAutoTurnoutState(turnout); 1005 } 1006 1007 /** 1008 * {@inheritDoc } 1009 */ 1010 @Override 1011 public String getSignalMastState(SignalMast mast, SignalMast destination) { 1012 if (!destList.containsKey(destination)) { 1013 return null; 1014 } 1015 return destList.get(destination).getSignalMastState(mast); 1016 } 1017 1018 /** 1019 * {@inheritDoc } 1020 */ 1021 @Override 1022 public String getAutoSignalMastState(SignalMast mast, SignalMast destination) { 1023 if (!destList.containsKey(destination)) { 1024 return null; 1025 } 1026 return destList.get(destination).getAutoSignalMastState(mast); 1027 } 1028 1029 /** 1030 * {@inheritDoc } 1031 */ 1032 @Override 1033 public float getMaximumSpeed(SignalMast destination) { 1034 if (!destList.containsKey(destination)) { 1035 return -1; 1036 } 1037 return destList.get(destination).getMinimumSpeed(); 1038 } 1039 1040 /** 1041 * True from when a job to set the source mast aspect is scheduled until 1042 * it, and any repeat of it, has finished. 1043 */ 1044 volatile boolean inWait = false; 1045 1046 /** 1047 * True while the scheduled job is working out and setting the aspect. 1048 * Guarded by this. 1049 */ 1050 private boolean appearanceJobRunning = false; 1051 1052 /** 1053 * True if a change arrived while the job was running, so the job must run 1054 * again to take it into account. Guarded by this. 1055 */ 1056 private boolean appearanceJobRepeat = false; 1057 1058 /** 1059 * Before going active or checking that we can go active, wait half the 1060 * signal logic delay for things to settle down to help prevent a race 1061 * condition. 1062 * <p> 1063 * A change that arrives while a job is waiting to start needs nothing 1064 * more, as the job will see it. A change that arrives while the job is 1065 * running may come after the job has read the state it changed, so the 1066 * job then runs again. 1067 */ 1068 synchronized void setSignalAppearance() { 1069 log.debug("setMastAppearance (Aspect) called for {}", source.getDisplayName()); 1070 if (inWait) { 1071 if (appearanceJobRunning) { 1072 log.debug("setMastAppearance (Aspect) called while the aspect is being set, will run again"); 1073 appearanceJobRepeat = true; 1074 } else { 1075 log.debug("setMastAppearance (Aspect) called with inWait set, returning"); 1076 } 1077 return; 1078 } 1079 inWait = true; 1080 scheduleAppearanceJob(); 1081 } 1082 1083 /** 1084 * Schedule the job that sets the source mast aspect. Called with this 1085 * locked. 1086 */ 1087 private void scheduleAppearanceJob() { 1088 // The next line forces a single initialization of InstanceManager.getDefault(SignalSpeedMap.class) 1089 // before launching parallel threads 1090 InstanceManager.getDefault(SignalSpeedMap.class); 1091 1092 // The next line forces a single initialization of InstanceManager.getDefault(SignalMastLogicManager.class) 1093 // before launching delay 1094 int tempDelay = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalLogicDelay() / 2; 1095 log.debug("SignalMastLogicManager started (delay)"); 1096 ThreadingUtil.runOnLayoutDelayed(this::runAppearanceJob, tempDelay); 1097 } 1098 1099 private void runAppearanceJob() { 1100 synchronized (this) { 1101 appearanceJobRunning = true; 1102 appearanceJobRepeat = false; 1103 } 1104 try { 1105 setMastAppearance(); 1106 } finally { 1107 synchronized (this) { 1108 appearanceJobRunning = false; 1109 boolean repeat = appearanceJobRepeat && !disposing; 1110 appearanceJobRepeat = false; 1111 if (repeat) { 1112 try { 1113 scheduleAppearanceJob(); 1114 } catch (RuntimeException ex) { 1115 inWait = false; 1116 log.error("Could not set the aspect of {} again", source.getDisplayName(), ex); 1117 } 1118 } else { 1119 inWait = false; 1120 } 1121 } 1122 } 1123 } 1124 1125 /** 1126 * Evaluate the destination signal mast Aspect and set ours accordingly. 1127 */ 1128 void setMastAppearance() { 1129 log.debug("Set source Signal Mast Aspect"); 1130 if (disposing) { 1131 // a delayed check can still run after the logic has been disposed 1132 return; 1133 } 1134 if (getSourceMast().getHeld()) { 1135 log.debug("Signal is at a Held state so will set to the aspect defined for Held or Danger"); 1136 1137 String heldAspect = getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.HELD); 1138 if (heldAspect != null && !getSourceMast().isAspectDisabled(heldAspect)) { 1139 log.debug(" Setting to HELD value of {}", heldAspect); 1140 } else { 1141 heldAspect = getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER); 1142 log.debug(" Setting to DANGER value of {}", heldAspect); 1143 } 1144 String aspectSet = heldAspect; // for lambda 1145 try { 1146 ThreadingUtil.runOnLayout(() -> { 1147 getSourceMast().setAspect(aspectSet); 1148 }); 1149 } catch (RuntimeException ex) { 1150 log.error("Could not set held aspect {} on {}", aspectSet, getSourceMast().getDisplayName(), ex); 1151 } 1152 return; 1153 } 1154 if (!checkStates()) { 1155 log.debug("Advanced routes not clear, set Stop aspect"); 1156 setSourceAspect(stopAspect); 1157 return; 1158 } 1159 String[] advancedAspect; 1160 if (destination.getHeld()) { 1161 if (destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.HELD) != null) { 1162 advancedAspect = getSourceMast().getAppearanceMap().getValidAspectsForAdvancedAspect(destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.HELD)); 1163 } else { 1164 advancedAspect = getSourceMast().getAppearanceMap().getValidAspectsForAdvancedAspect(destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER)); 1165 } 1166 } else { 1167 advancedAspect = getSourceMast().getAppearanceMap().getValidAspectsForAdvancedAspect(destination.getAspect()); 1168 } 1169 1170 log.debug("distant aspect is {}", destination.getAspect()); 1171 log.debug("advanced aspect is {}", advancedAspect != null ? advancedAspect : "<null>"); 1172 1173 if (advancedAspect != null) { 1174 String aspect = stopAspect; 1175 if (destList.get(destination).permissiveBlock) { 1176 if (!getSourceMast().isPermissiveSmlDisabled()) { 1177 //if a block is in a permissive state then we set the permissive appearance 1178 aspect = getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE); 1179 } 1180 } else { 1181 for (String advancedAspect1 : advancedAspect) { 1182 if (!getSourceMast().isAspectDisabled(advancedAspect1)) { 1183 aspect = advancedAspect1; 1184 break; 1185 } 1186 } 1187 List<Integer> divergAspects = new ArrayList<>(); 1188 List<Integer> nonDivergAspects = new ArrayList<>(); 1189 List<Integer> eitherAspects = new ArrayList<>(); 1190 if (advancedAspect.length > 1) { 1191 float maxSigSpeed = -1; 1192 float maxPathSpeed = destList.get(destination).getMinimumSpeed(); 1193 boolean divergRoute = destList.get(destination).turnoutThrown; 1194 1195 log.debug("Diverging route? {}", divergRoute); 1196 boolean divergFlagsAvailable = false; 1197 //We split the aspects into two lists, one with diverging flag set, the other without. 1198 for (int i = 0; i < advancedAspect.length; i++) { 1199 String div = null; 1200 if (!getSourceMast().isAspectDisabled(advancedAspect[i])) { 1201 div = (String) getSourceMast().getSignalSystem().getProperty(advancedAspect[i], "route"); 1202 } 1203 if (div != null) { 1204 if (div.equals("Diverging")) { 1205 log.debug("Aspect {} added as Diverging Route", advancedAspect[i]); 1206 divergAspects.add(i); 1207 divergFlagsAvailable = true; 1208 log.debug("Using Diverging Flag"); 1209 } else if (div.equals("Either")) { 1210 log.debug("Aspect {} added as both Diverging and Normal Route", advancedAspect[i]); 1211 nonDivergAspects.add(i); 1212 divergAspects.add(i); 1213 divergFlagsAvailable = true; 1214 eitherAspects.add(i); 1215 log.debug("Using Diverging Flag"); 1216 } else { 1217 log.debug("Aspect {} added as Normal Route", advancedAspect[i]); 1218 nonDivergAspects.add(i); 1219 log.debug("Aspect {} added as Normal Route", advancedAspect[i]); 1220 } 1221 } else { 1222 nonDivergAspects.add(i); 1223 log.debug("Aspect {} added as Normal Route", advancedAspect[i]); 1224 } 1225 } 1226 if ((eitherAspects.equals(divergAspects)) && (divergAspects.size() < nonDivergAspects.size())) { 1227 //There are no unique diverging aspects 1228 log.debug("'Either' aspects equals divergAspects and is less than non-diverging aspects"); 1229 divergFlagsAvailable = false; 1230 } 1231 log.debug("path max speed : {}", maxPathSpeed); 1232 for (int i = 0; i < advancedAspect.length; i++) { 1233 if (!getSourceMast().isAspectDisabled(advancedAspect[i])) { 1234 String strSpeed = (String) getSourceMast().getSignalSystem().getProperty(advancedAspect[i], "speed"); 1235 log.debug("Aspect Speed = {} for aspect {}", strSpeed, advancedAspect[i]); 1236 /* if the diverg flags available is set and the diverg aspect 1237 array contains the entry then we will check this aspect. 1238 1239 If the diverg flag has not been set then we will check. 1240 */ 1241 log.debug("advanced aspect {}",advancedAspect[i]); 1242 if ((divergRoute && (divergFlagsAvailable) && (divergAspects.contains(i))) || ((divergRoute && !divergFlagsAvailable) || (!divergRoute)) && (nonDivergAspects.contains(i))) { 1243 log.debug("In list"); 1244 if ((strSpeed != null) && (!strSpeed.isEmpty())) { 1245 float speed = 0.0f; 1246 try { 1247 speed = Float.parseFloat(strSpeed); 1248 } catch (NumberFormatException nx) { 1249 // not a number, perhaps a name? 1250 try { 1251 speed = InstanceManager.getDefault(SignalSpeedMap.class).getSpeed(strSpeed); 1252 } catch (IllegalArgumentException ex) { 1253 // not a name either 1254 log.warn("Using speed = 0.0 because could not understand \"{}\"", strSpeed); 1255 } 1256 } 1257 //Integer state = Integer.parseInt(strSpeed); 1258 /* This pics out either the highest speed signal if there 1259 * is no block speed specified or the highest speed signal 1260 * that is under the minimum block speed. 1261 */ 1262 log.debug("{} signal state speed {} maxSigSpeed {} maxPathSpeed {}", destination.getDisplayName(), speed, maxSigSpeed, maxPathSpeed); 1263 if (maxPathSpeed == 0) { 1264 if (maxSigSpeed == -1) { 1265 log.debug("min speed on this route is equal to 0 so will set this as our max speed"); 1266 maxSigSpeed = speed; 1267 aspect = advancedAspect[i]; 1268 log.debug("Aspect to set is {}", aspect); 1269 } else if (speed > maxSigSpeed) { 1270 log.debug("new speed is faster than old will use this"); 1271 maxSigSpeed = speed; 1272 aspect = advancedAspect[i]; 1273 log.debug("Aspect to set is {}", aspect); 1274 } 1275 } else if ((speed > maxSigSpeed) && (maxSigSpeed < maxPathSpeed) && (speed <= maxPathSpeed)) { 1276 //Only set the speed to the lowest if the max speed is greater than the path speed 1277 //and the new speed is less than the last max speed 1278 log.debug("our minimum speed on this route is less than our state speed, we will set this as our max speed"); 1279 maxSigSpeed = speed; 1280 aspect = advancedAspect[i]; 1281 log.debug("Aspect to set is {}", aspect); 1282 } else if ((maxSigSpeed > maxPathSpeed) && (speed < maxSigSpeed)) { 1283 log.debug("our max signal speed is greater than our path speed on this route, our speed is less that the maxSigSpeed"); 1284 maxSigSpeed = speed; 1285 aspect = advancedAspect[i]; 1286 log.debug("Aspect to set is {}", aspect); 1287 1288 } else if (maxSigSpeed == -1) { 1289 log.debug("maxSigSpeed returned as -1"); 1290 maxSigSpeed = speed; 1291 aspect = advancedAspect[i]; 1292 log.debug("Aspect to set is {}", aspect); 1293 } 1294 } 1295 } 1296 } else { 1297 log.debug("Aspect has been disabled {}", advancedAspect[i]); 1298 } 1299 } 1300 } 1301 } 1302 if ((aspect != null) && (!aspect.isEmpty())) { 1303 log.debug("setMastAppearance setting aspect \"{}\"", aspect); 1304 String aspectSet = aspect; // for lambda 1305 try { 1306 ThreadingUtil.runOnLayout(() -> { 1307 setSourceAspect(aspectSet); 1308 }); 1309 } catch (Exception ex) { 1310 log.error("Exception while setting Signal Logic", ex); 1311 } 1312 return; 1313 } 1314 } 1315 log.debug("Aspect returned is not valid, setting stop"); 1316 ThreadingUtil.runOnLayout(() -> { 1317 setSourceAspect(stopAspect); 1318 }); 1319 } 1320 1321 /** 1322 * Set the aspect of the source mast, unless it already shows that aspect. 1323 * Hardware masts send a command to the layout every time their aspect is 1324 * set, so an unchanged aspect is not sent again. 1325 * 1326 * A null aspect, for example a mast whose appearance map has no Danger 1327 * aspect, is not passed to the mast. 1328 * 1329 * @param aspect the aspect to show 1330 */ 1331 private void setSourceAspect(@CheckForNull String aspect) { 1332 SignalMast mast = getSourceMast(); 1333 if (aspect == null) { 1334 log.warn("No aspect to set on source mast {}", mast.getDisplayName()); 1335 return; 1336 } 1337 if (aspect.equals(mast.getAspect())) { 1338 log.debug("Source mast {} already shows {}", mast.getDisplayName(), aspect); 1339 return; 1340 } 1341 mast.setAspect(aspect); 1342 } 1343 1344 /** 1345 * {@inheritDoc } 1346 */ 1347 @Override 1348 public void setConflictingLogic(SignalMast sm, LevelXing lx) { 1349 if (sm == null) { 1350 return; 1351 } 1352 log.debug("setConflicting logic mast {}", sm.getDisplayName()); 1353 if (sm == source) { 1354 log.debug("source is us so exit"); 1355 return; 1356 } 1357 Enumeration<SignalMast> en = destList.keys(); 1358 while (en.hasMoreElements()) { 1359 SignalMast dm = en.nextElement(); 1360 if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockAC())) { 1361 destList.get(dm).addAutoSignalMast(sm); 1362 } else if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockBD())) { 1363 destList.get(dm).addAutoSignalMast(sm); 1364 } else if (destList.get(dm).isAutoBlockIncluded(lx.getLayoutBlockAC())) { 1365 destList.get(dm).addAutoSignalMast(sm); 1366 } else if (destList.get(dm).isAutoBlockIncluded(lx.getLayoutBlockBD())) { 1367 destList.get(dm).addAutoSignalMast(sm); 1368 } else { 1369 log.debug("Block not found"); 1370 } 1371 } 1372 } 1373 1374 /** 1375 * {@inheritDoc } 1376 */ 1377 @Override 1378 public void removeConflictingLogic(SignalMast sm, LevelXing lx) { 1379 if (sm == source) { 1380 return; 1381 } 1382 Enumeration<SignalMast> en = destList.keys(); 1383 while (en.hasMoreElements()) { 1384 SignalMast dm = en.nextElement(); 1385 if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockAC())) { 1386 destList.get(dm).removeAutoSignalMast(sm); 1387 } else if (destList.get(dm).isBlockIncluded(lx.getLayoutBlockBD())) { 1388 destList.get(dm).removeAutoSignalMast(sm); 1389 } 1390 } 1391 } 1392 1393 /** 1394 * Class to store SML properties for a destination mast paired with this 1395 * source mast. 1396 */ 1397 private class DestinationMast { 1398 1399 LayoutBlock destinationBlock = null; 1400 LayoutBlock protectingBlock = null; //this is the block that the source signal is protecting 1401 1402 List<NamedBeanSetting> userSetTurnouts = new ArrayList<>(0); 1403 Hashtable<Turnout, Integer> autoTurnouts = new Hashtable<>(0); 1404 //Hashtable<Turnout, Boolean> turnoutThroats = new Hashtable<Turnout, Boolean>(0); 1405 //Hashtable<Turnout, Boolean> autoTurnoutThroats = new Hashtable<Turnout, Boolean>(0); 1406 1407 List<NamedBeanSetting> userSetMasts = new ArrayList<>(0); 1408 Hashtable<SignalMast, String> autoMasts = new Hashtable<>(0); 1409 List<NamedBeanSetting> userSetSensors = new ArrayList<>(0); 1410 List<NamedBeanSetting> userSetBlocks = new ArrayList<>(0); 1411 boolean turnoutThrown = false; 1412 boolean permissiveBlock = false; 1413 boolean disposed = false; 1414 1415 List<LevelXing> blockInXings = new ArrayList<>(); 1416 1417 //autoBlocks are for those automatically generated by the system. 1418 LinkedHashMap<Block, Integer> autoBlocks = new LinkedHashMap<>(0); 1419 1420 List<Block> xingAutoBlocks = new ArrayList<>(0); 1421 List<Block> dblCrossoverAutoBlocks = new ArrayList<>(0); 1422 SignalMast destinationSignalMast; 1423 volatile boolean active = false; 1424 boolean reportedActive = false; // active state last reported by checkStates 1425 boolean destMastInit = false; 1426 1427 float minimumBlockSpeed = 0.0f; 1428 1429 boolean useLayoutEditor = false; 1430 boolean useLayoutEditorTurnouts = false; 1431 boolean useLayoutEditorBlocks = false; 1432 boolean lockTurnouts = false; 1433 1434 NamedBeanHandle<Section> associatedSection = null; 1435 1436 DestinationMast(SignalMast destination) { 1437 this.destinationSignalMast = destination; 1438 if (destination.getAspect() == null) { 1439 try { 1440 destination.setAspect(destination.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER)); 1441 } catch (Exception ex) { 1442 log.error("Error while creating Signal Logic", ex); 1443 } 1444 } 1445 } 1446 1447 void updateDestinationMast(SignalMast newMast) { 1448 destinationSignalMast = newMast; 1449 if (destinationSignalMast.getAspect() == null) { 1450 try { 1451 destinationSignalMast.setAspect(destinationSignalMast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER)); 1452 } catch (Exception ex) { 1453 log.error("Error while creating Signal Logic", ex); 1454 } 1455 } 1456 } 1457 1458 LayoutBlock getProtectingBlock() { 1459 return protectingBlock; 1460 } 1461 1462 String getDisplayName() { 1463 return destinationSignalMast.getDisplayName(); 1464 } 1465 1466 String comment; 1467 1468 String getComment() { 1469 return comment; 1470 } 1471 1472 void setComment(String comment) { 1473 String old = this.comment; 1474 this.comment = comment; 1475 firePropertyChange(PROPERTY_COMMENT, old, comment); 1476 } 1477 1478 boolean isActive() { 1479 if (disposed) { 1480 log.error("checkState called even though this has been disposed of"); 1481 return false; 1482 } 1483 return active; 1484 } 1485 1486 float getMinimumSpeed() { 1487 return minimumBlockSpeed; 1488 } 1489 1490 boolean enable = true; 1491 1492 void setEnabled() { 1493 enable = true; 1494 firePropertyChange(PROPERTY_ENABLED, false, this.destinationSignalMast); 1495 } 1496 1497 void setDisabled() { 1498 enable = false; 1499 firePropertyChange(PROPERTY_ENABLED, true, this.destinationSignalMast); 1500 } 1501 1502 boolean isEnabled() { 1503 return enable; 1504 } 1505 1506 int store = STOREALL; 1507 1508 void setStore(int store) { 1509 this.store = store; 1510 } 1511 1512 int getStoreState() { 1513 return store; 1514 } 1515 1516 void setAssociatedSection(Section section) { 1517 if (section != null && (!useLayoutEditor || !useLayoutEditorBlocks)) { 1518 log.warn("This Logic {} to {} is not using the Layout Editor or its Blocks, the associated Section will not be populated correctly", source.getDisplayName(), destinationSignalMast.getDisplayName()); 1519 } 1520 if (section == null) { 1521 associatedSection = null; 1522 return; 1523 } 1524 associatedSection = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(section.getDisplayName(), section); 1525 if (!autoBlocks.isEmpty()) { // associatedSection is guaranteed to exist 1526 createSectionDetails(); 1527 } 1528 } 1529 1530 Section getAssociatedSection() { 1531 if (associatedSection != null) { 1532 return associatedSection.getBean(); 1533 } 1534 return null; 1535 } 1536 1537 void createSectionDetails() { 1538 getAssociatedSection().removeAllBlocksFromSection(); 1539 getAutoBlocksBetweenMasts().forEach(key -> { 1540 getAssociatedSection().addBlock(key); 1541 }); 1542 String dir = Path.decodeDirection(getFacingBlock().getNeighbourDirection(getProtectingBlock())); 1543 EntryPoint ep = new EntryPoint(getProtectingBlock().getBlock(), getFacingBlock().getBlock(), dir); 1544 ep.setTypeForward(); 1545 getAssociatedSection().addToForwardList(ep); 1546 1547 LayoutBlock proDestLBlock = InstanceManager.getDefault(LayoutBlockManager.class).getProtectedBlockByNamedBean(destinationSignalMast, destinationBlock.getMaxConnectedPanel()); 1548 if (proDestLBlock != null) { 1549 log.debug("Add protecting Block {}", proDestLBlock.getDisplayName()); 1550 dir = Path.decodeDirection(proDestLBlock.getNeighbourDirection(destinationBlock)); 1551 ep = new EntryPoint(destinationBlock.getBlock(), proDestLBlock.getBlock(), dir); 1552 ep.setTypeReverse(); 1553 getAssociatedSection().addToReverseList(ep); 1554 } else { 1555 log.debug(" ### Protecting Block not found ### "); 1556 } 1557 } 1558 1559 boolean isTurnoutLockAllowed() { 1560 return lockTurnouts; 1561 } 1562 1563 void allowTurnoutLock(boolean lock) { 1564 if (lockTurnouts == lock) { 1565 return; 1566 } 1567 if (!lock) { 1568 clearTurnoutLock(); 1569 } 1570 lockTurnouts = lock; 1571 } 1572 1573 void setTurnouts(Hashtable<NamedBeanHandle<Turnout>, Integer> turnouts) { 1574 if (this.userSetTurnouts != null) { 1575 userSetTurnouts.forEach(nbh -> 1576 nbh.getBean().removePropertyChangeListener(propertyTurnoutListener)); 1577 } 1578 destMastInit = false; 1579 if (turnouts == null) { 1580 userSetTurnouts = new ArrayList<>(0); 1581 } else { 1582 userSetTurnouts = new ArrayList<>(); 1583 Enumeration<NamedBeanHandle<Turnout>> e = turnouts.keys(); 1584 while (e.hasMoreElements()) { 1585 NamedBeanHandle<Turnout> nbh = e.nextElement(); 1586 NamedBeanSetting nbs = new NamedBeanSetting(nbh, turnouts.get(nbh)); 1587 userSetTurnouts.add(nbs); 1588 } 1589 } 1590 firePropertyChange(PROPERTY_TURNOUTS, null, this.destinationSignalMast); 1591 } 1592 1593 void setAutoTurnouts(Hashtable<Turnout, Integer> turnouts) { 1594 log.debug("{} called setAutoTurnouts with {}", destinationSignalMast.getDisplayName(), 1595 (turnouts != null ? "" + turnouts.size() + " turnouts in hash table" : "null hash table reference")); 1596 if (this.autoTurnouts != null) { 1597 Enumeration<Turnout> keys = this.autoTurnouts.keys(); 1598 while (keys.hasMoreElements()) { 1599 Turnout key = keys.nextElement(); 1600 key.removePropertyChangeListener(propertyTurnoutListener); 1601 } 1602 //minimumBlockSpeed = 0; 1603 } 1604 destMastInit = false; 1605 if (turnouts == null) { 1606 this.autoTurnouts = new Hashtable<>(0); 1607 } else { 1608 this.autoTurnouts = new Hashtable<>(turnouts); 1609 } 1610 firePropertyChange(PROPERTY_AUTO_TURNOUTS, null, this.destinationSignalMast); 1611 } 1612 1613 void setBlocks(Hashtable<Block, Integer> blocks) { 1614 log.debug("{} Set blocks called", destinationSignalMast.getDisplayName()); 1615 if (this.userSetBlocks != null) { 1616 userSetBlocks.forEach( nbh -> 1617 nbh.getBean().removePropertyChangeListener(propertyBlockListener)); 1618 } 1619 destMastInit = false; 1620 1621 userSetBlocks = new ArrayList<>(0); 1622 if (blocks != null) { 1623 userSetBlocks = new ArrayList<>(); 1624 Enumeration<Block> e = blocks.keys(); 1625 while (e.hasMoreElements()) { 1626 Block blk = e.nextElement(); 1627 NamedBeanHandle<?> nbh = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(blk.getDisplayName(), blk); 1628 NamedBeanSetting nbs = new NamedBeanSetting(nbh, blocks.get(blk)); 1629 userSetBlocks.add(nbs); 1630 } 1631 } 1632 firePropertyChange(PROPERTY_BLOCKS, null, this.destinationSignalMast); 1633 } 1634 1635 public void setAutoBlocks(LinkedHashMap<Block, Integer> blocks) { 1636 if (log.isDebugEnabled()) { 1637 log.debug("{} called setAutoBlocks with {}", destinationSignalMast.getDisplayName(), 1638 (blocks != null ? "" + blocks.size() + " blocks in hash table" : "null hash table reference")); 1639 } 1640 if (this.autoBlocks != null) { 1641 autoBlocks.keySet().forEach( key -> 1642 key.removePropertyChangeListener(propertyBlockListener)); 1643 } 1644 destMastInit = false; 1645 if (blocks == null) { 1646 this.autoBlocks = new LinkedHashMap<>(0); 1647 1648 } else { 1649 this.autoBlocks = new LinkedHashMap<>(blocks); 1650 //We shall remove the facing block in the list. 1651 if (facingBlock != null && autoBlocks.containsKey(facingBlock.getBlock())) { 1652 autoBlocks.remove(facingBlock.getBlock()); 1653 } 1654 if (getAssociatedSection() != null) { 1655 createSectionDetails(); 1656 } 1657 } 1658 firePropertyChange(PROPERTY_AUTO_BLOCKS, null, this.destinationSignalMast); 1659 } 1660 1661 void setMasts(Hashtable<SignalMast, String> masts) { 1662 if (this.userSetMasts != null) { 1663 userSetMasts.forEach( nbh -> 1664 nbh.getBean().removePropertyChangeListener(propertySignalMastListener)); 1665 } 1666 1667 destMastInit = false; 1668 1669 if (masts == null) { 1670 userSetMasts = new ArrayList<>(0); 1671 } else { 1672 userSetMasts = new ArrayList<>(); 1673 Enumeration<SignalMast> e = masts.keys(); 1674 while (e.hasMoreElements()) { 1675 SignalMast mast = e.nextElement(); 1676 NamedBeanHandle<?> nbh = InstanceManager.getDefault(NamedBeanHandleManager.class) 1677 .getNamedBeanHandle(mast.getDisplayName(), mast); 1678 NamedBeanSetting nbs = new NamedBeanSetting(nbh, masts.get(mast)); 1679 userSetMasts.add(nbs); 1680 } 1681 } 1682 firePropertyChange(PROPERTY_MASTS, null, this.destinationSignalMast); 1683 } 1684 1685 /** 1686 * 1687 * @param newAutoMasts Hashtable of signal masts and set to Aspects 1688 * @param overwrite When true, replace an existing autoMasts list in 1689 * the SML 1690 */ 1691 void setAutoMasts(Hashtable<SignalMast, String> newAutoMasts, boolean overwrite) { 1692 log.debug("{} setAutoMast Called", destinationSignalMast.getDisplayName()); 1693 if (this.autoMasts != null) { 1694 Enumeration<SignalMast> keys = this.autoMasts.keys(); 1695 while (keys.hasMoreElements()) { 1696 SignalMast key = keys.nextElement(); 1697 key.removePropertyChangeListener(propertySignalMastListener); 1698 } 1699 //minimumBlockSpeed = 0; 1700 } 1701 destMastInit = false; 1702 if (overwrite) { 1703 if (newAutoMasts == null) { 1704 this.autoMasts = new Hashtable<>(0); 1705 } else { 1706 this.autoMasts = new Hashtable<>(newAutoMasts); 1707 } 1708 } else { 1709 if (newAutoMasts == null) { 1710 this.autoMasts = new Hashtable<>(0); 1711 } else { 1712 Enumeration<SignalMast> keys = newAutoMasts.keys(); 1713 while (keys.hasMoreElements()) { 1714 SignalMast key = keys.nextElement(); 1715 this.autoMasts.put(key, newAutoMasts.get(key)); 1716 } 1717 } 1718 } 1719 //kick off the process to add back in signal masts at crossings. 1720 for (int i = 0; i < blockInXings.size(); i++) { 1721 blockInXings.get(i).addSignalMastLogic(source); 1722 } 1723 1724 firePropertyChange(PROPERTY_AUTO_MASTS, null, this.destinationSignalMast); 1725 } 1726 1727 void setSensors(Hashtable<NamedBeanHandle<Sensor>, Integer> sensors) { 1728 if (this.userSetSensors != null) { 1729 userSetSensors.forEach(nbh -> 1730 nbh.getBean().removePropertyChangeListener(propertySensorListener)); 1731 } 1732 destMastInit = false; 1733 1734 if (sensors == null) { 1735 userSetSensors = new ArrayList<>(0); 1736 } else { 1737 userSetSensors = new ArrayList<>(); 1738 Enumeration<NamedBeanHandle<Sensor>> e = sensors.keys(); 1739 while (e.hasMoreElements()) { 1740 NamedBeanHandle<Sensor> nbh = e.nextElement(); 1741 NamedBeanSetting nbs = new NamedBeanSetting(nbh, sensors.get(nbh)); 1742 userSetSensors.add(nbs); 1743 } 1744 } 1745 firePropertyChange(PROPERTY_SENSORS, null, this.destinationSignalMast); 1746 } 1747 1748 void addSensor(NamedBeanHandle<Sensor> sen, int state) { 1749 for (NamedBeanSetting nbh : userSetSensors) { 1750 if (nbh.getBean().equals(sen.getBean())) { 1751 return; 1752 } 1753 } 1754 sen.getBean().addPropertyChangeListener(propertySensorListener); 1755 NamedBeanSetting nbs = new NamedBeanSetting(sen, state); 1756 userSetSensors.add(nbs); 1757 firePropertyChange(PROPERTY_SENSORS, null, this.destinationSignalMast); 1758 } 1759 1760// not used now, preserved for later use 1761// void removeSensor(NamedBeanHandle<Sensor> sen) { 1762// for (NamedBeanSetting nbh : userSetSensors) { 1763// if (nbh.getBean().equals(sen.getBean())) { 1764// sen.getBean().removePropertyChangeListener(propertySensorListener); 1765// userSetSensors.remove(nbh); 1766// firePropertyChange("sensors", null, this.destination); 1767// return; 1768// } 1769// } 1770// } 1771 1772 void removeSensor(Sensor sen) { 1773 for (NamedBeanSetting nbh : userSetSensors) { 1774 if (nbh.getBean().equals(sen)) { 1775 sen.removePropertyChangeListener(propertySensorListener); 1776 userSetSensors.remove(nbh); 1777 firePropertyChange(PROPERTY_SENSORS, null, this.destinationSignalMast); 1778 return; 1779 } 1780 } 1781 } 1782 1783 List<Block> getBlocks() { 1784 List<Block> out = new ArrayList<>(); 1785 userSetBlocks.forEach( nbh -> out.add((Block) nbh.getBean())); 1786 return out; 1787 } 1788 1789 List<Block> getAutoBlocks() { 1790 List<Block> out = new ArrayList<>(); 1791 Set<Block> blockKeys = autoBlocks.keySet(); 1792 blockKeys.forEach(key -> out.add(key)); 1793 return out; 1794 } 1795 1796 List<Block> getAutoBlocksBetweenMasts() { 1797 if (destList.get(destinationSignalMast).xingAutoBlocks.isEmpty() 1798 && destList.get(destinationSignalMast).dblCrossoverAutoBlocks.isEmpty()) { 1799 return getAutoBlocks(); 1800 } 1801 List<Block> returnList = getAutoBlocks(); 1802 for (Block blk : getAutoBlocks()) { 1803 if (xingAutoBlocks.contains(blk)) { 1804 returnList.remove(blk); 1805 } 1806 if (dblCrossoverAutoBlocks.contains(blk)) { 1807 returnList.remove(blk); 1808 } 1809 } 1810 1811 return returnList; 1812 } 1813 1814 List<Turnout> getTurnouts() { 1815 List<Turnout> out = new ArrayList<>(); 1816 userSetTurnouts.forEach( nbh -> out.add((Turnout) nbh.getBean())); 1817 return out; 1818 } 1819 1820 void removeTurnout(Turnout turn) { 1821 Iterator<NamedBeanSetting> nbh = userSetTurnouts.iterator(); 1822 while (nbh.hasNext()) { 1823 NamedBeanSetting i = nbh.next(); 1824 if (i.getBean().equals(turn)) { 1825 turn.removePropertyChangeListener(propertyTurnoutListener); 1826 nbh.remove(); 1827 firePropertyChange(PROPERTY_TURNOUTS, null, this.destinationSignalMast); 1828 } 1829 } 1830 } 1831 1832 @SuppressWarnings("unchecked") // (NamedBeanHandle<Turnout>) nbh.getNamedBean() is unchecked cast 1833 List<NamedBeanHandle<Turnout>> getNamedTurnouts() { 1834 List<NamedBeanHandle<Turnout>> out = new ArrayList<>(); 1835 userSetTurnouts.forEach(nbh -> 1836 out.add((NamedBeanHandle<Turnout>) nbh.getNamedBean())); 1837 return out; 1838 } 1839 1840 List<Turnout> getAutoTurnouts() { 1841 List<Turnout> out = new ArrayList<>(); 1842 Enumeration<Turnout> en = autoTurnouts.keys(); 1843 while (en.hasMoreElements()) { 1844 out.add(en.nextElement()); 1845 } 1846 return out; 1847 } 1848 1849 List<SignalMast> getSignalMasts() { 1850 List<SignalMast> out = new ArrayList<>(); 1851 userSetMasts.forEach( nbh -> out.add((SignalMast) nbh.getBean())); 1852 return out; 1853 } 1854 1855 List<SignalMast> getAutoSignalMasts() { 1856 List<SignalMast> out = new ArrayList<>(); 1857 Enumeration<SignalMast> en = autoMasts.keys(); 1858 while (en.hasMoreElements()) { 1859 out.add(en.nextElement()); 1860 } 1861 return out; 1862 } 1863 1864 List<Sensor> getSensors() { 1865 List<Sensor> out = new ArrayList<>(); 1866 userSetSensors.forEach( nbh -> out.add((Sensor) nbh.getBean())); 1867 return out; 1868 } 1869 1870 @SuppressWarnings("unchecked") // (NamedBeanHandle<Sensor>) nbh.getNamedBean() is unchecked cast 1871 List<NamedBeanHandle<Sensor>> getNamedSensors() { 1872 List<NamedBeanHandle<Sensor>> out = new ArrayList<>(); 1873 userSetSensors.forEach( nbh -> out.add((NamedBeanHandle<Sensor>) nbh.getNamedBean())); 1874 return out; 1875 } 1876 1877 boolean isBlockIncluded(Block block) { 1878 return userSetBlocks.stream().anyMatch(nbh -> (nbh.getBean().equals(block))); 1879 } 1880 1881 boolean isAutoBlockIncluded(LayoutBlock block) { 1882 if (block != null) { 1883 return autoBlocks.containsKey(block.getBlock()); 1884 } 1885 return false; 1886 } 1887 1888 boolean isAutoBlockIncluded(Block block) { 1889 return autoBlocks.containsKey(block); 1890 } 1891 1892 boolean isBlockIncluded(LayoutBlock block) { 1893 return userSetBlocks.stream().anyMatch(nbh -> (nbh.getBean().equals(block.getBlock()))); 1894 } 1895 1896 boolean isTurnoutIncluded(Turnout turnout) { 1897 return userSetTurnouts.stream().anyMatch(nbh -> (nbh.getBean().equals(turnout))); 1898 } 1899 1900 boolean isSensorIncluded(Sensor sensor) { 1901 return userSetSensors.stream().anyMatch(nbh -> (nbh.getBean().equals(sensor))); 1902 } 1903 1904 boolean isSignalMastIncluded(SignalMast signal) { 1905 return userSetMasts.stream().anyMatch(nbh -> (nbh.getBean().equals(signal))); 1906 } 1907 1908 int getAutoBlockState(Block block) { 1909 if (autoBlocks == null) { 1910 return -1; 1911 } 1912 return autoBlocks.get(block); 1913 } 1914 1915 int getBlockState(Block block) { 1916 if (userSetBlocks == null) { 1917 return -1; 1918 } 1919 for (NamedBeanSetting nbh : userSetBlocks) { 1920 if (nbh.getBean().equals(block)) { 1921 return nbh.getSetting(); 1922 } 1923 } 1924 return -1; 1925 } 1926 1927 int getSensorState(Sensor sensor) { 1928 if (userSetSensors == null) { 1929 return -1; 1930 } 1931 for (NamedBeanSetting nbh : userSetSensors) { 1932 if (nbh.getBean().equals(sensor)) { 1933 return nbh.getSetting(); 1934 } 1935 } 1936 return -1; 1937 } 1938 1939 int getTurnoutState(Turnout turnout) { 1940 if (userSetTurnouts == null) { 1941 return -1; 1942 } 1943 for (NamedBeanSetting nbh : userSetTurnouts) { 1944 if (nbh.getBean().equals(turnout)) { 1945 return nbh.getSetting(); 1946 } 1947 } 1948 return -1; 1949 } 1950 1951 int getAutoTurnoutState(Turnout turnout) { 1952 if (autoTurnouts == null) { 1953 return -1; 1954 } 1955 if (autoTurnouts.containsKey(turnout)) { 1956 return autoTurnouts.get(turnout); 1957 } 1958 return -1; 1959 } 1960 1961 String getSignalMastState(SignalMast mast) { 1962 if (userSetMasts == null) { 1963 return null; 1964 } 1965 for (NamedBeanSetting nbh : userSetMasts) { 1966 if (nbh.getBean().equals(mast)) { 1967 return nbh.getStringSetting(); 1968 } 1969 } 1970 return null; 1971 } 1972 1973 String getAutoSignalMastState(SignalMast mast) { 1974 if (autoMasts == null) { 1975 return null; 1976 } 1977 return autoMasts.get(mast); 1978 } 1979 1980 /** 1981 * True from when a check of this pair is scheduled until it, and any 1982 * repeat of it, has finished. 1983 */ 1984 volatile boolean inWait = false; 1985 1986 /** 1987 * The number of checks of this pair running now: a scheduled one, and 1988 * the first one, which {@link #initialise()} runs on its own thread. 1989 * Guarded by this. 1990 */ 1991 private int checksRunning = 0; 1992 1993 /** 1994 * True if something on the route changed while a check was running, 1995 * so what it found may be out of date and the check must run again. 1996 * Guarded by this. 1997 */ 1998 private boolean checkRepeat = false; 1999 2000 /* 2001 * Before going active or checking that we can go active, wait 2002 * for things to settle down to help prevent a race condition. 2003 * A change that arrives while a check is running may come after the 2004 * check has read the item that changed, so the check then runs again. 2005 */ 2006 synchronized void checkState() { 2007 if (disposed) { 2008 log.error("checkState called even though this has been disposed of {}", 2009 getSourceMast().getDisplayName()); 2010 return; 2011 } 2012 2013 if (!enable) { 2014 return; 2015 } 2016 if (checksRunning > 0) { 2017 checkRepeat = true; 2018 } 2019 if (inWait) { 2020 return; 2021 } 2022 2023 log.debug("check Signal Dest State called"); 2024 inWait = true; 2025 scheduleCheck(); 2026 } 2027 2028 /** 2029 * Schedule the check of this pair. Called with this locked. 2030 */ 2031 private void scheduleCheck() { 2032 // The next line forces a single initialization of InstanceManager.getDefault(SignalMastLogicManager.class) 2033 // before launching parallel threads 2034 int tempDelay = InstanceManager.getDefault(SignalMastLogicManager.class).getSignalLogicDelay(); 2035 2036 ThreadingUtil.runOnLayoutDelayed(() -> runCheck(true), tempDelay); 2037 } 2038 2039 /** 2040 * Check this pair, as a scheduled check or straight away. 2041 * 2042 * @param scheduled true for the check scheduled by 2043 * {@link #checkState()}, which clears 2044 * {@link #inWait} when it has finished 2045 */ 2046 private void runCheck(boolean scheduled) { 2047 synchronized (this) { 2048 if (checksRunning == 0) { 2049 checkRepeat = false; 2050 } 2051 checksRunning++; 2052 } 2053 try { 2054 // a delayed check can still run after this pair has been disposed 2055 if (!disposed) { 2056 checkStateDetails(); 2057 } 2058 } finally { 2059 synchronized (this) { 2060 checksRunning--; 2061 boolean repeat = false; 2062 if (checksRunning == 0) { 2063 // a scheduled check that has not started yet will see the change anyway 2064 repeat = checkRepeat && !disposed && enable && (scheduled || !inWait); 2065 checkRepeat = false; 2066 } 2067 if (repeat) { 2068 inWait = true; 2069 try { 2070 scheduleCheck(); 2071 } catch (RuntimeException ex) { 2072 inWait = false; 2073 log.error("Could not check {} to {} again", getSourceMast().getDisplayName(), 2074 destinationSignalMast.getDisplayName(), ex); 2075 } 2076 } else if (scheduled) { 2077 inWait = false; 2078 } 2079 } 2080 } 2081 } 2082 2083 /** 2084 * A bean on the route has changed so that the route is no longer 2085 * set: go to danger at once. A check that is running may already have 2086 * read that bean, so it must not put back what it found; it runs 2087 * again instead. Called by the bean listeners, on whatever thread the 2088 * bean reports its change on. 2089 */ 2090 private void routeNoLongerSet() { 2091 boolean wasActive; 2092 synchronized (this) { 2093 if (checksRunning > 0) { 2094 checkRepeat = true; 2095 } 2096 wasActive = active; 2097 active = false; 2098 } 2099 if (wasActive) { 2100 setSignalAppearance(); 2101 } 2102 } 2103 2104 /** 2105 * Check the details of this source-destination signal mast logic pair. 2106 * Steps through every sensor, turnout etc. before setting the SML 2107 * Aspect on the source mast via { 2108 * 2109 * @see #setSignalAppearance } and { 2110 * @see #setMastAppearance } 2111 */ 2112 private void checkStateDetails() { 2113 turnoutThrown = false; 2114 permissiveBlock = false; 2115 if (disposed) { 2116 log.error("checkStateDetails called even though this has been disposed of {} {}", 2117 getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName()); 2118 return; 2119 } 2120 if (!enable) { 2121 return; 2122 } 2123 log.debug("From {} to {} internal check state", getSourceMast().getDisplayName(), 2124 destinationSignalMast.getDisplayName()); 2125 boolean wasActive = active; 2126 active = false; 2127 if ((useLayoutEditor) && (autoTurnouts.isEmpty()) && (autoBlocks.isEmpty())) { 2128 return; 2129 } 2130 boolean state = true; 2131 Enumeration<Turnout> keys = autoTurnouts.keys(); 2132 while (keys.hasMoreElements()) { 2133 Turnout key = keys.nextElement(); 2134 if (key.getKnownState() != autoTurnouts.get(key)) { 2135 if (key.getState() != autoTurnouts.get(key)) { 2136 if (isTurnoutIncluded(key)) { 2137 if (key.getState() != getTurnoutState(key)) { 2138 state = false; 2139 } else if (key.getState() == Turnout.THROWN) { 2140 turnoutThrown = true; 2141 } 2142 } else { 2143 state = false; 2144 } 2145 } 2146 } else if (key.getState() == Turnout.THROWN) { 2147 turnoutThrown = true; 2148 } 2149 } 2150 2151 for (NamedBeanSetting nbh : userSetTurnouts) { 2152 Turnout key = (Turnout) nbh.getBean(); 2153 if (key.getKnownState() != nbh.getSetting()) { 2154 state = false; 2155 } else if (key.getState() == Turnout.THROWN) { 2156 turnoutThrown = true; 2157 } 2158 } 2159 2160 Enumeration<SignalMast> mastKeys = autoMasts.keys(); 2161 while (mastKeys.hasMoreElements()) { 2162 SignalMast key = mastKeys.nextElement(); 2163 String aspect = key.getAspect(); 2164 log.debug("key {} {} {}", key.getDisplayName(), aspect, autoMasts.get(key)); 2165 if ((aspect != null) && (!aspect.equals(autoMasts.get(key)))) { 2166 if (isSignalMastIncluded(key)) { 2167 //Basically if we have a blank aspect, we don't care about the state of the signalmast 2168 if (!getSignalMastState(key).isEmpty()) { 2169 if (!aspect.equals(getSignalMastState(key))) { 2170 state = false; 2171 } 2172 } 2173 } else { 2174 state = false; 2175 } 2176 } 2177 } 2178 for (NamedBeanSetting nbh : userSetMasts) { 2179 SignalMast key = (SignalMast) nbh.getBean(); 2180 String aspect = key.getAspect(); 2181 if ((aspect == null) || (!aspect.equals(nbh.getStringSetting()))) { 2182 state = false; 2183 } 2184 } 2185 2186 for (NamedBeanSetting nbh : userSetSensors) { 2187 Sensor key = (Sensor) nbh.getBean(); 2188 if (key.getKnownState() != nbh.getSetting()) { 2189 state = false; 2190 } 2191 } 2192 2193 for (Map.Entry<Block, Integer> entry : this.autoBlocks.entrySet()) { 2194 log.debug(" entry {} {} {}", entry.getKey().getDisplayName(), 2195 entry.getKey().getState(), entry.getValue()); 2196 if (entry.getKey().getState() != autoBlocks.get(entry.getKey())) { 2197 if (isBlockIncluded(entry.getKey())) { 2198 if (getBlockState(entry.getKey()) != 0x03) { 2199 if (entry.getKey().getState() != getBlockState(entry.getKey())) { 2200 if (entry.getKey().getState() == Block.OCCUPIED && entry.getKey().getPermissiveWorking()) { 2201 permissiveBlock = true; 2202 } else { 2203 state = false; 2204 } 2205 } 2206 } 2207 } else { 2208 if (entry.getKey().getState() == Block.OCCUPIED && entry.getKey().getPermissiveWorking()) { 2209 permissiveBlock = true; 2210 } else if (entry.getKey().getState() == Block.UNDETECTED) { 2211 log.debug("Block {} is UNDETECTED so treat as unoccupied", entry.getKey().getDisplayName()); 2212 } else { 2213 state = false; 2214 } 2215 } 2216 } 2217 } 2218 2219 for (NamedBeanSetting nbh : userSetBlocks) { 2220 Block key = (Block) nbh.getBean(); 2221 if (nbh.getSetting() != 0x03) { 2222 if (key.getState() != nbh.getSetting()) { 2223 if (key.getState() == Block.OCCUPIED && key.getPermissiveWorking()) { 2224 permissiveBlock = true; 2225 } else { 2226 state = false; 2227 } 2228 } 2229 } 2230 } 2231 if (permissiveBlock 2232 /*If a block has been found to be permissive, but the source signalmast 2233 does not support a call-on/permissive aspect then the route can not be set*/ 2234 && getSourceMast().getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.PERMISSIVE) == null) { 2235 state = false; 2236 } 2237 2238 /*This check is purely for use with the dispatcher. A block set to "useExtraColor" has been 2239 allocated; if it is held by a train other than the one approaching this mast, the signal 2240 is set to danger.*/ 2241 if (state && getAssociatedSection() != null 2242 && InstanceManager.isInitialized(jmri.jmrit.dispatcher.DispatcherFrame.class) 2243 && InstanceManager.getNullableDefault(LayoutBlockManager.class) != null) { 2244 2245 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 2246 List<Block> routeBlocks = new ArrayList<>(autoBlocks.keySet()); 2247 for (NamedBeanSetting nbh : userSetBlocks) { 2248 routeBlocks.add((Block) nbh.getBean()); 2249 } 2250 if (isRouteHeldByAnotherTrain(routeBlocks, lbm)) { 2251 state = false; 2252 } 2253 } 2254 2255 synchronized (this) { 2256 if (checkRepeat) { 2257 // something on the route changed while this check ran, so 2258 // what it found may be out of date: stay at danger until 2259 // the check has run again 2260 state = false; 2261 } 2262 if (!state) { 2263 turnoutThrown = false; 2264 permissiveBlock = false; 2265 } 2266 active = state; 2267 } 2268 // The source mast only needs setting again if this pair has changed 2269 // or it is the pair the source mast is currently showing. 2270 if (state != wasActive || destination == destinationSignalMast) { 2271 ThreadingUtil.runOnLayout(() -> { 2272 setSignalAppearance(); 2273 }); 2274 } 2275 } 2276 2277 /** 2278 * Set up this source-destination signal mast logic pair. Steps through 2279 * every list defined on the source mast. 2280 */ 2281 void initialise() { 2282 if ((destMastInit) || (disposed)) { 2283 return; 2284 } 2285 2286 active = false; 2287 turnoutThrown = false; 2288 permissiveBlock = false; 2289 if ((useLayoutEditor) && (autoTurnouts.isEmpty()) && (autoBlocks.isEmpty()) && (autoMasts.isEmpty())) { 2290 return; 2291 } 2292 2293 calculateSpeed(); 2294 2295 // Each listener is removed before it is added, so that initialising 2296 // again (e.g. after one list has been changed) does not add it twice. 2297 Enumeration<Turnout> keys = autoTurnouts.keys(); 2298 while (keys.hasMoreElements()) { 2299 Turnout key = keys.nextElement(); 2300 key.removePropertyChangeListener(propertyTurnoutListener); 2301 key.addPropertyChangeListener(propertyTurnoutListener); 2302 } 2303 2304 for (NamedBeanSetting nbh : userSetTurnouts) { 2305 Turnout key = (Turnout) nbh.getBean(); 2306 key.removePropertyChangeListener(propertyTurnoutListener); 2307 key.addPropertyChangeListener(propertyTurnoutListener, nbh.getBeanName(), 2308 "Signal Mast Logic:" + source.getDisplayName() + " to " + destinationSignalMast.getDisplayName()); 2309 } 2310 2311 Enumeration<SignalMast> mastKeys = autoMasts.keys(); 2312 while (mastKeys.hasMoreElements()) { 2313 SignalMast key = mastKeys.nextElement(); 2314 log.debug("{} auto mast add list {}", destinationSignalMast.getDisplayName(), key.getDisplayName()); 2315 key.removePropertyChangeListener(propertySignalMastListener); 2316 key.addPropertyChangeListener(propertySignalMastListener); 2317 } 2318 2319 for (NamedBeanSetting nbh : userSetMasts) { 2320 SignalMast key = (SignalMast) nbh.getBean(); 2321 key.removePropertyChangeListener(propertySignalMastListener); 2322 key.addPropertyChangeListener(propertySignalMastListener); 2323 } 2324 for (NamedBeanSetting nbh : userSetSensors) { 2325 Sensor sensor = (Sensor) nbh.getBean(); 2326 sensor.removePropertyChangeListener(propertySensorListener); 2327 sensor.addPropertyChangeListener(propertySensorListener, nbh.getBeanName(), 2328 "Signal Mast Logic:" + source.getDisplayName() + " to " + destinationSignalMast.getDisplayName()); 2329 } 2330 2331 for (Block key : autoBlocks.keySet()) { 2332 log.debug("{} auto block add list {}", destinationSignalMast.getDisplayName(), key.getDisplayName()); 2333 key.removePropertyChangeListener(propertyBlockListener); 2334 key.addPropertyChangeListener(propertyBlockListener); 2335 } 2336 2337 for (NamedBeanSetting nbh : userSetBlocks) { 2338 Block key = (Block) nbh.getBean(); 2339 key.removePropertyChangeListener(propertyBlockListener); 2340 key.addPropertyChangeListener(propertyBlockListener); 2341 } 2342 listenForSectionHolders(); 2343 destMastInit = true; 2344 2345 // Use the same checks as when one of these beans changes. 2346 runCheck(false); 2347 } 2348 2349 void useLayoutEditor(boolean boo) throws JmriException { 2350 log.debug("{} called useLayoutEditor({}), is {}", 2351 destinationSignalMast.getDisplayName(), boo, useLayoutEditor); 2352 if (useLayoutEditor == boo) { 2353 return; 2354 } 2355 useLayoutEditor = boo; 2356 if ((boo) && (InstanceManager.getDefault(LayoutBlockManager.class).routingStablised())) { 2357 // JmriException considered normal if there is no valid path using the layout editor. 2358 setupLayoutEditorDetails(); 2359 } else { 2360 destinationBlock = null; 2361 // the facing block belongs to the source mast, so keep it while 2362 // another destination still uses the Layout Editor 2363 if (destList.values().stream().noneMatch(DestinationMast::useLayoutEditor)) { 2364 facingBlock = null; 2365 } 2366 protectingBlock = null; 2367 setAutoBlocks(null); 2368 setAutoTurnouts(null); 2369 } 2370 } 2371 2372 void useLayoutEditorDetails(boolean turnouts, boolean blocks) throws JmriException { 2373 log.debug("{} use layout editor details called {}", 2374 destinationSignalMast.getDisplayName(), useLayoutEditor); 2375 useLayoutEditorTurnouts = turnouts; 2376 useLayoutEditorBlocks = blocks; 2377 if ((useLayoutEditor) && (InstanceManager.getDefault(LayoutBlockManager.class).routingStablised())) { 2378 // JmriException considered normal if there is no valid path using the Layout Editor. 2379 setupLayoutEditorDetails(); 2380 } 2381 } 2382 2383 void setupLayoutEditorDetails() throws JmriException { 2384 log.debug("setupLayoutEditorDetails: useLayoutEditor={} disposed={}", useLayoutEditor, disposed); 2385 if ((!useLayoutEditor) || (disposed)) { 2386 return; 2387 } 2388 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 2389 if ( destinationBlock != null) { 2390 log.debug("{} Set use layout editor", destinationSignalMast.getDisplayName()); 2391 } 2392 Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class); 2393 List<LayoutBlock> protectingBlocks = new ArrayList<>(); 2394 // We don't care which Layout Editor panel the signal mast is on, just so long as 2395 // the routing is done via layout blocks. 2396 remoteProtectingBlock = null; 2397 for (LayoutEditor panel : layout) { 2398 log.debug("{} Layout name {}", destinationSignalMast.getDisplayName(), panel.getLayoutName()); 2399 if (facingBlock == null) { 2400 facingBlock = lbm.getFacingBlockByNamedBean(getSourceMast(), panel); 2401 } 2402 if (protectingBlock == null && protectingBlocks.isEmpty()) { 2403 //This is wrong 2404 protectingBlocks = lbm.getProtectingBlocksByNamedBean(getSourceMast(), panel); 2405 } 2406 if (destinationBlock == null) { 2407 destinationBlock = lbm.getFacingBlockByNamedBean(destinationSignalMast, panel); 2408 } 2409 if (remoteProtectingBlock == null) { 2410 remoteProtectingBlock = lbm.getProtectedBlockByNamedBean(destinationSignalMast, panel); 2411 } 2412 } 2413 // At this point, if we are not using the Layout Editor turnout or block 2414 // details then there is no point in trying to gather them. 2415 if ((!useLayoutEditorTurnouts) && (!useLayoutEditorBlocks)) { 2416 return; 2417 } 2418 if (facingBlock == null) { 2419 log.error("No facing block found for source mast {}", getSourceMast().getDisplayName()); 2420 throw new JmriException("No facing block found for source mast " + getSourceMast().getDisplayName()); 2421 } 2422 if (destinationBlock == null) { 2423 log.error("No facing block found for destination mast {}", destinationSignalMast.getDisplayName()); 2424 throw new JmriException("No facing block found for destination mast " + destinationSignalMast.getDisplayName()); 2425 } 2426 List<LayoutBlock> lblks = new ArrayList<>(); 2427 if (protectingBlock == null) { 2428 log.debug("protecting block is null"); 2429 String pBlkNames = ""; 2430 StringBuffer lBlksNamesBuf = new StringBuffer(); 2431 for (LayoutBlock pBlk : protectingBlocks) { 2432 log.debug("checking layoutBlock {}", pBlk.getDisplayName()); 2433 pBlkNames = pBlkNames + pBlk.getDisplayName() + " (" + lbm.getLayoutBlockConnectivityTools().checkValidDest(facingBlock, pBlk, destinationBlock, remoteProtectingBlock, LayoutBlockConnectivityTools.Routing.MASTTOMAST) + "), "; 2434 if (lbm.getLayoutBlockConnectivityTools().checkValidDest(facingBlock, pBlk, destinationBlock, remoteProtectingBlock, LayoutBlockConnectivityTools.Routing.MASTTOMAST)) { 2435 try { 2436 lblks = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks(facingBlock, destinationBlock, pBlk, true, LayoutBlockConnectivityTools.Routing.MASTTOMAST); 2437 protectingBlock = pBlk; 2438 log.debug("building path names..."); 2439 for (LayoutBlock lBlk : lblks) { 2440 lBlksNamesBuf.append(" "); 2441 lBlksNamesBuf.append(lBlk.getDisplayName()); 2442 } 2443 break; 2444 } catch (JmriException ee) { 2445 log.debug("path not found this time"); 2446 } 2447 } 2448 } 2449 String lBlksNames = new String(lBlksNamesBuf); 2450 2451 if (protectingBlock == null) { 2452 throw new JmriException("Path not valid, protecting block is null. Protecting block: " + pBlkNames 2453 + " not connected to " + facingBlock.getDisplayName() + ". Layout block names: " + lBlksNames); 2454 } 2455 } 2456 if (!lbm.getLayoutBlockConnectivityTools().checkValidDest(facingBlock,protectingBlock, 2457 destinationBlock, remoteProtectingBlock, LayoutBlockConnectivityTools.Routing.MASTTOMAST)) { 2458 throw new JmriException("Path not valid, destination check failed."); 2459 } 2460 if (log.isDebugEnabled()) { 2461 log.debug("{} face {}", destinationSignalMast.getDisplayName(), facingBlock); 2462 log.debug("{} prot {}", destinationSignalMast.getDisplayName(), protectingBlock); 2463 log.debug("{} dest {}", destinationSignalMast.getDisplayName(), destinationBlock); 2464 } 2465 2466 if (destinationBlock != null && protectingBlock != null && facingBlock != null) { 2467 setAutoMasts(null, true); 2468 if (log.isDebugEnabled()) { 2469 log.debug("{} face {}", destinationSignalMast.getDisplayName(), facingBlock.getDisplayName()); 2470 log.debug("{} prot {}", destinationSignalMast.getDisplayName(), protectingBlock.getDisplayName()); 2471 log.debug("{} dest {}", destinationSignalMast.getDisplayName(), destinationBlock.getDisplayName()); 2472 } 2473 2474 try { 2475 lblks = lbm.getLayoutBlockConnectivityTools().getLayoutBlocks( 2476 facingBlock, destinationBlock, protectingBlock, 2477 true, LayoutBlockConnectivityTools.Routing.MASTTOMAST); 2478 } catch (JmriException ee) { 2479 log.error("No blocks found by the layout editor for pair {}-{}", 2480 source.getDisplayName(), destinationSignalMast.getDisplayName()); 2481 } 2482 LinkedHashMap<Block, Integer> block = setupLayoutEditorTurnoutDetails(lblks); 2483 2484 for (int i = 0; i < blockInXings.size(); i++) { 2485 blockInXings.get(i).removeSignalMastLogic(source); 2486 } 2487 blockInXings = new ArrayList<>(0); 2488 xingAutoBlocks = new ArrayList<>(0); 2489 for (LayoutEditor lay : layout) { 2490 for (LevelXing levelXing : lay.getLevelXings()) { 2491 //Looking for a crossing that both layout blocks defined and they are individual. 2492 if ((levelXing.getLayoutBlockAC() != null) 2493 && (levelXing.getLayoutBlockBD() != null) 2494 && (levelXing.getLayoutBlockAC() != levelXing.getLayoutBlockBD())) { 2495 if (lblks.contains(levelXing.getLayoutBlockAC()) && 2496 levelXing.getLayoutBlockAC() != facingBlock) { // Don't include the facing xing blocks 2497 block.put(levelXing.getLayoutBlockBD().getBlock(), Block.UNOCCUPIED); 2498 xingAutoBlocks.add(levelXing.getLayoutBlockBD().getBlock()); 2499 blockInXings.add(levelXing); 2500 } else if (lblks.contains(levelXing.getLayoutBlockBD()) && 2501 levelXing.getLayoutBlockBD() != facingBlock) { // Don't include the facing xing blocks 2502 block.put(levelXing.getLayoutBlockAC().getBlock(), Block.UNOCCUPIED); 2503 xingAutoBlocks.add(levelXing.getLayoutBlockAC().getBlock()); 2504 blockInXings.add(levelXing); 2505 } 2506 } 2507 } 2508 } 2509 if (useLayoutEditorBlocks) { 2510 setAutoBlocks(block); 2511 } else { 2512 setAutoBlocks(null); 2513 } 2514 if (!useLayoutEditorTurnouts) { 2515 setAutoTurnouts(null); 2516 } 2517 2518 setupAutoSignalMast(null, false); 2519 } 2520 initialise(); 2521 } 2522 2523 /** 2524 * From a list of Layout Blocks, search for included Turnouts and their 2525 * Set To state. 2526 * 2527 * @param lblks List of Layout Blocks 2528 * @return a list of block - turnout state pairs 2529 */ 2530 LinkedHashMap<Block, Integer> setupLayoutEditorTurnoutDetails(List<LayoutBlock> lblks) { 2531 ConnectivityUtil connection; 2532 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList; 2533 Hashtable<Turnout, Integer> turnoutSettings = new Hashtable<>(); 2534 LinkedHashMap<Block, Integer> block = new LinkedHashMap<>(); 2535 for (int i = 0; i < lblks.size(); i++) { 2536 log.debug("layoutblock {}",lblks.get(i).getDisplayName()); 2537 block.put(lblks.get(i).getBlock(), Block.UNOCCUPIED); 2538 if ((i > 0)) { 2539 int nxtBlk = i + 1; 2540 int preBlk = i - 1; 2541 if (i == lblks.size() - 1) { 2542 nxtBlk = i; 2543 } 2544 //We use the best connectivity for the current block. 2545 connection = new ConnectivityUtil(lblks.get(i).getMaxConnectedPanel()); 2546 if (i == lblks.size() - 1 && remoteProtectingBlock != null) { 2547 turnoutList = connection.getTurnoutList(lblks.get(i) 2548 .getBlock(), lblks.get(preBlk).getBlock(), remoteProtectingBlock.getBlock()); 2549 }else{ 2550 turnoutList = connection.getTurnoutList(lblks.get(i) 2551 .getBlock(), lblks.get(preBlk).getBlock(), lblks.get(nxtBlk).getBlock()); 2552 } 2553 for (int x = 0; x < turnoutList.size(); x++) { 2554 LayoutTurnout lt = turnoutList.get(x).getObject(); 2555 if (lt instanceof LayoutSlip) { 2556 LayoutSlip ls = (LayoutSlip) lt; 2557 int slipState = turnoutList.get(x).getExpectedState(); 2558 int taState = ls.getTurnoutState(slipState); 2559 Turnout tTemp = ls.getTurnout(); 2560 if (tTemp == null ) { 2561 log.error("Unexpected null Turnout in {}, skipped", ls); 2562 continue; // skip this one in loop, what else can you do? 2563 } 2564 turnoutSettings.put(ls.getTurnout(), taState); 2565 int tbState = ls.getTurnoutBState(slipState); 2566 turnoutSettings.put(ls.getTurnoutB(), tbState); 2567 } else if ( lt != null ) { 2568 String t = lt.getTurnoutName(); 2569 // temporary = why is this looking up the Turnout instead of using getTurnout()? 2570 Turnout turnout = InstanceManager.turnoutManagerInstance().getTurnout(t); 2571 if (log.isDebugEnabled()) { 2572 if ( (lt.getTurnoutType() == LayoutTurnout.TurnoutType.RH_TURNOUT || 2573 lt.getTurnoutType() == LayoutTurnout.TurnoutType.LH_TURNOUT || 2574 lt.getTurnoutType() == LayoutTurnout.TurnoutType.WYE_TURNOUT) 2575 && (!lt.getBlockName().isEmpty())) { 2576 log.debug("turnout in list is straight left/right wye"); 2577 log.debug("turnout block Name {}", lt.getBlockName()); 2578 log.debug("current {} - pre {}", lblks.get(i).getBlock().getDisplayName(), lblks.get(preBlk).getBlock().getDisplayName()); 2579 log.debug("A {}", lt.getConnectA()); 2580 log.debug("B {}", lt.getConnectB()); 2581 log.debug("C {}", lt.getConnectC()); 2582 log.debug("D {}", lt.getConnectD()); 2583 } 2584 } 2585 if (turnout != null ) { 2586 turnoutSettings.put(turnout, turnoutList.get(x).getExpectedState()); 2587 } 2588 Turnout tempT; 2589 if ((tempT = lt.getSecondTurnout()) != null) { 2590 turnoutSettings.put(tempT, turnoutList.get(x).getExpectedState()); 2591 } 2592 /* TODO: We could do with a more intelligent way to deal with double crossovers, other than 2593 just looking at the state of the other conflicting blocks, such as looking at Signalmasts 2594 that protect the other blocks and the settings of any other turnouts along the way. 2595 */ 2596 if (lt.getTurnoutType() == LayoutTurnout.TurnoutType.DOUBLE_XOVER) { 2597 LayoutBlock tempLB; 2598 if (turnoutList.get(x).getExpectedState() == Turnout.THROWN) { 2599 if (lt.getLayoutBlock() == lblks.get(i) || lt.getLayoutBlockC() == lblks.get(i)) { 2600 // A or C, add B and D to remove list unless A=B or C=D 2601 if ((tempLB = lt.getLayoutBlockB()) != null) { 2602 if (!tempLB.equals(lt.getLayoutBlock())) { 2603 dblCrossoverAutoBlocks.add(tempLB.getBlock()); 2604 } 2605 block.put(tempLB.getBlock(), Block.UNOCCUPIED); 2606 } 2607 if ((tempLB = lt.getLayoutBlockD()) != null) { 2608 if (!tempLB.equals(lt.getLayoutBlockC())) { 2609 dblCrossoverAutoBlocks.add(tempLB.getBlock()); 2610 } 2611 block.put(tempLB.getBlock(), Block.UNOCCUPIED); 2612 } 2613 } else if (lt.getLayoutBlockB() == lblks.get(i) || lt.getLayoutBlockD() == lblks.get(i)) { 2614 // B or D, add A and C to remove list unless A=B or C=D 2615 if ((tempLB = lt.getLayoutBlock()) != null) { 2616 if (!tempLB.equals(lt.getLayoutBlockB())) { 2617 dblCrossoverAutoBlocks.add(tempLB.getBlock()); 2618 } 2619 block.put(tempLB.getBlock(), Block.UNOCCUPIED); 2620 } 2621 if ((tempLB = lt.getLayoutBlockC()) != null) { 2622 if (!tempLB.equals(lt.getLayoutBlockD())) { 2623 dblCrossoverAutoBlocks.add(tempLB.getBlock()); 2624 } 2625 block.put(tempLB.getBlock(), Block.UNOCCUPIED); 2626 } 2627 } 2628 } 2629 } 2630 } 2631 } 2632 } 2633 } 2634 // ----- Begin Turntable Alignment Check ----- 2635 // For paths onto a turntable, we must add a condition for the turntable's alignment. The 2636 // turnout that controls the turntable position is associated with the specific ray track. 2637 // Each ray track can have a control turnout assigned. Setting this turnout to a specific 2638 // state (usually THROWN) is the command to align the turntable to that ray. 2639 // The following logic adds a CONDITION to the Signal Mast Logic, requiring that the 2640 // correct ray's turnout is in its required state before the signal will clear. 2641 // This does NOT command the turnout to move; it only checks its state for the interlocking. 2642 Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class); 2643 for (LayoutEditor lay : layout) { 2644 for (LayoutTurntable turntable : lay.getLayoutTurntables()) { 2645 // Check for a path from an Approach Mast to the Buffer Mast (Path 3). 2646 // The destination block is the turntable block, and the destination mast is the buffer mast. 2647 if (turntable.getLayoutBlock() == destinationBlock && turntable.getBufferMast() == destinationSignalMast) { 2648 // The source mast's facing block is the ray block for this path. 2649 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 2650 if (ray.getConnect() != null && ray.getConnect().getLayoutBlock() == DefaultSignalMastLogic.this.facingBlock) { 2651 // This is the correct ray. Get its control turnout and required state. 2652 Turnout rayTurnout = ray.getTurnout(); 2653 int requiredState = ray.getTurnoutState(); 2654 if (rayTurnout != null) { 2655 turnoutSettings.put(rayTurnout, requiredState); 2656 } 2657 break; // Found the ray, no need to check others. 2658 } 2659 } 2660 } 2661 // Check for a path from the Exit Mast to a remote mast (Path 1). 2662 // The source mast is the turntable's exit mast. 2663 if (turntable.getExitSignalMast() == getSourceMast()) { 2664 // The protecting block is the ray block for this path. 2665 for (LayoutTurntable.RayTrack ray : turntable.getRayTrackList()) { 2666 if (ray.getConnect() != null && ray.getConnect().getLayoutBlock() == protectingBlock) { 2667 // This is the correct ray. Get its control turnout and required state. 2668 Turnout rayTurnout = ray.getTurnout(); 2669 int requiredState = ray.getTurnoutState(); 2670 if (rayTurnout != null) { 2671 turnoutSettings.put(rayTurnout, requiredState); 2672 } 2673 break; // Found the ray, no need to check others. 2674 } 2675 } 2676 } 2677 } 2678 } 2679 // ----- End Turntable Alignment Check ----- 2680 // ----- Begin Traverser Alignment Check ----- 2681 for (LayoutEditor lay : layout) { 2682 for (LayoutTraverser traverser : lay.getLayoutTraversers()) { 2683 // Check for a path from an Approach Mast to the Buffer Mast (Path 3). 2684 // The destination block is the traverser block, and the destination mast is the buffer mast. 2685 if (traverser.getLayoutBlock() == destinationBlock && traverser.getBufferMast() == destinationSignalMast) { 2686 // The source mast's facing block is the slot block for this path. 2687 for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) { 2688 if (slot.getConnect() != null && slot.getConnect().getLayoutBlock() == DefaultSignalMastLogic.this.facingBlock) { 2689 // This is the correct slot. Get its control turnout and required state. 2690 Turnout slotTurnout = slot.getTurnout(); 2691 int requiredState = slot.getTurnoutState(); 2692 if (slotTurnout != null) { 2693 turnoutSettings.put(slotTurnout, requiredState); 2694 } 2695 break; // Found the slot, no need to check others. 2696 } 2697 } 2698 } 2699 // Check for a path from the Exit Mast to a remote mast (Path 1). 2700 // The source mast is the traverser's exit mast. 2701 if (traverser.getExitSignalMast() == getSourceMast()) { 2702 // The protecting block is the slot block for this path. 2703 for (LayoutTraverser.SlotTrack slot : traverser.getSlotList()) { 2704 if (slot.getConnect() != null && slot.getConnect().getLayoutBlock() == protectingBlock) { 2705 // This is the correct slot. Get its control turnout and required state. 2706 Turnout slotTurnout = slot.getTurnout(); 2707 int requiredState = slot.getTurnoutState(); 2708 if (slotTurnout != null) { 2709 turnoutSettings.put(slotTurnout, requiredState); 2710 } 2711 break; // Found the slot, no need to check others. 2712 } 2713 } 2714 } 2715 } 2716 } 2717 // ----- End Traverser Alignment Check ----- 2718 if (useLayoutEditorTurnouts) { 2719 setAutoTurnouts(turnoutSettings); 2720 } 2721 return block; 2722 } 2723 2724 /** 2725 * Generate auto signalmast for a given SML. Looks through all the other 2726 * logics to see if there are any blocks that are in common and thus 2727 * will add the other signal mast protecting that block. 2728 * 2729 * @param sml The Signal Mast Logic for which to set up 2730 * autoSignalMasts 2731 * @param overwrite When true, replace an existing autoMasts list in the 2732 * SML 2733 */ 2734 void setupAutoSignalMast(SignalMastLogic sml, boolean overwrite) { 2735 if (!allowAutoSignalMastGeneration) { 2736 return; 2737 } 2738 List<SignalMastLogic> smlList = InstanceManager.getDefault(SignalMastLogicManager.class) 2739 .getLogicsByDestination(destinationSignalMast); 2740 List<Block> allBlock = new ArrayList<>(); 2741 2742 userSetBlocks.forEach(nbh -> allBlock.add((Block) nbh.getBean())); 2743 2744 Set<Block> blockKeys = autoBlocks.keySet(); 2745 blockKeys.stream().filter(key -> (!allBlock.contains(key))).forEachOrdered(key -> 2746 allBlock.add(key)); 2747 Hashtable<SignalMast, String> masts; 2748 if (sml != null) { 2749 masts = autoMasts; 2750 if (sml.areBlocksIncluded(allBlock)) { 2751 SignalMast mast = sml.getSourceMast(); 2752 String danger = mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER); 2753 masts.put(mast, danger); 2754 } else { 2755 //No change so will leave. 2756 return; 2757 } 2758 } else { 2759 masts = new Hashtable<>(); 2760 for (int i = 0; i < smlList.size(); i++) { 2761 if (smlList.get(i).areBlocksIncluded(allBlock)) { 2762 SignalMast mast = smlList.get(i).getSourceMast(); 2763 String danger = mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER); 2764 masts.put(mast, danger); 2765 } 2766 } 2767 } 2768 setAutoMasts(masts, overwrite); 2769 } 2770 2771 /** 2772 * Add a certain Signal Mast to the list of AutoSignalMasts for this 2773 * SML. 2774 * 2775 * @param mast The Signal Mast to be added 2776 */ 2777 void addAutoSignalMast(SignalMast mast) { 2778 log.debug("{} add mast to auto list {}", destinationSignalMast.getDisplayName(), mast); 2779 String danger = mast.getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER); 2780 if (danger == null) { 2781 log.error("Can not add SignalMast {} to logic for {} to {} " 2782 + "as it does not have a Danger appearance configured", 2783 mast.getDisplayName(), source.getDisplayName(), destinationSignalMast.getDisplayName()); 2784 return; 2785 } 2786 this.autoMasts.put(mast, danger); 2787 if (destMastInit) { 2788 mast.addPropertyChangeListener(propertySignalMastListener); 2789 } 2790 firePropertyChange(PROPERTY_AUTO_MASTS, null, this.destinationSignalMast); 2791 } 2792 2793 /** 2794 * Remove a certain Signal Mast from the list of AutoSignalMasts for 2795 * this SML. 2796 * 2797 * @param mast The Signal Mast to be removed 2798 */ 2799 void removeAutoSignalMast(SignalMast mast) { 2800 this.autoMasts.remove(mast); 2801 if (destMastInit) { 2802 mast.removePropertyChangeListener(propertySignalMastListener); 2803 } 2804 firePropertyChange(PROPERTY_AUTO_MASTS, this.destinationSignalMast, null); 2805 } 2806 2807 boolean useLayoutEditor() { 2808 return useLayoutEditor; 2809 } 2810 2811 boolean useLayoutEditorBlocks() { 2812 return useLayoutEditorBlocks; 2813 } 2814 2815 boolean useLayoutEditorTurnouts() { 2816 return useLayoutEditorTurnouts; 2817 } 2818 2819 boolean allowAutoSignalMastGeneration = false; 2820 2821 boolean allowAutoSignalMastGen() { 2822 return allowAutoSignalMastGeneration; 2823 } 2824 2825 void allowAutoSignalMastGen(boolean gen) { 2826 if (allowAutoSignalMastGeneration == gen) { 2827 return; 2828 } 2829 allowAutoSignalMastGeneration = gen; 2830 } 2831 2832 /** 2833 * Remove references from this Destination Mast and clear lists, so that 2834 * it can eventually be garbage-collected. 2835 * <p> 2836 * Note: This does not stop any delayed operations that might be queued. 2837 */ 2838 void dispose() { 2839 disposed = true; 2840 stopListeningForSectionHolders(); 2841 clearTurnoutLock(); 2842 destinationSignalMast.removePropertyChangeListener(propertyDestinationMastListener); 2843 setBlocks(null); 2844 setAutoBlocks(null); 2845 setTurnouts(null); 2846 setAutoTurnouts(null); 2847 setSensors(null); 2848 setMasts(null); 2849 setAutoMasts(null, true); 2850 } 2851 2852 void lockTurnouts() { 2853 // We do not allow the turnouts to be locked if we are disposing the logic, 2854 // if the logic is not active, or if we do not allow the turnouts to be locked. 2855 if ((disposed) || (!lockTurnouts) || (!active)) { 2856 return; 2857 } 2858 2859 userSetTurnouts.stream().map(nbh -> (Turnout) nbh.getBean()).forEachOrdered(key -> { 2860 key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, true); 2861 }); 2862 Enumeration<Turnout> keys = autoTurnouts.keys(); 2863 while (keys.hasMoreElements()) { 2864 Turnout key = keys.nextElement(); 2865 key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, true); 2866 } 2867 } 2868 2869 void clearTurnoutLock() { 2870 // We do not allow the turnout lock to be cleared if we are not active, 2871 // and the lock flag has not been set. 2872 if ((!lockTurnouts) && (!active)) { 2873 return; 2874 } 2875 2876 Enumeration<Turnout> keys = autoTurnouts.keys(); 2877 while (keys.hasMoreElements()) { 2878 Turnout key = keys.nextElement(); 2879 key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, false); 2880 } 2881 2882 userSetTurnouts.stream().map(nbh -> (Turnout) nbh.getBean()).forEachOrdered(key -> 2883 key.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, false)); 2884 } 2885 2886 protected void calculateSpeed() { 2887 log.debug("{} calculate the speed setting for this logic ie what the signalmast will display", destinationSignalMast.getDisplayName()); 2888 minimumBlockSpeed = 0.0f; 2889 Enumeration<Turnout> keys = autoTurnouts.keys(); 2890 while (keys.hasMoreElements()) { 2891 Turnout key = keys.nextElement(); 2892 log.debug("{} turnout {}", destinationSignalMast.getDisplayName(), key.getDisplayName()); 2893 if (!isTurnoutIncluded(key)) { 2894 if (autoTurnouts.get(key) == Turnout.CLOSED) { 2895 if (((key.getStraightLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getStraightLimit() != -1)) { 2896 minimumBlockSpeed = key.getStraightLimit(); 2897 log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed); 2898 } 2899 } else { 2900 if (((key.getDivergingLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getDivergingLimit() != -1)) { 2901 minimumBlockSpeed = key.getDivergingLimit(); 2902 log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed); 2903 } 2904 } 2905 } 2906 } 2907 2908 userSetTurnouts.forEach(nbh -> { 2909 Turnout key = (Turnout) nbh.getBean(); 2910 if (nbh.getSetting() == Turnout.CLOSED) { 2911 if (((key.getStraightLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getStraightLimit() != -1)) { 2912 minimumBlockSpeed = key.getStraightLimit(); 2913 log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed); 2914 } 2915 } else if (nbh.getSetting() == Turnout.THROWN) { 2916 if (((key.getDivergingLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getDivergingLimit() != -1)) { 2917 minimumBlockSpeed = key.getDivergingLimit(); 2918 log.debug("{} turnout {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed); 2919 } 2920 } 2921 }); 2922 2923 Set<Block> autoBlockKeys = autoBlocks.keySet(); 2924 for (Block key : autoBlockKeys) { 2925 log.debug("{} auto block add list {}", destinationSignalMast.getDisplayName(), key.getDisplayName()); 2926 if (!isBlockIncluded(key)) { 2927 if (((key.getSpeedLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getSpeedLimit() != -1)) { 2928 minimumBlockSpeed = key.getSpeedLimit(); 2929 log.debug("{} block {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed); 2930 } 2931 } 2932 } 2933 for (NamedBeanSetting nbh : userSetBlocks) { 2934 Block key = (Block) nbh.getBean(); 2935 if (((key.getSpeedLimit() < minimumBlockSpeed) || (minimumBlockSpeed == 0)) && (key.getSpeedLimit() != -1)) { 2936 log.debug("{} block {} set speed to {}", destinationSignalMast.getDisplayName(), key.getDisplayName(), minimumBlockSpeed); 2937 minimumBlockSpeed = key.getSpeedLimit(); 2938 } 2939 } 2940 /*if(minimumBlockSpeed==-0.1f) 2941 minimumBlockSpeed==0.0f;*/ 2942 } 2943 2944 /** 2945 * Check whether a block on this route is reserved for a train other 2946 * than the one approaching the source mast. 2947 * <p> 2948 * Only blocks marked as allocated (shown in the extra color) are 2949 * checked. A Section records who holds it, see 2950 * {@link Section#getAllocatedTo()}; the approaching train is the 2951 * holder of the block the source mast faces. A marked block whose 2952 * holder is not the approaching train, or that has no holder, puts the 2953 * mast to danger. So does a marked block when no train holds the 2954 * facing block. 2955 * 2956 * @param routeBlocks the blocks on this route 2957 * @param lbm the layout block manager 2958 * @return true if the mast must show danger 2959 */ 2960 private boolean isRouteHeldByAnotherTrain(List<Block> routeBlocks, LayoutBlockManager lbm) { 2961 List<Block> marked = new ArrayList<>(); 2962 for (Block b : routeBlocks) { 2963 LayoutBlock lb = lbm.getLayoutBlock(b); 2964 if (lb != null && lb.getUseExtraColor()) { 2965 marked.add(b); 2966 } 2967 } 2968 if (marked.isEmpty()) { 2969 return false; 2970 } 2971 LayoutBlock approach = getApproachBlock(lbm); 2972 Object approaching = (approach == null) ? null : heldBy(approach.getBlock()); 2973 for (Block b : marked) { 2974 Object holder = heldBy(b); 2975 if (holder == null || holder != approaching) { 2976 log.debug("{} block {} is held by {}, approaching train is {}", 2977 destinationSignalMast.getDisplayName(), b.getDisplayName(), holder, approaching); 2978 return true; 2979 } 2980 } 2981 return false; 2982 } 2983 2984 /* 2985 * The block the source mast faces, where a train approaching the mast is. 2986 * Found from the Layout Editor panels when this logic does not use the 2987 * Layout Editor, without changing the stored facing block. 2988 */ 2989 private LayoutBlock getApproachBlock(LayoutBlockManager lbm) { 2990 if (getFacingBlock() != null) { 2991 return getFacingBlock(); 2992 } 2993 for (LayoutEditor editor : InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class)) { 2994 LayoutBlock lb = lbm.getFacingBlockByNamedBean(getSourceMast(), editor); 2995 if (lb != null) { 2996 return lb; 2997 } 2998 } 2999 return null; 3000 } 3001 3002 /* 3003 * The Sections this logic listens to for a change of holder, and the 3004 * blocks it cares about: those on the route and the block the source 3005 * mast faces. 3006 */ 3007 private final Set<Section> holderSections = new HashSet<>(); 3008 private final Set<Block> holderBlocks = new HashSet<>(); 3009 3010 /* 3011 * Listen for changes of holder on Sections, so the dispatcher check 3012 * above is made again when a train is given or releases a Section 3013 * containing one of the blocks this logic cares about. Every Section 3014 * is listened to, and the blocks are checked when the holder changes, 3015 * because a Section's blocks can be set after it is created without 3016 * any event being sent. 3017 */ 3018 private void listenForSectionHolders() { 3019 stopListeningForSectionHolders(); 3020 // Only use a Section manager that already exists: with no Sections 3021 // defined there is nothing to listen to, and asking for one would 3022 // create it. 3023 if (!InstanceManager.containsDefault(SectionManager.class)) { 3024 return; 3025 } 3026 SectionManager sm = InstanceManager.getDefault(SectionManager.class); 3027 holderBlocks.addAll(autoBlocks.keySet()); 3028 for (NamedBeanSetting nbh : userSetBlocks) { 3029 holderBlocks.add((Block) nbh.getBean()); 3030 } 3031 if (holderBlocks.isEmpty()) { 3032 return; 3033 } 3034 if (InstanceManager.containsDefault(LayoutBlockManager.class)) { 3035 LayoutBlock approach = getApproachBlock(InstanceManager.getDefault(LayoutBlockManager.class)); 3036 if (approach != null && approach.getBlock() != null) { 3037 holderBlocks.add(approach.getBlock()); 3038 } 3039 } 3040 sm.addPropertyChangeListener(Manager.PROPERTY_BEANS, sectionManagerListener); 3041 for (Section sec : sm.getNamedBeanSet()) { 3042 listenTo(sec); 3043 } 3044 } 3045 3046 private void listenTo(Section sec) { 3047 if (!holderSections.contains(sec)) { 3048 sec.addPropertyChangeListener(Section.PROPERTY_ALLOCATED_TO, sectionHolderListener); 3049 holderSections.add(sec); 3050 } 3051 } 3052 3053 private void stopListeningForSectionHolders() { 3054 for (Section sec : holderSections) { 3055 sec.removePropertyChangeListener(Section.PROPERTY_ALLOCATED_TO, sectionHolderListener); 3056 } 3057 holderSections.clear(); 3058 holderBlocks.clear(); 3059 if (InstanceManager.containsDefault(SectionManager.class)) { 3060 InstanceManager.getDefault(SectionManager.class) 3061 .removePropertyChangeListener(Manager.PROPERTY_BEANS, sectionManagerListener); 3062 } 3063 } 3064 3065 private final PropertyChangeListener sectionHolderListener = e -> { 3066 Section sec = (Section) e.getSource(); 3067 for (Block b : holderBlocks) { 3068 if (sec.containsBlock(b)) { 3069 log.debug("{} section {} now held by {}", destinationSignalMast.getDisplayName(), 3070 sec.getDisplayName(), e.getNewValue()); 3071 checkState(); 3072 return; 3073 } 3074 } 3075 }; 3076 3077 /* 3078 * A Section added is listened to; one removed is dropped. An event 3079 * without a Section (sent after a silenced bulk load) starts again. 3080 */ 3081 private final PropertyChangeListener sectionManagerListener = e -> { 3082 if (!destMastInit || disposed) { 3083 return; 3084 } 3085 if (e.getNewValue() instanceof Section) { 3086 listenTo((Section) e.getNewValue()); 3087 } else if (e.getOldValue() instanceof Section) { 3088 Section sec = (Section) e.getOldValue(); 3089 if (holderSections.remove(sec)) { 3090 sec.removePropertyChangeListener(Section.PROPERTY_ALLOCATED_TO, sectionHolderListener); 3091 } 3092 } else { 3093 listenForSectionHolders(); 3094 } 3095 }; 3096 3097 /* 3098 * The holder of an allocated Section containing the block, or null. 3099 */ 3100 private Object heldBy(Block b) { 3101 if (b == null || !InstanceManager.containsDefault(SectionManager.class)) { 3102 return null; 3103 } 3104 for (Section sec : InstanceManager.getDefault(SectionManager.class).getNamedBeanSet()) { 3105 Object holder = sec.getAllocatedTo(); 3106 if (holder != null && sec.containsBlock(b)) { 3107 return holder; 3108 } 3109 } 3110 return null; 3111 } 3112 3113 protected PropertyChangeListener propertySensorListener = new PropertyChangeListener() { 3114 @Override 3115 public void propertyChange(PropertyChangeEvent e) { 3116 Sensor sen = (Sensor) e.getSource(); 3117 log.debug("{} to {} destination sensor {} trigger {}",source.getDisplayName(), destinationSignalMast.getDisplayName(), sen.getDisplayName(), e.getPropertyName()); 3118 if ( Sensor.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) { 3119 int now = ((Integer) e.getNewValue()); 3120 log.debug("current value {} value we want {}", now, getSensorState(sen)); 3121 if (isSensorIncluded(sen) && getSensorState(sen) != now) { 3122 log.debug("Sensor {} caused the signalmast to be set to danger", sen.getDisplayName()); 3123 //getSourceMast().setAspect(stopAspect); 3124 routeNoLongerSet(); 3125 } else if (getSensorState(sen) == now) { 3126 log.debug("{} sensor {} triggers a calculation of change", destinationSignalMast.getDisplayName(), sen.getDisplayName()); 3127 checkState(); 3128 } 3129 } 3130 } 3131 }; 3132 3133 protected PropertyChangeListener propertyTurnoutListener = new PropertyChangeListener() { 3134 @Override 3135 public void propertyChange(PropertyChangeEvent e) { 3136 Turnout turn = (Turnout) e.getSource(); 3137 // log.debug(destination.getDisplayName() + " destination sensor "+ sen.getDisplayName() + "trigger"); 3138 if ( Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) { 3139 //Need to check this against the manual list vs auto list 3140 //The manual list should over-ride the auto list 3141 int now = ((Integer) e.getNewValue()); 3142 if (isTurnoutIncluded(turn)) { 3143 if (getTurnoutState(turn) != now) { 3144 log.debug("Turnout {} caused the signalmast to be set", turn.getDisplayName()); 3145 log.debug("From {} to {} Turnout {} caused the signalmast to be set to danger", getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName(), turn.getDisplayName()); 3146 routeNoLongerSet(); 3147 } else { 3148 log.debug("{} turnout {} triggers a calculation of change", destinationSignalMast.getDisplayName(), turn.getDisplayName()); 3149 checkState(); 3150 } 3151 } else if (autoTurnouts.containsKey(turn)) { 3152 if (getAutoTurnoutState(turn) != now) { 3153 log.debug("Turnout {} auto caused the signalmast to be set", turn.getDisplayName()); 3154 log.debug("From {} to {} Auto Turnout {} auto caused the signalmast to be set to danger", getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName(), turn.getDisplayName()); 3155 routeNoLongerSet(); 3156 } else { 3157 log.debug("From {} to {} turnout {} triggers a calculation of change", getSourceMast().getDisplayName(), destinationSignalMast.getDisplayName(), turn.getDisplayName()); 3158 checkState(); 3159 } 3160 } 3161 3162 } else if ( Turnout.PROPERTY_TURNOUT_STRAIGHT_SPEED.equals(e.getPropertyName()) 3163 || Turnout.PROPERTY_TURNOUT_DIVERGING_SPEED.equals(e.getPropertyName())) { 3164 calculateSpeed(); 3165 } 3166 } 3167 }; 3168 3169 /** 3170 * Check if a block on this route has become occupied while the route 3171 * needs it to be unoccupied. 3172 * 3173 * @param block the block to check 3174 * @return true if the block is occupied, not in permissive working, 3175 * and this logic needs it to be unoccupied 3176 */ 3177 boolean isOccupiedAgainstRoute(Block block) { 3178 if (block.getState() != Block.OCCUPIED || block.getPermissiveWorking()) { 3179 return false; 3180 } 3181 int required; 3182 if (isBlockIncluded(block)) { 3183 required = getBlockState(block); 3184 } else if (autoBlocks.containsKey(block)) { 3185 required = getAutoBlockState(block); 3186 } else { 3187 return false; 3188 } 3189 return required != Block.OCCUPIED && required != 0x03; 3190 } 3191 3192 protected PropertyChangeListener propertyBlockListener = new PropertyChangeListener() { 3193 @Override 3194 public void propertyChange(PropertyChangeEvent e) { 3195 Block block = (Block) e.getSource(); 3196 log.debug("{} destination block {} trigger {} {}", destinationSignalMast.getDisplayName(), block.getDisplayName(), e.getPropertyName(), e.getNewValue()); 3197 if ( Block.PROPERTY_STATE.equals(e.getPropertyName()) && isOccupiedAgainstRoute(block)) { 3198 // Going to danger is not delayed, only a change towards clear is. 3199 log.debug("Block {} occupied, caused the signalmast to be set to danger", block.getDisplayName()); 3200 routeNoLongerSet(); 3201 } else if ( Block.PROPERTY_STATE.equals(e.getPropertyName()) || Block.PROPERTY_ALLOCATED.equals(e.getPropertyName())) { 3202 // TODO: what is this? 3203 log.debug("Included in user entered block {}", Boolean.toString(isBlockIncluded(block))); 3204 log.debug("Included in AutoGenerated Block {}", Boolean.toString(autoBlocks.containsKey(block))); 3205 if (isBlockIncluded(block)) { 3206 log.debug("{} in manual block", destinationSignalMast.getDisplayName()); 3207 log.debug(" state: {} {}", getBlockState(block), block.getState()); 3208 checkState(); 3209 } else if (autoBlocks.containsKey(block)) { 3210 log.debug("{} in auto block", destinationSignalMast.getDisplayName()); 3211 log.debug(" states: {} {}", getAutoBlockState(block), block.getState()); 3212 checkState(); 3213 } else { 3214 log.debug("{} Not found", destinationSignalMast.getDisplayName()); 3215 } 3216 } else if ( e.getPropertyName().equals("BlockSpeedChange")) { 3217 calculateSpeed(); 3218 } 3219 } 3220 }; 3221 3222 protected PropertyChangeListener propertySignalMastListener = new PropertyChangeListener() { 3223 @Override 3224 public void propertyChange(PropertyChangeEvent e) { 3225 3226 SignalMast mast = (SignalMast) e.getSource(); 3227 log.debug("{} signalmast change {} {}", destinationSignalMast.getDisplayName(), mast.getDisplayName(), e.getPropertyName()); 3228 // log.debug(destination.getDisplayName() + " destination sensor "+ sen.getDisplayName() + "trigger"); 3229 if ( SignalMast.PROPERTY_ASPECT.equals(e.getPropertyName())) { 3230 3231 String now = ((String) e.getNewValue()); 3232 log.debug("{} match property {}", destinationSignalMast.getDisplayName(), now); 3233 if (isSignalMastIncluded(mast)) { 3234 if (!now.equals(getSignalMastState(mast))) { 3235 log.debug("{} in mast list SignalMast {} caused the signalmast to be set", destinationSignalMast.getDisplayName(), mast.getDisplayName()); 3236 log.debug("SignalMast {} caused the signalmast to be set", mast.getDisplayName()); 3237 routeNoLongerSet(); 3238 } else { 3239 log.debug("{} in mast list signalmast change", destinationSignalMast.getDisplayName()); 3240 checkState(); 3241 } 3242 } else if (autoMasts.containsKey(mast)) { 3243 if (!now.equals(getAutoSignalMastState(mast))) { 3244 log.debug("SignalMast {} caused the signalmast to be set", mast.getDisplayName()); 3245 log.debug("{} in auto mast list SignalMast {} caused the signalmast to be set", destinationSignalMast.getDisplayName(), mast.getDisplayName()); 3246 routeNoLongerSet(); 3247 } else { 3248 log.debug("{} in auto mast list signalmast change", destinationSignalMast.getDisplayName()); 3249 checkState(); 3250 } 3251 } 3252 } 3253 } 3254 }; 3255 3256 private class NamedBeanSetting { 3257 3258 NamedBeanHandle<?> namedBean; 3259 int setting = 0; 3260 String strSetting = null; 3261 3262 NamedBeanSetting(NamedBeanHandle<?> namedBean, int setting) { 3263 this.namedBean = namedBean; 3264 this.setting = setting; 3265 } 3266 3267 NamedBeanSetting(NamedBeanHandle<?> namedBean, String setting) { 3268 this.namedBean = namedBean; 3269 strSetting = setting; 3270 } 3271 3272 NamedBean getBean() { 3273 return namedBean.getBean(); 3274 } 3275 3276 NamedBeanHandle<?> getNamedBean() { 3277 return namedBean; 3278 } 3279 3280 int getSetting() { 3281 return setting; 3282 } 3283 3284 String getStringSetting() { 3285 return strSetting; 3286 } 3287 3288 String getBeanName() { 3289 return namedBean.getName(); 3290 } 3291 } 3292 } 3293 3294 /** 3295 * The listener on the destination Signal Mast. 3296 */ 3297 private PropertyChangeListener propertyDestinationMastListener = new PropertyChangeListener() { 3298 @Override 3299 public void propertyChange(PropertyChangeEvent e) { 3300 SignalMast mast = (SignalMast) e.getSource(); 3301 if (mast == destination) { 3302 log.debug("destination mast change {}", mast.getDisplayName()); 3303 setSignalAppearance(); 3304 } 3305 } 3306 }; 3307 3308 /** 3309 * The listener on the source Signal Mast. 3310 */ 3311 private PropertyChangeListener propertySourceMastListener = new PropertyChangeListener() { 3312 @Override 3313 public void propertyChange(PropertyChangeEvent e) { 3314 SignalMast mast = (SignalMast) e.getSource(); 3315 if ((mast == source) && ( SignalMast.PROPERTY_HELD.equals(e.getPropertyName()))) { 3316 log.debug("source mast change {} {}", mast.getDisplayName(), e.getPropertyName()); 3317 setSignalAppearance(); 3318 } 3319 } 3320 }; 3321 3322 //@todo need to think how we deal with auto generated lists based upon the layout editor. 3323 @Override 3324 public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 3325 NamedBean nb = (NamedBean) evt.getOldValue(); 3326 if (Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) { 3327 boolean found = false; 3328 StringBuilder message = new StringBuilder(); 3329 if (nb instanceof SignalMast) { 3330 if (nb.equals(source)) { 3331 message.append("Has SignalMast Logic attached which will be <b>Deleted</b> to <ul>"); 3332 for (SignalMast sm : getDestinationList()) { 3333 message.append("<li>"); 3334 message.append(sm.getDisplayName()); 3335 message.append("</li>"); 3336 } 3337 message.append("</ul>"); 3338 throw new PropertyVetoException(message.toString(), evt); 3339 3340 } else if (isDestinationValid((SignalMast) nb)) { 3341 throw new PropertyVetoException("Is the end point mast for logic attached to signal mast " + source.getDisplayName() + " which will be <b>Deleted</b> ", evt); 3342 } 3343 for (SignalMast sm : getDestinationList()) { 3344 if (isSignalMastIncluded((SignalMast) nb, sm)) { 3345 message.append("<li>"); 3346 message.append("Used in conflicting logic of ").append(source.getDisplayName()) 3347 .append(" & ").append(sm.getDisplayName()).append("</li>"); 3348 } 3349 } 3350 } 3351 if (nb instanceof Turnout) { 3352 for (SignalMast sm : getDestinationList()) { 3353 if (isTurnoutIncluded((Turnout) nb, sm)) { 3354 message.append("<li>Is in logic between Signal Masts ").append(source.getDisplayName()) 3355 .append(" ").append(sm.getDisplayName()).append("</li>"); 3356 found = true; 3357 } 3358 } 3359 } 3360 if (nb instanceof Sensor) { 3361 for (SignalMast sm : getDestinationList()) { 3362 if (isSensorIncluded((Sensor) nb, sm)) { 3363 message.append("<li>"); 3364 message.append("Is in logic between Signal Masts ").append(source.getDisplayName()) 3365 .append(" ").append(sm.getDisplayName()).append("</li>"); 3366 found = true; 3367 } 3368 } 3369 } 3370 if (found) { 3371 throw new PropertyVetoException(message.toString(), evt); 3372 } 3373 } else if (Manager.PROPERTY_DO_DELETE.equals(evt.getPropertyName())) { 3374 if (nb instanceof SignalMast) { 3375 if (nb.equals(source)) { 3376 dispose(); 3377 } 3378 if (isDestinationValid((SignalMast) nb)) { 3379 removeDestination((SignalMast) nb); 3380 } 3381 for (SignalMast sm : getDestinationList()) { 3382 if (isSignalMastIncluded((SignalMast) nb, sm)) { 3383 log.warn("Unhandled condition: signal mast included during DoDelete"); 3384 // @todo need to deal with this situation 3385 } 3386 } 3387 } 3388 if (nb instanceof Turnout) { 3389 Turnout t = (Turnout) nb; 3390 getDestinationList().stream().filter(sm -> (isTurnoutIncluded(t, sm))).forEachOrdered(sm -> { 3391 removeTurnout(t, sm); 3392 }); 3393 } 3394 if (nb instanceof Sensor) { 3395 Sensor s = (Sensor) nb; 3396 getDestinationList().stream().filter(sm -> (isSensorIncluded(s, sm))).forEachOrdered(sm -> { 3397 removeSensor(s, sm); 3398 }); 3399 } 3400 } 3401 } 3402 3403 /** 3404 * Note: This does not stop any delayed operations that might be queued. 3405 */ 3406 @Override 3407 public void dispose() { 3408 disposing = true; 3409 getSourceMast().removePropertyChangeListener(propertySourceMastListener); 3410 Enumeration<SignalMast> en = destList.keys(); 3411 while (en.hasMoreElements()) { 3412 SignalMast dm = en.nextElement(); 3413 destList.get(dm).dispose(); 3414 } 3415 super.dispose(); // release any prop change listeners 3416 } 3417 3418 /** 3419 * {@inheritDoc } 3420 */ 3421 @Override 3422 public String getBeanType() { 3423 return Bundle.getMessage("BeanNameSignalMastLogic"); 3424 } 3425 3426 /** 3427 * No valid integer state, always return a constant. 3428 * 3429 * @return Always zero 3430 */ 3431 @Override 3432 public int getState() { 3433 return 0; 3434 } 3435 3436 @Override 3437 public void setState(int i) { 3438 } 3439 3440 /** 3441 * {@inheritDoc } 3442 */ 3443 @Override 3444 public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) { 3445 List<NamedBeanUsageReport> report = new ArrayList<>(); 3446 if (bean != null) { 3447 if (bean.equals(getSourceMast())) { 3448 report.add(new NamedBeanUsageReport("SMLSourceMast")); // NOI18N 3449 } 3450 getDestinationList().forEach((dest) -> { 3451 if (bean.equals(dest)) { 3452 report.add(new NamedBeanUsageReport("SMLDestinationMast")); // NOI18N 3453 } 3454 getAutoBlocks(dest).forEach((block) -> { 3455 if (bean.equals(block)) { 3456 report.add(new NamedBeanUsageReport("SMLBlockAuto", dest)); // NOI18N 3457 } 3458 }); 3459 getBlocks(dest).forEach((block) -> { 3460 if (bean.equals(block)) { 3461 report.add(new NamedBeanUsageReport("SMLBlockUser", dest)); // NOI18N 3462 } 3463 }); 3464 getAutoTurnouts(dest).forEach((turnout) -> { 3465 if (bean.equals(turnout)) { 3466 report.add(new NamedBeanUsageReport("SMLTurnoutAuto", dest)); // NOI18N 3467 } 3468 }); 3469 getTurnouts(dest).forEach((turnout) -> { 3470 if (bean.equals(turnout)) { 3471 report.add(new NamedBeanUsageReport("SMLTurnoutUser", dest)); // NOI18N 3472 } 3473 }); 3474 getSensors(dest).forEach((sensor) -> { 3475 if (bean.equals(sensor)) { 3476 report.add(new NamedBeanUsageReport("SMLSensor", dest)); // NOI18N 3477 } 3478 }); 3479 getAutoMasts(dest).forEach((mast) -> { 3480 if (bean.equals(mast)) { 3481 report.add(new NamedBeanUsageReport("SMLMastAuto", dest)); // NOI18N 3482 } 3483 }); 3484 getSignalMasts(dest).forEach((mast) -> { 3485 if (bean.equals(mast)) { 3486 report.add(new NamedBeanUsageReport("SMLMastUser", dest)); // NOI18N 3487 } 3488 }); 3489 }); 3490 } 3491 return report; 3492 } 3493 3494 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultSignalMastLogic.class); 3495 3496}