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