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