001package jmri.jmrit.display.layoutEditor; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.text.MessageFormat; 006import java.util.*; 007 008import javax.annotation.CheckForNull; 009import javax.annotation.Nonnull; 010 011import jmri.*; 012import jmri.util.MathUtil; 013 014/** 015 * A LayoutTurntable is a representation used by LayoutEditor to display a 016 * turntable. 017 * <p> 018 * A LayoutTurntable has a variable number of connection points, called 019 * RayTracks, each radiating from the center of the turntable. Each of these 020 * points should be connected to a TrackSegment. 021 * <p> 022 * Each radiating segment (RayTrack) gets its Block information from its 023 * connected track segment. 024 * <p> 025 * Each radiating segment (RayTrack) has a unique connection index. The 026 * connection index is set when the RayTrack is created, and cannot be changed. 027 * This connection index is used to maintain the identity of the radiating 028 * segment to its connected Track Segment as ray tracks are added and deleted by 029 * the user. 030 * <p> 031 * The radius of the turntable circle is variable by the user. 032 * <p> 033 * Each radiating segment (RayTrack) connecting point is a fixed distance from 034 * the center of the turntable. The user may vary the angle of the radiating 035 * segment. Angles are measured from the vertical (12 o'clock) position in a 036 * clockwise manner. For example, 30 degrees is 1 o'clock, 60 degrees is 2 037 * o'clock, 90 degrees is 3 o'clock, etc. 038 * <p> 039 * Each radiating segment is drawn from its connection point to the turntable 040 * circle in the direction of the turntable center. 041 * 042 * @author Dave Duchamp Copyright (c) 2007 043 * @author George Warner Copyright (c) 2017-2018 044 */ 045public class LayoutTurntable extends LayoutTrack { 046 047 /** 048 * Constructor method 049 * 050 * @param id the name for the turntable 051 * @param models what layout editor panel to put it in 052 */ 053 public LayoutTurntable(@Nonnull String id, @Nonnull LayoutEditor models) { 054 super(id, models); 055 056 radius = 25.0; // initial default, change asap. 057 } 058 059 // defined constants 060 // operational instance variables (not saved between sessions) 061 private NamedBeanHandle<LayoutBlock> namedLayoutBlock = null; 062 063 private boolean turnoutControlled = false; 064 private double radius = 25.0; 065 private int lastKnownIndex = -1; 066 067 // persistent instance variables (saved between sessions) 068 069 // temporary: this is referenced directly from LayoutTurntable, which 070 // should be using _functional_ accessors here. 071 public final List<RayTrack> rayTrackList = new ArrayList<>(); // list of Ray Track objects 072 073 /** 074 * Get a string that represents this object. This should only be used for 075 * debugging. 076 * 077 * @return the string 078 */ 079 @Override 080 @Nonnull 081 public String toString() { 082 return "LayoutTurntable " + getName(); 083 } 084 085 // 086 // Accessor methods 087 // 088 /** 089 * Get the radius for this turntable. 090 * 091 * @return the radius for this turntable 092 */ 093 public double getRadius() { 094 return radius; 095 } 096 097 /** 098 * Set the radius for this turntable. 099 * 100 * @param r the radius for this turntable 101 */ 102 public void setRadius(double r) { 103 radius = r; 104 } 105 106 /** 107 * @return the layout block name 108 */ 109 @Nonnull 110 public String getBlockName() { 111 String result = null; 112 if (namedLayoutBlock != null) { 113 result = namedLayoutBlock.getName(); 114 } 115 return ((result == null) ? "" : result); 116 } 117 118 /** 119 * @return the layout block 120 */ 121 @CheckForNull 122 public LayoutBlock getLayoutBlock() { 123 return (namedLayoutBlock != null) ? namedLayoutBlock.getBean() : null; 124 } 125 126 /** 127 * Set up a LayoutBlock for this LayoutTurntable. 128 * 129 * @param newLayoutBlock the LayoutBlock to set 130 */ 131 public void setLayoutBlock(@CheckForNull LayoutBlock newLayoutBlock) { 132 LayoutBlock layoutBlock = getLayoutBlock(); 133 if (layoutBlock != newLayoutBlock) { 134 /// block has changed, if old block exists, decrement use 135 if (layoutBlock != null) { 136 layoutBlock.decrementUse(); 137 } 138 if (newLayoutBlock != null) { 139 String newName = newLayoutBlock.getUserName(); 140 if ((newName != null) && !newName.isEmpty()) { 141 namedLayoutBlock = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(newName, newLayoutBlock); 142 } else { 143 namedLayoutBlock = null; 144 } 145 } else { 146 namedLayoutBlock = null; 147 } 148 } 149 } 150 151 /** 152 * Set up a LayoutBlock for this LayoutTurntable. 153 * 154 * @param name the name of the new LayoutBlock 155 */ 156 public void setLayoutBlockByName(@CheckForNull String name) { 157 if ((name != null) && !name.isEmpty()) { 158 setLayoutBlock(models.provideLayoutBlock(name)); 159 } 160 } 161 162 /** 163 * Add a ray at the specified angle. 164 * 165 * @param angle the angle 166 * @return the RayTrack 167 */ 168 public RayTrack addRay(double angle) { 169 RayTrack rt = new RayTrack(angle, getNewIndex()); 170 rayTrackList.add(rt); 171 return rt; 172 } 173 174 private int getNewIndex() { 175 int index = -1; 176 if (rayTrackList.isEmpty()) { 177 return 0; 178 } 179 180 boolean found = true; 181 while (found) { 182 index++; 183 found = false; // assume failure (pessimist!) 184 for (RayTrack rt : rayTrackList) { 185 if (index == rt.getConnectionIndex()) { 186 found = true; 187 } 188 } 189 } 190 return index; 191 } 192 193 // the following method is only for use in loading layout turntables 194 public void addRayTrack(double angle, int index, String name) { 195 RayTrack rt = new RayTrack(angle, index); 196 /// if (ray!=null) { 197 rayTrackList.add(rt); 198 rt.connectName = name; 199 //} 200 } 201 202 /** 203 * Get the connection for the ray with this index. 204 * 205 * @param index the index 206 * @return the connection for the ray with this value of getConnectionIndex 207 */ 208 @CheckForNull 209 public TrackSegment getRayConnectIndexed(int index) { 210 TrackSegment result = null; 211 for (RayTrack rt : rayTrackList) { 212 if (rt.getConnectionIndex() == index) { 213 result = rt.getConnect(); 214 break; 215 } 216 } 217 return result; 218 } 219 220 /** 221 * Get the connection for the ray at the index in the rayTrackList. 222 * 223 * @param i the index in the rayTrackList 224 * @return the connection for the ray at that index in the rayTrackList or null 225 */ 226 @CheckForNull 227 public TrackSegment getRayConnectOrdered(int i) { 228 TrackSegment result = null; 229 230 if (i < rayTrackList.size()) { 231 RayTrack rt = rayTrackList.get(i); 232 if (rt != null) { 233 result = rt.getConnect(); 234 } 235 } 236 return result; 237 } 238 239 /** 240 * Set the connection for the ray at the index in the rayTrackList. 241 * 242 * @param ts the connection 243 * @param index the index in the rayTrackList 244 */ 245 public void setRayConnect(@CheckForNull TrackSegment ts, int index) { 246 for (RayTrack rt : rayTrackList) { 247 if (rt.getConnectionIndex() == index) { 248 rt.setConnect(ts); 249 break; 250 } 251 } 252 } 253 254 // should only be used by xml save code 255 @Nonnull 256 public List<RayTrack> getRayTrackList() { 257 return rayTrackList; 258 } 259 260 /** 261 * Get the number of rays on turntable. 262 * 263 * @return the number of rays 264 */ 265 public int getNumberRays() { 266 return rayTrackList.size(); 267 } 268 269 /** 270 * Get the index for the ray at this position in the rayTrackList. 271 * 272 * @param i the position in the rayTrackList 273 * @return the index 274 */ 275 public int getRayIndex(int i) { 276 int result = 0; 277 if (i < rayTrackList.size()) { 278 RayTrack rt = rayTrackList.get(i); 279 result = rt.getConnectionIndex(); 280 } 281 return result; 282 } 283 284 /** 285 * Get the angle for the ray at this position in the rayTrackList. 286 * 287 * @param i the position in the rayTrackList 288 * @return the angle 289 */ 290 public double getRayAngle(int i) { 291 double result = 0.0; 292 if (i < rayTrackList.size()) { 293 RayTrack rt = rayTrackList.get(i); 294 result = rt.getAngle(); 295 } 296 return result; 297 } 298 299 /** 300 * Set the turnout and state for the ray with this index. 301 * 302 * @param index the index 303 * @param turnoutName the turnout name 304 * @param state the state 305 */ 306 public void setRayTurnout(int index, @CheckForNull String turnoutName, int state) { 307 boolean found = false; // assume failure (pessimist!) 308 for (RayTrack rt : rayTrackList) { 309 if (rt.getConnectionIndex() == index) { 310 rt.setTurnout(turnoutName, state); 311 found = true; 312 break; 313 } 314 } 315 if (!found) { 316 log.error("{}.setRayTurnout({}, {}, {}); Attempt to add Turnout control to a non-existant ray track", 317 getName(), index, turnoutName, state); 318 } 319 } 320 321 /** 322 * Get the name of the turnout for the ray at this index. 323 * 324 * @param i the index 325 * @return name of the turnout for the ray at this index 326 */ 327 @CheckForNull 328 public String getRayTurnoutName(int i) { 329 String result = null; 330 if (i < rayTrackList.size()) { 331 RayTrack rt = rayTrackList.get(i); 332 result = rt.getTurnoutName(); 333 } 334 return result; 335 } 336 337 /** 338 * Get the turnout for the ray at this index. 339 * 340 * @param i the index 341 * @return the turnout for the ray at this index 342 */ 343 @CheckForNull 344 public Turnout getRayTurnout(int i) { 345 Turnout result = null; 346 if (i < rayTrackList.size()) { 347 RayTrack rt = rayTrackList.get(i); 348 result = rt.getTurnout(); 349 } 350 return result; 351 } 352 353 /** 354 * Get the state of the turnout for the ray at this index. 355 * 356 * @param i the index 357 * @return state of the turnout for the ray at this index 358 */ 359 public int getRayTurnoutState(int i) { 360 int result = 0; 361 if (i < rayTrackList.size()) { 362 RayTrack rt = rayTrackList.get(i); 363 result = rt.getTurnoutState(); 364 } 365 return result; 366 } 367 368 /** 369 * Get if the ray at this index is disabled. 370 * 371 * @param i the index 372 * @return true if disabled 373 */ 374 public boolean isRayDisabled(int i) { 375 boolean result = false; // assume not disabled 376 if (i < rayTrackList.size()) { 377 RayTrack rt = rayTrackList.get(i); 378 result = rt.isDisabled(); 379 } 380 return result; 381 } 382 383 /** 384 * Set the disabled state of the ray at this index. 385 * 386 * @param i the index 387 * @param boo the state 388 */ 389 public void setRayDisabled(int i, boolean boo) { 390 if (i < rayTrackList.size()) { 391 RayTrack rt = rayTrackList.get(i); 392 rt.setDisabled(boo); 393 } 394 } 395 396 /** 397 * Get the disabled when occupied state of the ray at this index. 398 * 399 * @param i the index 400 * @return the state 401 */ 402 public boolean isRayDisabledWhenOccupied(int i) { 403 boolean result = false; // assume not disabled when occupied 404 if (i < rayTrackList.size()) { 405 RayTrack rt = rayTrackList.get(i); 406 result = rt.isDisabledWhenOccupied(); 407 } 408 return result; 409 } 410 411 /** 412 * Set the disabled when occupied state of the ray at this index. 413 * 414 * @param i the index 415 * @param boo the state 416 */ 417 public void setRayDisabledWhenOccupied(int i, boolean boo) { 418 if (i < rayTrackList.size()) { 419 RayTrack rt = rayTrackList.get(i); 420 rt.setDisabledWhenOccupied(boo); 421 } 422 } 423 424 /** 425 * {@inheritDoc} 426 */ 427 @Override 428 public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException { 429 LayoutTrack result = null; 430 if (HitPointType.isTurntableRayHitType(connectionType)) { 431 result = getRayConnectIndexed(connectionType.turntableTrackIndex()); 432 } else { 433 String errString = MessageFormat.format("{0}.getCoordsForConnectionType({1}); Invalid connection type", 434 getName(), connectionType); // NOI18N 435 log.error("will throw {}", errString); // NOI18N 436 throw new jmri.JmriException(errString); 437 } 438 return result; 439 } 440 441 /** 442 * {@inheritDoc} 443 */ 444 @Override 445 public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException { 446 if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) { 447 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid type", 448 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 449 log.error("will throw {}", errString); // NOI18N 450 throw new jmri.JmriException(errString); 451 } 452 if (HitPointType.isTurntableRayHitType(connectionType)) { 453 if ((o == null) || (o instanceof TrackSegment)) { 454 setRayConnect((TrackSegment) o, connectionType.turntableTrackIndex()); 455 } else { 456 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid object: {4}", 457 getName(), connectionType, o.getName(), 458 type, o.getClass().getName()); // NOI18N 459 log.error("will throw {}", errString); // NOI18N 460 throw new jmri.JmriException(errString); 461 } 462 } else { 463 String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid connection type", 464 getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N 465 log.error("will throw {}", errString); // NOI18N 466 throw new jmri.JmriException(errString); 467 } 468 } 469 470 /** 471 * Test if ray with this index is a mainline track or not. 472 * <p> 473 * Defaults to false (not mainline) if connecting track segment is missing. 474 * 475 * @param index the index 476 * @return true if connecting track segment is mainline 477 */ 478 public boolean isMainlineIndexed(int index) { 479 boolean result = false; // assume failure (pessimist!) 480 481 for (RayTrack rt : rayTrackList) { 482 if (rt.getConnectionIndex() == index) { 483 TrackSegment ts = rt.getConnect(); 484 if (ts != null) { 485 result = ts.isMainline(); 486 break; 487 } 488 } 489 } 490 return result; 491 } 492 493 /** 494 * Test if ray at this index is a mainline track or not. 495 * <p> 496 * Defaults to false (not mainline) if connecting track segment is missing 497 * 498 * @param i the index 499 * @return true if connecting track segment is mainline 500 */ 501 public boolean isMainlineOrdered(int i) { 502 boolean result = false; // assume failure (pessimist!) 503 if (i < rayTrackList.size()) { 504 RayTrack rt = rayTrackList.get(i); 505 if (rt != null) { 506 TrackSegment ts = rt.getConnect(); 507 if (ts != null) { 508 result = ts.isMainline(); 509 } 510 } 511 } 512 return result; 513 } 514 515 @Override 516 public boolean isMainline() { 517 return false; 518 } 519 520 521 public String tLayoutBlockName = ""; 522 523 /** 524 * Initialization method The name of each track segment connected to a ray 525 * track is initialized by by LayoutTurntableXml, then the following method 526 * is called after the entire LayoutEditor is loaded to set the specific 527 * TrackSegment objects. 528 * 529 * @param p the layout editor 530 */ 531 @Override 532 public void setObjects(@Nonnull LayoutEditor p) { 533 if (tLayoutBlockName != null && !tLayoutBlockName.isEmpty()) { 534 setLayoutBlockByName(tLayoutBlockName); 535 } 536 tLayoutBlockName = null; /// release this memory 537 538 rayTrackList.forEach((rt) -> { 539 rt.setConnect(p.getFinder().findTrackSegmentByName(rt.connectName)); 540 }); 541 } 542 543 /** 544 * Is this turntable turnout controlled? 545 * 546 * @return true if so 547 */ 548 public boolean isTurnoutControlled() { 549 return turnoutControlled; 550 } 551 552 /** 553 * Set if this turntable is turnout controlled. 554 * 555 * @param boo set true if so 556 */ 557 public void setTurnoutControlled(boolean boo) { 558 turnoutControlled = boo; 559 } 560 561 /** 562 * Set turntable position to the ray with this index. 563 * 564 * @param index the index 565 */ 566 public void setPosition(int index) { 567 if (isTurnoutControlled()) { 568 boolean found = false; // assume failure (pessimist!) 569 for (RayTrack rt : rayTrackList) { 570 if (rt.getConnectionIndex() == index) { 571 lastKnownIndex = index; 572 rt.setPosition(); 573 models.redrawPanel(); 574 models.setDirty(); 575 found = true; 576 break; 577 } 578 } 579 if (!found) { 580 log.error("{}.setPosition({}); Attempt to set the position on a non-existant ray track", 581 getName(), index); 582 } 583 } 584 } 585 586 /** 587 * Get the turntable position. 588 * 589 * @return the turntable position 590 */ 591 public int getPosition() { 592 return lastKnownIndex; 593 } 594 595 /** 596 * Delete this ray track. 597 * 598 * @param rayTrack the ray track 599 */ 600 public void deleteRay(@Nonnull RayTrack rayTrack) { 601 TrackSegment t = null; 602 if (rayTrackList == null) { 603 log.error("{}.deleteRay(null); rayTrack is null", getName()); 604 } else { 605 t = rayTrack.getConnect(); 606 getRayTrackList().remove(rayTrack.getConnectionIndex()); 607 rayTrack.dispose(); 608 } 609 if (t != null) { 610 models.removeTrackSegment(t); 611 } 612 613 // update the panel 614 models.redrawPanel(); 615 models.setDirty(); 616 } 617 618 /** 619 * Remove this object from display and persistance. 620 */ 621 public void remove() { 622 // remove from persistance by flagging inactive 623 active = false; 624 } 625 626 private boolean active = true; 627 628 /** 629 * Get if turntable is active. 630 * "active" means that the object is still displayed, and should be stored. 631 * @return true if active, else false. 632 */ 633 public boolean isActive() { 634 return active; 635 } 636 637 public class RayTrack { 638 639 /** 640 * constructor for RayTracks 641 * 642 * @param angle its angle 643 * @param index its index 644 */ 645 public RayTrack(double angle, int index) { 646 rayAngle = MathUtil.wrapPM360(angle); 647 connect = null; 648 connectionIndex = index; 649 650 disabled = false; 651 disableWhenOccupied = false; 652 } 653 654 // persistant instance variables 655 private double rayAngle = 0.0; 656 private TrackSegment connect = null; 657 private int connectionIndex = -1; 658 659 private boolean disabled = false; 660 private boolean disableWhenOccupied = false; 661 662 // 663 // Accessor routines 664 // 665 /** 666 * Set ray track disabled. 667 * 668 * @param boo set true to disable 669 */ 670 public void setDisabled(boolean boo) { 671 if (disabled != boo) { 672 disabled = boo; 673 if (models != null) { 674 models.redrawPanel(); 675 } 676 } 677 } 678 679 /** 680 * Is this ray track disabled? 681 * 682 * @return true if so 683 */ 684 public boolean isDisabled() { 685 return disabled; 686 } 687 688 /** 689 * Set ray track disabled if occupied. 690 * 691 * @param boo set true to disable if occupied 692 */ 693 public void setDisabledWhenOccupied(boolean boo) { 694 if (disableWhenOccupied != boo) { 695 disableWhenOccupied = boo; 696 if (models != null) { 697 models.redrawPanel(); 698 } 699 } 700 } 701 702 /** 703 * Is ray track disabled if occupied? 704 * 705 * @return true if so 706 */ 707 public boolean isDisabledWhenOccupied() { 708 return disableWhenOccupied; 709 } 710 711 /** 712 * get the track segment connected to this ray 713 * 714 * @return the track segment connected to this ray 715 */ 716 // @CheckForNull termporary until we know whether this really can be null or not 717 public TrackSegment getConnect() { 718 return connect; 719 } 720 721 /** 722 * Set the track segment connected to this ray. 723 * 724 * @param ts the track segment to connect to this ray 725 */ 726 public void setConnect(TrackSegment ts) { 727 connect = ts; 728 } 729 730 /** 731 * Get the angle for this ray. 732 * 733 * @return the angle for this ray 734 */ 735 public double getAngle() { 736 return rayAngle; 737 } 738 739 /** 740 * Set the angle for this ray. 741 * 742 * @param an the angle for this ray 743 */ 744 public void setAngle(double an) { 745 rayAngle = MathUtil.wrapPM360(an); 746 } 747 748 /** 749 * Get the connection index for this ray. 750 * 751 * @return the connection index for this ray 752 */ 753 public int getConnectionIndex() { 754 return connectionIndex; 755 } 756 757 /** 758 * Is this ray occupied? 759 * 760 * @return true if occupied 761 */ 762 public boolean isOccupied() { // temporary - accessed by View - is this topology or visualization? 763 boolean result = false; // assume not 764 if (connect != null) { // does it have a connection? (yes) 765 LayoutBlock lb = connect.getLayoutBlock(); 766 if (lb != null) { // does the connection have a block? (yes) 767 // is the block occupied? 768 result = (lb.getOccupancy() == LayoutBlock.OCCUPIED); 769 } 770 } 771 return result; 772 } 773 774 // initialization instance variable (used when loading a LayoutEditor) 775 public String connectName = ""; 776 777 private NamedBeanHandle<Turnout> namedTurnout; 778 // Turnout t; 779 private int turnoutState; 780 private PropertyChangeListener mTurnoutListener; 781 782 /** 783 * Set the turnout and state for this ray track. 784 * 785 * @param turnoutName the turnout name 786 * @param state its state 787 */ 788 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", 789 justification="2nd check of turnoutName is considered redundant by SpotBugs, but required by ecj") // temporary 790 public void setTurnout(@Nonnull String turnoutName, int state) { 791 Turnout turnout = null; 792 if (mTurnoutListener == null) { 793 mTurnoutListener = (PropertyChangeEvent e) -> { 794 if (getTurnout().getKnownState() == turnoutState) { 795 lastKnownIndex = connectionIndex; 796 models.redrawPanel(); 797 models.setDirty(); 798 } 799 }; 800 } 801 if (turnoutName != null) { 802 turnout = jmri.InstanceManager.turnoutManagerInstance().getTurnout(turnoutName); 803 } 804 if (namedTurnout != null && namedTurnout.getBean() != turnout) { 805 namedTurnout.getBean().removePropertyChangeListener(mTurnoutListener); 806 } 807 if (turnout != null && (namedTurnout == null || namedTurnout.getBean() != turnout)) { 808 if (turnoutName != null && !turnoutName.isEmpty()) { 809 namedTurnout = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutName, turnout); 810 turnout.addPropertyChangeListener(mTurnoutListener, turnoutName, "Layout Editor Turntable"); 811 } 812 } 813 if (turnout == null) { 814 namedTurnout = null; 815 } 816 817 if (this.turnoutState != state) { 818 this.turnoutState = state; 819 } 820 } 821 822 /** 823 * Set the position for this ray track. 824 */ 825 public void setPosition() { 826 if (namedTurnout != null) { 827 if (disableWhenOccupied && isOccupied()) { 828 log.debug("Can not setPosition of turntable ray when it is occupied"); 829 } else { 830 getTurnout().setCommandedState(turnoutState); 831 } 832 } 833 } 834 835 /** 836 * Get the turnout for this ray track. 837 * 838 * @return the turnout or null 839 */ 840 // @CheckForNull temporary until we have central paradigm for null 841 public Turnout getTurnout() { 842 if (namedTurnout == null) { 843 return null; 844 } 845 return namedTurnout.getBean(); 846 } 847 848 /** 849 * Get the turnout name for the ray track. 850 * 851 * @return the turnout name 852 */ 853 @CheckForNull 854 public String getTurnoutName() { 855 if (namedTurnout == null) { 856 return null; 857 } 858 return namedTurnout.getName(); 859 } 860 861 /** 862 * Get the state for the turnout for this ray track. 863 * 864 * @return the state 865 */ 866 public int getTurnoutState() { 867 return turnoutState; 868 } 869 870 /** 871 * Dispose of this ray track. 872 */ 873 void dispose() { 874 if (getTurnout() != null) { 875 getTurnout().removePropertyChangeListener(mTurnoutListener); 876 } 877 if (lastKnownIndex == connectionIndex) { 878 lastKnownIndex = -1; 879 } 880 } 881 } // class RayTrack 882 883 /** 884 * {@inheritDoc} 885 */ 886 @Override 887 protected void reCheckBlockBoundary() { 888 // nothing to see here... move along... 889 } 890 891 /** 892 * {@inheritDoc} 893 */ 894 @Override 895 @CheckForNull 896 protected List<LayoutConnectivity> getLayoutConnectivity() { 897 // nothing to see here... move along... 898 return null; 899 } 900 901 /** 902 * {@inheritDoc} 903 */ 904 @Override 905 @Nonnull 906 public List<HitPointType> checkForFreeConnections() { 907 List<HitPointType> result = new ArrayList<>(); 908 909 for (int k = 0; k < getNumberRays(); k++) { 910 if (getRayConnectOrdered(k) == null) { 911 result.add(HitPointType.turntableTrackIndexedValue(k)); 912 } 913 } 914 return result; 915 } 916 917 /** 918 * {@inheritDoc} 919 */ 920 @Override 921 public boolean checkForUnAssignedBlocks() { 922 // Layout turnouts get their block information from the 923 // track segments attached to their rays so... 924 // nothing to see here... move along... 925 return true; 926 } 927 928 /** 929 * {@inheritDoc} 930 */ 931 @Override 932 public void checkForNonContiguousBlocks( 933 @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) { 934 /* 935 * For each (non-null) blocks of this track do: 936 * #1) If it's got an entry in the blockNamesToTrackNameSetMap then 937 * #2) If this track is already in the TrackNameSet for this block 938 * then return (done!) 939 * #3) else add a new set (with this block// track) to 940 * blockNamesToTrackNameSetMap and check all the connections in this 941 * block (by calling the 2nd method below) 942 * <p> 943 * Basically, we're maintaining contiguous track sets for each block found 944 * (in blockNamesToTrackNameSetMap) 945 */ 946 947 // We're using a map here because it is convient to 948 // use it to pair up blocks and connections 949 Map<LayoutTrack, String> blocksAndTracksMap = new HashMap<>(); 950 for (int k = 0; k < getNumberRays(); k++) { 951 TrackSegment ts = getRayConnectOrdered(k); 952 if (ts != null) { 953 String blockName = ts.getBlockName(); 954 blocksAndTracksMap.put(ts, blockName); 955 } 956 } 957 958 List<Set<String>> TrackNameSets; 959 Set<String> TrackNameSet; 960 for (Map.Entry<LayoutTrack, String> entry : blocksAndTracksMap.entrySet()) { 961 LayoutTrack theConnect = entry.getKey(); 962 String theBlockName = entry.getValue(); 963 964 TrackNameSet = null; // assume not found (pessimist!) 965 TrackNameSets = blockNamesToTrackNameSetsMap.get(theBlockName); 966 if (TrackNameSets != null) { // (#1) 967 for (Set<String> checkTrackNameSet : TrackNameSets) { 968 if (checkTrackNameSet.contains(getName())) { // (#2) 969 TrackNameSet = checkTrackNameSet; 970 break; 971 } 972 } 973 } else { // (#3) 974 log.debug("*New block (''{}'') trackNameSets", theBlockName); 975 TrackNameSets = new ArrayList<>(); 976 blockNamesToTrackNameSetsMap.put(theBlockName, TrackNameSets); 977 } 978 if (TrackNameSet == null) { 979 TrackNameSet = new LinkedHashSet<>(); 980 TrackNameSets.add(TrackNameSet); 981 } 982 if (TrackNameSet.add(getName())) { 983 log.debug("* Add track ''{}'' to trackNameSet for block ''{}''", getName(), theBlockName); 984 } 985 theConnect.collectContiguousTracksNamesInBlockNamed(theBlockName, TrackNameSet); 986 } 987 } 988 989 /** 990 * {@inheritDoc} 991 */ 992 @Override 993 public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName, 994 @Nonnull Set<String> TrackNameSet) { 995 if (!TrackNameSet.contains(getName())) { 996 // for all the rays with matching blocks in this turnout 997 // #1) if its track segment's block is in this block 998 // #2) add turntable to TrackNameSet (if not already there) 999 // #3) if the track segment isn't in the TrackNameSet 1000 // #4) flood it 1001 for (int k = 0; k < getNumberRays(); k++) { 1002 TrackSegment ts = getRayConnectOrdered(k); 1003 if (ts != null) { 1004 String blk = ts.getBlockName(); 1005 if ((!blk.isEmpty()) && (blk.equals(blockName))) { // (#1) 1006 // if we are added to the TrackNameSet 1007 if (TrackNameSet.add(getName())) { 1008 log.debug("* Add track ''{}'' for block ''{}''", getName(), blockName); 1009 } 1010 // it's time to play... flood your neighbours! 1011 ts.collectContiguousTracksNamesInBlockNamed(blockName, 1012 TrackNameSet); // (#4) 1013 } 1014 } 1015 } 1016 } 1017 } 1018 1019 /** 1020 * {@inheritDoc} 1021 */ 1022 @Override 1023 public void setAllLayoutBlocks(LayoutBlock layoutBlock) { 1024 // turntables don't have blocks... 1025 // nothing to see here, move along... 1026 } 1027 1028 /** 1029 * {@inheritDoc} 1030 */ 1031 @Override 1032 public boolean canRemove() { 1033 return true; 1034 } 1035 1036 /** 1037 * {@inheritDoc} 1038 */ 1039 @Override 1040 public String getTypeName() { 1041 return Bundle.getMessage("TypeName_Turntable"); 1042 } 1043 1044 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurntable.class); 1045 1046}