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