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