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