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