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