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