001package jmri.jmrit.operations.rollingstock.cars; 002 003import java.beans.PropertyChangeEvent; 004 005import org.slf4j.Logger; 006import org.slf4j.LoggerFactory; 007 008import jmri.InstanceManager; 009import jmri.jmrit.operations.locations.*; 010import jmri.jmrit.operations.locations.schedules.Schedule; 011import jmri.jmrit.operations.locations.schedules.ScheduleItem; 012import jmri.jmrit.operations.rollingstock.RollingStock; 013import jmri.jmrit.operations.routes.RouteLocation; 014import jmri.jmrit.operations.trains.TrainCommon; 015import jmri.jmrit.operations.trains.schedules.TrainSchedule; 016import jmri.jmrit.operations.trains.schedules.TrainScheduleManager; 017 018/** 019 * Represents a car on the layout 020 * 021 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2012, 2013, 2014, 022 * 2015, 2023 023 */ 024public class Car extends RollingStock { 025 026 CarLoads carLoads = InstanceManager.getDefault(CarLoads.class); 027 028 protected boolean _passenger = false; 029 protected boolean _hazardous = false; 030 protected boolean _caboose = false; 031 protected boolean _fred = false; 032 protected boolean _utility = false; 033 protected boolean _loadGeneratedByStaging = false; 034 protected Kernel _kernel = null; 035 protected String _loadName = carLoads.getDefaultEmptyName(); 036 protected int _wait = 0; 037 038 protected Location _rweDestination = null; // return when empty destination 039 protected Track _rweDestTrack = null; // return when empty track 040 protected String _rweLoadName = carLoads.getDefaultEmptyName(); 041 042 protected Location _rwlDestination = null; // return when loaded destination 043 protected Track _rwlDestTrack = null; // return when loaded track 044 protected String _rwlLoadName = carLoads.getDefaultLoadName(); 045 046 // schedule items 047 protected String _scheduleId = NONE; // the schedule id assigned to this car 048 protected String _nextLoadName = NONE; // next load by schedule 049 protected Location _finalDestination = null; 050 protected Track _finalDestTrack = null; // final track by schedule or router 051 protected Location _previousFinalDestination = null; 052 protected Track _previousFinalDestTrack = null; 053 protected String _previousScheduleId = NONE; 054 protected String _pickupScheduleId = NONE; 055 056 public static final String EXTENSION_REGEX = " "; 057 public static final String CABOOSE_EXTENSION = Bundle.getMessage("(C)"); 058 public static final String FRED_EXTENSION = Bundle.getMessage("(F)"); 059 public static final String PASSENGER_EXTENSION = Bundle.getMessage("(P)"); 060 public static final String UTILITY_EXTENSION = Bundle.getMessage("(U)"); 061 public static final String HAZARDOUS_EXTENSION = Bundle.getMessage("(H)"); 062 063 public static final String LOAD_CHANGED_PROPERTY = "Car load changed"; // NOI18N 064 public static final String RWE_LOAD_CHANGED_PROPERTY = "Car RWE load changed"; // NOI18N 065 public static final String RWL_LOAD_CHANGED_PROPERTY = "Car RWL load changed"; // NOI18N 066 public static final String WAIT_CHANGED_PROPERTY = "Car wait changed"; // NOI18N 067 public static final String FINAL_DESTINATION_CHANGED_PROPERTY = "Car final destination changed"; // NOI18N 068 public static final String FINAL_DESTINATION_TRACK_CHANGED_PROPERTY = "Car final destination track changed"; // NOI18N 069 public static final String RETURN_WHEN_EMPTY_CHANGED_PROPERTY = "Car return when empty changed"; // NOI18N 070 public static final String RETURN_WHEN_LOADED_CHANGED_PROPERTY = "Car return when loaded changed"; // NOI18N 071 public static final String SCHEDULE_ID_CHANGED_PROPERTY = "car schedule id changed"; // NOI18N 072 public static final String KERNEL_NAME_CHANGED_PROPERTY = "kernel name changed"; // NOI18N 073 074 public Car() { 075 super(); 076 loaded = true; 077 } 078 079 public Car(String road, String number) { 080 super(road, number); 081 loaded = true; 082 log.debug("New car ({} {})", road, number); 083 addPropertyChangeListeners(); 084 } 085 086 public Car copy() { 087 Car car = new Car(); 088 car.setBuilt(getBuilt()); 089 car.setColor(getColor()); 090 car.setLength(getLength()); 091 car.setLoadName(getLoadName()); 092 car.setReturnWhenEmptyLoadName(getReturnWhenEmptyLoadName()); 093 car.setReturnWhenLoadedLoadName(getReturnWhenLoadedLoadName()); 094 car.setNumber(getNumber()); 095 car.setOwnerName(getOwnerName()); 096 car.setRoadName(getRoadName()); 097 car.setTypeName(getTypeName()); 098 car.setCaboose(isCaboose()); 099 car.setFred(hasFred()); 100 car.setPassenger(isPassenger()); 101 car.loaded = true; 102 return car; 103 } 104 105 public void setCarHazardous(boolean hazardous) { 106 boolean old = _hazardous; 107 _hazardous = hazardous; 108 if (!old == hazardous) { 109 setDirtyAndFirePropertyChange("car hazardous", old ? "true" : "false", hazardous ? "true" : "false"); // NOI18N 110 } 111 } 112 113 public boolean isCarHazardous() { 114 return _hazardous; 115 } 116 117 public boolean isCarLoadHazardous() { 118 return carLoads.isHazardous(getTypeName(), getLoadName()); 119 } 120 121 /** 122 * Used to determine if the car is hazardous or the car's load is hazardous. 123 * 124 * @return true if the car or car's load is hazardous. 125 */ 126 public boolean isHazardous() { 127 return isCarHazardous() || isCarLoadHazardous(); 128 } 129 130 public void setPassenger(boolean passenger) { 131 boolean old = _passenger; 132 _passenger = passenger; 133 if (!old == passenger) { 134 setDirtyAndFirePropertyChange("car passenger", old ? "true" : "false", passenger ? "true" : "false"); // NOI18N 135 } 136 } 137 138 public boolean isPassenger() { 139 return _passenger; 140 } 141 142 public void setFred(boolean fred) { 143 boolean old = _fred; 144 _fred = fred; 145 if (!old == fred) { 146 setDirtyAndFirePropertyChange("car has fred", old ? "true" : "false", fred ? "true" : "false"); // NOI18N 147 } 148 } 149 150 /** 151 * Used to determine if car has FRED (Flashing Rear End Device). 152 * 153 * @return true if car has FRED. 154 */ 155 public boolean hasFred() { 156 return _fred; 157 } 158 159 public void setLoadName(String load) { 160 String old = _loadName; 161 _loadName = load; 162 if (!old.equals(load)) { 163 setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, old, load); 164 } 165 } 166 167 /** 168 * The load name assigned to this car. 169 * 170 * @return The load name assigned to this car. 171 */ 172 public String getLoadName() { 173 return _loadName; 174 } 175 176 public void setReturnWhenEmptyLoadName(String load) { 177 String old = _rweLoadName; 178 _rweLoadName = load; 179 if (!old.equals(load)) { 180 setDirtyAndFirePropertyChange(RWE_LOAD_CHANGED_PROPERTY, old, load); 181 } 182 } 183 184 public String getReturnWhenEmptyLoadName() { 185 return _rweLoadName; 186 } 187 188 public void setReturnWhenLoadedLoadName(String load) { 189 String old = _rwlLoadName; 190 _rwlLoadName = load; 191 if (!old.equals(load)) { 192 setDirtyAndFirePropertyChange(RWL_LOAD_CHANGED_PROPERTY, old, load); 193 } 194 } 195 196 public String getReturnWhenLoadedLoadName() { 197 return _rwlLoadName; 198 } 199 200 /** 201 * Gets the car's load's priority. 202 * 203 * @return The car's load priority. 204 */ 205 public String getLoadPriority() { 206 return (carLoads.getPriority(getTypeName(), getLoadName())); 207 } 208 209 /** 210 * Gets the car load's type, empty or load. 211 * 212 * @return type empty or type load 213 */ 214 public String getLoadType() { 215 return (carLoads.getLoadType(getTypeName(), getLoadName())); 216 } 217 218 public String getPickupComment() { 219 return carLoads.getPickupComment(getTypeName(), getLoadName()); 220 } 221 222 public String getDropComment() { 223 return carLoads.getDropComment(getTypeName(), getLoadName()); 224 } 225 226 public void setLoadGeneratedFromStaging(boolean fromStaging) { 227 _loadGeneratedByStaging = fromStaging; 228 } 229 230 public boolean isLoadGeneratedFromStaging() { 231 return _loadGeneratedByStaging; 232 } 233 234 /** 235 * Used to keep track of which item in a schedule was used for this car. 236 * 237 * @param id The ScheduleItem id for this car. 238 */ 239 public void setScheduleItemId(String id) { 240 log.debug("Set schedule item id ({}) for car ({})", id, toString()); 241 String old = _scheduleId; 242 _scheduleId = id; 243 if (!old.equals(id)) { 244 setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id); 245 } 246 } 247 248 public String getScheduleItemId() { 249 return _scheduleId; 250 } 251 252 public ScheduleItem getScheduleItem(Track track) { 253 ScheduleItem si = null; 254 // arrived at spur? 255 if (track != null && track.isSpur() && !getScheduleItemId().equals(NONE)) { 256 Schedule sch = track.getSchedule(); 257 if (sch == null) { 258 log.error("Schedule null for car ({}) at spur ({})", toString(), track.getName()); 259 } else { 260 si = sch.getItemById(getScheduleItemId()); 261 } 262 } 263 return si; 264 } 265 266 /** 267 * Only here for backwards compatibility before version 5.1.4. The next load 268 * name for this car. Normally set by a schedule. 269 * 270 * @param load the next load name. 271 */ 272 public void setNextLoadName(String load) { 273 String old = _nextLoadName; 274 _nextLoadName = load; 275 if (!old.equals(load)) { 276 setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, old, load); 277 } 278 } 279 280 public String getNextLoadName() { 281 return _nextLoadName; 282 } 283 284 @Override 285 public String getWeightTons() { 286 String weight = super.getWeightTons(); 287 if (!_weightTons.equals(DEFAULT_WEIGHT)) { 288 return weight; 289 } 290 if (!isCaboose() && !isPassenger()) { 291 return weight; 292 } 293 // .9 tons/foot for caboose and passenger cars 294 try { 295 weight = Integer.toString((int) (Double.parseDouble(getLength()) * .9)); 296 } catch (Exception e) { 297 log.debug("Car ({}) length not set for caboose or passenger car", toString()); 298 } 299 return weight; 300 } 301 302 /** 303 * Returns a car's weight adjusted for load. An empty car's weight is 1/3 304 * the car's loaded weight. 305 */ 306 @Override 307 public int getAdjustedWeightTons() { 308 int weightTons = 0; 309 try { 310 // get loaded weight 311 weightTons = Integer.parseInt(getWeightTons()); 312 // adjust for empty weight if car is empty, 1/3 of loaded weight 313 if (!isCaboose() && !isPassenger() && getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 314 weightTons = weightTons / 3; 315 } 316 } catch (NumberFormatException e) { 317 log.debug("Car ({}) weight not set", toString()); 318 } 319 return weightTons; 320 } 321 322 public void setWait(int count) { 323 int old = _wait; 324 _wait = count; 325 if (old != count) { 326 setDirtyAndFirePropertyChange(WAIT_CHANGED_PROPERTY, old, count); 327 } 328 } 329 330 public int getWait() { 331 return _wait; 332 } 333 334 /** 335 * Sets when this car will be picked up (day of the week) 336 * 337 * @param id See TrainSchedule.java 338 */ 339 public void setPickupScheduleId(String id) { 340 String old = _pickupScheduleId; 341 _pickupScheduleId = id; 342 if (!old.equals(id)) { 343 setDirtyAndFirePropertyChange("car pickup schedule changes", old, id); // NOI18N 344 } 345 } 346 347 public String getPickupScheduleId() { 348 return _pickupScheduleId; 349 } 350 351 public String getPickupScheduleName() { 352 TrainSchedule sch = InstanceManager.getDefault(TrainScheduleManager.class) 353 .getScheduleById(getPickupScheduleId()); 354 if (sch != null) { 355 return sch.getName(); 356 } 357 return NONE; 358 } 359 360 /** 361 * Sets the final destination for a car. 362 * 363 * @param destination The final destination for this car. 364 */ 365 public void setFinalDestination(Location destination) { 366 Location old = _finalDestination; 367 if (old != null) { 368 old.removePropertyChangeListener(this); 369 } 370 _finalDestination = destination; 371 if (_finalDestination != null) { 372 _finalDestination.addPropertyChangeListener(this); 373 } 374 if ((old != null && !old.equals(destination)) || (destination != null && !destination.equals(old))) { 375 setDirtyAndFirePropertyChange(FINAL_DESTINATION_CHANGED_PROPERTY, old, destination); 376 } 377 } 378 379 public Location getFinalDestination() { 380 return _finalDestination; 381 } 382 383 public String getFinalDestinationName() { 384 if (_finalDestination != null) { 385 return _finalDestination.getName(); 386 } 387 return NONE; 388 } 389 390 public String getSplitFinalDestinationName() { 391 return TrainCommon.splitString(getFinalDestinationName()); 392 } 393 394 public void setFinalDestinationTrack(Track track) { 395 Track old = _finalDestTrack; 396 _finalDestTrack = track; 397 if ((old != null && !old.equals(track)) || (track != null && !track.equals(old))) { 398 if (old != null) { 399 old.removePropertyChangeListener(this); 400 old.deleteReservedInRoute(this); 401 } 402 if (_finalDestTrack != null) { 403 _finalDestTrack.addReservedInRoute(this); 404 _finalDestTrack.addPropertyChangeListener(this); 405 } 406 setDirtyAndFirePropertyChange(FINAL_DESTINATION_TRACK_CHANGED_PROPERTY, old, track); 407 } 408 } 409 410 public Track getFinalDestinationTrack() { 411 return _finalDestTrack; 412 } 413 414 public String getFinalDestinationTrackName() { 415 if (_finalDestTrack != null) { 416 return _finalDestTrack.getName(); 417 } 418 return NONE; 419 } 420 421 public String getSplitFinalDestinationTrackName() { 422 return TrainCommon.splitString(getFinalDestinationTrackName()); 423 } 424 425 public void setPreviousFinalDestination(Location location) { 426 _previousFinalDestination = location; 427 } 428 429 public Location getPreviousFinalDestination() { 430 return _previousFinalDestination; 431 } 432 433 public void setPreviousFinalDestinationTrack(Track track) { 434 _previousFinalDestTrack = track; 435 } 436 437 public Track getPreviousFinalDestinationTrack() { 438 return _previousFinalDestTrack; 439 } 440 441 public void setPreviousScheduleId(String id) { 442 _previousScheduleId = id; 443 } 444 445 public String getPreviousScheduleId() { 446 return _previousScheduleId; 447 } 448 449 public void setReturnWhenEmptyDestination(Location destination) { 450 Location old = _rweDestination; 451 _rweDestination = destination; 452 if ((old != null && !old.equals(destination)) || (destination != null && !destination.equals(old))) { 453 setDirtyAndFirePropertyChange(RETURN_WHEN_EMPTY_CHANGED_PROPERTY, null, null); 454 } 455 } 456 457 public Location getReturnWhenEmptyDestination() { 458 return _rweDestination; 459 } 460 461 public String getReturnWhenEmptyDestinationName() { 462 if (getReturnWhenEmptyDestination() != null) { 463 return getReturnWhenEmptyDestination().getName(); 464 } 465 return NONE; 466 } 467 468 public String getSplitReturnWhenEmptyDestinationName() { 469 return TrainCommon.splitString(getReturnWhenEmptyDestinationName()); 470 } 471 472 public void setReturnWhenEmptyDestTrack(Track track) { 473 Track old = _rweDestTrack; 474 _rweDestTrack = track; 475 if ((old != null && !old.equals(track)) || (track != null && !track.equals(old))) { 476 setDirtyAndFirePropertyChange(RETURN_WHEN_EMPTY_CHANGED_PROPERTY, null, null); 477 } 478 } 479 480 public Track getReturnWhenEmptyDestTrack() { 481 return _rweDestTrack; 482 } 483 484 public String getReturnWhenEmptyDestTrackName() { 485 if (getReturnWhenEmptyDestTrack() != null) { 486 return getReturnWhenEmptyDestTrack().getName(); 487 } 488 return NONE; 489 } 490 491 public String getSplitReturnWhenEmptyDestinationTrackName() { 492 return TrainCommon.splitString(getReturnWhenEmptyDestTrackName()); 493 } 494 495 public void setReturnWhenLoadedDestination(Location destination) { 496 Location old = _rwlDestination; 497 _rwlDestination = destination; 498 if ((old != null && !old.equals(destination)) || (destination != null && !destination.equals(old))) { 499 setDirtyAndFirePropertyChange(RETURN_WHEN_LOADED_CHANGED_PROPERTY, null, null); 500 } 501 } 502 503 public Location getReturnWhenLoadedDestination() { 504 return _rwlDestination; 505 } 506 507 public String getReturnWhenLoadedDestinationName() { 508 if (getReturnWhenLoadedDestination() != null) { 509 return getReturnWhenLoadedDestination().getName(); 510 } 511 return NONE; 512 } 513 514 public void setReturnWhenLoadedDestTrack(Track track) { 515 Track old = _rwlDestTrack; 516 _rwlDestTrack = track; 517 if ((old != null && !old.equals(track)) || (track != null && !track.equals(old))) { 518 setDirtyAndFirePropertyChange(RETURN_WHEN_LOADED_CHANGED_PROPERTY, null, null); 519 } 520 } 521 522 public Track getReturnWhenLoadedDestTrack() { 523 return _rwlDestTrack; 524 } 525 526 public String getReturnWhenLoadedDestTrackName() { 527 if (getReturnWhenLoadedDestTrack() != null) { 528 return getReturnWhenLoadedDestTrack().getName(); 529 } 530 return NONE; 531 } 532 533 /** 534 * Used to determine is car has been given a Return When Loaded (RWL) 535 * address or custom load 536 * 537 * @return true if car has RWL 538 */ 539 protected boolean isRwlEnabled() { 540 if (!getReturnWhenLoadedLoadName().equals(carLoads.getDefaultLoadName()) || 541 getReturnWhenLoadedDestination() != null) { 542 return true; 543 } 544 return false; 545 } 546 547 public void setCaboose(boolean caboose) { 548 boolean old = _caboose; 549 _caboose = caboose; 550 if (!old == caboose) { 551 setDirtyAndFirePropertyChange("car is caboose", old ? "true" : "false", caboose ? "true" : "false"); // NOI18N 552 } 553 } 554 555 public boolean isCaboose() { 556 return _caboose; 557 } 558 559 public void setUtility(boolean utility) { 560 boolean old = _utility; 561 _utility = utility; 562 if (!old == utility) { 563 setDirtyAndFirePropertyChange("car is utility", old ? "true" : "false", utility ? "true" : "false"); // NOI18N 564 } 565 } 566 567 public boolean isUtility() { 568 return _utility; 569 } 570 571 /** 572 * Used to determine if car is performing a local move. A local move is when 573 * a car is moved to a different track at the same location. Car has to be 574 * assigned to a train. 575 * 576 * @return true if local move 577 */ 578 public boolean isLocalMove() { 579 if (getTrain() == null && getLocation() != null) { 580 return getSplitLocationName().equals(getSplitDestinationName()); 581 } 582 if (getRouteLocation() == null || getRouteDestination() == null) { 583 return false; 584 } 585 if (getRouteLocation().equals(getRouteDestination()) && getTrack() != null) { 586 return true; 587 } 588 if (getTrain().isLocalSwitcher() && 589 getRouteLocation().getSplitName() 590 .equals(getRouteDestination().getSplitName()) && 591 getTrack() != null) { 592 return true; 593 } 594 // look for sequential locations with the "same" name 595 if (getRouteLocation().getSplitName().equals( 596 getRouteDestination().getSplitName()) && getTrain().getRoute() != null) { 597 boolean foundRl = false; 598 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 599 if (foundRl) { 600 if (getRouteDestination().getSplitName() 601 .equals(rl.getSplitName())) { 602 // user can specify the "same" location two more more 603 // times in a row 604 if (getRouteDestination() != rl) { 605 continue; 606 } else { 607 return true; 608 } 609 } else { 610 return false; 611 } 612 } 613 if (getRouteLocation().equals(rl)) { 614 foundRl = true; 615 } 616 } 617 } 618 return false; 619 } 620 621 /** 622 * A kernel is a group of cars that are switched as a unit. 623 * 624 * @param kernel The assigned Kernel for this car. 625 */ 626 public void setKernel(Kernel kernel) { 627 if (_kernel == kernel) { 628 return; 629 } 630 String old = ""; 631 if (_kernel != null) { 632 old = _kernel.getName(); 633 _kernel.delete(this); 634 } 635 _kernel = kernel; 636 String newName = ""; 637 if (_kernel != null) { 638 _kernel.add(this); 639 newName = _kernel.getName(); 640 } 641 if (!old.equals(newName)) { 642 setDirtyAndFirePropertyChange(KERNEL_NAME_CHANGED_PROPERTY, old, newName); // NOI18N 643 } 644 } 645 646 public Kernel getKernel() { 647 return _kernel; 648 } 649 650 public String getKernelName() { 651 if (_kernel != null) { 652 return _kernel.getName(); 653 } 654 return NONE; 655 } 656 657 /** 658 * Used to determine if car is lead car in a kernel 659 * 660 * @return true if lead car in a kernel 661 */ 662 public boolean isLead() { 663 if (getKernel() != null) { 664 return getKernel().isLead(this); 665 } 666 return false; 667 } 668 669 /** 670 * Updates all cars in a kernel. After the update, the cars will all have 671 * the same final destination, load, and next load. 672 */ 673 public void updateKernel() { 674 if (isLead()) { 675 for (Car car : getKernel().getCars()) { 676 car.setScheduleItemId(getScheduleItemId()); 677 car.setFinalDestination(getFinalDestination()); 678 car.setFinalDestinationTrack(getFinalDestinationTrack()); 679 car.setLoadGeneratedFromStaging(isLoadGeneratedFromStaging()); 680 if (InstanceManager.getDefault(CarLoads.class).containsName(car.getTypeName(), getLoadName())) { 681 car.setLoadName(getLoadName()); 682 } 683 } 684 } 685 } 686 687 /** 688 * Used to determine if a car can be set out at a destination (location). 689 * Track is optional. In addition to all of the tests that checkDestination 690 * performs, spurs with schedules are also checked. 691 * 692 * @return status OKAY, TYPE, ROAD, LENGTH, ERROR_TRACK, CAPACITY, SCHEDULE, 693 * CUSTOM 694 */ 695 @Override 696 public String checkDestination(Location destination, Track track) { 697 String status = super.checkDestination(destination, track); 698 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 699 return status; 700 } 701 // now check to see if the track has a schedule 702 if (track == null) { 703 return status; 704 } 705 String statusSchedule = track.checkSchedule(this); 706 if (status.startsWith(Track.LENGTH) && statusSchedule.equals(Track.OKAY)) { 707 return status; 708 } 709 return statusSchedule; 710 } 711 712 /** 713 * Sets the car's destination on the layout 714 * 715 * @param track (yard, spur, staging, or interchange track) 716 * @return "okay" if successful, "type" if the rolling stock's type isn't 717 * acceptable, or "length" if the rolling stock length didn't fit, 718 * or Schedule if the destination will not accept the car because 719 * the spur has a schedule and the car doesn't meet the schedule 720 * requirements. Also changes the car load status when the car 721 * reaches its destination. 722 */ 723 @Override 724 public String setDestination(Location destination, Track track) { 725 return setDestination(destination, track, false); 726 } 727 728 /** 729 * Sets the car's destination on the layout 730 * 731 * @param track (yard, spur, staging, or interchange track) 732 * @param force when true ignore track length, type, and road when setting 733 * destination 734 * @return "okay" if successful, "type" if the rolling stock's type isn't 735 * acceptable, or "length" if the rolling stock length didn't fit, 736 * or Schedule if the destination will not accept the car because 737 * the spur has a schedule and the car doesn't meet the schedule 738 * requirements. Also changes the car load status when the car 739 * reaches its destination. 740 */ 741 @Override 742 public String setDestination(Location destination, Track track, boolean force) { 743 // save destination name and track in case car has reached its 744 // destination 745 String destinationName = getDestinationName(); 746 Track destinationTrack = getDestinationTrack(); 747 String status = super.setDestination(destination, track, force); 748 // return if not Okay 749 if (!status.equals(Track.OKAY)) { 750 return status; 751 } 752 // now check to see if the track has a schedule 753 if (track != null && destinationTrack != track && loaded) { 754 status = track.scheduleNext(this); 755 if (!status.equals(Track.OKAY)) { 756 return status; 757 } 758 } 759 // done? 760 if (destinationName.equals(NONE) || (destination != null) || getTrain() == null) { 761 return status; 762 } 763 // car was in a train and has been dropped off, update load, RWE could 764 // set a new final destination 765 loadNext(destinationTrack); 766 return status; 767 } 768 769 /** 770 * Called when setting a car's destination to this spur. Loads the car with 771 * a final destination which is the ship address for the schedule item. 772 * 773 * @param scheduleItem The schedule item to be applied this this car 774 */ 775 public void loadNext(ScheduleItem scheduleItem) { 776 if (scheduleItem == null) { 777 return; // should never be null 778 } 779 // set the car's final destination and track 780 setFinalDestination(scheduleItem.getDestination()); 781 setFinalDestinationTrack(scheduleItem.getDestinationTrack()); 782 // bump hit count for this schedule item 783 scheduleItem.setHits(scheduleItem.getHits() + 1); 784 // set all cars in kernel same final destination 785 updateKernel(); 786 } 787 788 /** 789 * Called when car is delivered to track. Updates the car's wait, pickup 790 * day, and load if spur. If staging, can swap default loads, force load to 791 * default empty, or replace custom loads with the default empty load. Can 792 * trigger RWE or RWL. 793 * 794 * @param track the destination track for this car 795 */ 796 public void loadNext(Track track) { 797 setLoadGeneratedFromStaging(false); 798 if (track != null) { 799 if (track.isSpur()) { 800 ScheduleItem si = getScheduleItem(track); 801 if (si == null) { 802 log.debug("Schedule item ({}) is null for car ({}) at spur ({})", getScheduleItemId(), toString(), 803 track.getName()); 804 } else { 805 setWait(si.getWait()); 806 setPickupScheduleId(si.getPickupTrainScheduleId()); 807 } 808 updateLoad(track); 809 } 810 // update load optionally when car reaches staging 811 else if (track.isStaging()) { 812 if (track.isLoadSwapEnabled() && getLoadName().equals(carLoads.getDefaultEmptyName())) { 813 setLoadLoaded(); 814 } else if ((track.isLoadSwapEnabled() || track.isLoadEmptyEnabled()) && 815 getLoadName().equals(carLoads.getDefaultLoadName())) { 816 setLoadEmpty(); 817 } else if (track.isRemoveCustomLoadsEnabled() && 818 !getLoadName().equals(carLoads.getDefaultEmptyName()) && 819 !getLoadName().equals(carLoads.getDefaultLoadName())) { 820 // remove this car's final destination if it has one 821 setFinalDestination(null); 822 setFinalDestinationTrack(null); 823 if (getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && isRwlEnabled()) { 824 setLoadLoaded(); 825 // car arriving into staging with the RWE load? 826 } else if (getLoadName().equals(getReturnWhenEmptyLoadName())) { 827 setLoadName(carLoads.getDefaultEmptyName()); 828 } else { 829 setLoadEmpty(); // note that RWE sets the car's final 830 // destination 831 } 832 } 833 } 834 } 835 } 836 837 /** 838 * Updates a car's load when placed at a spur. Load change delayed if wait 839 * count is greater than zero. 840 * 841 * @param track The spur the car is sitting on 842 */ 843 public void updateLoad(Track track) { 844 if (track.isDisableLoadChangeEnabled()) { 845 return; 846 } 847 if (getWait() > 0) { 848 return; // change load name when wait count reaches 0 849 } 850 // arriving at spur with a schedule? 851 String loadName = NONE; 852 ScheduleItem si = getScheduleItem(track); 853 if (si != null) { 854 loadName = si.getShipLoadName(); // can be NONE 855 } else { 856 // for backwards compatibility before version 5.1.4 857 log.debug("Schedule item ({}) is null for car ({}) at spur ({}), using next load name", getScheduleItemId(), 858 toString(), track.getName()); 859 loadName = getNextLoadName(); 860 } 861 setNextLoadName(NONE); 862 if (!loadName.equals(NONE)) { 863 setLoadName(loadName); 864 // RWE or RWL load and no destination? 865 if (getLoadName().equals(getReturnWhenEmptyLoadName()) && getFinalDestination() == null) { 866 setReturnWhenEmpty(); 867 } else if (getLoadName().equals(getReturnWhenLoadedLoadName()) && getFinalDestination() == null) { 868 setReturnWhenLoaded(); 869 } 870 } else { 871 // flip load names 872 if (getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 873 setLoadLoaded(); 874 } else { 875 setLoadEmpty(); 876 } 877 } 878 setScheduleItemId(Car.NONE); 879 } 880 881 /** 882 * Sets the car's load to empty, triggers RWE load and destination if 883 * enabled. 884 */ 885 private void setLoadEmpty() { 886 if (!getLoadName().equals(getReturnWhenEmptyLoadName())) { 887 setLoadName(getReturnWhenEmptyLoadName()); // default RWE load is 888 // the "E" load 889 setReturnWhenEmpty(); 890 } 891 } 892 893 /* 894 * Don't set return address if in staging with the same RWE address and 895 * don't set return address if at the RWE address 896 */ 897 private void setReturnWhenEmpty() { 898 if (getReturnWhenEmptyDestination() != null && 899 (getLocation() != getReturnWhenEmptyDestination() || 900 (!getReturnWhenEmptyDestination().isStaging() && 901 getTrack() != getReturnWhenEmptyDestTrack()))) { 902 setFinalDestination(getReturnWhenEmptyDestination()); 903 if (getReturnWhenEmptyDestTrack() != null) { 904 setFinalDestinationTrack(getReturnWhenEmptyDestTrack()); 905 } 906 log.debug("Car ({}) has return when empty destination ({}, {}) load {}", toString(), 907 getFinalDestinationName(), getFinalDestinationTrackName(), getLoadName()); 908 } 909 } 910 911 /** 912 * Sets the car's load to loaded, triggers RWL load and destination if 913 * enabled. 914 */ 915 private void setLoadLoaded() { 916 if (!getLoadName().equals(getReturnWhenLoadedLoadName())) { 917 setLoadName(getReturnWhenLoadedLoadName()); // default RWL load is 918 // the "L" load 919 setReturnWhenLoaded(); 920 } 921 } 922 923 /* 924 * Don't set return address if in staging with the same RWL address and 925 * don't set return address if at the RWL address 926 */ 927 private void setReturnWhenLoaded() { 928 if (getReturnWhenLoadedDestination() != null && 929 (getLocation() != getReturnWhenLoadedDestination() || 930 (!getReturnWhenLoadedDestination().isStaging() && 931 getTrack() != getReturnWhenLoadedDestTrack()))) { 932 setFinalDestination(getReturnWhenLoadedDestination()); 933 if (getReturnWhenLoadedDestTrack() != null) { 934 setFinalDestinationTrack(getReturnWhenLoadedDestTrack()); 935 } 936 log.debug("Car ({}) has return when loaded destination ({}, {}) load {}", toString(), 937 getFinalDestinationName(), getFinalDestinationTrackName(), getLoadName()); 938 } 939 } 940 941 public String getTypeExtensions() { 942 StringBuffer buf = new StringBuffer(); 943 if (isCaboose()) { 944 buf.append(EXTENSION_REGEX + CABOOSE_EXTENSION); 945 } 946 if (hasFred()) { 947 buf.append(EXTENSION_REGEX + FRED_EXTENSION); 948 } 949 if (isPassenger()) { 950 buf.append(EXTENSION_REGEX + PASSENGER_EXTENSION + EXTENSION_REGEX + getBlocking()); 951 } 952 if (isUtility()) { 953 buf.append(EXTENSION_REGEX + UTILITY_EXTENSION); 954 } 955 if (isCarHazardous()) { 956 buf.append(EXTENSION_REGEX + HAZARDOUS_EXTENSION); 957 } 958 return buf.toString(); 959 } 960 961 @Override 962 public void reset() { 963 setScheduleItemId(getPreviousScheduleId()); // revert to previous 964 setNextLoadName(NONE); 965 setFinalDestination(getPreviousFinalDestination()); 966 setFinalDestinationTrack(getPreviousFinalDestinationTrack()); 967 if (isLoadGeneratedFromStaging()) { 968 setLoadGeneratedFromStaging(false); 969 setLoadName(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName()); 970 } 971 super.reset(); 972 } 973 974 @Override 975 public void dispose() { 976 setKernel(null); 977 setFinalDestination(null); // removes property change listener 978 setFinalDestinationTrack(null); // removes property change listener 979 InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this); 980 InstanceManager.getDefault(CarLengths.class).removePropertyChangeListener(this); 981 super.dispose(); 982 } 983 984 // used to stop a track's schedule from bumping when loading car database 985 private boolean loaded = false; 986 987 /** 988 * Construct this Entry from XML. This member has to remain synchronized 989 * with the detailed DTD in operations-cars.dtd 990 * 991 * @param e Car XML element 992 */ 993 public Car(org.jdom2.Element e) { 994 super(e); 995 loaded = true; 996 org.jdom2.Attribute a; 997 if ((a = e.getAttribute(Xml.PASSENGER)) != null) { 998 _passenger = a.getValue().equals(Xml.TRUE); 999 } 1000 if ((a = e.getAttribute(Xml.HAZARDOUS)) != null) { 1001 _hazardous = a.getValue().equals(Xml.TRUE); 1002 } 1003 if ((a = e.getAttribute(Xml.CABOOSE)) != null) { 1004 _caboose = a.getValue().equals(Xml.TRUE); 1005 } 1006 if ((a = e.getAttribute(Xml.FRED)) != null) { 1007 _fred = a.getValue().equals(Xml.TRUE); 1008 } 1009 if ((a = e.getAttribute(Xml.UTILITY)) != null) { 1010 _utility = a.getValue().equals(Xml.TRUE); 1011 } 1012 if ((a = e.getAttribute(Xml.KERNEL)) != null) { 1013 Kernel k = InstanceManager.getDefault(KernelManager.class).getKernelByName(a.getValue()); 1014 if (k != null) { 1015 setKernel(k); 1016 if ((a = e.getAttribute(Xml.LEAD_KERNEL)) != null && a.getValue().equals(Xml.TRUE)) { 1017 _kernel.setLead(this); 1018 } 1019 } else { 1020 log.error("Kernel {} does not exist", a.getValue()); 1021 } 1022 } 1023 if ((a = e.getAttribute(Xml.LOAD)) != null) { 1024 _loadName = a.getValue(); 1025 } 1026 if ((a = e.getAttribute(Xml.LOAD_FROM_STAGING)) != null && a.getValue().equals(Xml.TRUE)) { 1027 setLoadGeneratedFromStaging(true); 1028 } 1029 1030 if ((a = e.getAttribute(Xml.WAIT)) != null) { 1031 try { 1032 _wait = Integer.parseInt(a.getValue()); 1033 } catch (NumberFormatException nfe) { 1034 log.error("Wait count ({}) for car ({}) isn't a valid number!", a.getValue(), toString()); 1035 } 1036 } 1037 if ((a = e.getAttribute(Xml.PICKUP_SCHEDULE_ID)) != null) { 1038 _pickupScheduleId = a.getValue(); 1039 } 1040 if ((a = e.getAttribute(Xml.SCHEDULE_ID)) != null) { 1041 _scheduleId = a.getValue(); 1042 } 1043 // for backwards compatibility before version 5.1.4 1044 if ((a = e.getAttribute(Xml.NEXT_LOAD)) != null) { 1045 _nextLoadName = a.getValue(); 1046 } 1047 if ((a = e.getAttribute(Xml.NEXT_DEST_ID)) != null) { 1048 setFinalDestination(InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue())); 1049 } 1050 if (getFinalDestination() != null && (a = e.getAttribute(Xml.NEXT_DEST_TRACK_ID)) != null) { 1051 setFinalDestinationTrack(getFinalDestination().getTrackById(a.getValue())); 1052 } 1053 if ((a = e.getAttribute(Xml.PREVIOUS_NEXT_DEST_ID)) != null) { 1054 setPreviousFinalDestination( 1055 InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue())); 1056 } 1057 if (getPreviousFinalDestination() != null && (a = e.getAttribute(Xml.PREVIOUS_NEXT_DEST_TRACK_ID)) != null) { 1058 setPreviousFinalDestinationTrack(getPreviousFinalDestination().getTrackById(a.getValue())); 1059 } 1060 if ((a = e.getAttribute(Xml.PREVIOUS_SCHEDULE_ID)) != null) { 1061 setPreviousScheduleId(a.getValue()); 1062 } 1063 if ((a = e.getAttribute(Xml.RWE_DEST_ID)) != null) { 1064 _rweDestination = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue()); 1065 } 1066 if (_rweDestination != null && (a = e.getAttribute(Xml.RWE_DEST_TRACK_ID)) != null) { 1067 _rweDestTrack = _rweDestination.getTrackById(a.getValue()); 1068 } 1069 if ((a = e.getAttribute(Xml.RWE_LOAD)) != null) { 1070 _rweLoadName = a.getValue(); 1071 } 1072 if ((a = e.getAttribute(Xml.RWL_DEST_ID)) != null) { 1073 _rwlDestination = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue()); 1074 } 1075 if (_rwlDestination != null && (a = e.getAttribute(Xml.RWL_DEST_TRACK_ID)) != null) { 1076 _rwlDestTrack = _rwlDestination.getTrackById(a.getValue()); 1077 } 1078 if ((a = e.getAttribute(Xml.RWL_LOAD)) != null) { 1079 _rwlLoadName = a.getValue(); 1080 } 1081 addPropertyChangeListeners(); 1082 } 1083 1084 /** 1085 * Create an XML element to represent this Entry. This member has to remain 1086 * synchronized with the detailed DTD in operations-cars.dtd. 1087 * 1088 * @return Contents in a JDOM Element 1089 */ 1090 public org.jdom2.Element store() { 1091 org.jdom2.Element e = new org.jdom2.Element(Xml.CAR); 1092 super.store(e); 1093 if (isPassenger()) { 1094 e.setAttribute(Xml.PASSENGER, isPassenger() ? Xml.TRUE : Xml.FALSE); 1095 } 1096 if (isCarHazardous()) { 1097 e.setAttribute(Xml.HAZARDOUS, isCarHazardous() ? Xml.TRUE : Xml.FALSE); 1098 } 1099 if (isCaboose()) { 1100 e.setAttribute(Xml.CABOOSE, isCaboose() ? Xml.TRUE : Xml.FALSE); 1101 } 1102 if (hasFred()) { 1103 e.setAttribute(Xml.FRED, hasFred() ? Xml.TRUE : Xml.FALSE); 1104 } 1105 if (isUtility()) { 1106 e.setAttribute(Xml.UTILITY, isUtility() ? Xml.TRUE : Xml.FALSE); 1107 } 1108 if (getKernel() != null) { 1109 e.setAttribute(Xml.KERNEL, getKernelName()); 1110 if (isLead()) { 1111 e.setAttribute(Xml.LEAD_KERNEL, Xml.TRUE); 1112 } 1113 } 1114 1115 e.setAttribute(Xml.LOAD, getLoadName()); 1116 1117 if (isLoadGeneratedFromStaging()) { 1118 e.setAttribute(Xml.LOAD_FROM_STAGING, Xml.TRUE); 1119 } 1120 1121 if (getWait() != 0) { 1122 e.setAttribute(Xml.WAIT, Integer.toString(getWait())); 1123 } 1124 1125 if (!getPickupScheduleId().equals(NONE)) { 1126 e.setAttribute(Xml.PICKUP_SCHEDULE_ID, getPickupScheduleId()); 1127 } 1128 1129 if (!getScheduleItemId().equals(NONE)) { 1130 e.setAttribute(Xml.SCHEDULE_ID, getScheduleItemId()); 1131 } 1132 1133 // for backwards compatibility before version 5.1.4 1134 if (!getNextLoadName().equals(NONE)) { 1135 e.setAttribute(Xml.NEXT_LOAD, getNextLoadName()); 1136 } 1137 1138 if (getFinalDestination() != null) { 1139 e.setAttribute(Xml.NEXT_DEST_ID, getFinalDestination().getId()); 1140 if (getFinalDestinationTrack() != null) { 1141 e.setAttribute(Xml.NEXT_DEST_TRACK_ID, getFinalDestinationTrack().getId()); 1142 } 1143 } 1144 1145 if (getPreviousFinalDestination() != null) { 1146 e.setAttribute(Xml.PREVIOUS_NEXT_DEST_ID, getPreviousFinalDestination().getId()); 1147 if (getPreviousFinalDestinationTrack() != null) { 1148 e.setAttribute(Xml.PREVIOUS_NEXT_DEST_TRACK_ID, getPreviousFinalDestinationTrack().getId()); 1149 } 1150 } 1151 1152 if (!getPreviousScheduleId().equals(NONE)) { 1153 e.setAttribute(Xml.PREVIOUS_SCHEDULE_ID, getPreviousScheduleId()); 1154 } 1155 1156 if (getReturnWhenEmptyDestination() != null) { 1157 e.setAttribute(Xml.RWE_DEST_ID, getReturnWhenEmptyDestination().getId()); 1158 if (getReturnWhenEmptyDestTrack() != null) { 1159 e.setAttribute(Xml.RWE_DEST_TRACK_ID, getReturnWhenEmptyDestTrack().getId()); 1160 } 1161 } 1162 if (!getReturnWhenEmptyLoadName().equals(carLoads.getDefaultEmptyName())) { 1163 e.setAttribute(Xml.RWE_LOAD, getReturnWhenEmptyLoadName()); 1164 } 1165 1166 if (getReturnWhenLoadedDestination() != null) { 1167 e.setAttribute(Xml.RWL_DEST_ID, getReturnWhenLoadedDestination().getId()); 1168 if (getReturnWhenLoadedDestTrack() != null) { 1169 e.setAttribute(Xml.RWL_DEST_TRACK_ID, getReturnWhenLoadedDestTrack().getId()); 1170 } 1171 } 1172 if (!getReturnWhenLoadedLoadName().equals(carLoads.getDefaultLoadName())) { 1173 e.setAttribute(Xml.RWL_LOAD, getReturnWhenLoadedLoadName()); 1174 } 1175 1176 return e; 1177 } 1178 1179 @Override 1180 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 1181 // Set dirty 1182 InstanceManager.getDefault(CarManagerXml.class).setDirty(true); 1183 super.setDirtyAndFirePropertyChange(p, old, n); 1184 } 1185 1186 private void addPropertyChangeListeners() { 1187 InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this); 1188 InstanceManager.getDefault(CarLengths.class).addPropertyChangeListener(this); 1189 } 1190 1191 @Override 1192 public void propertyChange(PropertyChangeEvent e) { 1193 super.propertyChange(e); 1194 if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) { 1195 if (e.getOldValue().equals(getTypeName())) { 1196 log.debug("Car ({}) sees type name change old: ({}) new: ({})", toString(), e.getOldValue(), 1197 e.getNewValue()); // NOI18N 1198 setTypeName((String) e.getNewValue()); 1199 } 1200 } 1201 if (e.getPropertyName().equals(CarLengths.CARLENGTHS_NAME_CHANGED_PROPERTY)) { 1202 if (e.getOldValue().equals(getLength())) { 1203 log.debug("Car ({}) sees length name change old: ({}) new: ({})", toString(), e.getOldValue(), 1204 e.getNewValue()); // NOI18N 1205 setLength((String) e.getNewValue()); 1206 } 1207 } 1208 if (e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) { 1209 if (e.getSource() == _finalDestination) { 1210 log.debug("delete final destination for car: ({})", toString()); 1211 setFinalDestination(null); 1212 } 1213 } 1214 if (e.getPropertyName().equals(Track.DISPOSE_CHANGED_PROPERTY)) { 1215 if (e.getSource() == _finalDestTrack) { 1216 log.debug("delete final destination for car: ({})", toString()); 1217 setFinalDestinationTrack(null); 1218 } 1219 } 1220 } 1221 1222 private final static Logger log = LoggerFactory.getLogger(Car.class); 1223 1224}