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