001package jmri.jmrit.operations.locations; 002 003import java.util.*; 004 005import org.jdom2.Attribute; 006import org.jdom2.Element; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010import jmri.InstanceManager; 011import jmri.Reporter; 012import jmri.beans.PropertyChangeSupport; 013import jmri.jmrit.operations.locations.divisions.Division; 014import jmri.jmrit.operations.locations.schedules.*; 015import jmri.jmrit.operations.rollingstock.RollingStock; 016import jmri.jmrit.operations.rollingstock.cars.*; 017import jmri.jmrit.operations.rollingstock.engines.*; 018import jmri.jmrit.operations.routes.Route; 019import jmri.jmrit.operations.routes.RouteLocation; 020import jmri.jmrit.operations.setup.Setup; 021import jmri.jmrit.operations.trains.Train; 022import jmri.jmrit.operations.trains.TrainManager; 023import jmri.jmrit.operations.trains.schedules.TrainSchedule; 024import jmri.jmrit.operations.trains.schedules.TrainScheduleManager; 025import jmri.jmrit.operations.trains.trainbuilder.TrainCommon; 026 027/** 028 * Represents a location (track) on the layout Can be a spur, yard, staging, or 029 * interchange track. 030 * 031 * @author Daniel Boudreau Copyright (C) 2008 - 2014 032 */ 033public class Track extends PropertyChangeSupport { 034 035 public static final String NONE = ""; 036 037 protected String _id = NONE; 038 protected String _name = NONE; 039 protected String _trackType = NONE; // yard, spur, interchange or staging 040 protected Location _location; // the location for this track 041 protected int _trainDir = EAST + WEST + NORTH + SOUTH; // train directions 042 protected int _numberRS = 0; // number of cars and engines 043 protected int _numberCars = 0; // number of cars 044 protected int _numberEngines = 0; // number of engines 045 protected int _pickupRS = 0; // number of pick ups by trains 046 protected int _dropRS = 0; // number of set outs by trains 047 protected int _length = 0; // length of track 048 protected int _reserved = 0; // length of track reserved by trains 049 protected int _reservedLengthSetouts = 0; // reserved for car drops 050 protected int _reservedLengthPickups = 0; // reserved for car pulls 051 protected int _numberCarsEnRoute = 0; // number of cars en-route 052 protected int _usedLength = 0; // length of track filled by cars and engines 053 protected int _usedCloneLength = 0; // length of track filled by clone cars and engines 054 protected int _ignoreUsedLengthPercentage = IGNORE_0; 055 // ignore values 0 - 100% 056 public static final int IGNORE_0 = 0; 057 public static final int IGNORE_25 = 25; 058 public static final int IGNORE_50 = 50; 059 public static final int IGNORE_75 = 75; 060 public static final int IGNORE_100 = 100; 061 protected int _moves = 0; // count of the drops since creation 062 protected int _blockingOrder = 0; // the order tracks are serviced 063 protected String _alternateTrackId = NONE; // the alternate track id 064 protected String _comment = NONE; 065 066 // car types serviced by this track 067 protected List<String> _typeList = new ArrayList<>(); 068 069 // Manifest and switch list comments 070 protected boolean _printCommentManifest = true; 071 protected boolean _printCommentSwitchList = false; 072 protected String _commentPickup = NONE; 073 protected String _commentSetout = NONE; 074 protected String _commentBoth = NONE; 075 076 // road options 077 protected String _roadOption = ALL_ROADS; // controls car roads 078 protected List<String> _roadList = new ArrayList<>(); 079 080 // load options 081 protected String _loadOption = ALL_LOADS; // receive track load restrictions 082 protected List<String> _loadList = new ArrayList<>(); 083 protected String _shipLoadOption = ALL_LOADS;// ship track load restrictions 084 protected List<String> _shipLoadList = new ArrayList<>(); 085 086 // destinations that this track will service 087 protected String _destinationOption = ALL_DESTINATIONS; 088 protected List<String> _destinationIdList = new ArrayList<>(); 089 090 // schedule options 091 protected String _scheduleName = NONE; // Schedule name if there's one 092 protected String _scheduleId = NONE; // Schedule id if there's one 093 protected String _scheduleItemId = NONE; // the current scheduled item id 094 protected int _scheduleCount = 0; // item count 095 protected int _reservedEnRoute = 0; // length of cars en-route to this track 096 protected int _reservationFactor = 100; // percentage of track space for 097 // cars en-route 098 protected int _mode = MATCH; // default is match mode 099 protected boolean _holdCustomLoads = false; // hold cars with custom loads 100 101 // drop & pick up options 102 protected String _dropOption = ANY; // controls which route or train can set 103 // out cars 104 protected String _pickupOption = ANY; // controls which route or train can 105 // pick up cars 106 public static final String ANY = "Any"; // track accepts any train or route 107 public static final String TRAINS = "trains"; // track accepts trains 108 public static final String ROUTES = "routes"; // track accepts routes 109 public static final String EXCLUDE_TRAINS = "excludeTrains"; 110 public static final String EXCLUDE_ROUTES = "excludeRoutes"; 111 protected List<String> _dropList = new ArrayList<>(); 112 protected List<String> _pickupList = new ArrayList<>(); 113 114 // load options for staging 115 protected int _loadOptions = 0; 116 private static final int SWAP_GENERIC_LOADS = 1; 117 private static final int EMPTY_CUSTOM_LOADS = 2; 118 private static final int GENERATE_CUSTOM_LOADS = 4; 119 private static final int GENERATE_CUSTOM_LOADS_ANY_SPUR = 8; 120 private static final int EMPTY_GENERIC_LOADS = 16; 121 private static final int GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK = 32; 122 123 // load options for spur 124 private static final int DISABLE_LOAD_CHANGE = 64; 125 private static final int QUICK_SERVICE = 128; 126 127 // block options 128 protected int _blockOptions = 0; 129 private static final int BLOCK_CARS = 1; 130 131 // order cars are serviced 132 protected String _order = NORMAL; 133 public static final String NORMAL = Bundle.getMessage("Normal"); 134 public static final String FIFO = Bundle.getMessage("FIFO"); 135 public static final String LIFO = Bundle.getMessage("LIFO"); 136 137 // Priority 138 protected String _trackPriority = PRIORITY_NORMAL; 139 public static final String PRIORITY_HIGH = Bundle.getMessage("High"); 140 public static final String PRIORITY_MEDIUM = Bundle.getMessage("Medium"); 141 public static final String PRIORITY_NORMAL = Bundle.getMessage("Normal"); 142 public static final String PRIORITY_LOW = Bundle.getMessage("Low"); 143 144 // the four types of tracks 145 public static final String STAGING = "Staging"; 146 public static final String INTERCHANGE = "Interchange"; 147 public static final String YARD = "Yard"; 148 // note that code before 2020 (4.21.1) used Siding as the spur type 149 public static final String SPUR = "Spur"; 150 private static final String SIDING = "Siding"; // For loading older files 151 152 // train directions serviced by this track 153 public static final int EAST = 1; 154 public static final int WEST = 2; 155 public static final int NORTH = 4; 156 public static final int SOUTH = 8; 157 158 // how roads are serviced by this track 159 public static final String ALL_ROADS = Bundle.getMessage("All"); 160 // track accepts only certain roads 161 public static final String INCLUDE_ROADS = Bundle.getMessage("Include"); 162 // track excludes certain roads 163 public static final String EXCLUDE_ROADS = Bundle.getMessage("Exclude"); 164 165 // load options 166 public static final String ALL_LOADS = Bundle.getMessage("All"); 167 public static final String INCLUDE_LOADS = Bundle.getMessage("Include"); 168 public static final String EXCLUDE_LOADS = Bundle.getMessage("Exclude"); 169 170 // destination options 171 public static final String ALL_DESTINATIONS = Bundle.getMessage("All"); 172 public static final String INCLUDE_DESTINATIONS = Bundle.getMessage("Include"); 173 public static final String EXCLUDE_DESTINATIONS = Bundle.getMessage("Exclude"); 174 // when true only cars with final destinations are allowed to use track 175 protected boolean _onlyCarsWithFD = false; 176 177 // schedule modes 178 public static final int SEQUENTIAL = 0; 179 public static final int MATCH = 1; 180 181 // pickup status 182 public static final String PICKUP_OKAY = ""; 183 184 // pool 185 protected Pool _pool = null; 186 protected int _minimumLength = 0; 187 protected int _maximumLength = Integer.MAX_VALUE; 188 189 // return status when checking rolling stock 190 public static final String OKAY = Bundle.getMessage("okay"); 191 public static final String LENGTH = Bundle.getMessage("rollingStock") + 192 " " + 193 Bundle.getMessage("Length").toLowerCase(); // lower case in report 194 public static final String TYPE = Bundle.getMessage("type"); 195 public static final String ROAD = Bundle.getMessage("road"); 196 public static final String LOAD = Bundle.getMessage("load"); 197 public static final String CAPACITY = Bundle.getMessage("track") + " " + Bundle.getMessage("capacity"); 198 public static final String SCHEDULE = Bundle.getMessage("schedule"); 199 public static final String CUSTOM = Bundle.getMessage("custom"); 200 public static final String DESTINATION = Bundle.getMessage("carDestination"); 201 public static final String NO_FINAL_DESTINATION = Bundle.getMessage("noFinalDestination"); 202 private static final String DISABLED = "disabled"; 203 204 // For property change 205 public static final String TYPES_CHANGED_PROPERTY = "trackRollingStockTypes"; // NOI18N 206 public static final String ROADS_CHANGED_PROPERTY = "trackRoads"; // NOI18N 207 public static final String NAME_CHANGED_PROPERTY = "trackName"; // NOI18N 208 public static final String LENGTH_CHANGED_PROPERTY = "trackLength"; // NOI18N 209 public static final String MIN_LENGTH_CHANGED_PROPERTY = "trackMinLength"; // NOI18N 210 public static final String MAX_LENGTH_CHANGED_PROPERTY = "trackMaxLength"; // NOI18N 211 public static final String SCHEDULE_CHANGED_PROPERTY = "trackScheduleChange"; // NOI18N 212 public static final String DISPOSE_CHANGED_PROPERTY = "trackDispose"; // NOI18N 213 public static final String TRAIN_DIRECTION_CHANGED_PROPERTY = "trackTrainDirection"; // NOI18N 214 public static final String DROP_CHANGED_PROPERTY = "trackDrop"; // NOI18N 215 public static final String PICKUP_CHANGED_PROPERTY = "trackPickup"; // NOI18N 216 public static final String TRACK_TYPE_CHANGED_PROPERTY = "trackType"; // NOI18N 217 public static final String LOADS_CHANGED_PROPERTY = "trackLoads"; // NOI18N 218 public static final String POOL_CHANGED_PROPERTY = "trackPool"; // NOI18N 219 public static final String PLANNED_PICKUPS_CHANGED_PROPERTY = "plannedPickUps"; // NOI18N 220 public static final String LOAD_OPTIONS_CHANGED_PROPERTY = "trackLoadOptions"; // NOI18N 221 public static final String DESTINATIONS_CHANGED_PROPERTY = "trackDestinations"; // NOI18N 222 public static final String DESTINATION_OPTIONS_CHANGED_PROPERTY = "trackDestinationOptions"; // NOI18N 223 public static final String SCHEDULE_MODE_CHANGED_PROPERTY = "trackScheduleMode"; // NOI18N 224 public static final String SCHEDULE_ID_CHANGED_PROPERTY = "trackScheduleId"; // NOI18N 225 public static final String SERVICE_ORDER_CHANGED_PROPERTY = "trackServiceOrder"; // NOI18N 226 public static final String ALTERNATE_TRACK_CHANGED_PROPERTY = "trackAlternate"; // NOI18N 227 public static final String TRACK_BLOCKING_ORDER_CHANGED_PROPERTY = "trackBlockingOrder"; // NOI18N 228 public static final String TRACK_REPORTER_CHANGED_PROPERTY = "trackReporterChange"; // NOI18N 229 public static final String ROUTED_CHANGED_PROPERTY = "onlyCarsWithFinalDestinations"; // NOI18N 230 public static final String HOLD_CARS_CHANGED_PROPERTY = "trackHoldCarsWithCustomLoads"; // NOI18N 231 public static final String TRACK_COMMENT_CHANGED_PROPERTY = "trackComments"; // NOI18N 232 public static final String TRACK_FACTOR_CHANGED_PROPERTY = "trackReservationFactor"; // NOI18N 233 public static final String PRIORITY_CHANGED_PROPERTY = "trackPriority"; // NOI18N 234 235 // IdTag reader associated with this track. 236 protected Reporter _reader = null; 237 238 public Track(String id, String name, String type, Location location) { 239 log.debug("New ({}) track ({}) id: {}", type, name, id); 240 _location = location; 241 _trackType = type; 242 _name = name; 243 _id = id; 244 // a new track accepts all types 245 setTypeNames(InstanceManager.getDefault(CarTypes.class).getNames()); 246 setTypeNames(InstanceManager.getDefault(EngineTypes.class).getNames()); 247 } 248 249 /** 250 * Creates a copy of this track. 251 * 252 * @param newName The name of the new track. 253 * @param newLocation The location of the new track. 254 * @return Track 255 */ 256 public Track copyTrack(String newName, Location newLocation) { 257 Track newTrack = newLocation.addTrack(newName, getTrackType()); 258 newTrack.clearTypeNames(); // all types are accepted by a new track 259 260 newTrack.setAddCustomLoadsAnySpurEnabled(isAddCustomLoadsAnySpurEnabled()); 261 newTrack.setAddCustomLoadsAnyStagingTrackEnabled(isAddCustomLoadsAnyStagingTrackEnabled()); 262 newTrack.setAddCustomLoadsEnabled(isAddCustomLoadsEnabled()); 263 264 newTrack.setAlternateTrack(getAlternateTrack()); 265 newTrack.setBlockCarsEnabled(isBlockCarsEnabled()); 266 newTrack.setComment(getComment()); 267 newTrack.setCommentBoth(getCommentBothWithColor()); 268 newTrack.setCommentPickup(getCommentPickupWithColor()); 269 newTrack.setCommentSetout(getCommentSetoutWithColor()); 270 271 newTrack.setDestinationOption(getDestinationOption()); 272 newTrack.setDestinationIds(getDestinationIds()); 273 274 // must set option before setting ids 275 newTrack.setDropOption(getDropOption()); 276 newTrack.setDropIds(getDropIds()); 277 278 newTrack.setIgnoreUsedLengthPercentage(getIgnoreUsedLengthPercentage()); 279 newTrack.setLength(getLength()); 280 newTrack.setLoadEmptyEnabled(isLoadEmptyEnabled()); 281 newTrack.setLoadNames(getLoadNames()); 282 newTrack.setLoadOption(getLoadOption()); 283 newTrack.setLoadSwapEnabled(isLoadSwapEnabled()); 284 285 newTrack.setOnlyCarsWithFinalDestinationEnabled(isOnlyCarsWithFinalDestinationEnabled()); 286 287 // must set option before setting ids 288 newTrack.setPickupOption(getPickupOption()); 289 newTrack.setPickupIds(getPickupIds()); 290 291 // track pools are only shared within a specific location 292 if (getPool() != null) { 293 newTrack.setPool(newLocation.addPool(getPool().getName())); 294 newTrack.setPoolMinimumLength(getPoolMinimumLength()); 295 newTrack.setPoolMaximumLength(getPoolMaximumLength()); 296 } 297 298 newTrack.setPrintManifestCommentEnabled(isPrintManifestCommentEnabled()); 299 newTrack.setPrintSwitchListCommentEnabled(isPrintSwitchListCommentEnabled()); 300 301 newTrack.setRemoveCustomLoadsEnabled(isRemoveCustomLoadsEnabled()); 302 newTrack.setReservationFactor(getReservationFactor()); 303 newTrack.setRoadNames(getRoadNames()); 304 newTrack.setRoadOption(getRoadOption()); 305 newTrack.setSchedule(getSchedule()); 306 newTrack.setScheduleMode(getScheduleMode()); 307 newTrack.setServiceOrder(getServiceOrder()); 308 newTrack.setShipLoadNames(getShipLoadNames()); 309 newTrack.setShipLoadOption(getShipLoadOption()); 310 newTrack.setTrainDirections(getTrainDirections()); 311 newTrack.setTypeNames(getTypeNames()); 312 313 newTrack.setDisableLoadChangeEnabled(isDisableLoadChangeEnabled()); 314 newTrack.setQuickServiceEnabled(isQuickServiceEnabled()); 315 newTrack.setHoldCarsWithCustomLoadsEnabled(isHoldCarsWithCustomLoadsEnabled()); 316 newTrack.setTrackPriority(getTrackPriority()); 317 return newTrack; 318 } 319 320 // for combo boxes 321 @Override 322 public String toString() { 323 return _name; 324 } 325 326 public String getId() { 327 return _id; 328 } 329 330 public Location getLocation() { 331 return _location; 332 } 333 334 public void setName(String name) { 335 String old = _name; 336 _name = name; 337 if (!old.equals(name)) { 338 // recalculate max track name length 339 InstanceManager.getDefault(LocationManager.class).resetNameLengths(); 340 setDirtyAndFirePropertyChange(NAME_CHANGED_PROPERTY, old, name); 341 } 342 } 343 344 public String getName() { 345 return _name; 346 } 347 348 public String getSplitName() { 349 return TrainCommon.splitString(getName()); 350 } 351 352 public Division getDivision() { 353 return getLocation().getDivision(); 354 } 355 356 public String getDivisionName() { 357 return getLocation().getDivisionName(); 358 } 359 360 public boolean isSpur() { 361 return getTrackType().equals(Track.SPUR); 362 } 363 364 public boolean isYard() { 365 return getTrackType().equals(Track.YARD); 366 } 367 368 public boolean isInterchange() { 369 return getTrackType().equals(Track.INTERCHANGE); 370 } 371 372 public boolean isStaging() { 373 return getTrackType().equals(Track.STAGING); 374 } 375 376 public boolean hasMessages() { 377 if (!getCommentBoth().isBlank() || 378 !getCommentPickup().isBlank() || 379 !getCommentSetout().isBlank()) { 380 return true; 381 } 382 return false; 383 } 384 385 /** 386 * Gets the track type 387 * 388 * @return Track.SPUR Track.YARD Track.INTERCHANGE or Track.STAGING 389 */ 390 public String getTrackType() { 391 return _trackType; 392 } 393 394 /** 395 * Sets the track type, spur, interchange, yard, staging 396 * 397 * @param type Track.SPUR Track.YARD Track.INTERCHANGE Track.STAGING 398 */ 399 public void setTrackType(String type) { 400 String old = _trackType; 401 _trackType = type; 402 if (!old.equals(type)) { 403 setDirtyAndFirePropertyChange(TRACK_TYPE_CHANGED_PROPERTY, old, type); 404 } 405 } 406 407 public String getTrackTypeName() { 408 return (getTrackTypeName(getTrackType())); 409 } 410 411 public static String getTrackTypeName(String trackType) { 412 if (trackType.equals(Track.SPUR)) { 413 return Bundle.getMessage("Spur").toLowerCase(); 414 } 415 if (trackType.equals(Track.YARD)) { 416 return Bundle.getMessage("Yard").toLowerCase(); 417 } 418 if (trackType.equals(Track.INTERCHANGE)) { 419 return Bundle.getMessage("Class/Interchange"); // abbreviation 420 } 421 if (trackType.equals(Track.STAGING)) { 422 return Bundle.getMessage("Staging").toLowerCase(); 423 } 424 return ("unknown"); // NOI18N 425 } 426 427 public void setLength(int length) { 428 int old = _length; 429 _length = length; 430 if (old != length) { 431 setDirtyAndFirePropertyChange(LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length)); 432 } 433 } 434 435 public int getLength() { 436 return _length; 437 } 438 439 /** 440 * Sets the minimum length of this track when the track is in a pool. 441 * 442 * @param length minimum 443 */ 444 public void setPoolMinimumLength(int length) { 445 int old = _minimumLength; 446 _minimumLength = length; 447 if (old != length) { 448 setDirtyAndFirePropertyChange(MIN_LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length)); 449 } 450 } 451 452 public int getPoolMinimumLength() { 453 return _minimumLength; 454 } 455 456 /** 457 * Sets the maximum length of this track when the track is in a pool. 458 * 459 * @param length maximum 460 */ 461 public void setPoolMaximumLength(int length) { 462 int old = _maximumLength; 463 _maximumLength = length; 464 if (old != length) { 465 setDirtyAndFirePropertyChange(MAX_LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length)); 466 } 467 } 468 469 public int getPoolMaximumLength() { 470 return _maximumLength; 471 } 472 473 /** 474 * The amount of track space that is reserved for car drops or pick ups. Can 475 * be positive or negative. 476 * 477 * @param reserved the calculated track space 478 */ 479 protected void setReserved(int reserved) { 480 int old = _reserved; 481 _reserved = reserved; 482 if (old != reserved) { 483 setDirtyAndFirePropertyChange("trackReserved", Integer.toString(old), // NOI18N 484 Integer.toString(reserved)); // NOI18N 485 } 486 } 487 488 public int getReserved() { 489 return _reserved; 490 } 491 492 public void addReservedInRoute(Car car) { 493 int old = _reservedEnRoute; 494 _numberCarsEnRoute++; 495 _reservedEnRoute = old + car.getTotalLength(); 496 if (old != _reservedEnRoute) { 497 setDirtyAndFirePropertyChange("trackAddReservedInRoute", Integer.toString(old), // NOI18N 498 Integer.toString(_reservedEnRoute)); // NOI18N 499 } 500 } 501 502 public void deleteReservedInRoute(Car car) { 503 int old = _reservedEnRoute; 504 _numberCarsEnRoute--; 505 _reservedEnRoute = old - car.getTotalLength(); 506 if (old != _reservedEnRoute) { 507 setDirtyAndFirePropertyChange("trackDeleteReservedInRoute", Integer.toString(old), // NOI18N 508 Integer.toString(_reservedEnRoute)); // NOI18N 509 } 510 } 511 512 /** 513 * Used to determine how much track space is going to be consumed by cars in 514 * route to this track. See isSpaceAvailable(). 515 * 516 * @return The length of all cars en route to this track including couplers. 517 */ 518 public int getReservedInRoute() { 519 return _reservedEnRoute; 520 } 521 522 public int getNumberOfCarsInRoute() { 523 return _numberCarsEnRoute; 524 } 525 526 /** 527 * Set the reservation factor. Default 100 (100%). Used by the program when 528 * generating car loads from staging. A factor of 100% allows the program to 529 * fill a track with car loads. Numbers over 100% can overload a track. 530 * 531 * @param factor A number from 0 to 10000. 532 */ 533 public void setReservationFactor(int factor) { 534 int old = _reservationFactor; 535 _reservationFactor = factor; 536 if (old != factor) { 537 setDirtyAndFirePropertyChange(TRACK_FACTOR_CHANGED_PROPERTY, old, factor); // NOI18N 538 } 539 } 540 541 public int getReservationFactor() { 542 return _reservationFactor; 543 } 544 545 /** 546 * Sets the mode of operation for the schedule assigned to this track. 547 * 548 * @param mode Track.SEQUENTIAL or Track.MATCH 549 */ 550 public void setScheduleMode(int mode) { 551 int old = _mode; 552 _mode = mode; 553 if (old != mode) { 554 setDirtyAndFirePropertyChange(SCHEDULE_MODE_CHANGED_PROPERTY, old, mode); // NOI18N 555 } 556 } 557 558 /** 559 * Gets the mode of operation for the schedule assigned to this track. 560 * 561 * @return Mode of operation: Track.SEQUENTIAL or Track.MATCH 562 */ 563 public int getScheduleMode() { 564 return _mode; 565 } 566 567 public String getScheduleModeName() { 568 if (getScheduleMode() == Track.MATCH) { 569 return Bundle.getMessage("Match"); 570 } 571 return Bundle.getMessage("Sequential"); 572 } 573 574 public void setAlternateTrack(Track track) { 575 Track oldTrack = _location.getTrackById(_alternateTrackId); 576 String old = _alternateTrackId; 577 if (track != null) { 578 _alternateTrackId = track.getId(); 579 } else { 580 _alternateTrackId = NONE; 581 } 582 if (!old.equals(_alternateTrackId)) { 583 setDirtyAndFirePropertyChange(ALTERNATE_TRACK_CHANGED_PROPERTY, oldTrack, track); 584 } 585 } 586 587 /** 588 * Returns the alternate track for a spur 589 * 590 * @return alternate track 591 */ 592 public Track getAlternateTrack() { 593 if (!isSpur()) { 594 return null; 595 } 596 return _location.getTrackById(_alternateTrackId); 597 } 598 599 public void setHoldCarsWithCustomLoadsEnabled(boolean enable) { 600 boolean old = _holdCustomLoads; 601 _holdCustomLoads = enable; 602 setDirtyAndFirePropertyChange(HOLD_CARS_CHANGED_PROPERTY, old, enable); 603 } 604 605 /** 606 * If enabled (true), hold cars with custom loads rather than allowing them 607 * to go to staging if the spur and the alternate track were full. If 608 * disabled, cars with custom loads can be forwarded to staging when this 609 * spur and all others with this option are also false. 610 * 611 * @return True if enabled 612 */ 613 public boolean isHoldCarsWithCustomLoadsEnabled() { 614 return _holdCustomLoads; 615 } 616 617 /** 618 * Used to determine if there's space available at this track for the car. 619 * Considers cars en-route to this track. Used to prevent overloading the 620 * track. 621 * 622 * @param car The car to be set out. 623 * @return true if space available. 624 */ 625 public boolean isSpaceAvailable(Car car) { 626 int carLength = car.getTotalKernelLength(); 627 int trackLength = getLength(); 628 // is the car or kernel too long for the track? 629 if (trackLength < carLength && getPool() == null) { 630 return false; 631 } 632 // is track part of a pool? 633 if (getPool() != null && getPool().getMaxLengthTrack(this) < carLength) { 634 return false; 635 } 636 // ignore reservation factor unless car is departing staging 637 if (car.getTrack() != null && car.getTrack().isStaging()) { 638 return (getLength() * getReservationFactor() / 100 - (getReservedInRoute() + carLength) >= 0); 639 } 640 // if there's alternate, include that length in the calculation 641 if (getAlternateTrack() != null) { 642 trackLength = trackLength + getAlternateTrack().getLength(); 643 } 644 return (trackLength - (getReservedInRoute() + carLength) >= 0); 645 } 646 647 public void setUsedLength(int length) { 648 int old = _usedLength; 649 _usedLength = length; 650 if (old != length) { 651 setDirtyAndFirePropertyChange("trackUsedLength", Integer.toString(old), // NOI18N 652 Integer.toString(length)); 653 } 654 } 655 656 public int getUsedLength() { 657 return _usedLength; 658 } 659 660 public void setUsedCloneLength(int length) { 661 int old = _usedCloneLength; 662 _usedCloneLength = length; 663 if (old != length) { 664 setDirtyAndFirePropertyChange("trackUsedCloneLength", Integer.toString(old), // NOI18N 665 Integer.toString(length)); 666 } 667 } 668 669 public int getUsedCloneLength() { 670 return _usedCloneLength; 671 } 672 673 public int getTotalUsedLength() { 674 return getUsedLength() + getUsedCloneLength(); 675 } 676 677 /** 678 * The amount of consumed track space to be ignored when sending new rolling 679 * stock to the track. See Planned Pickups in help. 680 * 681 * @param percentage a number between 0 and 100 682 */ 683 public void setIgnoreUsedLengthPercentage(int percentage) { 684 int old = _ignoreUsedLengthPercentage; 685 _ignoreUsedLengthPercentage = percentage; 686 if (old != percentage) { 687 setDirtyAndFirePropertyChange(PLANNED_PICKUPS_CHANGED_PROPERTY, Integer.toString(old), 688 Integer.toString(percentage)); 689 } 690 } 691 692 public int getIgnoreUsedLengthPercentage() { 693 return _ignoreUsedLengthPercentage; 694 } 695 696 /** 697 * Sets the number of rolling stock (cars and or engines) on this track 698 */ 699 private void setNumberRS(int number) { 700 int old = _numberRS; 701 _numberRS = number; 702 if (old != number) { 703 setDirtyAndFirePropertyChange("trackNumberRS", Integer.toString(old), // NOI18N 704 Integer.toString(number)); // NOI18N 705 } 706 } 707 708 /** 709 * Sets the number of cars on this track 710 */ 711 private void setNumberCars(int number) { 712 int old = _numberCars; 713 _numberCars = number; 714 if (old != number) { 715 setDirtyAndFirePropertyChange("trackNumberCars", Integer.toString(old), // NOI18N 716 Integer.toString(number)); 717 } 718 } 719 720 /** 721 * Sets the number of engines on this track 722 */ 723 private void setNumberEngines(int number) { 724 int old = _numberEngines; 725 _numberEngines = number; 726 if (old != number) { 727 setDirtyAndFirePropertyChange("trackNumberEngines", Integer.toString(old), // NOI18N 728 Integer.toString(number)); 729 } 730 } 731 732 /** 733 * @return The number of rolling stock (cars and engines) on this track 734 */ 735 public int getNumberRS() { 736 return _numberRS; 737 } 738 739 /** 740 * @return The number of cars on this track 741 */ 742 public int getNumberCars() { 743 return _numberCars; 744 } 745 746 /** 747 * @return The number of engines on this track 748 */ 749 public int getNumberEngines() { 750 return _numberEngines; 751 } 752 753 /** 754 * Adds rolling stock to a specific track. 755 * 756 * @param rs The rolling stock to place on the track. 757 */ 758 public void addRS(RollingStock rs) { 759 if (!rs.isClone()) { 760 setNumberRS(getNumberRS() + 1); 761 if (rs.getClass() == Car.class) { 762 setNumberCars(getNumberCars() + 1); 763 } else if (rs.getClass() == Engine.class) { 764 setNumberEngines(getNumberEngines() + 1); 765 } 766 setUsedLength(getUsedLength() + rs.getTotalLength()); 767 } else { 768 setUsedCloneLength(getUsedCloneLength() + rs.getTotalLength()); 769 } 770 } 771 772 public void deleteRS(RollingStock rs) { 773 if (!rs.isClone()) { 774 setNumberRS(getNumberRS() - 1); 775 if (rs.getClass() == Car.class) { 776 setNumberCars(getNumberCars() - 1); 777 } else if (rs.getClass() == Engine.class) { 778 setNumberEngines(getNumberEngines() - 1); 779 } 780 setUsedLength(getUsedLength() - rs.getTotalLength()); 781 } else { 782 setUsedCloneLength(getUsedCloneLength() - rs.getTotalLength()); 783 } 784 } 785 786 /** 787 * Increments the number of cars and or engines that will be picked up by a 788 * train from this track. 789 * 790 * @param rs The rolling stock. 791 */ 792 public void addPickupRS(RollingStock rs) { 793 int old = _pickupRS; 794 _pickupRS++; 795 if (Setup.isBuildAggressive() && !rs.isClone()) { 796 setReserved(getReserved() - rs.getTotalLength()); 797 } 798 _reservedLengthPickups = _reservedLengthPickups + rs.getTotalLength(); 799 setDirtyAndFirePropertyChange("trackPickupRS", Integer.toString(old), // NOI18N 800 Integer.toString(_pickupRS)); 801 } 802 803 public void deletePickupRS(RollingStock rs) { 804 int old = _pickupRS; 805 if (Setup.isBuildAggressive() && !rs.isClone()) { 806 setReserved(getReserved() + rs.getTotalLength()); 807 } 808 _reservedLengthPickups = _reservedLengthPickups - rs.getTotalLength(); 809 _pickupRS--; 810 setDirtyAndFirePropertyChange("trackDeletePickupRS", Integer.toString(old), // NOI18N 811 Integer.toString(_pickupRS)); 812 } 813 814 /** 815 * @return the number of rolling stock (cars and or locos) that are 816 * scheduled for pick up from this track. 817 */ 818 public int getPickupRS() { 819 return _pickupRS; 820 } 821 822 public int getReservedLengthPickups() { 823 return _reservedLengthPickups; 824 } 825 826 public void addDropRS(RollingStock rs) { 827 int old = _dropRS; 828 _dropRS++; 829 bumpMoves(); 830 // don't reserve clones 831 if (rs.isClone()) { 832 log.debug("Ignoring clone {} add drop reserve", rs.toString()); 833 } else { 834 setReserved(getReserved() + rs.getTotalLength()); 835 } 836 _reservedLengthSetouts = _reservedLengthSetouts + rs.getTotalLength(); 837 setDirtyAndFirePropertyChange("trackAddDropRS", Integer.toString(old), Integer.toString(_dropRS)); // NOI18N 838 } 839 840 public void deleteDropRS(RollingStock rs) { 841 int old = _dropRS; 842 _dropRS--; 843 // don't reserve clones 844 if (rs.isClone()) { 845 log.debug("Ignoring clone {} delete drop reserve", rs.toString()); 846 } else { 847 setReserved(getReserved() - rs.getTotalLength()); 848 } 849 _reservedLengthSetouts = _reservedLengthSetouts - rs.getTotalLength(); 850 setDirtyAndFirePropertyChange("trackDeleteDropRS", Integer.toString(old), // NOI18N 851 Integer.toString(_dropRS)); 852 } 853 854 public int getDropRS() { 855 return _dropRS; 856 } 857 858 public int getReservedLengthSetouts() { 859 return _reservedLengthSetouts; 860 } 861 862 public void setComment(String comment) { 863 String old = _comment; 864 _comment = comment; 865 if (!old.equals(comment)) { 866 setDirtyAndFirePropertyChange("trackComment", old, comment); // NOI18N 867 } 868 } 869 870 public String getComment() { 871 return _comment; 872 } 873 874 public void setCommentPickup(String comment) { 875 String old = _commentPickup; 876 _commentPickup = comment; 877 if (!old.equals(comment)) { 878 setDirtyAndFirePropertyChange(TRACK_COMMENT_CHANGED_PROPERTY, old, comment); // NOI18N 879 } 880 } 881 882 public String getCommentPickup() { 883 return TrainCommon.getTextColorString(getCommentPickupWithColor()); 884 } 885 886 public String getCommentPickupWithColor() { 887 return _commentPickup; 888 } 889 890 public void setCommentSetout(String comment) { 891 String old = _commentSetout; 892 _commentSetout = comment; 893 if (!old.equals(comment)) { 894 setDirtyAndFirePropertyChange(TRACK_COMMENT_CHANGED_PROPERTY, old, comment); // NOI18N 895 } 896 } 897 898 public String getCommentSetout() { 899 return TrainCommon.getTextColorString(getCommentSetoutWithColor()); 900 } 901 902 public String getCommentSetoutWithColor() { 903 return _commentSetout; 904 } 905 906 public void setCommentBoth(String comment) { 907 String old = _commentBoth; 908 _commentBoth = comment; 909 if (!old.equals(comment)) { 910 setDirtyAndFirePropertyChange(TRACK_COMMENT_CHANGED_PROPERTY, old, comment); // NOI18N 911 } 912 } 913 914 public String getCommentBoth() { 915 return TrainCommon.getTextColorString(getCommentBothWithColor()); 916 } 917 918 public String getCommentBothWithColor() { 919 return _commentBoth; 920 } 921 922 public boolean isPrintManifestCommentEnabled() { 923 return _printCommentManifest; 924 } 925 926 public void setPrintManifestCommentEnabled(boolean enable) { 927 boolean old = isPrintManifestCommentEnabled(); 928 _printCommentManifest = enable; 929 setDirtyAndFirePropertyChange("trackPrintManifestComment", old, enable); 930 } 931 932 public boolean isPrintSwitchListCommentEnabled() { 933 return _printCommentSwitchList; 934 } 935 936 public void setPrintSwitchListCommentEnabled(boolean enable) { 937 boolean old = isPrintSwitchListCommentEnabled(); 938 _printCommentSwitchList = enable; 939 setDirtyAndFirePropertyChange("trackPrintSwitchListComment", old, enable); 940 } 941 942 /** 943 * Returns all of the rolling stock type names serviced by this track. 944 * 945 * @return rolling stock type names 946 */ 947 public String[] getTypeNames() { 948 List<String> list = new ArrayList<>(); 949 for (String typeName : _typeList) { 950 if (_location.acceptsTypeName(typeName)) { 951 list.add(typeName); 952 } 953 } 954 return list.toArray(new String[0]); 955 } 956 957 private void setTypeNames(String[] types) { 958 if (types.length > 0) { 959 Arrays.sort(types); 960 for (String type : types) { 961 if (!_typeList.contains(type)) { 962 _typeList.add(type); 963 } 964 } 965 } 966 } 967 968 private void clearTypeNames() { 969 _typeList.clear(); 970 } 971 972 public void addTypeName(String type) { 973 // insert at start of list, sort later 974 if (type == null || _typeList.contains(type)) { 975 return; 976 } 977 _typeList.add(0, type); 978 log.debug("Track ({}) add rolling stock type ({})", getName(), type); 979 setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() - 1, _typeList.size()); 980 } 981 982 public void deleteTypeName(String type) { 983 if (_typeList.remove(type)) { 984 log.debug("Track ({}) delete rolling stock type ({})", getName(), type); 985 setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() + 1, _typeList.size()); 986 } 987 } 988 989 public boolean isTypeNameAccepted(String type) { 990 if (!_location.acceptsTypeName(type)) { 991 return false; 992 } 993 return _typeList.contains(type); 994 } 995 996 /** 997 * Sets the train directions that can service this track 998 * 999 * @param direction EAST, WEST, NORTH, SOUTH 1000 */ 1001 public void setTrainDirections(int direction) { 1002 int old = _trainDir; 1003 _trainDir = direction; 1004 if (old != direction) { 1005 setDirtyAndFirePropertyChange(TRAIN_DIRECTION_CHANGED_PROPERTY, Integer.toString(old), 1006 Integer.toString(direction)); 1007 } 1008 } 1009 1010 public int getTrainDirections() { 1011 return _trainDir; 1012 } 1013 1014 public String getRoadOption() { 1015 return _roadOption; 1016 } 1017 1018 public String getRoadOptionString() { 1019 String s; 1020 if (getRoadOption().equals(Track.INCLUDE_ROADS)) { 1021 s = Bundle.getMessage("AcceptOnly") + " " + getRoadNames().length + " " + Bundle.getMessage("Roads"); 1022 } else if (getRoadOption().equals(Track.EXCLUDE_ROADS)) { 1023 s = Bundle.getMessage("Exclude") + " " + getRoadNames().length + " " + Bundle.getMessage("Roads"); 1024 } else { 1025 s = Bundle.getMessage("AcceptsAllRoads"); 1026 } 1027 return s; 1028 } 1029 1030 /** 1031 * Set the road option for this track. 1032 * 1033 * @param option ALLROADS, INCLUDEROADS, or EXCLUDEROADS 1034 */ 1035 public void setRoadOption(String option) { 1036 String old = _roadOption; 1037 _roadOption = option; 1038 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, old, option); 1039 } 1040 1041 public String[] getRoadNames() { 1042 String[] roads = _roadList.toArray(new String[0]); 1043 if (_roadList.size() > 0) { 1044 Arrays.sort(roads); 1045 } 1046 return roads; 1047 } 1048 1049 private void setRoadNames(String[] roads) { 1050 if (roads.length > 0) { 1051 Arrays.sort(roads); 1052 for (String roadName : roads) { 1053 if (!roadName.equals(NONE)) { 1054 _roadList.add(roadName); 1055 } 1056 } 1057 } 1058 } 1059 1060 public void addRoadName(String road) { 1061 if (!_roadList.contains(road)) { 1062 _roadList.add(road); 1063 log.debug("Track ({}) add car road ({})", getName(), road); 1064 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _roadList.size() - 1, _roadList.size()); 1065 } 1066 } 1067 1068 public void deleteRoadName(String road) { 1069 if (_roadList.remove(road)) { 1070 log.debug("Track ({}) delete car road ({})", getName(), road); 1071 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _roadList.size() + 1, _roadList.size()); 1072 } 1073 } 1074 1075 public boolean isRoadNameAccepted(String road) { 1076 if (getRoadOption().equals(ALL_ROADS)) { 1077 return true; 1078 } 1079 if (getRoadOption().equals(INCLUDE_ROADS)) { 1080 return _roadList.contains(road); 1081 } 1082 // exclude! 1083 return !_roadList.contains(road); 1084 } 1085 1086 public boolean containsRoadName(String road) { 1087 return _roadList.contains(road); 1088 } 1089 1090 /** 1091 * Gets the car receive load option for this track. 1092 * 1093 * @return ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS 1094 */ 1095 public String getLoadOption() { 1096 return _loadOption; 1097 } 1098 1099 public String getLoadOptionString() { 1100 String s; 1101 if (getLoadOption().equals(Track.INCLUDE_LOADS)) { 1102 s = Bundle.getMessage("AcceptOnly") + " " + getLoadNames().length + " " + Bundle.getMessage("Loads"); 1103 } else if (getLoadOption().equals(Track.EXCLUDE_LOADS)) { 1104 s = Bundle.getMessage("Exclude") + " " + getLoadNames().length + " " + Bundle.getMessage("Loads"); 1105 } else { 1106 s = Bundle.getMessage("AcceptsAllLoads"); 1107 } 1108 return s; 1109 } 1110 1111 /** 1112 * Set how this track deals with receiving car loads 1113 * 1114 * @param option ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS 1115 */ 1116 public void setLoadOption(String option) { 1117 String old = _loadOption; 1118 _loadOption = option; 1119 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, old, option); 1120 } 1121 1122 private void setLoadNames(String[] loads) { 1123 if (loads.length > 0) { 1124 Arrays.sort(loads); 1125 for (String loadName : loads) { 1126 if (!loadName.equals(NONE)) { 1127 _loadList.add(loadName); 1128 } 1129 } 1130 } 1131 } 1132 1133 /** 1134 * Provides a list of receive loads that the track will either service or 1135 * exclude. See setLoadOption 1136 * 1137 * @return Array of load names as Strings 1138 */ 1139 public String[] getLoadNames() { 1140 String[] loads = _loadList.toArray(new String[0]); 1141 if (_loadList.size() > 0) { 1142 Arrays.sort(loads); 1143 } 1144 return loads; 1145 } 1146 1147 /** 1148 * Add a receive load that the track will either service or exclude. See 1149 * setLoadOption 1150 * 1151 * @param load The string load name. 1152 */ 1153 public void addLoadName(String load) { 1154 if (!_loadList.contains(load)) { 1155 _loadList.add(load); 1156 log.debug("track ({}) add car load ({})", getName(), load); 1157 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() - 1, _loadList.size()); 1158 } 1159 } 1160 1161 /** 1162 * Delete a receive load name that the track will either service or exclude. 1163 * See setLoadOption 1164 * 1165 * @param load The string load name. 1166 */ 1167 public void deleteLoadName(String load) { 1168 if (_loadList.remove(load)) { 1169 log.debug("track ({}) delete car load ({})", getName(), load); 1170 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() + 1, _loadList.size()); 1171 } 1172 } 1173 1174 /** 1175 * Determine if track will service a specific receive load name. 1176 * 1177 * @param load the load name to check. 1178 * @return true if track will service this load. 1179 */ 1180 public boolean isLoadNameAccepted(String load) { 1181 if (getLoadOption().equals(ALL_LOADS)) { 1182 return true; 1183 } 1184 if (getLoadOption().equals(INCLUDE_LOADS)) { 1185 return _loadList.contains(load); 1186 } 1187 // exclude! 1188 return !_loadList.contains(load); 1189 } 1190 1191 /** 1192 * Determine if track will service a specific receive load and car type. 1193 * 1194 * @param load the load name to check. 1195 * @param type the type of car used to carry the load. 1196 * @return true if track will service this load. 1197 */ 1198 public boolean isLoadNameAndCarTypeAccepted(String load, String type) { 1199 if (getLoadOption().equals(ALL_LOADS)) { 1200 return true; 1201 } 1202 if (getLoadOption().equals(INCLUDE_LOADS)) { 1203 return _loadList.contains(load) || _loadList.contains(type + CarLoad.SPLIT_CHAR + load); 1204 } 1205 // exclude! 1206 return !_loadList.contains(load) && !_loadList.contains(type + CarLoad.SPLIT_CHAR + load); 1207 } 1208 1209 /** 1210 * Gets the car ship load option for this track. 1211 * 1212 * @return ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS 1213 */ 1214 public String getShipLoadOption() { 1215 if (!isStaging()) { 1216 return ALL_LOADS; 1217 } 1218 return _shipLoadOption; 1219 } 1220 1221 public String getShipLoadOptionString() { 1222 String s; 1223 if (getShipLoadOption().equals(Track.INCLUDE_LOADS)) { 1224 s = Bundle.getMessage("ShipOnly") + " " + getShipLoadNames().length + " " + Bundle.getMessage("Loads"); 1225 } else if (getShipLoadOption().equals(Track.EXCLUDE_LOADS)) { 1226 s = Bundle.getMessage("Exclude") + " " + getShipLoadNames().length + " " + Bundle.getMessage("Loads"); 1227 } else { 1228 s = Bundle.getMessage("ShipsAllLoads"); 1229 } 1230 return s; 1231 } 1232 1233 /** 1234 * Set how this track deals with shipping car loads 1235 * 1236 * @param option ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS 1237 */ 1238 public void setShipLoadOption(String option) { 1239 String old = _shipLoadOption; 1240 _shipLoadOption = option; 1241 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, old, option); 1242 } 1243 1244 private void setShipLoadNames(String[] loads) { 1245 if (loads.length > 0) { 1246 Arrays.sort(loads); 1247 for (String shipLoadName : loads) { 1248 if (!shipLoadName.equals(NONE)) { 1249 _shipLoadList.add(shipLoadName); 1250 } 1251 } 1252 } 1253 } 1254 1255 /** 1256 * Provides a list of ship loads that the track will either service or 1257 * exclude. See setShipLoadOption 1258 * 1259 * @return Array of load names as Strings 1260 */ 1261 public String[] getShipLoadNames() { 1262 String[] loads = _shipLoadList.toArray(new String[0]); 1263 if (_shipLoadList.size() > 0) { 1264 Arrays.sort(loads); 1265 } 1266 return loads; 1267 } 1268 1269 /** 1270 * Add a ship load that the track will either service or exclude. See 1271 * setShipLoadOption 1272 * 1273 * @param load The string load name. 1274 */ 1275 public void addShipLoadName(String load) { 1276 if (!_shipLoadList.contains(load)) { 1277 _shipLoadList.add(load); 1278 log.debug("track ({}) add car load ({})", getName(), load); 1279 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _shipLoadList.size() - 1, _shipLoadList.size()); 1280 } 1281 } 1282 1283 /** 1284 * Delete a ship load name that the track will either service or exclude. 1285 * See setLoadOption 1286 * 1287 * @param load The string load name. 1288 */ 1289 public void deleteShipLoadName(String load) { 1290 if (_shipLoadList.remove(load)) { 1291 log.debug("track ({}) delete car load ({})", getName(), load); 1292 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _shipLoadList.size() + 1, _shipLoadList.size()); 1293 } 1294 } 1295 1296 /** 1297 * Determine if track will service a specific ship load name. 1298 * 1299 * @param load the load name to check. 1300 * @return true if track will service this load. 1301 */ 1302 public boolean isLoadNameShipped(String load) { 1303 if (getShipLoadOption().equals(ALL_LOADS)) { 1304 return true; 1305 } 1306 if (getShipLoadOption().equals(INCLUDE_LOADS)) { 1307 return _shipLoadList.contains(load); 1308 } 1309 // exclude! 1310 return !_shipLoadList.contains(load); 1311 } 1312 1313 /** 1314 * Determine if track will service a specific ship load and car type. 1315 * 1316 * @param load the load name to check. 1317 * @param type the type of car used to carry the load. 1318 * @return true if track will service this load. 1319 */ 1320 public boolean isLoadNameAndCarTypeShipped(String load, String type) { 1321 if (getShipLoadOption().equals(ALL_LOADS)) { 1322 return true; 1323 } 1324 if (getShipLoadOption().equals(INCLUDE_LOADS)) { 1325 return _shipLoadList.contains(load) || _shipLoadList.contains(type + CarLoad.SPLIT_CHAR + load); 1326 } 1327 // exclude! 1328 return !_shipLoadList.contains(load) && !_shipLoadList.contains(type + CarLoad.SPLIT_CHAR + load); 1329 } 1330 1331 /** 1332 * Gets the drop option for this track. ANY means that all trains and routes 1333 * can drop cars to this track. The other four options are used to restrict 1334 * the track to certain trains or routes. 1335 * 1336 * @return ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES 1337 */ 1338 public String getDropOption() { 1339 if (isYard()) { 1340 return ANY; 1341 } 1342 return _dropOption; 1343 } 1344 1345 /** 1346 * Set the car drop option for this track. 1347 * 1348 * @param option ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES 1349 */ 1350 public void setDropOption(String option) { 1351 String old = _dropOption; 1352 _dropOption = option; 1353 if (!old.equals(option)) { 1354 _dropList.clear(); 1355 } 1356 setDirtyAndFirePropertyChange(DROP_CHANGED_PROPERTY, old, option); 1357 } 1358 1359 /** 1360 * Gets the pickup option for this track. ANY means that all trains and 1361 * routes can pull cars from this track. The other four options are used to 1362 * restrict the track to certain trains or routes. 1363 * 1364 * @return ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES 1365 */ 1366 public String getPickupOption() { 1367 if (isYard()) { 1368 return ANY; 1369 } 1370 return _pickupOption; 1371 } 1372 1373 /** 1374 * Set the car pick up option for this track. 1375 * 1376 * @param option ANY, TRAINS, ROUTES, EXCLUDE_TRAINS, or EXCLUDE_ROUTES 1377 */ 1378 public void setPickupOption(String option) { 1379 String old = _pickupOption; 1380 _pickupOption = option; 1381 if (!old.equals(option)) { 1382 _pickupList.clear(); 1383 } 1384 setDirtyAndFirePropertyChange(PICKUP_CHANGED_PROPERTY, old, option); 1385 } 1386 1387 public String[] getDropIds() { 1388 return _dropList.toArray(new String[0]); 1389 } 1390 1391 private void setDropIds(String[] ids) { 1392 for (String id : ids) { 1393 if (id != null) { 1394 _dropList.add(id); 1395 } 1396 } 1397 } 1398 1399 public void addDropId(String id) { 1400 if (!_dropList.contains(id)) { 1401 _dropList.add(id); 1402 log.debug("Track ({}) add drop id: {}", getName(), id); 1403 setDirtyAndFirePropertyChange(DROP_CHANGED_PROPERTY, null, id); 1404 } 1405 } 1406 1407 public void deleteDropId(String id) { 1408 if (_dropList.remove(id)) { 1409 log.debug("Track ({}) delete drop id: {}", getName(), id); 1410 setDirtyAndFirePropertyChange(DROP_CHANGED_PROPERTY, id, null); 1411 } 1412 } 1413 1414 /** 1415 * Determine if train can set out cars to this track. Based on the train's 1416 * id or train's route id. See setDropOption(option). 1417 * 1418 * @param train The Train to test. 1419 * @return true if the train can set out cars to this track. 1420 */ 1421 public boolean isDropTrainAccepted(Train train) { 1422 if (getDropOption().equals(ANY)) { 1423 return true; 1424 } 1425 if (getDropOption().equals(TRAINS)) { 1426 return containsDropId(train.getId()); 1427 } 1428 if (getDropOption().equals(EXCLUDE_TRAINS)) { 1429 return !containsDropId(train.getId()); 1430 } else if (train.getRoute() == null) { 1431 return false; 1432 } 1433 return isDropRouteAccepted(train.getRoute()); 1434 } 1435 1436 public boolean isDropRouteAccepted(Route route) { 1437 if (getDropOption().equals(ANY) || getDropOption().equals(TRAINS) || getDropOption().equals(EXCLUDE_TRAINS)) { 1438 return true; 1439 } 1440 if (getDropOption().equals(EXCLUDE_ROUTES)) { 1441 return !containsDropId(route.getId()); 1442 } 1443 return containsDropId(route.getId()); 1444 } 1445 1446 public boolean containsDropId(String id) { 1447 return _dropList.contains(id); 1448 } 1449 1450 public String[] getPickupIds() { 1451 return _pickupList.toArray(new String[0]); 1452 } 1453 1454 private void setPickupIds(String[] ids) { 1455 for (String id : ids) { 1456 if (id != null) { 1457 _pickupList.add(id); 1458 } 1459 } 1460 } 1461 1462 /** 1463 * Add train or route id to this track. 1464 * 1465 * @param id The string id for the train or route. 1466 */ 1467 public void addPickupId(String id) { 1468 if (!_pickupList.contains(id)) { 1469 _pickupList.add(id); 1470 log.debug("track ({}) add pick up id {}", getName(), id); 1471 setDirtyAndFirePropertyChange(PICKUP_CHANGED_PROPERTY, null, id); 1472 } 1473 } 1474 1475 public void deletePickupId(String id) { 1476 if (_pickupList.remove(id)) { 1477 log.debug("track ({}) delete pick up id {}", getName(), id); 1478 setDirtyAndFirePropertyChange(PICKUP_CHANGED_PROPERTY, id, null); 1479 } 1480 } 1481 1482 /** 1483 * Determine if train can pick up cars from this track. Based on the train's 1484 * id or train's route id. See setPickupOption(option). 1485 * 1486 * @param train The Train to test. 1487 * @return true if the train can pick up cars from this track. 1488 */ 1489 public boolean isPickupTrainAccepted(Train train) { 1490 if (getPickupOption().equals(ANY)) { 1491 return true; 1492 } 1493 if (getPickupOption().equals(TRAINS)) { 1494 return containsPickupId(train.getId()); 1495 } 1496 if (getPickupOption().equals(EXCLUDE_TRAINS)) { 1497 return !containsPickupId(train.getId()); 1498 } else if (train.getRoute() == null) { 1499 return false; 1500 } 1501 return isPickupRouteAccepted(train.getRoute()); 1502 } 1503 1504 public boolean isPickupRouteAccepted(Route route) { 1505 if (getPickupOption().equals(ANY) || 1506 getPickupOption().equals(TRAINS) || 1507 getPickupOption().equals(EXCLUDE_TRAINS)) { 1508 return true; 1509 } 1510 if (getPickupOption().equals(EXCLUDE_ROUTES)) { 1511 return !containsPickupId(route.getId()); 1512 } 1513 return containsPickupId(route.getId()); 1514 } 1515 1516 public boolean containsPickupId(String id) { 1517 return _pickupList.contains(id); 1518 } 1519 1520 /** 1521 * Checks to see if all car types can be pulled from this track 1522 * 1523 * @return PICKUP_OKAY if any train can pull all car types from this track 1524 */ 1525 public String checkPickups() { 1526 String status = PICKUP_OKAY; 1527 S1: for (String carType : InstanceManager.getDefault(CarTypes.class).getNames()) { 1528 if (!isTypeNameAccepted(carType)) { 1529 continue; 1530 } 1531 for (Train train : InstanceManager.getDefault(TrainManager.class).getTrainsByNameList()) { 1532 if (!train.isTypeNameAccepted(carType) || !isPickupTrainAccepted(train)) { 1533 continue; 1534 } 1535 // does the train services this location and track? 1536 Route route = train.getRoute(); 1537 if (route != null) { 1538 for (RouteLocation rLoc : route.getLocationsBySequenceList()) { 1539 if (rLoc.getName().equals(getLocation().getName()) && 1540 rLoc.isPickUpAllowed() && 1541 rLoc.getMaxCarMoves() > 0 && 1542 !train.isLocationSkipped(rLoc) && 1543 ((getTrainDirections() & rLoc.getTrainDirection()) != 0 || train.isLocalSwitcher()) && 1544 ((getLocation().getTrainDirections() & rLoc.getTrainDirection()) != 0 || 1545 train.isLocalSwitcher())) { 1546 1547 continue S1; // car type serviced by this train, try 1548 // next car type 1549 } 1550 } 1551 } 1552 } 1553 // None of the trains servicing this track can pick up car type 1554 status = Bundle.getMessage("ErrorNoTrain", getName(), carType); 1555 break; 1556 } 1557 return status; 1558 } 1559 1560 /** 1561 * A track has four priorities: PRIORITY_HIGH, PRIORITY_MEDIUM, 1562 * PRIORITY_NORMAL, and PRIORITY_LOW. Cars are serviced from a location 1563 * based on the track priority. Default is normal. 1564 * 1565 * @return track priority 1566 */ 1567 public String getTrackPriority() { 1568 return _trackPriority; 1569 } 1570 1571 public void setTrackPriority(String priority) { 1572 String old = _trackPriority; 1573 _trackPriority = priority; 1574 setDirtyAndFirePropertyChange(PRIORITY_CHANGED_PROPERTY, old, priority); 1575 } 1576 1577 /** 1578 * Used to determine if track can service the rolling stock. 1579 * 1580 * @param rs the car or loco to be tested 1581 * @return Error string starting with TYPE, ROAD, CAPACITY, LENGTH, 1582 * DESTINATION or LOAD if there's an issue. OKAY if track can 1583 * service Rolling Stock. 1584 */ 1585 public String isRollingStockAccepted(RollingStock rs) { 1586 // first determine if rolling stock can be move to the new location 1587 // note that there's code that checks for certain issues by checking the 1588 // first word of the status string returned 1589 if (!isTypeNameAccepted(rs.getTypeName())) { 1590 log.debug("Rolling stock ({}) type ({}) not accepted at location ({}, {}) wrong type", rs.toString(), 1591 rs.getTypeName(), getLocation().getName(), getName()); // NOI18N 1592 return TYPE + " (" + rs.getTypeName() + ")"; 1593 } 1594 if (!isRoadNameAccepted(rs.getRoadName())) { 1595 log.debug("Rolling stock ({}) road ({}) not accepted at location ({}, {}) wrong road", rs.toString(), 1596 rs.getRoadName(), getLocation().getName(), getName()); // NOI18N 1597 return ROAD + " (" + rs.getRoadName() + ")"; 1598 } 1599 // now determine if there's enough space for the rolling stock 1600 int rsLength = rs.getTotalLength(); 1601 // error check 1602 try { 1603 Integer.parseInt(rs.getLength()); 1604 } catch (Exception e) { 1605 return LENGTH + " (" + rs.getLength() + ")"; 1606 } 1607 1608 if (Car.class.isInstance(rs)) { 1609 Car car = (Car) rs; 1610 // does this track service the car's final destination? 1611 if (!isDestinationAccepted(car.getFinalDestination())) { 1612 // && getLocation() != car.getFinalDestination()) { // 4/14/2014 1613 // I can't remember why this was needed 1614 return DESTINATION + 1615 " (" + 1616 car.getFinalDestinationName() + 1617 ") " + 1618 Bundle.getMessage("carIsNotAllowed", getName()); // no 1619 } 1620 // does this track accept cars without a final destination? 1621 if (isOnlyCarsWithFinalDestinationEnabled() && 1622 car.getFinalDestination() == null && 1623 !car.isCaboose() && 1624 !car.hasFred()) { 1625 return NO_FINAL_DESTINATION; 1626 } 1627 // check for car in kernel 1628 if (car.isLead()) { 1629 rsLength = car.getKernel().getTotalLength(); 1630 } 1631 if (!isLoadNameAndCarTypeAccepted(car.getLoadName(), car.getTypeName())) { 1632 log.debug("Car ({}) load ({}) not accepted at location ({}, {})", rs.toString(), car.getLoadName(), 1633 getLocation(), getName()); // NOI18N 1634 return LOAD + " (" + car.getLoadName() + ")"; 1635 } 1636 } 1637 // check for loco in consist 1638 if (Engine.class.isInstance(rs)) { 1639 Engine eng = (Engine) rs; 1640 if (eng.isLead()) { 1641 rsLength = eng.getConsist().getTotalLength(); 1642 } 1643 } 1644 if (rs.getTrack() != this && 1645 rs.getDestinationTrack() != this) { 1646 if (getUsedLength() + getReserved() + rsLength > getLength() || 1647 getReservedLengthSetouts() + rsLength > getLength()) { 1648 // not enough track length check to see if track is in a pool 1649 if (getPool() != null && getPool().requestTrackLength(this, rsLength)) { 1650 return OKAY; 1651 } 1652 // ignore used length option? 1653 if (checkPlannedPickUps(rsLength)) { 1654 return OKAY; 1655 } 1656 // Is rolling stock too long for this track? 1657 if ((getLength() < rsLength && getPool() == null) || 1658 (getPool() != null && getPool().getTotalLengthTracks() < rsLength)) { 1659 return Bundle.getMessage("capacityIssue", 1660 CAPACITY, rsLength, Setup.getLengthUnit().toLowerCase(), getLength()); 1661 } 1662 // is track space available due to timing? 1663 String status = checkQuickServiceTrack(rs, rsLength); 1664 if (!status.equals(DISABLED)) { 1665 return status; 1666 } 1667 // The code assumes everything is fine with the track if the Length issue is returned. 1668 log.debug("Rolling stock ({}) not accepted at location ({}, {}) no room! Used {}, reserved {}", 1669 rs.toString(), getLocation().getName(), getName(), getUsedLength(), getReserved()); // NOI18N 1670 1671 return Bundle.getMessage("lengthIssue", 1672 LENGTH, rsLength, Setup.getLengthUnit().toLowerCase(), getAvailableTrackSpace(), getLength()); 1673 } 1674 } 1675 return OKAY; 1676 } 1677 1678 /** 1679 * Performs two checks, number of new set outs shouldn't exceed the track 1680 * length. The second check protects against overloading, the total number 1681 * of cars shouldn't exceed the track length plus the number of cars to 1682 * ignore. 1683 * 1684 * @param length rolling stock length 1685 * @return true if the program should ignore some percentage of the car's 1686 * length currently consuming track space. 1687 */ 1688 private boolean checkPlannedPickUps(int length) { 1689 if (getIgnoreUsedLengthPercentage() > IGNORE_0 && getAvailableTrackSpace() >= length) { 1690 return true; 1691 } 1692 return false; 1693 } 1694 1695 /** 1696 * @return true if there's space available due when cars are being pulled. 1697 * Allows new cars to be spotted to a quick service track after 1698 * pulls are completed by previous trains. Therefore the train being 1699 * built has to have a departure time that is later than the cars 1700 * being pulled from this track. Also includes track space created 1701 * by car pick ups by the train being built, but not delivered by 1702 * the train being built. 1703 */ 1704 private String checkQuickServiceTrack(RollingStock rs, int rsLength) { 1705 if (!isQuickServiceEnabled() || !Setup.isBuildOnTime()) { 1706 return DISABLED; 1707 } 1708 Train train = InstanceManager.getDefault(TrainManager.class).getTrainBuilding(); 1709 if (train == null) { 1710 return DISABLED; 1711 } 1712 int trainDepartureTimeMinutes = TrainCommon.convertStringTime(train.getDepartureTime()); 1713 // determine due to timing if there's space for this rolling stock 1714 CarManager carManager = InstanceManager.getDefault(CarManager.class); 1715 List<Car> cars = carManager.getList(this); 1716 // note that used can be larger than track length 1717 int trackSpaceAvalable = getLength() - getTotalUsedLength(); 1718 log.debug("track ({}) space available at start: {}", this.getName(), trackSpaceAvalable); 1719 for (Car car : cars) { 1720 log.debug("Car ({}) length {}, track ({}, {}) pick up time {}, to ({}), train ({}), last train ({})", 1721 car.toString(), car.getTotalLength(), car.getLocationName(), car.getTrackName(), 1722 car.getPickupTime(), car.getRouteDestination(), car.getTrain(), car.getLastTrain()); 1723 // cars being pulled by previous trains will free up track space 1724 if (car.getTrack() == this && car.getRouteDestination() != null && !car.getPickupTime().equals(Car.NONE)) { 1725 if (TrainCommon.convertStringTime(car.getPickupTime()) + 1726 Setup.getDwellTime() > trainDepartureTimeMinutes) { 1727 log.debug("Attempt to spot new car before pulls completed"); 1728 // car pulled after the train being built departs 1729 return Bundle.getMessage("lengthIssueCar", 1730 LENGTH, rsLength, Setup.getLengthUnit().toLowerCase(), trackSpaceAvalable, car.toString(), 1731 car.getTotalLength(), car.getTrain(), car.getPickupTime(), Setup.getDwellTime()); 1732 } 1733 trackSpaceAvalable = trackSpaceAvalable + car.getTotalLength(); 1734 log.debug("Car ({}) length {}, pull from ({}, {}) at {}", car.toString(), car.getTotalLength(), 1735 car.getLocationName(), car.getTrackName(), car.getPickupTime()); 1736 // cars pulled by the train being built also free up track space 1737 } else if (car.getTrack() == this && 1738 car.getRouteDestination() != null && 1739 car.getPickupTime().equals(Car.NONE) && 1740 car.getTrain() == train && 1741 car.getLastTrain() != train) { 1742 trackSpaceAvalable = trackSpaceAvalable + car.getTotalLength(); 1743 log.debug("Car ({}) length {}, pull from ({}, {})", car.toString(), car.getTotalLength(), 1744 car.getLocationName(), car.getTrackName()); 1745 } 1746 if (trackSpaceAvalable >= rsLength) { 1747 break; 1748 } 1749 } 1750 if (trackSpaceAvalable < rsLength) { 1751 // now check engines 1752 EngineManager engManager = InstanceManager.getDefault(EngineManager.class); 1753 List<Engine> engines = engManager.getList(this); 1754 // note that used can be larger than track length 1755 log.debug("Checking engines on track ({}) ", this.getName()); 1756 for (Engine eng : engines) { 1757 log.debug("Engine ({}) length {}, track ({}, {}) pick up time {}, to ({}), train ({}), last train ({})", 1758 eng.toString(), eng.getTotalLength(), eng.getLocationName(), eng.getTrackName(), 1759 eng.getPickupTime(), eng.getRouteDestination(), eng.getTrain(), eng.getLastTrain()); 1760 // engines being pulled by previous trains will free up track space 1761 if (eng.getTrack() == this && 1762 eng.getRouteDestination() != null && 1763 !eng.getPickupTime().equals(Engine.NONE)) { 1764 if (TrainCommon.convertStringTime(eng.getPickupTime()) + 1765 Setup.getDwellTime() > trainDepartureTimeMinutes) { 1766 log.debug("Attempt to spot new egine before pulls completed"); 1767 // engine pulled after the train being built departs 1768 return Bundle.getMessage("lengthIssueEng", 1769 LENGTH, rsLength, Setup.getLengthUnit().toLowerCase(), trackSpaceAvalable, 1770 eng.toString(), eng.getTotalLength(), eng.getTrain(), eng.getPickupTime(), 1771 Setup.getDwellTime()); 1772 } 1773 trackSpaceAvalable = trackSpaceAvalable + eng.getTotalLength(); 1774 log.debug("Engine ({}) length {}, pull from ({}, {}) at {}", eng.toString(), eng.getTotalLength(), 1775 eng.getLocationName(), eng.getTrackName(), eng.getPickupTime()); 1776 // engines pulled by the train being built also free up track space 1777 } else if (eng.getTrack() == this && 1778 eng.getRouteDestination() != null && 1779 eng.getPickupTime().equals(Car.NONE) && 1780 eng.getTrain() == train && 1781 eng.getLastTrain() != train) { 1782 trackSpaceAvalable = trackSpaceAvalable + eng.getTotalLength(); 1783 log.debug("Engine ({}) length {}, pull from ({}, {})", eng.toString(), eng.getTotalLength(), 1784 eng.getLocationName(), eng.getTrackName()); 1785 } 1786 if (trackSpaceAvalable >= rsLength) { 1787 break; 1788 } 1789 } 1790 } 1791 log.debug("Available space {} for track ({}, {}) rs ({}) length: {}", trackSpaceAvalable, 1792 this.getLocation().getName(), this.getName(), rs.toString(), rsLength); 1793 if (trackSpaceAvalable < rsLength) { 1794 return Bundle.getMessage("lengthIssue", 1795 LENGTH, rsLength, Setup.getLengthUnit().toLowerCase(), trackSpaceAvalable, getLength()); 1796 } 1797 return OKAY; 1798 } 1799 1800 /** 1801 * Available track space. Adjusted when a track is using the planned pickups 1802 * feature 1803 * 1804 * @return available track space 1805 */ 1806 public int getAvailableTrackSpace() { 1807 // calculate the available space 1808 int available = getLength() - 1809 (getUsedLength() * (IGNORE_100 - getIgnoreUsedLengthPercentage()) / IGNORE_100 + getReserved()); 1810 // could be less if track is overloaded 1811 int available3 = getLength() + 1812 (getLength() * getIgnoreUsedLengthPercentage() / IGNORE_100) - 1813 getUsedLength() - 1814 getReserved(); 1815 if (available3 < available) { 1816 available = available3; 1817 } 1818 // could be less based on track length 1819 int available2 = getLength() - getReservedLengthSetouts(); 1820 if (available2 < available) { 1821 available = available2; 1822 } 1823 return available; 1824 } 1825 1826 public int getMoves() { 1827 return _moves; 1828 } 1829 1830 public void setMoves(int moves) { 1831 int old = _moves; 1832 _moves = moves; 1833 setDirtyAndFirePropertyChange("trackMoves", old, moves); // NOI18N 1834 } 1835 1836 public void bumpMoves() { 1837 setMoves(getMoves() + 1); 1838 } 1839 1840 /** 1841 * Gets the blocking order for this track. Default is zero, in that case, 1842 * tracks are sorted by name. 1843 * 1844 * @return the blocking order 1845 */ 1846 public int getBlockingOrder() { 1847 return _blockingOrder; 1848 } 1849 1850 public void setBlockingOrder(int order) { 1851 int old = _blockingOrder; 1852 _blockingOrder = order; 1853 setDirtyAndFirePropertyChange(TRACK_BLOCKING_ORDER_CHANGED_PROPERTY, old, order); // NOI18N 1854 } 1855 1856 /** 1857 * Get the service order for this track. Yards and interchange have this 1858 * feature for cars. Staging has this feature for trains. 1859 * 1860 * @return Service order: Track.NORMAL, Track.FIFO, Track.LIFO 1861 */ 1862 public String getServiceOrder() { 1863 if (isSpur() || (isStaging() && getPool() == null)) { 1864 return NORMAL; 1865 } 1866 return _order; 1867 } 1868 1869 /** 1870 * Set the service order for this track. Only yards and interchange have 1871 * this feature. 1872 * 1873 * @param order Track.NORMAL, Track.FIFO, Track.LIFO 1874 */ 1875 public void setServiceOrder(String order) { 1876 String old = _order; 1877 _order = order; 1878 setDirtyAndFirePropertyChange(SERVICE_ORDER_CHANGED_PROPERTY, old, order); // NOI18N 1879 } 1880 1881 /** 1882 * Returns the name of the schedule. Note that this returns the schedule 1883 * name based on the schedule's id. A schedule's name can be modified by the 1884 * user. 1885 * 1886 * @return Schedule name 1887 */ 1888 public String getScheduleName() { 1889 if (getScheduleId().equals(NONE)) { 1890 return NONE; 1891 } 1892 Schedule schedule = getSchedule(); 1893 if (schedule == null) { 1894 log.error("No name schedule for id: {}", getScheduleId()); 1895 return NONE; 1896 } 1897 return schedule.getName(); 1898 } 1899 1900 public Schedule getSchedule() { 1901 if (getScheduleId().equals(NONE)) { 1902 return null; 1903 } 1904 Schedule schedule = InstanceManager.getDefault(ScheduleManager.class).getScheduleById(getScheduleId()); 1905 if (schedule == null) { 1906 log.error("No schedule for id: {}", getScheduleId()); 1907 } 1908 return schedule; 1909 } 1910 1911 public void setSchedule(Schedule schedule) { 1912 String scheduleId = NONE; 1913 if (schedule != null) { 1914 scheduleId = schedule.getId(); 1915 } 1916 setScheduleId(scheduleId); 1917 } 1918 1919 public String getScheduleId() { 1920 // Only spurs can have a schedule 1921 if (!isSpur()) { 1922 return NONE; 1923 } 1924 // old code only stored schedule name, so create id if needed. 1925 if (_scheduleId.equals(NONE) && !_scheduleName.equals(NONE)) { 1926 Schedule schedule = InstanceManager.getDefault(ScheduleManager.class).getScheduleByName(_scheduleName); 1927 if (schedule == null) { 1928 log.error("No schedule for name: {}", _scheduleName); 1929 } else { 1930 _scheduleId = schedule.getId(); 1931 } 1932 } 1933 return _scheduleId; 1934 } 1935 1936 public void setScheduleId(String id) { 1937 String old = _scheduleId; 1938 _scheduleId = id; 1939 if (!old.equals(id)) { 1940 Schedule schedule = InstanceManager.getDefault(ScheduleManager.class).getScheduleById(id); 1941 if (schedule == null) { 1942 _scheduleName = NONE; 1943 } else { 1944 // set the sequence to the first item in the list 1945 if (schedule.getItemsBySequenceList().size() > 0) { 1946 setScheduleItemId(schedule.getItemsBySequenceList().get(0).getId()); 1947 } 1948 setScheduleCount(0); 1949 } 1950 setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id); 1951 } 1952 } 1953 1954 /** 1955 * Recommend getCurrentScheduleItem() to get the current schedule item for 1956 * this track. Protects against user deleting a schedule item from the 1957 * schedule. 1958 * 1959 * @return schedule item id 1960 */ 1961 public String getScheduleItemId() { 1962 return _scheduleItemId; 1963 } 1964 1965 public void setScheduleItemId(String id) { 1966 log.debug("Set schedule item id ({}) for track ({})", id, getName()); 1967 String old = _scheduleItemId; 1968 _scheduleItemId = id; 1969 setDirtyAndFirePropertyChange(SCHEDULE_CHANGED_PROPERTY, old, id); 1970 } 1971 1972 /** 1973 * Get's the current schedule item for this track Protects against user 1974 * deleting an item in a shared schedule. Recommend using this versus 1975 * getScheduleItemId() as the id can be obsolete. 1976 * 1977 * @return The current ScheduleItem. 1978 */ 1979 public ScheduleItem getCurrentScheduleItem() { 1980 Schedule sch = getSchedule(); 1981 if (sch == null) { 1982 log.debug("Can not find schedule id: ({}) assigned to track ({})", getScheduleId(), getName()); 1983 return null; 1984 } 1985 ScheduleItem currentSi = sch.getItemById(getScheduleItemId()); 1986 if (currentSi == null && sch.getSize() > 0) { 1987 log.debug("Can not find schedule item id: ({}) for schedule ({})", getScheduleItemId(), getScheduleName()); 1988 // reset schedule 1989 setScheduleItemId((sch.getItemsBySequenceList().get(0)).getId()); 1990 currentSi = sch.getItemById(getScheduleItemId()); 1991 } 1992 return currentSi; 1993 } 1994 1995 /** 1996 * Increments the schedule count if there's a schedule and the schedule is 1997 * running in sequential mode. Resets the schedule count if the maximum is 1998 * reached and then goes to the next item in the schedule's list. 1999 */ 2000 public void bumpSchedule() { 2001 if (getSchedule() != null && getScheduleMode() == SEQUENTIAL) { 2002 // bump the schedule count 2003 setScheduleCount(getScheduleCount() + 1); 2004 if (getScheduleCount() >= getCurrentScheduleItem().getCount()) { 2005 setScheduleCount(0); 2006 // go to the next item in the schedule 2007 getNextScheduleItem(); 2008 } 2009 } 2010 } 2011 2012 public ScheduleItem getNextScheduleItem() { 2013 Schedule sch = getSchedule(); 2014 if (sch == null) { 2015 log.warn("Can not find schedule ({}) assigned to track ({})", getScheduleId(), getName()); 2016 return null; 2017 } 2018 List<ScheduleItem> items = sch.getItemsBySequenceList(); 2019 ScheduleItem nextSi = null; 2020 for (int i = 0; i < items.size(); i++) { 2021 nextSi = items.get(i); 2022 if (getCurrentScheduleItem() == nextSi) { 2023 if (++i < items.size()) { 2024 nextSi = items.get(i); 2025 } else { 2026 nextSi = items.get(0); 2027 } 2028 setScheduleItemId(nextSi.getId()); 2029 break; 2030 } 2031 } 2032 return nextSi; 2033 } 2034 2035 /** 2036 * Returns how many times the current schedule item has been accessed. 2037 * 2038 * @return count 2039 */ 2040 public int getScheduleCount() { 2041 return _scheduleCount; 2042 } 2043 2044 public void setScheduleCount(int count) { 2045 int old = _scheduleCount; 2046 _scheduleCount = count; 2047 setDirtyAndFirePropertyChange(SCHEDULE_CHANGED_PROPERTY, old, count); 2048 } 2049 2050 /** 2051 * Check to see if schedule is valid for the track at this location. 2052 * 2053 * @return SCHEDULE_OKAY if schedule okay, otherwise an error message. 2054 */ 2055 public String checkScheduleValid() { 2056 if (getScheduleId().equals(NONE)) { 2057 return Schedule.SCHEDULE_OKAY; 2058 } 2059 Schedule schedule = getSchedule(); 2060 if (schedule == null) { 2061 return Bundle.getMessage("CanNotFindSchedule", getScheduleId()); 2062 } 2063 return schedule.checkScheduleValid(this); 2064 } 2065 2066 /** 2067 * Checks to see if car can be placed on this spur using this schedule. 2068 * Returns OKAY if the schedule can service the car. 2069 * 2070 * @param car The Car to be tested. 2071 * @return Track.OKAY track.CUSTOM track.SCHEDULE 2072 */ 2073 public String checkSchedule(Car car) { 2074 // does car already have this destination? 2075 if (car.getDestinationTrack() == this) { 2076 return OKAY; 2077 } 2078 // only spurs can have a schedule 2079 if (!isSpur()) { 2080 return OKAY; 2081 } 2082 if (getScheduleId().equals(NONE)) { 2083 // does car have a custom load? 2084 if (car.getLoadName().equals(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()) || 2085 car.getLoadName().equals(InstanceManager.getDefault(CarLoads.class).getDefaultLoadName())) { 2086 return OKAY; // no 2087 } 2088 return Bundle.getMessage("carHasA", CUSTOM, LOAD, car.getLoadName()); 2089 } 2090 log.debug("Track ({}) has schedule ({}) mode {} ({})", getName(), getScheduleName(), getScheduleMode(), 2091 getScheduleModeName()); // NOI18N 2092 2093 ScheduleItem si = getCurrentScheduleItem(); 2094 // code check, should never be null 2095 if (si == null) { 2096 log.error("Could not find schedule item id: ({}) for schedule ({})", getScheduleItemId(), 2097 getScheduleName()); // NOI18N 2098 return SCHEDULE + " ERROR"; // NOI18N 2099 } 2100 if (getScheduleMode() == SEQUENTIAL) { 2101 return getSchedule().checkScheduleItem(si, car, this, true); 2102 } 2103 // schedule in is match mode search entire schedule for a match 2104 return getSchedule().searchSchedule(car, this); 2105 } 2106 2107 /** 2108 * Check to see if track has schedule and if it does will schedule the next 2109 * item in the list. Loads the car with the schedule id. 2110 * 2111 * @param car The Car to be modified. 2112 * @return Track.OKAY or Track.SCHEDULE 2113 */ 2114 public String scheduleNext(Car car) { 2115 // check for schedule, only spurs can have a schedule 2116 if (getSchedule() == null) { 2117 return OKAY; 2118 } 2119 // is car part of a kernel? 2120 if (car.getKernel() != null && !car.isLead()) { 2121 log.debug("Car ({}) is part of kernel ({}) not lead", car.toString(), car.getKernelName()); 2122 return OKAY; 2123 } 2124 // has the car already been assigned to this destination? 2125 if (!car.getScheduleItemId().equals(Car.NONE)) { 2126 log.debug("Car ({}) has schedule item id ({})", car.toString(), car.getScheduleItemId()); 2127 ScheduleItem si = car.getScheduleItem(this); 2128 if (si != null) { 2129 // bump hit count for this schedule item 2130 si.setHits(si.getHits() + 1); 2131 return OKAY; 2132 } 2133 log.debug("Schedule id ({}) not valid for track ({})", car.getScheduleItemId(), getName()); 2134 car.setScheduleItemId(Car.NONE); 2135 } 2136 // search schedule if match mode 2137 if (getScheduleMode() == MATCH && !getSchedule().searchSchedule(car, this).equals(OKAY)) { 2138 return Bundle.getMessage("matchMessage", SCHEDULE, getScheduleName(), 2139 getSchedule().hasRandomItem() ? Bundle.getMessage("Random") : ""); 2140 } 2141 // found a match or in sequential mode 2142 ScheduleItem currentSi = getCurrentScheduleItem(); 2143 log.debug("Destination track ({}) has schedule ({}) item id ({}) mode: {} ({})", getName(), getScheduleName(), 2144 getScheduleItemId(), getScheduleMode(), getScheduleModeName()); // NOI18N 2145 if (currentSi != null && 2146 getSchedule().checkScheduleItem(currentSi, car, this, false).equals(OKAY)) { 2147 car.setScheduleItemId(currentSi.getId()); 2148 // bump hit count for this schedule item 2149 currentSi.setHits(currentSi.getHits() + 1); 2150 // bump schedule 2151 bumpSchedule(); 2152 } else if (currentSi != null) { 2153 // build return failure message 2154 String scheduleName = ""; 2155 String currentTrainScheduleName = ""; 2156 TrainSchedule sch = InstanceManager.getDefault(TrainScheduleManager.class) 2157 .getScheduleById(InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId()); 2158 if (sch != null) { 2159 scheduleName = sch.getName(); 2160 } 2161 sch = InstanceManager.getDefault(TrainScheduleManager.class) 2162 .getScheduleById(currentSi.getSetoutTrainScheduleId()); 2163 if (sch != null) { 2164 currentTrainScheduleName = sch.getName(); 2165 } 2166 return Bundle.getMessage("sequentialMessage", SCHEDULE, getScheduleName(), getScheduleModeName(), 2167 car.toString(), car.getTypeName(), scheduleName, car.getRoadName(), car.getLoadName(), 2168 currentSi.getTypeName(), currentTrainScheduleName, currentSi.getRoadName(), 2169 currentSi.getReceiveLoadName()); 2170 } else { 2171 log.error("ERROR Track {} current schedule item is null!", getName()); 2172 return SCHEDULE + " ERROR Track " + getName() + " current schedule item is null!"; // NOI18N 2173 } 2174 return OKAY; 2175 } 2176 2177 public static final String TRAIN_SCHEDULE = "trainSchedule"; // NOI18N 2178 public static final String ALL = "all"; // NOI18N 2179 2180 public boolean checkScheduleAttribute(String attribute, String carType, Car car) { 2181 Schedule schedule = getSchedule(); 2182 if (schedule == null) { 2183 return true; 2184 } 2185 // if car is already placed at track, don't check car type and load 2186 if (car != null && car.getTrack() == this) { 2187 return true; 2188 } 2189 return schedule.checkScheduleAttribute(attribute, carType, car); 2190 } 2191 2192 /** 2193 * Enable changing the car generic load state when car arrives at this 2194 * track. 2195 * 2196 * @param enable when true, swap generic car load state 2197 */ 2198 public void setLoadSwapEnabled(boolean enable) { 2199 boolean old = isLoadSwapEnabled(); 2200 if (enable) { 2201 _loadOptions = _loadOptions | SWAP_GENERIC_LOADS; 2202 } else { 2203 _loadOptions = _loadOptions & 0xFFFF - SWAP_GENERIC_LOADS; 2204 } 2205 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2206 } 2207 2208 public boolean isLoadSwapEnabled() { 2209 return (0 != (_loadOptions & SWAP_GENERIC_LOADS)); 2210 } 2211 2212 /** 2213 * Enable setting the car generic load state to empty when car arrives at 2214 * this track. 2215 * 2216 * @param enable when true, set generic car load to empty 2217 */ 2218 public void setLoadEmptyEnabled(boolean enable) { 2219 boolean old = isLoadEmptyEnabled(); 2220 if (enable) { 2221 _loadOptions = _loadOptions | EMPTY_GENERIC_LOADS; 2222 } else { 2223 _loadOptions = _loadOptions & 0xFFFF - EMPTY_GENERIC_LOADS; 2224 } 2225 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2226 } 2227 2228 public boolean isLoadEmptyEnabled() { 2229 return (0 != (_loadOptions & EMPTY_GENERIC_LOADS)); 2230 } 2231 2232 /** 2233 * When enabled, remove Scheduled car loads. 2234 * 2235 * @param enable when true, remove Scheduled loads from cars 2236 */ 2237 public void setRemoveCustomLoadsEnabled(boolean enable) { 2238 boolean old = isRemoveCustomLoadsEnabled(); 2239 if (enable) { 2240 _loadOptions = _loadOptions | EMPTY_CUSTOM_LOADS; 2241 } else { 2242 _loadOptions = _loadOptions & 0xFFFF - EMPTY_CUSTOM_LOADS; 2243 } 2244 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2245 } 2246 2247 public boolean isRemoveCustomLoadsEnabled() { 2248 return (0 != (_loadOptions & EMPTY_CUSTOM_LOADS)); 2249 } 2250 2251 /** 2252 * When enabled, add custom car loads if there's a demand. 2253 * 2254 * @param enable when true, add custom loads to cars 2255 */ 2256 public void setAddCustomLoadsEnabled(boolean enable) { 2257 boolean old = isAddCustomLoadsEnabled(); 2258 if (enable) { 2259 _loadOptions = _loadOptions | GENERATE_CUSTOM_LOADS; 2260 } else { 2261 _loadOptions = _loadOptions & 0xFFFF - GENERATE_CUSTOM_LOADS; 2262 } 2263 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2264 } 2265 2266 public boolean isAddCustomLoadsEnabled() { 2267 return (0 != (_loadOptions & GENERATE_CUSTOM_LOADS)); 2268 } 2269 2270 /** 2271 * When enabled, add custom car loads if there's a demand by any 2272 * spur/industry. 2273 * 2274 * @param enable when true, add custom loads to cars 2275 */ 2276 public void setAddCustomLoadsAnySpurEnabled(boolean enable) { 2277 boolean old = isAddCustomLoadsAnySpurEnabled(); 2278 if (enable) { 2279 _loadOptions = _loadOptions | GENERATE_CUSTOM_LOADS_ANY_SPUR; 2280 } else { 2281 _loadOptions = _loadOptions & 0xFFFF - GENERATE_CUSTOM_LOADS_ANY_SPUR; 2282 } 2283 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2284 } 2285 2286 public boolean isAddCustomLoadsAnySpurEnabled() { 2287 return (0 != (_loadOptions & GENERATE_CUSTOM_LOADS_ANY_SPUR)); 2288 } 2289 2290 /** 2291 * When enabled, add custom car loads to cars in staging for new 2292 * destinations that are staging. 2293 * 2294 * @param enable when true, add custom load to car 2295 */ 2296 public void setAddCustomLoadsAnyStagingTrackEnabled(boolean enable) { 2297 boolean old = isAddCustomLoadsAnyStagingTrackEnabled(); 2298 if (enable) { 2299 _loadOptions = _loadOptions | GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK; 2300 } else { 2301 _loadOptions = _loadOptions & 0xFFFF - GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK; 2302 } 2303 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2304 } 2305 2306 public boolean isAddCustomLoadsAnyStagingTrackEnabled() { 2307 return (0 != (_loadOptions & GENERATE_CUSTOM_LOADS_ANY_STAGING_TRACK)); 2308 } 2309 2310 public boolean isModifyLoadsEnabled() { 2311 return isLoadEmptyEnabled() || 2312 isLoadSwapEnabled() || 2313 isRemoveCustomLoadsEnabled() || 2314 isAddCustomLoadsAnySpurEnabled() || 2315 isAddCustomLoadsAnyStagingTrackEnabled() || 2316 isAddCustomLoadsEnabled(); 2317 } 2318 2319 public void setDisableLoadChangeEnabled(boolean enable) { 2320 boolean old = isDisableLoadChangeEnabled(); 2321 if (enable) { 2322 _loadOptions = _loadOptions | DISABLE_LOAD_CHANGE; 2323 } else { 2324 _loadOptions = _loadOptions & 0xFFFF - DISABLE_LOAD_CHANGE; 2325 } 2326 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2327 } 2328 2329 public boolean isDisableLoadChangeEnabled() { 2330 return (0 != (_loadOptions & DISABLE_LOAD_CHANGE)); 2331 } 2332 2333 public void setQuickServiceEnabled(boolean enable) { 2334 boolean old = isQuickServiceEnabled(); 2335 if (enable) { 2336 _loadOptions = _loadOptions | QUICK_SERVICE; 2337 } else { 2338 _loadOptions = _loadOptions & 0xFFFF - QUICK_SERVICE; 2339 } 2340 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2341 } 2342 2343 public boolean isQuickServiceEnabled() { 2344 return 0 != (_loadOptions & QUICK_SERVICE); 2345 } 2346 2347 public void setBlockCarsEnabled(boolean enable) { 2348 boolean old = isBlockCarsEnabled(); 2349 if (enable) { 2350 _blockOptions = _blockOptions | BLOCK_CARS; 2351 } else { 2352 _blockOptions = _blockOptions & 0xFFFF - BLOCK_CARS; 2353 } 2354 setDirtyAndFirePropertyChange(LOAD_OPTIONS_CHANGED_PROPERTY, old, enable); 2355 } 2356 2357 /** 2358 * When enabled block cars from staging. 2359 * 2360 * @return true if blocking is enabled. 2361 */ 2362 public boolean isBlockCarsEnabled() { 2363 if (isStaging()) { 2364 return (0 != (_blockOptions & BLOCK_CARS)); 2365 } 2366 return false; 2367 } 2368 2369 public void setPool(Pool pool) { 2370 Pool old = _pool; 2371 _pool = pool; 2372 if (old != pool) { 2373 if (old != null) { 2374 old.remove(this); 2375 } 2376 if (_pool != null) { 2377 _pool.add(this); 2378 } 2379 setDirtyAndFirePropertyChange(POOL_CHANGED_PROPERTY, old, pool); 2380 } 2381 } 2382 2383 public Pool getPool() { 2384 return _pool; 2385 } 2386 2387 public String getPoolName() { 2388 if (getPool() != null) { 2389 return getPool().getName(); 2390 } 2391 return NONE; 2392 } 2393 2394 public int getDestinationListSize() { 2395 return _destinationIdList.size(); 2396 } 2397 2398 /** 2399 * adds a location to the list of acceptable destinations for this track. 2400 * 2401 * @param destination location that is acceptable 2402 */ 2403 public void addDestination(Location destination) { 2404 if (!_destinationIdList.contains(destination.getId())) { 2405 _destinationIdList.add(destination.getId()); 2406 setDirtyAndFirePropertyChange(DESTINATIONS_CHANGED_PROPERTY, null, destination.getName()); // NOI18N 2407 } 2408 } 2409 2410 public void deleteDestination(Location destination) { 2411 if (_destinationIdList.remove(destination.getId())) { 2412 setDirtyAndFirePropertyChange(DESTINATIONS_CHANGED_PROPERTY, destination.getName(), null); // NOI18N 2413 } 2414 } 2415 2416 /** 2417 * Returns true if destination is valid from this track. 2418 * 2419 * @param destination The Location to be checked. 2420 * @return true if track services the destination 2421 */ 2422 public boolean isDestinationAccepted(Location destination) { 2423 if (getDestinationOption().equals(ALL_DESTINATIONS) || destination == null) { 2424 return true; 2425 } 2426 return _destinationIdList.contains(destination.getId()); 2427 } 2428 2429 public void setDestinationIds(String[] ids) { 2430 for (String id : ids) { 2431 _destinationIdList.add(id); 2432 } 2433 } 2434 2435 public String[] getDestinationIds() { 2436 String[] ids = _destinationIdList.toArray(new String[0]); 2437 return ids; 2438 } 2439 2440 /** 2441 * Sets the destination option for this track. The three options are: 2442 * <p> 2443 * ALL_DESTINATIONS which means this track services all destinations, the 2444 * default. 2445 * <p> 2446 * INCLUDE_DESTINATIONS which means this track services only certain 2447 * destinations. 2448 * <p> 2449 * EXCLUDE_DESTINATIONS which means this track does not service certain 2450 * destinations. 2451 * 2452 * @param option Track.ALL_DESTINATIONS, Track.INCLUDE_DESTINATIONS, or 2453 * Track.EXCLUDE_DESTINATIONS 2454 */ 2455 public void setDestinationOption(String option) { 2456 String old = _destinationOption; 2457 _destinationOption = option; 2458 if (!option.equals(old)) { 2459 setDirtyAndFirePropertyChange(DESTINATION_OPTIONS_CHANGED_PROPERTY, old, option); // NOI18N 2460 } 2461 } 2462 2463 /** 2464 * Get destination option for interchange or staging track 2465 * 2466 * @return option 2467 */ 2468 public String getDestinationOption() { 2469 if (isInterchange() || isStaging()) { 2470 return _destinationOption; 2471 } 2472 return ALL_DESTINATIONS; 2473 } 2474 2475 public void setOnlyCarsWithFinalDestinationEnabled(boolean enable) { 2476 boolean old = _onlyCarsWithFD; 2477 _onlyCarsWithFD = enable; 2478 setDirtyAndFirePropertyChange(ROUTED_CHANGED_PROPERTY, old, enable); 2479 } 2480 2481 /** 2482 * When true the track will only accept cars that have a final destination 2483 * that can be serviced by the track. See acceptsDestination(Location). 2484 * 2485 * @return false if any car spotted, true if only cars with a FD. 2486 */ 2487 public boolean isOnlyCarsWithFinalDestinationEnabled() { 2488 if (isInterchange() || isStaging()) { 2489 return _onlyCarsWithFD; 2490 } 2491 return false; 2492 } 2493 2494 /** 2495 * Used to determine if track has been assigned as an alternate 2496 * 2497 * @return true if track is an alternate 2498 */ 2499 public boolean isAlternate() { 2500 for (Track track : getLocation().getTracksList()) { 2501 if (track.getAlternateTrack() == this) { 2502 return true; 2503 } 2504 } 2505 return false; 2506 } 2507 2508 public void dispose() { 2509 // change the name in case object is still in use, for example 2510 // ScheduleItem.java 2511 setName(Bundle.getMessage("NotValid", getName())); 2512 setPool(null); 2513 setDirtyAndFirePropertyChange(DISPOSE_CHANGED_PROPERTY, null, DISPOSE_CHANGED_PROPERTY); 2514 } 2515 2516 /** 2517 * Construct this Entry from XML. This member has to remain synchronized 2518 * with the detailed DTD in operations-location.dtd. 2519 * 2520 * @param e Consist XML element 2521 * @param location The Location loading this track. 2522 */ 2523 public Track(Element e, Location location) { 2524 _location = location; 2525 Attribute a; 2526 if ((a = e.getAttribute(Xml.ID)) != null) { 2527 _id = a.getValue(); 2528 } else { 2529 log.warn("no id attribute in track element when reading operations"); 2530 } 2531 if ((a = e.getAttribute(Xml.NAME)) != null) { 2532 _name = a.getValue(); 2533 } 2534 if ((a = e.getAttribute(Xml.TRACK_TYPE)) != null) { 2535 _trackType = a.getValue(); 2536 2537 // old way of storing track type before 4.21.1 2538 } else if ((a = e.getAttribute(Xml.LOC_TYPE)) != null) { 2539 if (a.getValue().equals(SIDING)) { 2540 _trackType = SPUR; 2541 } else { 2542 _trackType = a.getValue(); 2543 } 2544 } 2545 2546 if ((a = e.getAttribute(Xml.LENGTH)) != null) { 2547 try { 2548 _length = Integer.parseInt(a.getValue()); 2549 } catch (NumberFormatException nfe) { 2550 log.error("Track length isn't a vaild number for track {}", getName()); 2551 } 2552 } 2553 if ((a = e.getAttribute(Xml.MOVES)) != null) { 2554 try { 2555 _moves = Integer.parseInt(a.getValue()); 2556 } catch (NumberFormatException nfe) { 2557 log.error("Track moves isn't a vaild number for track {}", getName()); 2558 } 2559 2560 } 2561 if ((a = e.getAttribute(Xml.TRACK_PRIORITY)) != null) { 2562 _trackPriority = a.getValue(); 2563 } 2564 if ((a = e.getAttribute(Xml.BLOCKING_ORDER)) != null) { 2565 try { 2566 _blockingOrder = Integer.parseInt(a.getValue()); 2567 } catch (NumberFormatException nfe) { 2568 log.error("Track blocking order isn't a vaild number for track {}", getName()); 2569 } 2570 } 2571 if ((a = e.getAttribute(Xml.DIR)) != null) { 2572 try { 2573 _trainDir = Integer.parseInt(a.getValue()); 2574 } catch (NumberFormatException nfe) { 2575 log.error("Track service direction isn't a vaild number for track {}", getName()); 2576 } 2577 } 2578 // old way of reading track comment, see comments below for new format 2579 if ((a = e.getAttribute(Xml.COMMENT)) != null) { 2580 _comment = a.getValue(); 2581 } 2582 // new way of reading car types using elements added in 3.3.1 2583 if (e.getChild(Xml.TYPES) != null) { 2584 List<Element> carTypes = e.getChild(Xml.TYPES).getChildren(Xml.CAR_TYPE); 2585 String[] types = new String[carTypes.size()]; 2586 for (int i = 0; i < carTypes.size(); i++) { 2587 Element type = carTypes.get(i); 2588 if ((a = type.getAttribute(Xml.NAME)) != null) { 2589 types[i] = a.getValue(); 2590 } 2591 } 2592 setTypeNames(types); 2593 List<Element> locoTypes = e.getChild(Xml.TYPES).getChildren(Xml.LOCO_TYPE); 2594 types = new String[locoTypes.size()]; 2595 for (int i = 0; i < locoTypes.size(); i++) { 2596 Element type = locoTypes.get(i); 2597 if ((a = type.getAttribute(Xml.NAME)) != null) { 2598 types[i] = a.getValue(); 2599 } 2600 } 2601 setTypeNames(types); 2602 } // old way of reading car types up to version 3.2 2603 else if ((a = e.getAttribute(Xml.CAR_TYPES)) != null) { 2604 String names = a.getValue(); 2605 String[] types = names.split("%%"); // NOI18N 2606 setTypeNames(types); 2607 } 2608 if ((a = e.getAttribute(Xml.CAR_LOAD_OPTION)) != null) { 2609 _loadOption = a.getValue(); 2610 } 2611 // new way of reading car loads using elements 2612 if (e.getChild(Xml.CAR_LOADS) != null) { 2613 List<Element> carLoads = e.getChild(Xml.CAR_LOADS).getChildren(Xml.CAR_LOAD); 2614 String[] loads = new String[carLoads.size()]; 2615 for (int i = 0; i < carLoads.size(); i++) { 2616 Element load = carLoads.get(i); 2617 if ((a = load.getAttribute(Xml.NAME)) != null) { 2618 loads[i] = a.getValue(); 2619 } 2620 } 2621 setLoadNames(loads); 2622 } // old way of reading car loads up to version 3.2 2623 else if ((a = e.getAttribute(Xml.CAR_LOADS)) != null) { 2624 String names = a.getValue(); 2625 String[] loads = names.split("%%"); // NOI18N 2626 log.debug("Track ({}) {} car loads: {}", getName(), getLoadOption(), names); 2627 setLoadNames(loads); 2628 } 2629 if ((a = e.getAttribute(Xml.CAR_SHIP_LOAD_OPTION)) != null) { 2630 _shipLoadOption = a.getValue(); 2631 } 2632 // new way of reading car loads using elements 2633 if (e.getChild(Xml.CAR_SHIP_LOADS) != null) { 2634 List<Element> carLoads = e.getChild(Xml.CAR_SHIP_LOADS).getChildren(Xml.CAR_LOAD); 2635 String[] loads = new String[carLoads.size()]; 2636 for (int i = 0; i < carLoads.size(); i++) { 2637 Element load = carLoads.get(i); 2638 if ((a = load.getAttribute(Xml.NAME)) != null) { 2639 loads[i] = a.getValue(); 2640 } 2641 } 2642 setShipLoadNames(loads); 2643 } 2644 // new way of reading drop ids using elements 2645 if (e.getChild(Xml.DROP_IDS) != null) { 2646 List<Element> dropIds = e.getChild(Xml.DROP_IDS).getChildren(Xml.DROP_ID); 2647 String[] ids = new String[dropIds.size()]; 2648 for (int i = 0; i < dropIds.size(); i++) { 2649 Element dropId = dropIds.get(i); 2650 if ((a = dropId.getAttribute(Xml.ID)) != null) { 2651 ids[i] = a.getValue(); 2652 } 2653 } 2654 setDropIds(ids); 2655 } // old way of reading drop ids up to version 3.2 2656 else if ((a = e.getAttribute(Xml.DROP_IDS)) != null) { 2657 String names = a.getValue(); 2658 String[] ids = names.split("%%"); // NOI18N 2659 setDropIds(ids); 2660 } 2661 if ((a = e.getAttribute(Xml.DROP_OPTION)) != null) { 2662 _dropOption = a.getValue(); 2663 } 2664 2665 // new way of reading pick up ids using elements 2666 if (e.getChild(Xml.PICKUP_IDS) != null) { 2667 List<Element> pickupIds = e.getChild(Xml.PICKUP_IDS).getChildren(Xml.PICKUP_ID); 2668 String[] ids = new String[pickupIds.size()]; 2669 for (int i = 0; i < pickupIds.size(); i++) { 2670 Element pickupId = pickupIds.get(i); 2671 if ((a = pickupId.getAttribute(Xml.ID)) != null) { 2672 ids[i] = a.getValue(); 2673 } 2674 } 2675 setPickupIds(ids); 2676 } // old way of reading pick up ids up to version 3.2 2677 else if ((a = e.getAttribute(Xml.PICKUP_IDS)) != null) { 2678 String names = a.getValue(); 2679 String[] ids = names.split("%%"); // NOI18N 2680 setPickupIds(ids); 2681 } 2682 if ((a = e.getAttribute(Xml.PICKUP_OPTION)) != null) { 2683 _pickupOption = a.getValue(); 2684 } 2685 2686 // new way of reading car roads using elements 2687 if (e.getChild(Xml.CAR_ROADS) != null) { 2688 List<Element> carRoads = e.getChild(Xml.CAR_ROADS).getChildren(Xml.CAR_ROAD); 2689 String[] roads = new String[carRoads.size()]; 2690 for (int i = 0; i < carRoads.size(); i++) { 2691 Element road = carRoads.get(i); 2692 if ((a = road.getAttribute(Xml.NAME)) != null) { 2693 roads[i] = a.getValue(); 2694 } 2695 } 2696 setRoadNames(roads); 2697 } // old way of reading car roads up to version 3.2 2698 else if ((a = e.getAttribute(Xml.CAR_ROADS)) != null) { 2699 String names = a.getValue(); 2700 String[] roads = names.split("%%"); // NOI18N 2701 setRoadNames(roads); 2702 } 2703 if ((a = e.getAttribute(Xml.CAR_ROAD_OPTION)) != null) { 2704 _roadOption = a.getValue(); 2705 } else if ((a = e.getAttribute(Xml.CAR_ROAD_OPERATION)) != null) { 2706 _roadOption = a.getValue(); 2707 } 2708 2709 if ((a = e.getAttribute(Xml.SCHEDULE)) != null) { 2710 _scheduleName = a.getValue(); 2711 } 2712 if ((a = e.getAttribute(Xml.SCHEDULE_ID)) != null) { 2713 _scheduleId = a.getValue(); 2714 } 2715 if ((a = e.getAttribute(Xml.ITEM_ID)) != null) { 2716 _scheduleItemId = a.getValue(); 2717 } 2718 if ((a = e.getAttribute(Xml.ITEM_COUNT)) != null) { 2719 try { 2720 _scheduleCount = Integer.parseInt(a.getValue()); 2721 } catch (NumberFormatException nfe) { 2722 log.error("Schedule count isn't a vaild number for track {}", getName()); 2723 } 2724 } 2725 if ((a = e.getAttribute(Xml.FACTOR)) != null) { 2726 try { 2727 _reservationFactor = Integer.parseInt(a.getValue()); 2728 } catch (NumberFormatException nfe) { 2729 log.error("Reservation factor isn't a vaild number for track {}", getName()); 2730 } 2731 } 2732 if ((a = e.getAttribute(Xml.SCHEDULE_MODE)) != null) { 2733 try { 2734 _mode = Integer.parseInt(a.getValue()); 2735 } catch (NumberFormatException nfe) { 2736 log.error("Schedule mode isn't a vaild number for track {}", getName()); 2737 } 2738 } 2739 if ((a = e.getAttribute(Xml.HOLD_CARS_CUSTOM)) != null) { 2740 setHoldCarsWithCustomLoadsEnabled(a.getValue().equals(Xml.TRUE)); 2741 } 2742 if ((a = e.getAttribute(Xml.ONLY_CARS_WITH_FD)) != null) { 2743 setOnlyCarsWithFinalDestinationEnabled(a.getValue().equals(Xml.TRUE)); 2744 } 2745 2746 if ((a = e.getAttribute(Xml.ALTERNATIVE)) != null) { 2747 _alternateTrackId = a.getValue(); 2748 } 2749 2750 if ((a = e.getAttribute(Xml.LOAD_OPTIONS)) != null) { 2751 try { 2752 _loadOptions = Integer.parseInt(a.getValue()); 2753 } catch (NumberFormatException nfe) { 2754 log.error("Load options isn't a vaild number for track {}", getName()); 2755 } 2756 } 2757 if ((a = e.getAttribute(Xml.BLOCK_OPTIONS)) != null) { 2758 try { 2759 _blockOptions = Integer.parseInt(a.getValue()); 2760 } catch (NumberFormatException nfe) { 2761 log.error("Block options isn't a vaild number for track {}", getName()); 2762 } 2763 } 2764 if ((a = e.getAttribute(Xml.ORDER)) != null) { 2765 _order = a.getValue(); 2766 } 2767 if ((a = e.getAttribute(Xml.POOL)) != null) { 2768 setPool(getLocation().addPool(a.getValue())); 2769 if ((a = e.getAttribute(Xml.MIN_LENGTH)) != null) { 2770 try { 2771 _minimumLength = Integer.parseInt(a.getValue()); 2772 } catch (NumberFormatException nfe) { 2773 log.error("Minimum pool length isn't a vaild number for track {}", getName()); 2774 } 2775 } 2776 if ((a = e.getAttribute(Xml.MAX_LENGTH)) != null) { 2777 try { 2778 _maximumLength = Integer.parseInt(a.getValue()); 2779 } catch (NumberFormatException nfe) { 2780 log.error("Maximum pool length isn't a vaild number for track {}", getName()); 2781 } 2782 } 2783 } 2784 if ((a = e.getAttribute(Xml.IGNORE_USED_PERCENTAGE)) != null) { 2785 try { 2786 _ignoreUsedLengthPercentage = Integer.parseInt(a.getValue()); 2787 } catch (NumberFormatException nfe) { 2788 log.error("Ignore used percentage isn't a vaild number for track {}", getName()); 2789 } 2790 } 2791 if ((a = e.getAttribute(Xml.TRACK_DESTINATION_OPTION)) != null) { 2792 _destinationOption = a.getValue(); 2793 } 2794 if (e.getChild(Xml.DESTINATIONS) != null) { 2795 List<Element> eDestinations = e.getChild(Xml.DESTINATIONS).getChildren(Xml.DESTINATION); 2796 for (Element eDestination : eDestinations) { 2797 if ((a = eDestination.getAttribute(Xml.ID)) != null) { 2798 _destinationIdList.add(a.getValue()); 2799 } 2800 } 2801 } 2802 2803 if (e.getChild(Xml.COMMENTS) != null) { 2804 if (e.getChild(Xml.COMMENTS).getChild(Xml.TRACK) != null && 2805 (a = e.getChild(Xml.COMMENTS).getChild(Xml.TRACK).getAttribute(Xml.COMMENT)) != null) { 2806 _comment = a.getValue(); 2807 } 2808 if (e.getChild(Xml.COMMENTS).getChild(Xml.BOTH) != null && 2809 (a = e.getChild(Xml.COMMENTS).getChild(Xml.BOTH).getAttribute(Xml.COMMENT)) != null) { 2810 _commentBoth = a.getValue(); 2811 } 2812 if (e.getChild(Xml.COMMENTS).getChild(Xml.PICKUP) != null && 2813 (a = e.getChild(Xml.COMMENTS).getChild(Xml.PICKUP).getAttribute(Xml.COMMENT)) != null) { 2814 _commentPickup = a.getValue(); 2815 } 2816 if (e.getChild(Xml.COMMENTS).getChild(Xml.SETOUT) != null && 2817 (a = e.getChild(Xml.COMMENTS).getChild(Xml.SETOUT).getAttribute(Xml.COMMENT)) != null) { 2818 _commentSetout = a.getValue(); 2819 } 2820 if (e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_MANIFEST) != null && 2821 (a = e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_MANIFEST).getAttribute(Xml.COMMENT)) != null) { 2822 _printCommentManifest = a.getValue().equals(Xml.TRUE); 2823 } 2824 if (e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_SWITCH_LISTS) != null && 2825 (a = e.getChild(Xml.COMMENTS).getChild(Xml.PRINT_SWITCH_LISTS).getAttribute(Xml.COMMENT)) != null) { 2826 _printCommentSwitchList = a.getValue().equals(Xml.TRUE); 2827 } 2828 } 2829 2830 if ((a = e.getAttribute(Xml.READER)) != null) { 2831 try { 2832 Reporter r = jmri.InstanceManager.getDefault(jmri.ReporterManager.class).provideReporter(a.getValue()); 2833 _reader = r; 2834 } catch (IllegalArgumentException ex) { 2835 log.warn("Not able to find reader: {} for location ({})", a.getValue(), getName()); 2836 } 2837 } 2838 } 2839 2840 /** 2841 * Create an XML element to represent this Entry. This member has to remain 2842 * synchronized with the detailed DTD in operations-location.dtd. 2843 * 2844 * @return Contents in a JDOM Element 2845 */ 2846 public Element store() { 2847 Element e = new Element(Xml.TRACK); 2848 e.setAttribute(Xml.ID, getId()); 2849 e.setAttribute(Xml.NAME, getName()); 2850 e.setAttribute(Xml.TRACK_TYPE, getTrackType()); 2851 e.setAttribute(Xml.DIR, Integer.toString(getTrainDirections())); 2852 e.setAttribute(Xml.LENGTH, Integer.toString(getLength())); 2853 e.setAttribute(Xml.MOVES, Integer.toString(getMoves() - getDropRS())); 2854 if (!getTrackPriority().equals(PRIORITY_NORMAL)) { 2855 e.setAttribute(Xml.TRACK_PRIORITY, getTrackPriority()); 2856 } 2857 if (getBlockingOrder() != 0) { 2858 e.setAttribute(Xml.BLOCKING_ORDER, Integer.toString(getBlockingOrder())); 2859 } 2860 // build list of car types for this track 2861 String[] types = getTypeNames(); 2862 // new way of saving car types using elements 2863 Element eTypes = new Element(Xml.TYPES); 2864 for (String type : types) { 2865 // don't save types that have been deleted by user 2866 if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) { 2867 Element eType = new Element(Xml.LOCO_TYPE); 2868 eType.setAttribute(Xml.NAME, type); 2869 eTypes.addContent(eType); 2870 } else if (InstanceManager.getDefault(CarTypes.class).containsName(type)) { 2871 Element eType = new Element(Xml.CAR_TYPE); 2872 eType.setAttribute(Xml.NAME, type); 2873 eTypes.addContent(eType); 2874 } 2875 } 2876 e.addContent(eTypes); 2877 2878 // build list of car roads for this track 2879 if (!getRoadOption().equals(ALL_ROADS)) { 2880 e.setAttribute(Xml.CAR_ROAD_OPTION, getRoadOption()); 2881 String[] roads = getRoadNames(); 2882 // new way of saving road names 2883 Element eRoads = new Element(Xml.CAR_ROADS); 2884 for (String road : roads) { 2885 Element eRoad = new Element(Xml.CAR_ROAD); 2886 eRoad.setAttribute(Xml.NAME, road); 2887 eRoads.addContent(eRoad); 2888 } 2889 e.addContent(eRoads); 2890 } 2891 2892 // save list of car loads for this track 2893 if (!getLoadOption().equals(ALL_LOADS)) { 2894 e.setAttribute(Xml.CAR_LOAD_OPTION, getLoadOption()); 2895 String[] loads = getLoadNames(); 2896 // new way of saving car loads using elements 2897 Element eLoads = new Element(Xml.CAR_LOADS); 2898 for (String load : loads) { 2899 Element eLoad = new Element(Xml.CAR_LOAD); 2900 eLoad.setAttribute(Xml.NAME, load); 2901 eLoads.addContent(eLoad); 2902 } 2903 e.addContent(eLoads); 2904 } 2905 2906 // save list of car loads for this track 2907 if (!getShipLoadOption().equals(ALL_LOADS)) { 2908 e.setAttribute(Xml.CAR_SHIP_LOAD_OPTION, getShipLoadOption()); 2909 String[] loads = getShipLoadNames(); 2910 // new way of saving car loads using elements 2911 Element eLoads = new Element(Xml.CAR_SHIP_LOADS); 2912 for (String load : loads) { 2913 Element eLoad = new Element(Xml.CAR_LOAD); 2914 eLoad.setAttribute(Xml.NAME, load); 2915 eLoads.addContent(eLoad); 2916 } 2917 e.addContent(eLoads); 2918 } 2919 2920 if (!getDropOption().equals(ANY)) { 2921 e.setAttribute(Xml.DROP_OPTION, getDropOption()); 2922 // build list of drop ids for this track 2923 String[] dropIds = getDropIds(); 2924 // new way of saving drop ids using elements 2925 Element eDropIds = new Element(Xml.DROP_IDS); 2926 for (String id : dropIds) { 2927 Element eDropId = new Element(Xml.DROP_ID); 2928 eDropId.setAttribute(Xml.ID, id); 2929 eDropIds.addContent(eDropId); 2930 } 2931 e.addContent(eDropIds); 2932 } 2933 2934 if (!getPickupOption().equals(ANY)) { 2935 e.setAttribute(Xml.PICKUP_OPTION, getPickupOption()); 2936 // build list of pickup ids for this track 2937 String[] pickupIds = getPickupIds(); 2938 // new way of saving pick up ids using elements 2939 Element ePickupIds = new Element(Xml.PICKUP_IDS); 2940 for (String id : pickupIds) { 2941 Element ePickupId = new Element(Xml.PICKUP_ID); 2942 ePickupId.setAttribute(Xml.ID, id); 2943 ePickupIds.addContent(ePickupId); 2944 } 2945 e.addContent(ePickupIds); 2946 } 2947 2948 if (getSchedule() != null) { 2949 e.setAttribute(Xml.SCHEDULE, getScheduleName()); 2950 e.setAttribute(Xml.SCHEDULE_ID, getScheduleId()); 2951 e.setAttribute(Xml.ITEM_ID, getScheduleItemId()); 2952 e.setAttribute(Xml.ITEM_COUNT, Integer.toString(getScheduleCount())); 2953 e.setAttribute(Xml.FACTOR, Integer.toString(getReservationFactor())); 2954 e.setAttribute(Xml.SCHEDULE_MODE, Integer.toString(getScheduleMode())); 2955 e.setAttribute(Xml.HOLD_CARS_CUSTOM, isHoldCarsWithCustomLoadsEnabled() ? Xml.TRUE : Xml.FALSE); 2956 } 2957 if (isInterchange() || isStaging()) { 2958 e.setAttribute(Xml.ONLY_CARS_WITH_FD, isOnlyCarsWithFinalDestinationEnabled() ? Xml.TRUE : Xml.FALSE); 2959 } 2960 if (getAlternateTrack() != null) { 2961 e.setAttribute(Xml.ALTERNATIVE, getAlternateTrack().getId()); 2962 } 2963 if (_loadOptions != 0) { 2964 e.setAttribute(Xml.LOAD_OPTIONS, Integer.toString(_loadOptions)); 2965 } 2966 if (isBlockCarsEnabled()) { 2967 e.setAttribute(Xml.BLOCK_OPTIONS, Integer.toString(_blockOptions)); 2968 } 2969 if (!getServiceOrder().equals(NORMAL)) { 2970 e.setAttribute(Xml.ORDER, getServiceOrder()); 2971 } 2972 if (getPool() != null) { 2973 e.setAttribute(Xml.POOL, getPool().getName()); 2974 e.setAttribute(Xml.MIN_LENGTH, Integer.toString(getPoolMinimumLength())); 2975 if (getPoolMaximumLength() != Integer.MAX_VALUE) { 2976 e.setAttribute(Xml.MAX_LENGTH, Integer.toString(getPoolMaximumLength())); 2977 } 2978 } 2979 if (getIgnoreUsedLengthPercentage() > IGNORE_0) { 2980 e.setAttribute(Xml.IGNORE_USED_PERCENTAGE, Integer.toString(getIgnoreUsedLengthPercentage())); 2981 } 2982 2983 if ((isStaging() || isInterchange()) && !getDestinationOption().equals(ALL_DESTINATIONS)) { 2984 e.setAttribute(Xml.TRACK_DESTINATION_OPTION, getDestinationOption()); 2985 // save destinations if they exist 2986 String[] destIds = getDestinationIds(); 2987 if (destIds.length > 0) { 2988 Element destinations = new Element(Xml.DESTINATIONS); 2989 for (String id : destIds) { 2990 Location loc = InstanceManager.getDefault(LocationManager.class).getLocationById(id); 2991 if (loc != null) { 2992 Element destination = new Element(Xml.DESTINATION); 2993 destination.setAttribute(Xml.ID, id); 2994 destination.setAttribute(Xml.NAME, loc.getName()); 2995 destinations.addContent(destination); 2996 } 2997 } 2998 e.addContent(destinations); 2999 } 3000 } 3001 // save manifest track comments if they exist 3002 if (!getComment().equals(NONE) || 3003 !getCommentBothWithColor().equals(NONE) || 3004 !getCommentPickupWithColor().equals(NONE) || 3005 !getCommentSetoutWithColor().equals(NONE)) { 3006 Element comments = new Element(Xml.COMMENTS); 3007 Element track = new Element(Xml.TRACK); 3008 Element both = new Element(Xml.BOTH); 3009 Element pickup = new Element(Xml.PICKUP); 3010 Element setout = new Element(Xml.SETOUT); 3011 Element printManifest = new Element(Xml.PRINT_MANIFEST); 3012 Element printSwitchList = new Element(Xml.PRINT_SWITCH_LISTS); 3013 3014 comments.addContent(track); 3015 comments.addContent(both); 3016 comments.addContent(pickup); 3017 comments.addContent(setout); 3018 comments.addContent(printManifest); 3019 comments.addContent(printSwitchList); 3020 3021 track.setAttribute(Xml.COMMENT, getComment()); 3022 both.setAttribute(Xml.COMMENT, getCommentBothWithColor()); 3023 pickup.setAttribute(Xml.COMMENT, getCommentPickupWithColor()); 3024 setout.setAttribute(Xml.COMMENT, getCommentSetoutWithColor()); 3025 printManifest.setAttribute(Xml.COMMENT, isPrintManifestCommentEnabled() ? Xml.TRUE : Xml.FALSE); 3026 printSwitchList.setAttribute(Xml.COMMENT, isPrintSwitchListCommentEnabled() ? Xml.TRUE : Xml.FALSE); 3027 3028 e.addContent(comments); 3029 } 3030 if (getReporter() != null) { 3031 e.setAttribute(Xml.READER, getReporter().getDisplayName()); 3032 } 3033 return e; 3034 } 3035 3036 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 3037 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 3038 firePropertyChange(p, old, n); 3039 } 3040 3041 /* 3042 * set the jmri.Reporter object associated with this location. 3043 * 3044 * @param reader jmri.Reporter object. 3045 */ 3046 public void setReporter(Reporter r) { 3047 Reporter old = _reader; 3048 _reader = r; 3049 if (old != r) { 3050 setDirtyAndFirePropertyChange(TRACK_REPORTER_CHANGED_PROPERTY, old, r); 3051 } 3052 } 3053 3054 /* 3055 * get the jmri.Reporter object associated with this location. 3056 * 3057 * @return jmri.Reporter object. 3058 */ 3059 public Reporter getReporter() { 3060 return _reader; 3061 } 3062 3063 public String getReporterName() { 3064 if (getReporter() != null) { 3065 return getReporter().getDisplayName(); 3066 } 3067 return ""; 3068 } 3069 3070 private final static Logger log = LoggerFactory.getLogger(Track.class); 3071 3072}