001package jmri.jmrit.operations.locations; 002 003import java.awt.Point; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006 007import javax.swing.JComboBox; 008 009import org.jdom2.Attribute; 010import org.jdom2.Element; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014import jmri.InstanceManager; 015import jmri.Reporter; 016import jmri.beans.Identifiable; 017import jmri.beans.PropertyChangeSupport; 018import jmri.jmrit.operations.OperationsPanel; 019import jmri.jmrit.operations.locations.divisions.Division; 020import jmri.jmrit.operations.locations.divisions.DivisionManager; 021import jmri.jmrit.operations.rollingstock.RollingStock; 022import jmri.jmrit.operations.rollingstock.cars.*; 023import jmri.jmrit.operations.rollingstock.engines.Engine; 024import jmri.jmrit.operations.rollingstock.engines.EngineTypes; 025import jmri.jmrit.operations.setup.Control; 026import jmri.jmrit.operations.setup.Setup; 027import jmri.jmrit.operations.trains.TrainCommon; 028import jmri.util.PhysicalLocation; 029 030/** 031 * Represents a location on the layout 032 * 033 * @author Daniel Boudreau Copyright (C) 2008, 2012, 2013 034 */ 035public class Location extends PropertyChangeSupport implements Identifiable, PropertyChangeListener { 036 037 public static final String LOC_TRACK_REGIX = "s"; 038 039 public static final String NONE = ""; 040 public static final int RANGE_DEFAULT = 25; 041 042 protected String _id = NONE; // location id 043 protected String _name = NONE; 044 protected int _IdNumber = 0; // last track id number created 045 protected int _numberRS = 0; // number of cars and engines (total rolling 046 // stock) 047 protected int _numberCars = 0; // number of cars 048 protected int _numberEngines = 0; // number of engines 049 protected int _pickupRS = 0; 050 protected int _dropRS = 0; 051 protected int _trainDir = EAST + WEST + NORTH + SOUTH; // train directions 052 protected int _length = 0; // length of all tracks at this location 053 protected int _usedLength = 0; // length of track filled by cars and engines 054 protected String _comment = NONE; 055 protected String _switchListComment = NONE; // optional switch list comment 056 protected boolean _switchList = true; // when true print switchlist 057 protected String _defaultPrinter = NONE; // the default printer name 058 protected String _status = UNKNOWN; // print switch list status 059 protected int _switchListState = SW_CREATE; // switch list state 060 protected Point _trainIconEast = new Point(); // coordinates east bound 061 protected Point _trainIconWest = new Point(); 062 protected Point _trainIconNorth = new Point(); 063 protected Point _trainIconSouth = new Point(); 064 protected int _trainIconRangeX = RANGE_DEFAULT; 065 protected int _trainIconRangeY = RANGE_DEFAULT; 066 protected Hashtable<String, Track> _trackHashTable = new Hashtable<>(); 067 protected PhysicalLocation _physicalLocation = new PhysicalLocation(); 068 protected List<String> _listTypes = new ArrayList<>(); 069 protected Division _division = null; 070 071 // IdTag reader associated with this location. 072 protected Reporter _reader = null; 073 074 // Pool 075 protected int _idPoolNumber = 0; 076 protected Hashtable<String, Pool> _poolHashTable = new Hashtable<>(); 077 078 public static final String NORMAL = "1"; // types of track allowed at this 079 // location 080 public static final String STAGING = "2"; // staging only 081 082 public static final int EAST = 1; // train direction serviced by this 083 // location 084 public static final int WEST = 2; 085 public static final int NORTH = 4; 086 public static final int SOUTH = 8; 087 088 // Switch list status 089 public static final String UNKNOWN = ""; 090 public static final String PRINTED = Bundle.getMessage("Printed"); 091 public static final String CSV_GENERATED = Bundle.getMessage("CsvGenerated"); 092 public static final String MODIFIED = Bundle.getMessage("Modified"); 093 public static final String UPDATED = Bundle.getMessage("Updated"); 094 095 // Switch list states 096 public static final int SW_CREATE = 0; // create new switch list 097 public static final int SW_APPEND = 1; // append train into to switch list 098 public static final int SW_PRINTED = 2; // switch list printed 099 100 // For property change 101 public static final String TRACK_LISTLENGTH_CHANGED_PROPERTY = "trackListLength"; // NOI18N 102 public static final String TYPES_CHANGED_PROPERTY = "locationTypes"; // NOI18N 103 public static final String TRAIN_DIRECTION_CHANGED_PROPERTY = "locationTrainDirection"; // NOI18N 104 public static final String LENGTH_CHANGED_PROPERTY = "locationTrackLengths"; // NOI18N 105 public static final String USEDLENGTH_CHANGED_PROPERTY = "locationUsedLength"; // NOI18N 106 public static final String NAME_CHANGED_PROPERTY = "locationName"; // NOI18N 107 public static final String SWITCHLIST_CHANGED_PROPERTY = "switchList"; // NOI18N 108 public static final String DISPOSE_CHANGED_PROPERTY = "locationDispose"; // NOI18N 109 public static final String STATUS_CHANGED_PROPERTY = "locationStatus"; // NOI18N 110 public static final String POOL_LENGTH_CHANGED_PROPERTY = "poolLengthChanged"; // NOI18N 111 public static final String SWITCHLIST_COMMENT_CHANGED_PROPERTY = "switchListComment";// NOI18N 112 public static final String TRACK_BLOCKING_ORDER_CHANGED_PROPERTY = "locationTrackBlockingOrder";// NOI18N 113 public static final String LOCATION_REPORTER_CHANGED_PROPERTY = "locationReporterChange"; // NOI18N 114 public static final String LOCATION_DIVISION_CHANGED_PROPERTY = "homeDivisionChange"; // NOI18N 115 116 public Location(String id, String name) { 117 log.debug("New location ({}) id: {}", name, id); 118 _name = name; 119 _id = id; 120 // a new location accepts all types 121 setTypeNames(InstanceManager.getDefault(CarTypes.class).getNames()); 122 setTypeNames(InstanceManager.getDefault(EngineTypes.class).getNames()); 123 addPropertyChangeListeners(); 124 } 125 126 @Override 127 public String getId() { 128 return _id; 129 } 130 131 /** 132 * Sets the location's name. 133 * 134 * @param name The string name for this location. 135 */ 136 public void setName(String name) { 137 String old = _name; 138 _name = name; 139 if (!old.equals(name)) { 140 // recalculate max location name length for Manifests 141 InstanceManager.getDefault(LocationManager.class).resetNameLengths(); 142 setDirtyAndFirePropertyChange(NAME_CHANGED_PROPERTY, old, name); 143 } 144 } 145 146 // for combo boxes 147 @Override 148 public String toString() { 149 return _name; 150 } 151 152 public String getName() { 153 return _name; 154 } 155 156 public String getSplitName() { 157 return TrainCommon.splitString(getName()); 158 } 159 160 /** 161 * Makes a copy of this location. 162 * 163 * @param newLocation the location to copy to 164 */ 165 public void copyLocation(Location newLocation) { 166 newLocation.setComment(getCommentWithColor()); 167 newLocation.setDefaultPrinterName(getDefaultPrinterName()); 168 newLocation.setSwitchListComment(getSwitchListCommentWithColor()); 169 newLocation.setSwitchListEnabled(isSwitchListEnabled()); 170 newLocation.setTrainDirections(getTrainDirections()); 171 // TODO should we set the train icon coordinates? 172 // rolling stock serviced by this location 173 for (String type : newLocation.getTypeNames()) { 174 if (acceptsTypeName(type)) { 175 continue; 176 } else { 177 newLocation.deleteTypeName(type); 178 } 179 } 180 copyTracksLocation(newLocation); 181 } 182 183 /** 184 * Copies all of the tracks at this location. If there's a track already at 185 * the copy to location with the same name, the track is skipped. 186 * 187 * @param location the location to copy the tracks to. 188 */ 189 public void copyTracksLocation(Location location) { 190 for (Track track : getTracksList()) { 191 if (location.getTrackByName(track.getName(), null) != null) { 192 continue; 193 } 194 track.copyTrack(track.getName(), location); 195 } 196 } 197 198 public PhysicalLocation getPhysicalLocation() { 199 return (_physicalLocation); 200 } 201 202 public void setPhysicalLocation(PhysicalLocation l) { 203 _physicalLocation = l; 204 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 205 } 206 207 /** 208 * Set total length of all tracks for this location 209 * 210 * @param length The integer sum of all tracks at this location. 211 */ 212 public void setLength(int length) { 213 int old = _length; 214 _length = length; 215 if (old != length) { 216 setDirtyAndFirePropertyChange(LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length)); 217 } 218 } 219 220 /** 221 * @return total length of all tracks for this location 222 */ 223 public int getLength() { 224 return _length; 225 } 226 227 public void setUsedLength(int length) { 228 int old = _usedLength; 229 _usedLength = length; 230 if (old != length) { 231 setDirtyAndFirePropertyChange(USEDLENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length)); 232 } 233 } 234 235 /** 236 * @return The length of the track that is occupied by cars and engines 237 */ 238 public int getUsedLength() { 239 return _usedLength; 240 } 241 242 /** 243 * Used to determine if location is setup for staging 244 * 245 * @return true if location is setup as staging 246 */ 247 public boolean isStaging() { 248 return hasTrackType(Track.STAGING); 249 } 250 251 /** 252 * @return True if location has spurs 253 */ 254 public boolean hasSpurs() { 255 return hasTrackType(Track.SPUR); 256 } 257 258 /** 259 * @return True if location has classification/interchange tracks 260 */ 261 public boolean hasInterchanges() { 262 return hasTrackType(Track.INTERCHANGE); 263 } 264 265 /** 266 * @return True if location has yard tracks 267 */ 268 public boolean hasYards() { 269 return hasTrackType(Track.YARD); 270 } 271 272 /** 273 * @param trackType The track type to check. 274 * @return True if location has the track type specified Track.INTERCHANGE 275 * Track.YARD Track.SPUR Track.Staging 276 */ 277 public boolean hasTrackType(String trackType) { 278 Track track; 279 Enumeration<Track> en = _trackHashTable.elements(); 280 while (en.hasMoreElements()) { 281 track = en.nextElement(); 282 if (track.getTrackType().equals(trackType)) { 283 return true; 284 } 285 } 286 return false; 287 } 288 289 /** 290 * Change all tracks at this location to type 291 * 292 * @param type Track.INTERCHANGE Track.YARD Track.SPUR Track.Staging 293 */ 294 public void changeTrackType(String type) { 295 List<Track> tracks = getTracksByNameList(null); 296 for (Track track : tracks) { 297 track.setTrackType(type); 298 } 299 } 300 301 public int getNumberOfTracks() { 302 return _trackHashTable.size(); 303 } 304 305 /** 306 * Sets the train directions that this location can service. EAST means that 307 * an Eastbound train can service the location. 308 * 309 * @param direction Any combination of EAST WEST NORTH SOUTH 310 */ 311 public void setTrainDirections(int direction) { 312 int old = _trainDir; 313 _trainDir = direction; 314 if (old != direction) { 315 setDirtyAndFirePropertyChange(TRAIN_DIRECTION_CHANGED_PROPERTY, Integer.toString(old), 316 Integer.toString(direction)); 317 } 318 } 319 320 /** 321 * Gets the train directions that this location can service. EAST means that 322 * an Eastbound train can service the location. 323 * 324 * @return Any combination of EAST WEST NORTH SOUTH 325 */ 326 public int getTrainDirections() { 327 return _trainDir; 328 } 329 330 /** 331 * Sets the quantity of rolling stock for this location 332 * 333 * @param number An integer representing the quantity of rolling stock at 334 * this location. 335 */ 336 public void setNumberRS(int number) { 337 int old = _numberRS; 338 _numberRS = number; 339 if (old != number) { 340 setDirtyAndFirePropertyChange("locationNumberRS", Integer.toString(old), Integer.toString(number)); // NOI18N 341 } 342 } 343 344 /** 345 * Gets the number of cars and engines at this location 346 * 347 * @return number of cars at this location 348 */ 349 public int getNumberRS() { 350 return _numberRS; 351 } 352 353 /** 354 * Sets the number of cars at this location 355 */ 356 private void setNumberCars(int number) { 357 int old = _numberCars; 358 _numberCars = number; 359 if (old != number) { 360 setDirtyAndFirePropertyChange("locationNumberCars", Integer.toString(old), // NOI18N 361 Integer.toString(number)); // NOI18N 362 } 363 } 364 365 /** 366 * @return The number of cars at this location 367 */ 368 public int getNumberCars() { 369 return _numberCars; 370 } 371 372 /** 373 * Sets the number of engines at this location 374 */ 375 private void setNumberEngines(int number) { 376 int old = _numberEngines; 377 _numberEngines = number; 378 if (old != number) { 379 setDirtyAndFirePropertyChange("locationNumberEngines", Integer.toString(old), // NOI18N 380 Integer.toString(number)); // NOI18N 381 } 382 } 383 384 /** 385 * @return The number of engines at this location 386 */ 387 public int getNumberEngines() { 388 return _numberEngines; 389 } 390 391 /** 392 * When true, a switchlist is desired for this location. Used for preview 393 * and printing a manifest for a single location 394 * 395 * @param switchList When true, switch lists are enabled for this location. 396 */ 397 public void setSwitchListEnabled(boolean switchList) { 398 boolean old = _switchList; 399 _switchList = switchList; 400 if (old != switchList) { 401 setDirtyAndFirePropertyChange(SWITCHLIST_CHANGED_PROPERTY, old ? "true" : "false", // NOI18N 402 switchList ? "true" : "false"); // NOI18N 403 } 404 } 405 406 /** 407 * Used to determine if switch list is needed for this location 408 * 409 * @return true if switch list needed 410 */ 411 public boolean isSwitchListEnabled() { 412 return _switchList; 413 } 414 415 public void setDefaultPrinterName(String name) { 416 String old = _defaultPrinter; 417 _defaultPrinter = name; 418 if (!old.equals(name)) { 419 setDirtyAndFirePropertyChange("locationDefaultPrinter", old, name); // NOI18N 420 } 421 } 422 423 public String getDefaultPrinterName() { 424 return _defaultPrinter; 425 } 426 427 /** 428 * Sets the print status for this location's switch list 429 * 430 * @param status UNKNOWN PRINTED MODIFIED UPDATED CSV_GENERATED 431 */ 432 public void setStatus(String status) { 433 String old = _status; 434 _status = status; 435 if (!old.equals(status)) { 436 setDirtyAndFirePropertyChange(STATUS_CHANGED_PROPERTY, old, status); 437 } 438 } 439 440 /** 441 * The print status for this location's switch list 442 * 443 * @return UNKNOWN PRINTED MODIFIED UPDATED CSV_GENERATED 444 */ 445 public String getStatus() { 446 return _status; 447 } 448 449 /** 450 * @param state Location.SW_CREATE Location.SW_PRINTED Location.SW_APPEND 451 */ 452 public void setSwitchListState(int state) { 453 int old = _switchListState; 454 _switchListState = state; 455 if (old != state) { 456 setDirtyAndFirePropertyChange("locationSwitchListState", old, state); // NOI18N 457 } 458 } 459 460 /** 461 * Returns the state of the switch list for this location. 462 * 463 * @return Location.SW_CREATE, Location.SW_PRINTED or Location.SW_APPEND 464 */ 465 public int getSwitchListState() { 466 return _switchListState; 467 } 468 469 /** 470 * Sets the train icon coordinates for an eastbound train arriving at this 471 * location. 472 * 473 * @param point The XY coordinates on the panel. 474 */ 475 public void setTrainIconEast(Point point) { 476 Point old = _trainIconEast; 477 _trainIconEast = point; 478 setDirtyAndFirePropertyChange("locationTrainIconEast", old.toString(), point.toString()); // NOI18N 479 } 480 481 public Point getTrainIconEast() { 482 return _trainIconEast; 483 } 484 485 public void setTrainIconWest(Point point) { 486 Point old = _trainIconWest; 487 _trainIconWest = point; 488 setDirtyAndFirePropertyChange("locationTrainIconWest", old.toString(), point.toString()); // NOI18N 489 } 490 491 public Point getTrainIconWest() { 492 return _trainIconWest; 493 } 494 495 public void setTrainIconNorth(Point point) { 496 Point old = _trainIconNorth; 497 _trainIconNorth = point; 498 setDirtyAndFirePropertyChange("locationTrainIconNorth", old.toString(), point.toString()); // NOI18N 499 } 500 501 public Point getTrainIconNorth() { 502 return _trainIconNorth; 503 } 504 505 public void setTrainIconSouth(Point point) { 506 Point old = _trainIconSouth; 507 _trainIconSouth = point; 508 setDirtyAndFirePropertyChange("locationTrainIconSouth", old.toString(), point.toString()); // NOI18N 509 } 510 511 public Point getTrainIconSouth() { 512 return _trainIconSouth; 513 } 514 515 /** 516 * Sets the X range for detecting the manual movement of a train icon. 517 * 518 * @param x the +/- range for detection 519 */ 520 public void setTrainIconRangeX(int x) { 521 int old = _trainIconRangeX; 522 _trainIconRangeX = x; 523 if (old != x) { 524 setDirtyAndFirePropertyChange("trainIconRangeX", Integer.toString(old), Integer.toString(x)); // NOI18N 525 } 526 } 527 528 /** 529 * Used to determine the sensitivity when a user is manually moving a train 530 * icon on a panel. 531 * 532 * @return the x +/- range for a train icon 533 */ 534 public int getTrainIconRangeX() { 535 return _trainIconRangeX; 536 } 537 538 /** 539 * Sets the Y range for detecting the manual movement of a train icon. 540 * 541 * @param y the +/- range for detection 542 */ 543 public void setTrainIconRangeY(int y) { 544 int old = _trainIconRangeY; 545 _trainIconRangeY = y; 546 if (old != y) { 547 setDirtyAndFirePropertyChange("trainIconRangeY", Integer.toString(old), Integer.toString(y)); // NOI18N 548 } 549 } 550 551 /** 552 * Used to determine the sensitivity when a user is manually moving a train 553 * icon on a panel. 554 * 555 * @return the y +/- range for a train icon 556 */ 557 public int getTrainIconRangeY() { 558 return _trainIconRangeY; 559 } 560 561 /** 562 * Adds rolling stock to a specific location. 563 * 564 * @param rs The RollingStock to add. 565 */ 566 public void addRS(RollingStock rs) { 567 setNumberRS(getNumberRS() + 1); 568 if (rs.getClass() == Car.class) { 569 setNumberCars(getNumberCars() + 1); 570 } else if (rs.getClass() == Engine.class) { 571 setNumberEngines(getNumberEngines() + 1); 572 } 573 setUsedLength(getUsedLength() + rs.getTotalLength()); 574 } 575 576 public void deleteRS(RollingStock rs) { 577 setNumberRS(getNumberRS() - 1); 578 if (rs.getClass() == Car.class) { 579 setNumberCars(getNumberCars() - 1); 580 } else if (rs.getClass() == Engine.class) { 581 setNumberEngines(getNumberEngines() - 1); 582 } 583 setUsedLength(getUsedLength() - rs.getTotalLength()); 584 } 585 586 /** 587 * Increments the number of cars and or engines that will be picked up by a 588 * train at this location. 589 */ 590 public void addPickupRS() { 591 int old = _pickupRS; 592 _pickupRS++; 593 setDirtyAndFirePropertyChange("locationAddPickupRS", Integer.toString(old), Integer.toString(_pickupRS)); // NOI18N 594 } 595 596 /** 597 * Decrements the number of cars and or engines that will be picked up by a 598 * train at this location. 599 */ 600 public void deletePickupRS() { 601 int old = _pickupRS; 602 _pickupRS--; 603 setDirtyAndFirePropertyChange("locationDeletePickupRS", Integer.toString(old), Integer.toString(_pickupRS)); // NOI18N 604 } 605 606 /** 607 * Increments the number of cars and or engines that will be dropped off by 608 * trains at this location. 609 */ 610 public void addDropRS() { 611 int old = _dropRS; 612 _dropRS++; 613 setDirtyAndFirePropertyChange("locationAddDropRS", Integer.toString(old), Integer.toString(_dropRS)); // NOI18N 614 } 615 616 /** 617 * Decrements the number of cars and or engines that will be dropped off by 618 * trains at this location. 619 */ 620 public void deleteDropRS() { 621 int old = _dropRS; 622 _dropRS--; 623 setDirtyAndFirePropertyChange("locationDeleteDropRS", Integer.toString(old), Integer.toString(_dropRS)); // NOI18N 624 } 625 626 /** 627 * @return the number of cars and engines that are scheduled for pick up at 628 * this location. 629 */ 630 public int getPickupRS() { 631 return _pickupRS; 632 } 633 634 /** 635 * @return the number of cars and engines that are scheduled for drop at 636 * this location. 637 */ 638 public int getDropRS() { 639 return _dropRS; 640 } 641 642 public void setDivision(Division division) { 643 Division old = _division; 644 _division = division; 645 if (old != _division) { 646 setDirtyAndFirePropertyChange(LOCATION_DIVISION_CHANGED_PROPERTY, old, division); 647 } 648 } 649 650 /** 651 * Gets the division for this location 652 * 653 * @return the division for this location 654 */ 655 public Division getDivision() { 656 return _division; 657 } 658 659 public String getDivisionName() { 660 if (getDivision() != null) { 661 return getDivision().getName(); 662 } 663 return NONE; 664 } 665 666 public String getDivisionId() { 667 if (getDivision() != null) { 668 return getDivision().getId(); 669 } 670 return NONE; 671 } 672 673 public void setComment(String comment) { 674 String old = _comment; 675 _comment = comment; 676 if (!old.equals(comment)) { 677 setDirtyAndFirePropertyChange("locationComment", old, comment); // NOI18N 678 } 679 } 680 681 /** 682 * Gets the comment text without color attributes 683 * 684 * @return the comment text 685 */ 686 public String getComment() { 687 return TrainCommon.getTextColorString(getCommentWithColor()); 688 } 689 690 /** 691 * Gets the comment text with optional color attributes 692 * 693 * @return text with optional color attributes 694 */ 695 public String getCommentWithColor() { 696 return _comment; 697 } 698 699 public void setSwitchListComment(String comment) { 700 String old = _switchListComment; 701 _switchListComment = comment; 702 if (!old.equals(comment)) { 703 setDirtyAndFirePropertyChange(SWITCHLIST_COMMENT_CHANGED_PROPERTY, old, comment); 704 } 705 } 706 707 /** 708 * Gets the switch list comment text without color attributes 709 * 710 * @return the comment text 711 */ 712 public String getSwitchListComment() { 713 return TrainCommon.getTextColorString(getSwitchListCommentWithColor()); 714 } 715 716 /** 717 * Gets the switch list comment text with optional color attributes 718 * 719 * @return text with optional color attributes 720 */ 721 public String getSwitchListCommentWithColor() { 722 return _switchListComment; 723 } 724 725 public String[] getTypeNames() { 726 return _listTypes.toArray(new String[0]); 727 } 728 729 private void setTypeNames(String[] types) { 730 if (types.length > 0) { 731 Arrays.sort(types); 732 for (String type : types) { 733 _listTypes.add(type); 734 } 735 } 736 } 737 738 /** 739 * Adds the specific type of rolling stock to the will service list 740 * 741 * @param type of rolling stock that location will service 742 */ 743 public void addTypeName(String type) { 744 // insert at start of list, sort later 745 if (type == null || _listTypes.contains(type)) { 746 return; 747 } 748 _listTypes.add(0, type); 749 log.debug("Location ({}) add rolling stock type ({})", getName(), type); 750 setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _listTypes.size() - 1, _listTypes.size()); 751 } 752 753 public void deleteTypeName(String type) { 754 if (_listTypes.remove(type)) { 755 log.debug("Location ({}) delete rolling stock type ({})", getName(), type); 756 setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _listTypes.size() + 1, _listTypes.size()); 757 } 758 } 759 760 public boolean acceptsTypeName(String type) { 761 return _listTypes.contains(type); 762 } 763 764 /** 765 * Adds a track to this location. Valid track types are spurs, yards, 766 * staging and interchange tracks. 767 * 768 * @param name of track 769 * @param type of track, Track.INTERCHANGE, Track.SPUR, Track.STAGING, 770 * Track.YARD 771 * @return Track 772 */ 773 public Track addTrack(String name, String type) { 774 Track track = getTrackByName(name, type); 775 if (track == null) { 776 _IdNumber++; 777 // create track id 778 String id = getId() + LOC_TRACK_REGIX + Integer.toString(_IdNumber); 779 log.debug("Adding new ({}) to ({}) track name ({}) id: {}", type, getName(), name, id); 780 track = new Track(id, name, type, this); 781 // recalculate max location name length for Manifests 782 InstanceManager.getDefault(LocationManager.class).resetNameLengths(); 783 register(track); 784 } 785 resetMoves(); // give all of the tracks equal weighting 786 return track; 787 } 788 789 /** 790 * Remember a NamedBean Object created outside the manager. 791 * 792 * @param track The Track to be loaded at this location. 793 */ 794 public void register(Track track) { 795 Integer old = Integer.valueOf(getNumberOfTracks()); 796 _trackHashTable.put(track.getId(), track); 797 // add to the locations's available track length 798 setLength(getLength() + track.getLength()); 799 // save last id number created 800 String[] getId = track.getId().split(LOC_TRACK_REGIX); 801 int id = Integer.parseInt(getId[1]); 802 if (id > _IdNumber) { 803 _IdNumber = id; 804 } 805 setDirtyAndFirePropertyChange(TRACK_LISTLENGTH_CHANGED_PROPERTY, old, Integer.valueOf(getNumberOfTracks())); 806 // listen for name and state changes to forward 807 track.addPropertyChangeListener(this); 808 } 809 810 public void deleteTrack(Track track) { 811 if (track != null) { 812 track.removePropertyChangeListener(this); 813 // subtract from the locations's available track length 814 setLength(getLength() - track.getLength()); 815 track.dispose(); 816 Integer old = Integer.valueOf(getNumberOfTracks()); 817 _trackHashTable.remove(track.getId()); 818 setDirtyAndFirePropertyChange(TRACK_LISTLENGTH_CHANGED_PROPERTY, old, 819 Integer.valueOf(getNumberOfTracks())); 820 } 821 } 822 823 /** 824 * Get track at this location by name and type. Track type can be null. 825 * 826 * @param name track's name 827 * @param type track type 828 * @return track at location 829 */ 830 public Track getTrackByName(String name, String type) { 831 Track track; 832 Enumeration<Track> en = _trackHashTable.elements(); 833 while (en.hasMoreElements()) { 834 track = en.nextElement(); 835 if (type == null) { 836 if (track.getName().equals(name)) { 837 return track; 838 } 839 } else if (track.getName().equals(name) && track.getTrackType().equals(type)) { 840 return track; 841 } 842 } 843 return null; 844 } 845 846 public Track getTrackById(String id) { 847 return _trackHashTable.get(id); 848 } 849 850 /** 851 * Gets a list of track ids ordered by id for this location. 852 * 853 * @return list of track ids for this location 854 */ 855 public List<String> getTrackIdsByIdList() { 856 List<String> out = new ArrayList<>(); 857 Enumeration<String> en = _trackHashTable.keys(); 858 while (en.hasMoreElements()) { 859 out.add(en.nextElement()); 860 } 861 Collections.sort(out); 862 return out; 863 } 864 865 /** 866 * Gets a sorted by id list of tracks for this location. 867 * 868 * @return Sorted list of tracks by id for this location. 869 */ 870 public List<Track> getTracksByIdList() { 871 List<Track> out = new ArrayList<>(); 872 List<String> trackIds = getTrackIdsByIdList(); 873 for (String id : trackIds) { 874 out.add(getTrackById(id)); 875 } 876 return out; 877 } 878 879 /** 880 * Gets a unsorted list of the tracks at this location. 881 * 882 * @return tracks at this location. 883 */ 884 public List<Track> getTracksList() { 885 List<Track> out = new ArrayList<>(); 886 Enumeration<Track> en = _trackHashTable.elements(); 887 while (en.hasMoreElements()) { 888 out.add(en.nextElement()); 889 } 890 return out; 891 } 892 893 /** 894 * Sorted list by track name. Returns a list of tracks of a given track 895 * type. If type is null returns all tracks for the location. 896 * 897 * @param type track type: Track.YARD, Track.SPUR, Track.INTERCHANGE, 898 * Track.STAGING 899 * @return list of tracks ordered by name for this location 900 */ 901 public List<Track> getTracksByNameList(String type) { 902 List<Track> out = new ArrayList<>(); 903 for (Track track : getTracksByIdList()) { 904 boolean locAdded = false; 905 for (int j = 0; j < out.size(); j++) { 906 if (track.getName().compareToIgnoreCase(out.get(j).getName()) < 0 && 907 (type != null && track.getTrackType().equals(type) || type == null)) { 908 out.add(j, track); 909 locAdded = true; 910 break; 911 } 912 } 913 if (!locAdded && (type != null && track.getTrackType().equals(type) || type == null)) { 914 out.add(track); 915 } 916 } 917 return out; 918 } 919 920 /** 921 * Sorted list by track moves. Returns a list of a given track type. If type 922 * is null, all tracks for the location are returned. Tracks with schedules 923 * are placed at the start of the list. Tracks that are alternates are 924 * removed. 925 * 926 * @param type track type: Track.YARD, Track.SPUR, Track.INTERCHANGE, 927 * Track.STAGING 928 * @return list of tracks at this location ordered by moves 929 */ 930 public List<Track> getTracksByMoves(String type) { 931 List<Track> moveList = new ArrayList<>(); 932 for (Track track : getTracksByIdList()) { 933 boolean locAdded = false; 934 for (int j = 0; j < moveList.size(); j++) { 935 if (track.getMoves() < moveList.get(j).getMoves() && 936 (type != null && track.getTrackType().equals(type) || type == null)) { 937 moveList.add(j, track); 938 locAdded = true; 939 break; 940 } 941 } 942 if (!locAdded && (type != null && track.getTrackType().equals(type) || type == null)) { 943 moveList.add(track); 944 } 945 } 946 // remove alternate tracks from the list 947 // bias tracks with schedules to the start of the list 948 List<Track> out = new ArrayList<>(); 949 for (int i = 0; i < moveList.size(); i++) { 950 Track track = moveList.get(i); 951 if (track.isAlternate()) { 952 moveList.remove(i--); 953 } else if (!track.getScheduleId().equals(NONE)) { 954 out.add(track); 955 moveList.remove(i--); 956 } 957 } 958 for (Track track : moveList) { 959 out.add(track); 960 } 961 return out; 962 } 963 964 /** 965 * Sorted list by track blocking order. Returns a list of a given track 966 * type. If type is null, all tracks for the location are returned. 967 * 968 * @param type track type: Track.YARD, Track.SPUR, Track.INTERCHANGE, 969 * Track.STAGING 970 * @return list of tracks at this location ordered by blocking order 971 */ 972 public List<Track> getTracksByBlockingOrderList(String type) { 973 List<Track> orderList = new ArrayList<>(); 974 for (Track track : getTracksByNameList(type)) { 975 for (int j = 0; j < orderList.size(); j++) { 976 if (track.getBlockingOrder() < orderList.get(j).getBlockingOrder()) { 977 orderList.add(j, track); 978 break; 979 } 980 } 981 if (!orderList.contains(track)) { 982 orderList.add(track); 983 } 984 } 985 return orderList; 986 } 987 988 public void resetTracksByBlockingOrder() { 989 for (Track track : getTracksList()) { 990 track.setBlockingOrder(0); 991 } 992 setDirtyAndFirePropertyChange(TRACK_BLOCKING_ORDER_CHANGED_PROPERTY, true, false); 993 } 994 995 public void resequnceTracksByBlockingOrder() { 996 int order = 1; 997 for (Track track : getTracksByBlockingOrderList(null)) { 998 track.setBlockingOrder(order++); 999 } 1000 setDirtyAndFirePropertyChange(TRACK_BLOCKING_ORDER_CHANGED_PROPERTY, true, false); 1001 } 1002 1003 public void changeTrackBlockingOrderEarlier(Track track) { 1004 // if track blocking order is 0, then the blocking table has never been 1005 // initialized 1006 if (track.getBlockingOrder() != 0) { 1007 // first adjust the track being replaced 1008 Track repalceTrack = getTrackByBlockingOrder(track.getBlockingOrder() - 1); 1009 if (repalceTrack != null) { 1010 repalceTrack.setBlockingOrder(track.getBlockingOrder()); 1011 } 1012 track.setBlockingOrder(track.getBlockingOrder() - 1); 1013 // move the end of order 1014 if (track.getBlockingOrder() <= 0) 1015 track.setBlockingOrder(getNumberOfTracks() + 1); 1016 } 1017 resequnceTracksByBlockingOrder(); 1018 } 1019 1020 public void changeTrackBlockingOrderLater(Track track) { 1021 // if track blocking order is 0, then the blocking table has never been 1022 // initialized 1023 if (track.getBlockingOrder() != 0) { 1024 // first adjust the track being replaced 1025 Track repalceTrack = getTrackByBlockingOrder(track.getBlockingOrder() + 1); 1026 if (repalceTrack != null) { 1027 repalceTrack.setBlockingOrder(track.getBlockingOrder()); 1028 } 1029 track.setBlockingOrder(track.getBlockingOrder() + 1); 1030 // move the start of order 1031 if (track.getBlockingOrder() > getNumberOfTracks()) 1032 track.setBlockingOrder(0); 1033 } 1034 resequnceTracksByBlockingOrder(); 1035 } 1036 1037 private Track getTrackByBlockingOrder(int order) { 1038 for (Track track : getTracksList()) { 1039 if (track.getBlockingOrder() == order) 1040 return track; 1041 } 1042 return null; // not found! 1043 } 1044 1045 public boolean isTrackAtLocation(Track track) { 1046 if (track == null) { 1047 return true; 1048 } 1049 return _trackHashTable.contains(track); 1050 } 1051 1052 /** 1053 * Reset the move count for all tracks at this location 1054 */ 1055 public void resetMoves() { 1056 List<Track> tracks = getTracksList(); 1057 for (Track track : tracks) { 1058 track.setMoves(0); 1059 } 1060 } 1061 1062 /** 1063 * Updates a JComboBox with all of the track locations for this location. 1064 * 1065 * @param box JComboBox to be updated. 1066 */ 1067 public void updateComboBox(JComboBox<Track> box) { 1068 box.removeAllItems(); 1069 box.addItem(null); 1070 List<Track> tracks = getTracksByNameList(null); 1071 for (Track track : tracks) { 1072 box.addItem(track); 1073 } 1074 OperationsPanel.padComboBox(box, InstanceManager.getDefault(LocationManager.class).getMaxTrackNameLength()); 1075 } 1076 1077 /** 1078 * Updates a JComboBox with tracks that can service the rolling stock. 1079 * 1080 * @param box JComboBox to be updated. 1081 * @param rs Rolling Stock to be serviced 1082 * @param filter When true, remove tracks not able to service rs. 1083 * @param isDestination When true, the tracks are destinations for the rs. 1084 */ 1085 public void updateComboBox(JComboBox<Track> box, RollingStock rs, boolean filter, boolean isDestination) { 1086 updateComboBox(box); 1087 if (!filter || rs == null) { 1088 return; 1089 } 1090 List<Track> tracks = getTracksByNameList(null); 1091 for (Track track : tracks) { 1092 String status = ""; 1093 if (isDestination) { 1094 status = rs.checkDestination(this, track); 1095 } else { 1096 status = rs.testLocation(this, track); 1097 } 1098 if (status.equals(Track.OKAY) && (!isDestination || !track.isStaging())) { 1099 box.setSelectedItem(track); 1100 log.debug("Available track: {} for location: {}", track.getName(), getName()); 1101 } else { 1102 box.removeItem(track); 1103 } 1104 } 1105 } 1106 1107 /** 1108 * Adds a track pool for this location. A track pool is a set of tracks 1109 * where the length of the tracks is shared between all of them. 1110 * 1111 * @param name the name of the Pool to create 1112 * @return Pool 1113 */ 1114 public Pool addPool(String name) { 1115 Pool pool = getPoolByName(name); 1116 if (pool == null) { 1117 _idPoolNumber++; 1118 String id = getId() + "p" + Integer.toString(_idPoolNumber); 1119 log.debug("creating new pool ({}) id: {}", name, id); 1120 pool = new Pool(id, name); 1121 register(pool); 1122 } 1123 return pool; 1124 } 1125 1126 public void removePool(Pool pool) { 1127 if (pool != null) { 1128 _poolHashTable.remove(pool.getId()); 1129 setDirtyAndFirePropertyChange(POOL_LENGTH_CHANGED_PROPERTY, Integer.valueOf(_poolHashTable.size() + 1), 1130 Integer.valueOf(_poolHashTable.size())); 1131 } 1132 } 1133 1134 public Pool getPoolByName(String name) { 1135 Pool pool; 1136 Enumeration<Pool> en = _poolHashTable.elements(); 1137 while (en.hasMoreElements()) { 1138 pool = en.nextElement(); 1139 if (pool.getName().equals(name)) { 1140 return pool; 1141 } 1142 } 1143 return null; 1144 } 1145 1146 public void register(Pool pool) { 1147 Integer old = Integer.valueOf(_poolHashTable.size()); 1148 _poolHashTable.put(pool.getId(), pool); 1149 // find last id created 1150 String[] getId = pool.getId().split("p"); 1151 int id = Integer.parseInt(getId[1]); 1152 if (id > _idPoolNumber) { 1153 _idPoolNumber = id; 1154 } 1155 setDirtyAndFirePropertyChange(POOL_LENGTH_CHANGED_PROPERTY, old, Integer.valueOf(_poolHashTable.size())); 1156 } 1157 1158 public void updatePoolComboBox(JComboBox<Pool> box) { 1159 box.removeAllItems(); 1160 box.addItem(null); 1161 for (Pool pool : getPoolsByNameList()) { 1162 box.addItem(pool); 1163 } 1164 } 1165 1166 /** 1167 * Gets a list of Pools for this location. 1168 * 1169 * @return A list of Pools 1170 */ 1171 public List<Pool> getPoolsByNameList() { 1172 List<Pool> pools = new ArrayList<>(); 1173 Enumeration<Pool> en = _poolHashTable.elements(); 1174 while (en.hasMoreElements()) { 1175 pools.add(en.nextElement()); 1176 } 1177 return pools; 1178 } 1179 1180 /** 1181 * True if this location has a track with pick up or set out restrictions. 1182 * 1183 * @return True if there are restrictions at this location. 1184 */ 1185 public boolean hasServiceRestrictions() { 1186 Track track; 1187 Enumeration<Track> en = _trackHashTable.elements(); 1188 while (en.hasMoreElements()) { 1189 track = en.nextElement(); 1190 if (!track.getDropOption().equals(Track.ANY) || !track.getPickupOption().equals(Track.ANY)) { 1191 return true; 1192 } 1193 } 1194 return false; 1195 } 1196 1197 /** 1198 * Used to determine if there are Pools at this location. 1199 * 1200 * @return True if there are Pools at this location 1201 */ 1202 public boolean hasPools() { 1203 return _poolHashTable.size() > 0; 1204 } 1205 1206 /** 1207 * Used to determine if there are any planned pickups at this location. 1208 * 1209 * @return True if there are planned pickups 1210 */ 1211 public boolean hasPlannedPickups() { 1212 List<Track> tracks = getTracksList(); 1213 for (Track track : tracks) { 1214 if (track.getIgnoreUsedLengthPercentage() > Track.IGNORE_0) { 1215 return true; 1216 } 1217 } 1218 return false; 1219 } 1220 1221 /** 1222 * Used to determine if there are any load restrictions at this location. 1223 * 1224 * @return True if there are load restrictions 1225 */ 1226 public boolean hasLoadRestrictions() { 1227 List<Track> tracks = getTracksList(); 1228 for (Track track : tracks) { 1229 if (!track.getLoadOption().equals(Track.ALL_LOADS)) { 1230 return true; 1231 } 1232 } 1233 return false; 1234 } 1235 1236 /** 1237 * Used to determine if there are any load ship restrictions at this 1238 * location. 1239 * 1240 * @return True if there are load ship restrictions 1241 */ 1242 public boolean hasShipLoadRestrictions() { 1243 List<Track> tracks = getTracksList(); 1244 for (Track track : tracks) { 1245 if (!track.getShipLoadOption().equals(Track.ALL_LOADS)) { 1246 return true; 1247 } 1248 } 1249 return false; 1250 } 1251 1252 /** 1253 * Used to determine if there are any road restrictions at this location. 1254 * 1255 * @return True if there are road restrictions 1256 */ 1257 public boolean hasRoadRestrictions() { 1258 List<Track> tracks = getTracksList(); 1259 for (Track track : tracks) { 1260 if (!track.getRoadOption().equals(Track.ALL_ROADS)) { 1261 return true; 1262 } 1263 } 1264 return false; 1265 } 1266 1267 /** 1268 * Used to determine if there are any track destination restrictions at this 1269 * location. 1270 * 1271 * @return True if there are destination restrictions 1272 */ 1273 public boolean hasDestinationRestrictions() { 1274 List<Track> tracks = getTracksList(); 1275 for (Track track : tracks) { 1276 if (!track.getDestinationOption().equals(Track.ALL_DESTINATIONS)) { 1277 return true; 1278 } 1279 } 1280 return false; 1281 } 1282 1283 public boolean hasAlternateTracks() { 1284 for (Track track : getTracksList()) { 1285 if (track.getAlternateTrack() != null) { 1286 return true; 1287 } 1288 } 1289 return false; 1290 } 1291 1292 public boolean hasOrderRestrictions() { 1293 for (Track track : getTracksList()) { 1294 if (!track.getServiceOrder().equals(Track.NORMAL)) { 1295 return true; 1296 } 1297 } 1298 return false; 1299 } 1300 1301 public boolean hasSchedules() { 1302 for (Track track : getTracksList()) { 1303 if (track.isSpur() && track.getSchedule() != null) { 1304 return true; 1305 } 1306 } 1307 return false; 1308 } 1309 1310 public boolean hasWork() { 1311 return (getDropRS() != 0 || getPickupRS() != 0); 1312 } 1313 1314 public boolean hasDisableLoadChange() { 1315 for (Track track : getTracksList()) { 1316 if (track.isSpur() && track.isDisableLoadChangeEnabled()) { 1317 return true; 1318 } 1319 } 1320 return false; 1321 } 1322 1323 public boolean hasTracksWithRestrictedTrainDirections() { 1324 int trainDirections = getTrainDirections() & Setup.getTrainDirection(); 1325 for (Track track : getTracksList()) { 1326 if (trainDirections != (track.getTrainDirections() & trainDirections)) { 1327 return true; 1328 } 1329 } 1330 return false; 1331 } 1332 1333 public boolean hasTrackMessages() { 1334 for (Track track : getTracksList()) { 1335 if (track.hasMessages()) { 1336 return true; 1337 } 1338 } 1339 return false; 1340 } 1341 1342 public boolean hasReporters() { 1343 for (Track track : getTracksList()) { 1344 if (track.getReporter() != null) { 1345 return true; 1346 } 1347 } 1348 return false; 1349 } 1350 1351 /* 1352 * set the jmri.Reporter object associated with this location. 1353 * 1354 * @param reader jmri.Reporter object. 1355 */ 1356 public void setReporter(Reporter r) { 1357 Reporter old = _reader; 1358 _reader = r; 1359 if (old != r) { 1360 setDirtyAndFirePropertyChange(LOCATION_REPORTER_CHANGED_PROPERTY, old, r); 1361 } 1362 } 1363 1364 /* 1365 * get the jmri.Reporter object associated with this location. 1366 * 1367 * @return jmri.Reporter object. 1368 */ 1369 public Reporter getReporter() { 1370 return _reader; 1371 } 1372 1373 public String getReporterName() { 1374 if (getReporter() != null) { 1375 return getReporter().getDisplayName(); 1376 } 1377 return ""; 1378 } 1379 1380 public void dispose() { 1381 List<Track> tracks = getTracksList(); 1382 for (Track track : tracks) { 1383 deleteTrack(track); 1384 } 1385 InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this); 1386 InstanceManager.getDefault(CarRoads.class).removePropertyChangeListener(this); 1387 InstanceManager.getDefault(EngineTypes.class).removePropertyChangeListener(this); 1388 // Change name in case object is still in use, for example Schedules 1389 setName(Bundle.getMessage("NotValid", getName())); 1390 setDirtyAndFirePropertyChange(DISPOSE_CHANGED_PROPERTY, null, DISPOSE_CHANGED_PROPERTY); 1391 } 1392 1393 private void addPropertyChangeListeners() { 1394 InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this); 1395 InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this); 1396 InstanceManager.getDefault(EngineTypes.class).addPropertyChangeListener(this); 1397 } 1398 1399 /** 1400 * Construct this Entry from XML. This member has to remain synchronized 1401 * with the detailed DTD in operations-locations.dtd 1402 * 1403 * @param e Consist XML element 1404 */ 1405 public Location(Element e) { 1406 Attribute a; 1407 if ((a = e.getAttribute(Xml.ID)) != null) { 1408 _id = a.getValue(); 1409 } else { 1410 log.warn("no id attribute in location element when reading operations"); 1411 } 1412 if ((a = e.getAttribute(Xml.NAME)) != null) { 1413 _name = a.getValue(); 1414 } 1415 if ((a = e.getAttribute(Xml.DIVISION_ID)) != null) { 1416 _division = InstanceManager.getDefault(DivisionManager.class).getDivisionById(a.getValue()); 1417 } 1418 // TODO remove the following 3 lines in 2023 1419 if ((a = e.getAttribute(Xml.DIVISION_ID_ERROR)) != null) { 1420 _division = InstanceManager.getDefault(DivisionManager.class).getDivisionById(a.getValue()); 1421 } 1422 if ((a = e.getAttribute(Xml.DIR)) != null) { 1423 try { 1424 _trainDir = Integer.parseInt(a.getValue()); 1425 } catch (NumberFormatException nfe) { 1426 log.error("Train directions isn't a vaild number for location {}", getName()); 1427 } 1428 } 1429 if ((a = e.getAttribute(Xml.SWITCH_LIST)) != null) { 1430 _switchList = (a.getValue().equals(Xml.TRUE)); 1431 } 1432 if ((a = e.getAttribute(Xml.SWITCH_LIST_STATE)) != null) { 1433 try { 1434 _switchListState = Integer.parseInt(a.getValue()); 1435 } catch (NumberFormatException nfe) { 1436 log.error("Switch list state isn't a vaild number for location {}", getName()); 1437 } 1438 if (getSwitchListState() == SW_PRINTED) { 1439 setStatus(PRINTED); 1440 } 1441 } 1442 if ((a = e.getAttribute(Xml.PRINTER_NAME)) != null) { 1443 _defaultPrinter = a.getValue(); 1444 } 1445 // load train icon coordinates 1446 Attribute x; 1447 Attribute y; 1448 try { 1449 if ((x = e.getAttribute(Xml.EAST_TRAIN_ICON_X)) != null && 1450 (y = e.getAttribute(Xml.EAST_TRAIN_ICON_Y)) != null) { 1451 setTrainIconEast(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue()))); 1452 } 1453 if ((x = e.getAttribute(Xml.WEST_TRAIN_ICON_X)) != null && 1454 (y = e.getAttribute(Xml.WEST_TRAIN_ICON_Y)) != null) { 1455 setTrainIconWest(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue()))); 1456 } 1457 if ((x = e.getAttribute(Xml.NORTH_TRAIN_ICON_X)) != null && 1458 (y = e.getAttribute(Xml.NORTH_TRAIN_ICON_Y)) != null) { 1459 setTrainIconNorth(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue()))); 1460 } 1461 if ((x = e.getAttribute(Xml.SOUTH_TRAIN_ICON_X)) != null && 1462 (y = e.getAttribute(Xml.SOUTH_TRAIN_ICON_Y)) != null) { 1463 setTrainIconSouth(new Point(Integer.parseInt(x.getValue()), Integer.parseInt(y.getValue()))); 1464 } 1465 1466 if ((x = e.getAttribute(Xml.TRAIN_ICON_RANGE_X)) != null) { 1467 setTrainIconRangeX(Integer.parseInt(x.getValue())); 1468 } 1469 if ((y = e.getAttribute(Xml.TRAIN_ICON_RANGE_Y)) != null) { 1470 setTrainIconRangeY(Integer.parseInt(y.getValue())); 1471 } 1472 1473 } catch (NumberFormatException nfe) { 1474 log.error("Train icon coordinates aren't vaild for location {}", getName()); 1475 } 1476 1477 if ((a = e.getAttribute(Xml.COMMENT)) != null) { 1478 _comment = a.getValue(); 1479 } 1480 1481 if ((a = e.getAttribute(Xml.SWITCH_LIST_COMMENT)) != null) { 1482 _switchListComment = a.getValue(); 1483 } 1484 if ((a = e.getAttribute(Xml.PHYSICAL_LOCATION)) != null) { 1485 _physicalLocation = PhysicalLocation.parse(a.getValue()); 1486 } 1487 // new way of reading car types using elements added in 3.3.1 1488 if (e.getChild(Xml.TYPES) != null) { 1489 List<Element> carTypes = e.getChild(Xml.TYPES).getChildren(Xml.CAR_TYPE); 1490 String[] types = new String[carTypes.size()]; 1491 for (int i = 0; i < carTypes.size(); i++) { 1492 Element type = carTypes.get(i); 1493 if ((a = type.getAttribute(Xml.NAME)) != null) { 1494 types[i] = a.getValue(); 1495 } 1496 } 1497 setTypeNames(types); 1498 List<Element> locoTypes = e.getChild(Xml.TYPES).getChildren(Xml.LOCO_TYPE); 1499 types = new String[locoTypes.size()]; 1500 for (int i = 0; i < locoTypes.size(); i++) { 1501 Element type = locoTypes.get(i); 1502 if ((a = type.getAttribute(Xml.NAME)) != null) { 1503 types[i] = a.getValue(); 1504 } 1505 } 1506 setTypeNames(types); 1507 } // old way of reading car types up to version 2.99.6 1508 else if ((a = e.getAttribute(Xml.CAR_TYPES)) != null) { 1509 String names = a.getValue(); 1510 String[] Types = names.split("%%"); // NOI18N 1511 setTypeNames(Types); 1512 } 1513 // early version of operations called tracks "secondary" 1514 if (e.getChildren(Xml.SECONDARY) != null) { 1515 List<Element> eTracks = e.getChildren(Xml.SECONDARY); 1516 for (Element eTrack : eTracks) { 1517 register(new Track(eTrack, this)); 1518 } 1519 } 1520 if (e.getChildren(Xml.TRACK) != null) { 1521 List<Element> eTracks = e.getChildren(Xml.TRACK); 1522 log.debug("location ({}) has {} tracks", getName(), eTracks.size()); 1523 for (Element eTrack : eTracks) { 1524 register(new Track(eTrack, this)); 1525 } 1526 } 1527 if (e.getAttribute(Xml.READER) != null) { 1528 // @SuppressWarnings("unchecked") 1529 try { 1530 Reporter r = jmri.InstanceManager.getDefault(jmri.ReporterManager.class) 1531 .provideReporter(e.getAttribute(Xml.READER).getValue()); 1532 _reader = r; 1533 } catch (IllegalArgumentException ex) { 1534 log.warn("Not able to find reader: {} for location ({})", e.getAttribute(Xml.READER).getValue(), 1535 getName()); 1536 } 1537 } 1538 addPropertyChangeListeners(); 1539 } 1540 1541 /** 1542 * Create an XML element to represent this Entry. This member has to remain 1543 * synchronized with the detailed DTD in operations-locations.dtd. 1544 * 1545 * @return Contents in a JDOM Element 1546 */ 1547 public Element store() { 1548 Element e = new Element(Xml.LOCATION); 1549 e.setAttribute(Xml.ID, getId()); 1550 e.setAttribute(Xml.NAME, getName()); 1551 if (!getDivisionId().equals(NONE)) { 1552 e.setAttribute(Xml.DIVISION_ID, getDivisionId()); 1553 } 1554 // backwards compatibility starting 2/6/2021, remove after 2023 1555 e.setAttribute(Xml.OPS, isStaging() ? STAGING : NORMAL); 1556 e.setAttribute(Xml.DIR, Integer.toString(getTrainDirections())); 1557 e.setAttribute(Xml.SWITCH_LIST, isSwitchListEnabled() ? Xml.TRUE : Xml.FALSE); 1558 if (!Setup.isSwitchListRealTime()) { 1559 e.setAttribute(Xml.SWITCH_LIST_STATE, Integer.toString(getSwitchListState())); 1560 } 1561 if (!getDefaultPrinterName().equals(NONE)) { 1562 e.setAttribute(Xml.PRINTER_NAME, getDefaultPrinterName()); 1563 } 1564 if (!getTrainIconEast().equals(new Point())) { 1565 e.setAttribute(Xml.EAST_TRAIN_ICON_X, Integer.toString(getTrainIconEast().x)); 1566 e.setAttribute(Xml.EAST_TRAIN_ICON_Y, Integer.toString(getTrainIconEast().y)); 1567 } 1568 if (!getTrainIconWest().equals(new Point())) { 1569 e.setAttribute(Xml.WEST_TRAIN_ICON_X, Integer.toString(getTrainIconWest().x)); 1570 e.setAttribute(Xml.WEST_TRAIN_ICON_Y, Integer.toString(getTrainIconWest().y)); 1571 } 1572 if (!getTrainIconNorth().equals(new Point())) { 1573 e.setAttribute(Xml.NORTH_TRAIN_ICON_X, Integer.toString(getTrainIconNorth().x)); 1574 e.setAttribute(Xml.NORTH_TRAIN_ICON_Y, Integer.toString(getTrainIconNorth().y)); 1575 } 1576 if (!getTrainIconSouth().equals(new Point())) { 1577 e.setAttribute(Xml.SOUTH_TRAIN_ICON_X, Integer.toString(getTrainIconSouth().x)); 1578 e.setAttribute(Xml.SOUTH_TRAIN_ICON_Y, Integer.toString(getTrainIconSouth().y)); 1579 } 1580 if (getTrainIconRangeX() != RANGE_DEFAULT) { 1581 e.setAttribute(Xml.TRAIN_ICON_RANGE_X, Integer.toString(getTrainIconRangeX())); 1582 } 1583 if (getTrainIconRangeY() != RANGE_DEFAULT) { 1584 e.setAttribute(Xml.TRAIN_ICON_RANGE_Y, Integer.toString(getTrainIconRangeY())); 1585 } 1586 if (_reader != null) { 1587 e.setAttribute(Xml.READER, _reader.getDisplayName()); 1588 } 1589 // build list of rolling stock types for this location 1590 String[] types = getTypeNames(); 1591 // new way of saving car types 1592 Element eTypes = new Element(Xml.TYPES); 1593 for (String type : types) { 1594 // don't save types that have been deleted by user 1595 if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) { 1596 Element eType = new Element(Xml.LOCO_TYPE); 1597 eType.setAttribute(Xml.NAME, type); 1598 eTypes.addContent(eType); 1599 } else if (InstanceManager.getDefault(CarTypes.class).containsName(type)) { 1600 Element eType = new Element(Xml.CAR_TYPE); 1601 eType.setAttribute(Xml.NAME, type); 1602 eTypes.addContent(eType); 1603 } 1604 } 1605 e.addContent(eTypes); 1606 1607 // save physical location if not default 1608 if (getPhysicalLocation() != null && !getPhysicalLocation().equals(PhysicalLocation.Origin)) { 1609 e.setAttribute(Xml.PHYSICAL_LOCATION, getPhysicalLocation().toString()); 1610 } 1611 1612 e.setAttribute(Xml.COMMENT, getCommentWithColor()); 1613 e.setAttribute(Xml.SWITCH_LIST_COMMENT, getSwitchListCommentWithColor()); 1614 1615 List<Track> tracks = getTracksByIdList(); 1616 for (Track track : tracks) { 1617 e.addContent(track.store()); 1618 } 1619 1620 return e; 1621 } 1622 1623 private void replaceType(String oldType, String newType) { 1624 if (acceptsTypeName(oldType)) { 1625 if (newType != null) { 1626 addTypeName(newType); 1627 } 1628 // now adjust tracks 1629 List<Track> tracks = getTracksList(); 1630 for (Track track : tracks) { 1631 if (track.isTypeNameAccepted(oldType)) { 1632 track.deleteTypeName(oldType); 1633 if (newType != null) { 1634 track.addTypeName(newType); 1635 } 1636 } 1637 // adjust custom loads 1638 String[] loadNames = track.getLoadNames(); 1639 for (String load : loadNames) { 1640 String[] splitLoad = load.split(CarLoad.SPLIT_CHAR); 1641 if (splitLoad.length > 1) { 1642 if (splitLoad[0].equals(oldType)) { 1643 track.deleteLoadName(load); 1644 if (newType != null) { 1645 load = newType + CarLoad.SPLIT_CHAR + splitLoad[1]; 1646 track.addLoadName(load); 1647 } 1648 } 1649 } 1650 } 1651 loadNames = track.getShipLoadNames(); 1652 for (String load : loadNames) { 1653 String[] splitLoad = load.split(CarLoad.SPLIT_CHAR); 1654 if (splitLoad.length > 1) { 1655 if (splitLoad[0].equals(oldType)) { 1656 track.deleteShipLoadName(load); 1657 if (newType != null) { 1658 load = newType + CarLoad.SPLIT_CHAR + splitLoad[1]; 1659 track.addShipLoadName(load); 1660 } 1661 } 1662 } 1663 } 1664 } 1665 deleteTypeName(oldType); 1666 } 1667 } 1668 1669 private void replaceRoad(String oldRoad, String newRoad) { 1670 // now adjust any track locations 1671 List<Track> tracks = getTracksList(); 1672 for (Track track : tracks) { 1673 if (track.containsRoadName(oldRoad)) { 1674 track.deleteRoadName(oldRoad); 1675 if (newRoad != null) { 1676 track.addRoadName(newRoad); 1677 } 1678 } 1679 } 1680 } 1681 1682 @Override 1683 public void propertyChange(java.beans.PropertyChangeEvent e) { 1684 if (Control.SHOW_PROPERTY) { 1685 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), 1686 e.getNewValue()); 1687 } 1688 // update length of tracks at this location if track length changes 1689 if (e.getPropertyName().equals(Track.LENGTH_CHANGED_PROPERTY)) { 1690 setLength(getLength() - 1691 Integer.parseInt((String) e.getOldValue()) + 1692 Integer.parseInt((String) e.getNewValue())); 1693 } 1694 // if a track type change, must update all tables 1695 if (e.getPropertyName().equals(Track.TRACK_TYPE_CHANGED_PROPERTY)) { 1696 setDirtyAndFirePropertyChange(TRACK_LISTLENGTH_CHANGED_PROPERTY, null, null); 1697 } 1698 if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY) || 1699 e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) || 1700 e.getPropertyName().equals(EngineTypes.ENGINETYPES_NAME_CHANGED_PROPERTY)) { 1701 replaceType((String) e.getOldValue(), (String) e.getNewValue()); 1702 } 1703 if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) { 1704 replaceRoad((String) e.getOldValue(), (String) e.getNewValue()); 1705 } 1706 } 1707 1708 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 1709 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 1710 firePropertyChange(p, old, n); 1711 } 1712 1713 private final static Logger log = LoggerFactory.getLogger(Location.class); 1714 1715}