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