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