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