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