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