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