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