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