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