001package jmri.jmrit.operations.rollingstock; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.text.SimpleDateFormat; 006import java.time.LocalDateTime; 007import java.time.ZoneId; 008import java.time.format.DateTimeFormatter; 009import java.util.Date; 010 011import jmri.*; 012import jmri.beans.Identifiable; 013import jmri.beans.PropertyChangeSupport; 014import jmri.jmrit.operations.locations.*; 015import jmri.jmrit.operations.locations.divisions.Division; 016import jmri.jmrit.operations.locations.divisions.DivisionManager; 017import jmri.jmrit.operations.rollingstock.cars.*; 018import jmri.jmrit.operations.routes.RouteLocation; 019import jmri.jmrit.operations.setup.Setup; 020import jmri.jmrit.operations.trains.Train; 021import jmri.jmrit.operations.trains.TrainManager; 022import jmri.jmrit.operations.trains.trainbuilder.TrainCommon; 023 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027/** 028 * Represents rolling stock, both powered (locomotives) and not powered (cars) 029 * on the layout. 030 * 031 * @author Daniel Boudreau Copyright (C) 2009, 2010, 2013, 2023, 2026 032 */ 033public abstract class RollingStock extends PropertyChangeSupport implements Identifiable, PropertyChangeListener { 034 035 public static final String NONE = ""; 036 public static final int DEFAULT_BLOCKING_ORDER = 0; 037 public static final int MAX_BLOCKING_ORDER = 100; 038 public static final boolean FORCE = true; // ignore length, type, etc. when setting car's track 039 protected static final String DEFAULT_WEIGHT = "0"; 040 041 public static final String CLONE = TrainCommon.HYPHEN + "(Clone)"; // NOI18N 042 // parentheses are special chars 043 public static final String CLONE_REGEX = TrainCommon.HYPHEN + "\\(Clone\\)"; // NOI18N 044 045 protected String _id = NONE; 046 protected String _order = NONE; 047 protected String _number = NONE; 048 protected String _road = NONE; 049 protected String _type = NONE; 050 protected String _length = "0"; 051 protected String _color = "Black"; 052 protected String _weight = DEFAULT_WEIGHT; 053 protected String _weightTons = DEFAULT_WEIGHT; 054 protected String _built = NONE; 055 protected String _owner = NONE; 056 protected String _comment = NONE; 057 protected String _routeId = NONE; // saved route for interchange tracks 058 protected String _rfid = NONE; 059 protected String _value = NONE; 060 protected LocalDateTime _lastDate = null; 061 protected boolean _locationUnknown = false; 062 protected boolean _outOfService = false; 063 protected boolean _selected = false; 064 065 protected Location _location = null; 066 protected Track _track = null; 067 protected Location _destination = null; 068 protected Track _trackDestination = null; 069 protected Train _train = null; 070 protected RouteLocation _routeLocation = null; 071 protected RouteLocation _routeDestination = null; 072 protected RouteLocation _routeDestinationTiming = null; 073 protected Division _division = null; 074 protected boolean _clone = false; 075 protected int _cloneOrder = 9999999; 076 protected int _moves = 0; 077 protected String _lastLocationId = LOCATION_UNKNOWN; // the rollingstock's last location id 078 protected String _lastTrackId = LOCATION_UNKNOWN; // the rollingstock's last track id 079 protected Train _lastTrain = null; // the last train moving this rs 080 protected int _blocking = DEFAULT_BLOCKING_ORDER; 081 protected String _pickupTime = NONE; 082 protected String _setoutTime = NONE; 083 084 protected IdTag _tag = null; 085 protected PropertyChangeListener _tagListener = null; 086 protected Location _whereLastSeen = null; // location reported by tag reader 087 protected LocalDateTime _whenLastSeen = null; // date reported by tag reader 088 089 public static final String LOCATION_UNKNOWN = "0"; 090 091 protected int number = 0; // used by rolling stock manager for sort by number 092 private final LocalDateTime epoch = LocalDateTime.ofInstant(new java.util.GregorianCalendar().getGregorianChange().toInstant(), ZoneId.systemDefault()); 093 094 public static final String ERROR_TRACK = "ERROR wrong track for location"; // checks for coding error // NOI18N 095 096 // property changes 097 public static final String TRACK_CHANGED_PROPERTY = "rolling stock track location"; // NOI18N 098 public static final String DESTINATION_TRACK_CHANGED_PROPERTY = "rolling stock track destination"; // NOI18N 099 public static final String TRAIN_CHANGED_PROPERTY = "rolling stock train"; // NOI18N 100 public static final String LENGTH_CHANGED_PROPERTY = "rolling stock length"; // NOI18N 101 public static final String TYPE_CHANGED_PROPERTY = "rolling stock type"; // NOI18N 102 public static final String ROUTE_LOCATION_CHANGED_PROPERTY = "rolling stock route location"; // NOI18N 103 public static final String ROUTE_DESTINATION_CHANGED_PROPERTY = "rolling stock route destination"; // NOI18N 104 public static final String COMMENT_CHANGED_PROPERTY = "rolling stock comment"; // NOI18N 105 106 // the draw bar length must only be calculated once at startup 107 public static final int COUPLERS = Setup.getLengthUnit().equals(Setup.FEET) 108 ? Integer.parseInt(Bundle.getMessage("DrawBarLengthFeet")) 109 : Integer.parseInt(Bundle.getMessage("DrawBarLengthMeter")); // stocks TODO catch empty/non-integer value 110 111 LocationManager locationManager = InstanceManager.getDefault(LocationManager.class); 112 113 public RollingStock() { 114 _lastDate = epoch; 115 } 116 117 public RollingStock(String road, String number) { 118 this(); 119 log.debug("New rolling stock ({} {})", road, number); 120 _road = road; 121 _number = number; 122 _id = createId(road, number); 123 addPropertyChangeListeners(); 124 } 125 126 public static String createId(String road, String number) { 127 return road + number; 128 } 129 130 @Override 131 public String getId() { 132 return _id; 133 } 134 135 public abstract RollingStock copy(); 136 137 public RollingStock copy(RollingStock rs) { 138 rs.setBuilt(getBuilt()); 139 rs.setColorName(getColorName()); 140 rs.setLength(getLength()); 141 rs.setWeightTons(getWeightTons()); 142 rs.setNumber(getNumber()); 143 rs.setOwnerName(getOwnerName()); 144 rs.setRoadName(getRoadName()); 145 rs.setTypeName(getTypeName()); 146 rs.setComment(getComment()); 147 rs.setBlocking(getBlocking()); 148 rs.setLastTrain(getLastTrain()); 149 rs.setLastDate(getLastDate()); 150 rs.setLastLocationId(getLastLocationId()); 151 rs.setLastTrackId(getLastTrackId()); 152 rs.setLastRouteId(getLastRouteId()); 153 rs.setDivision(getDivision()); 154 return rs; 155 } 156 157 /** 158 * Set the rolling stock identification or road number 159 * 160 * @param number The rolling stock road number. 161 */ 162 public void setNumber(String number) { 163 String oldNumber = _number; 164 _number = number; 165 if (!oldNumber.equals(number)) { 166 firePropertyChange("rolling stock number", oldNumber, number); // NOI18N 167 String oldId = _id; 168 _id = createId(_road, number); 169 setDirtyAndFirePropertyChange(Xml.ID, oldId, _id); 170 } 171 } 172 173 public String getNumber() { 174 return _number; 175 } 176 177 public void setRoadName(String road) { 178 String old = _road; 179 _road = road; 180 if (!old.equals(road)) { 181 firePropertyChange("rolling stock road", old, road); // NOI18N 182 String oldId = _id; 183 _id = createId(road, _number); 184 setDirtyAndFirePropertyChange(Xml.ID, oldId, _id); 185 } 186 } 187 188 public String getRoadName() { 189 return _road; 190 } 191 192 /** 193 * For combobox and identification 194 */ 195 @Override 196 public String toString() { 197 return getRoadName() + " " + getNumber(); 198 } 199 200 /** 201 * Sets the type of rolling stock. "Boxcar" for example is a type of car. 202 * 203 * @param type The type of rolling stock. 204 */ 205 public void setTypeName(String type) { 206 String old = _type; 207 _type = type; 208 if (!old.equals(type)) { 209 setDirtyAndFirePropertyChange("rolling stock type", old, type); // NOI18N 210 } 211 } 212 213 public String getTypeName() { 214 return _type; 215 } 216 217 protected boolean _lengthChange = false; // used for loco length change 218 219 /** 220 * Sets the body length of the rolling stock. For example, a 40' boxcar 221 * would be entered as 40 feet. Coupler length is added by the program when 222 * determining if a car could fit on a track. 223 * 224 * @see #getTotalLength() 225 * @param length the body length in feet or meters 226 */ 227 public void setLength(String length) { 228 String old = _length; 229 if (!old.equals(length)) { 230 // adjust used length if rolling stock is at a location 231 if (getLocation() != null && getTrack() != null) { 232 getLocation().setUsedLength( 233 getLocation().getUsedLength() + Integer.parseInt(length) - Integer.parseInt(old)); 234 getTrack().setUsedLength(getTrack().getUsedLength() + Integer.parseInt(length) - Integer.parseInt(old)); 235 if (getDestination() != null && getDestinationTrack() != null && !_lengthChange) { 236 _lengthChange = true; // prevent recursive loop, and we want the "old" engine length 237 log.debug("Rolling stock ({}) has destination ({}, {})", this, getDestination().getName(), 238 getDestinationTrack().getName()); 239 getTrack().deletePickupRS(this); 240 getDestinationTrack().deleteDropRS(this); 241 // now change the length and update tracks 242 _length = length; 243 getTrack().addPickupRS(this); 244 getDestinationTrack().addDropRS(this); 245 _lengthChange = false; // done 246 } 247 } 248 _length = length; 249 setDirtyAndFirePropertyChange(LENGTH_CHANGED_PROPERTY, old, length); 250 } 251 } 252 253 /** 254 * gets the body length of the rolling stock 255 * 256 * @return length in feet or meters 257 * @see #getTotalLength() 258 */ 259 public String getLength() { 260 return _length; 261 } 262 263 public int getLengthInteger() { 264 try { 265 return Integer.parseInt(getLength()); 266 } catch (NumberFormatException e) { 267 log.error("Rolling stock ({}) length ({}) is not valid ", this, getLength()); 268 } 269 return 0; 270 } 271 272 /** 273 * Returns the length of the rolling stock including the couplers 274 * 275 * @return total length of the rolling stock in feet or meters 276 */ 277 public int getTotalLength() { 278 return getLengthInteger() + RollingStock.COUPLERS; 279 } 280 281 public void setColorName(String color) { 282 String old = _color; 283 _color = color; 284 if (!old.equals(color)) { 285 setDirtyAndFirePropertyChange("rolling stock color", old, color); // NOI18N 286 } 287 } 288 289 public String getColorName() { 290 return _color; 291 } 292 293 /** 294 * @param weight rolling stock weight in ounces. 295 */ 296 public void setWeight(String weight) { 297 String old = _weight; 298 _weight = weight; 299 if (!old.equals(weight)) { 300 setDirtyAndFirePropertyChange("rolling stock weight", old, weight); // NOI18N 301 } 302 } 303 304 public String getWeight() { 305 return _weight; 306 } 307 308 /** 309 * Sets the full scale weight in tons. 310 * 311 * @param weight full scale rolling stock weight in tons. 312 */ 313 public void setWeightTons(String weight) { 314 String old = _weightTons; 315 _weightTons = weight; 316 if (!old.equals(weight)) { 317 setDirtyAndFirePropertyChange("rolling stock weight tons", old, weight); // NOI18N 318 } 319 } 320 321 public String getWeightTons() { 322 if (!_weightTons.equals(DEFAULT_WEIGHT)) { 323 return _weightTons; 324 } 325 // calculate the ton weight based on actual weight 326 double weight = 0; 327 try { 328 weight = Double.parseDouble(getWeight()); 329 } catch (NumberFormatException e) { 330 log.trace("Weight not set for rolling stock ({})", this); 331 } 332 return Integer.toString((int) (weight * Setup.getScaleTonRatio())); 333 } 334 335 public int getAdjustedWeightTons() { 336 int weightTons = 0; 337 try { 338 // get loaded weight 339 weightTons = Integer.parseInt(getWeightTons()); 340 } catch (NumberFormatException e) { 341 log.debug("Rolling stock ({}) weight not set", this); 342 } 343 return weightTons; 344 } 345 346 /** 347 * Set the date that the rolling stock was built. Use 4 digits for the year, 348 * or the format MM-YY where MM is the two digit month, and YY is the last 349 * two years if the rolling stock was built in the 1900s. Use MM-YYYY for 350 * units build after 1999. 351 * 352 * @param built The string built date. 353 */ 354 public void setBuilt(String built) { 355 String old = _built; 356 _built = built; 357 if (!old.equals(built)) { 358 setDirtyAndFirePropertyChange("rolling stock built", old, built); // NOI18N 359 } 360 } 361 362 public String getBuilt() { 363 return _built; 364 } 365 366 /** 367 * @return location unknown symbol, out of service symbol, or none if car 368 * okay 369 */ 370 public String getStatus() { 371 return (isLocationUnknown() ? "<?> " : (isOutOfService() ? "<O> " : NONE)); // NOI18N 372 } 373 374 public Location getLocation() { 375 return _location; 376 } 377 378 /** 379 * Get rolling stock's location name 380 * 381 * @return empty string if rolling stock isn't on layout 382 */ 383 public String getLocationName() { 384 if (getLocation() != null) { 385 return getLocation().getName(); 386 } 387 return NONE; 388 } 389 390 public String getSplitLocationName() { 391 return TrainCommon.splitString(getLocationName()); 392 } 393 394 /** 395 * Get rolling stock's location id 396 * 397 * @return empty string if rolling stock isn't on the layout 398 */ 399 public String getLocationId() { 400 if (getLocation() != null) { 401 return getLocation().getId(); 402 } 403 return NONE; 404 } 405 406 public Track getTrack() { 407 return _track; 408 } 409 410 /** 411 * Set the rolling stock's location and track. Doesn't do any checking and 412 * does not fire a property change. Used exclusively by the Router code. Use 413 * setLocation(Location, Track) instead. 414 * 415 * @param track to place the rolling stock on. 416 */ 417 public void setTrack(Track track) { 418 if (track != null) { 419 _location = track.getLocation(); 420 } 421 _track = track; 422 } 423 424 /** 425 * Get rolling stock's track name 426 * 427 * @return empty string if rolling stock isn't on a track 428 */ 429 public String getTrackName() { 430 if (getTrack() != null) { 431 return getTrack().getName(); 432 } 433 return NONE; 434 } 435 436 public String getSplitTrackName() { 437 return TrainCommon.splitString(getTrackName()); 438 } 439 440 public String getTrackTypeName() { 441 if (getTrack() != null) { 442 return getTrack().getTrackTypeName(); 443 } 444 return NONE; 445 } 446 447 /** 448 * Get rolling stock's track id 449 * 450 * @return empty string if rolling stock isn't on a track 451 */ 452 public String getTrackId() { 453 if (getTrack() != null) { 454 return getTrack().getId(); 455 } 456 return NONE; 457 } 458 459 /** 460 * Sets rolling stock location on the layout 461 * 462 * @param location The Location. 463 * @param track (yard, spur, staging, or interchange track) 464 * @return "okay" if successful, "type" if the rolling stock's type isn't 465 * acceptable, "road" if rolling stock road isn't acceptable, or 466 * "length" if the rolling stock length didn't fit. 467 */ 468 public String setLocation(Location location, Track track) { 469 return setLocation(location, track, !FORCE); // don't force 470 } 471 472 /** 473 * Sets rolling stock location on the layout 474 * 475 * @param location The Location. 476 * @param track (yard, spur, staging, or interchange track) 477 * @param force when true place rolling stock ignore track length, type, 478 * and road 479 * @return "okay" if successful, "type" if the rolling stock's type isn't 480 * acceptable, "road" if rolling stock road isn't acceptable, or 481 * "length" if the rolling stock length didn't fit. 482 */ 483 public String setLocation(Location location, Track track, boolean force) { 484 Location oldLocation = getLocation(); 485 Track oldTrack = getTrack(); 486 // first determine if rolling stock can be move to the new location 487 if (!force && (oldLocation != location || oldTrack != track)) { 488 String status = testLocation(location, track); 489 if (!status.equals(Track.OKAY)) { 490 return status; 491 } 492 } 493 // now update 494 _location = location; 495 _track = track; 496 497 if (oldLocation != location || oldTrack != track) { 498 // update rolling stock location on layout, maybe this should be a property 499 // change? 500 // first remove rolling stock from existing location 501 if (oldLocation != null) { 502 oldLocation.deleteRS(this); 503 oldLocation.removePropertyChangeListener(this); 504 // if track is null, then rolling stock is in a train 505 if (oldTrack != null) { 506 oldTrack.deleteRS(this); 507 oldTrack.removePropertyChangeListener(this); 508 // if there's a destination then pickup complete 509 if (getDestination() != null) { 510 oldLocation.deletePickupRS(); 511 oldTrack.deletePickupRS(this); 512 // don't update rs's previous location if just re-staging 513 if (!oldLocation.isStaging() || 514 location == null || 515 !location.isStaging() || 516 getTrain() != null && 517 getTrain().getRoute() != null && 518 getTrain().getRoute().size() > 2) { 519 setLastLocationId(oldLocation.getId()); 520 setLastTrackId(oldTrack.getId()); 521 } 522 } 523 } 524 } 525 if (getLocation() != null) { 526 getLocation().addRS(this); 527 // Need to know if location name changes so we can forward to listeners 528 getLocation().addPropertyChangeListener(this); 529 } 530 if (getTrack() != null) { 531 getTrack().addRS(this); 532 // Need to know if location name changes so we can forward to listeners 533 getTrack().addPropertyChangeListener(this); 534 // if there's a destination then there's a pick up 535 if (getDestination() != null) { 536 getLocation().addPickupRS(); 537 getTrack().addPickupRS(this); 538 } 539 } 540 setDirtyAndFirePropertyChange(TRACK_CHANGED_PROPERTY, oldTrack, track); 541 } 542 return Track.OKAY; 543 } 544 545 /** 546 * Used to confirm that a track is associated with a location, and that 547 * rolling stock can be placed on track. 548 * 549 * @param location The location 550 * @param track The track, can be null 551 * @return OKAY if track exists at location and rolling stock can be placed 552 * on track, ERROR_TRACK if track isn't at location, other if 553 * rolling stock can't be placed on track. 554 */ 555 public String testLocation(Location location, Track track) { 556 if (track == null) { 557 return Track.OKAY; 558 } 559 if (location != null && !location.isTrackAtLocation(track)) { 560 return ERROR_TRACK; 561 } 562 return track.isRollingStockAccepted(this); 563 } 564 565 /** 566 * Sets rolling stock destination on the layout 567 * 568 * @param destination The Location. 569 * @param track (yard, spur, staging, or interchange track) 570 * @return "okay" if successful, "type" if the rolling stock's type isn't 571 * acceptable, or "length" if the rolling stock length didn't fit. 572 */ 573 public String setDestination(Location destination, Track track) { 574 return setDestination(destination, track, !RollingStock.FORCE); 575 } 576 577 /** 578 * Sets rolling stock destination on the layout 579 * 580 * @param destination The Location. 581 * @param track (yard, spur, staging, or interchange track) 582 * @param force when true ignore track length, type, and road when 583 * setting destination 584 * @return "okay" if successful, "type" if the rolling stock's type isn't 585 * acceptable, or "length" if the rolling stock length didn't fit. 586 */ 587 public String setDestination(Location destination, Track track, boolean force) { 588 // first determine if rolling stock can be move to the new destination 589 if (!force) { 590 String status = rsCheckDestination(destination, track); 591 if (!status.equals(Track.OKAY)) { 592 return status; 593 } 594 } 595 // now set the rolling stock destination! 596 Location oldDestination = getDestination(); 597 _destination = destination; 598 Track oldTrack = getDestinationTrack(); 599 _trackDestination = track; 600 601 if (oldDestination != destination || oldTrack != track) { 602 if (oldDestination != null) { 603 oldDestination.deleteDropRS(); 604 oldDestination.removePropertyChangeListener(this); 605 // delete pick up in case destination is null 606 if (getLocation() != null && getTrack() != null) { 607 getLocation().deletePickupRS(); 608 getTrack().deletePickupRS(this); 609 } 610 } 611 if (oldTrack != null) { 612 oldTrack.deleteDropRS(this); 613 oldTrack.removePropertyChangeListener(this); 614 } 615 if (getDestination() != null) { 616 getDestination().addDropRS(); 617 if (getLocation() != null && getTrack() != null) { 618 getLocation().addPickupRS(); 619 getTrack().addPickupRS(this); 620 } 621 // Need to know if destination name changes so we can forward to listeners 622 getDestination().addPropertyChangeListener(this); 623 } 624 if (getDestinationTrack() != null) { 625 getDestinationTrack().addDropRS(this); 626 // Need to know if destination name changes so we can forward to listeners 627 getDestinationTrack().addPropertyChangeListener(this); 628 } else { 629 // rolling stock has been terminated or reset 630 if (getTrain() != null && getTrain().getRoute() != null) { 631 setLastRouteId(getTrain().getRoute().getId()); 632 } 633 setRouteLocation(null); 634 setRouteDestination(null); 635 } 636 setDirtyAndFirePropertyChange(DESTINATION_TRACK_CHANGED_PROPERTY, oldTrack, track); 637 } 638 return Track.OKAY; 639 } 640 641 /** 642 * Used to check destination track to see if it will accept rolling stock 643 * 644 * @param destination The Location. 645 * @param track The Track at destination. 646 * @return status OKAY, TYPE, ROAD, LENGTH, ERROR_TRACK 647 */ 648 public String checkDestination(Location destination, Track track) { 649 return rsCheckDestination(destination, track); 650 } 651 652 private String rsCheckDestination(Location destination, Track track) { 653 // first perform a code check 654 if (destination != null && !destination.isTrackAtLocation(track)) { 655 return ERROR_TRACK; 656 } 657 if (destination != null && !destination.acceptsTypeName(getTypeName())) { 658 return Track.TYPE + " (" + getTypeName() + ")"; 659 } 660 if (destination == null || track == null) { 661 return Track.OKAY; 662 } 663 return track.isRollingStockAccepted(this); 664 } 665 666 public Location getDestination() { 667 return _destination; 668 } 669 670 /** 671 * Sets rolling stock destination without reserving destination track space 672 * or drop count. Does not fire a property change. Used by car router to 673 * test destinations. Use setDestination(Location, Track) instead. 674 * 675 * @param destination for the rolling stock 676 */ 677 public void setDestination(Location destination) { 678 _destination = destination; 679 } 680 681 public String getDestinationName() { 682 if (getDestination() != null) { 683 return getDestination().getName(); 684 } 685 return NONE; 686 } 687 688 public String getSplitDestinationName() { 689 return TrainCommon.splitString(getDestinationName()); 690 } 691 692 public String getDestinationId() { 693 if (getDestination() != null) { 694 return getDestination().getId(); 695 } 696 return NONE; 697 } 698 699 /** 700 * Sets rolling stock destination track without reserving destination track 701 * space or drop count. Used by car router to test destinations. Does not 702 * fire a property change. Use setDestination(Location, Track) instead. 703 * 704 * @param track The Track for set out at destination. 705 */ 706 public void setDestinationTrack(Track track) { 707 if (track != null) { 708 _destination = track.getLocation(); 709 } 710 _trackDestination = track; 711 } 712 713 public Track getDestinationTrack() { 714 return _trackDestination; 715 } 716 717 public String getDestinationTrackName() { 718 if (getDestinationTrack() != null) { 719 return getDestinationTrack().getName(); 720 } 721 return NONE; 722 } 723 724 public String getSplitDestinationTrackName() { 725 return TrainCommon.splitString(getDestinationTrackName()); 726 } 727 728 public String getDestinationTrackId() { 729 if (getDestinationTrack() != null) { 730 return getDestinationTrack().getId(); 731 } 732 return NONE; 733 } 734 735 public void setClone(boolean clone) { 736 boolean old = _clone; 737 _clone = clone; 738 if (!old == clone) { 739 setDirtyAndFirePropertyChange("clone", old, clone); // NOI18N 740 } 741 } 742 743 public boolean isClone() { 744 return _clone; 745 } 746 747 public void setCloneOrder(int number) { 748 _cloneOrder = number; 749 } 750 751 public int getCloneOrder() { 752 return _cloneOrder; 753 } 754 755 public void setDivision(Division division) { 756 Division old = _division; 757 _division = division; 758 if (old != _division) { 759 setDirtyAndFirePropertyChange("homeDivisionChange", old, division); // NOI18N 760 } 761 } 762 763 public Division getDivision() { 764 return _division; 765 } 766 767 public String getDivisionName() { 768 if (getDivision() != null) { 769 return getDivision().getName(); 770 } 771 return NONE; 772 } 773 774 public String getDivisionId() { 775 if (getDivision() != null) { 776 return getDivision().getId(); 777 } 778 return NONE; 779 } 780 781 /** 782 * Used to block cars from staging 783 * 784 * @param id The location id from where the car came from before going into 785 * staging. 786 */ 787 public void setLastLocationId(String id) { 788 _lastLocationId = id; 789 } 790 791 public String getLastLocationId() { 792 return _lastLocationId; 793 } 794 795 public String getLastLocationName() { 796 Location location = locationManager.getLocationById(getLastLocationId()); 797 if (location != null) { 798 return location.getName(); 799 } 800 return NONE; 801 } 802 803 public void setLastTrackId(String id) { 804 _lastTrackId = id; 805 } 806 807 public String getLastTrackId() { 808 return _lastTrackId; 809 } 810 811 public String getLastTrackName() { 812 Location location = locationManager.getLocationById(getLastLocationId()); 813 if (location != null) { 814 Track track = location.getTrackById(getLastTrackId()); 815 if (track != null) { 816 return track.getName(); 817 } 818 } 819 return NONE; 820 } 821 822 public void setMoves(int moves) { 823 int old = _moves; 824 _moves = moves; 825 if (old != moves) { 826 setDirtyAndFirePropertyChange("rolling stock moves", old, moves); // NOI18N 827 } 828 } 829 830 public int getMoves() { 831 return _moves; 832 } 833 834 /** 835 * Sets the train that will service this rolling stock. 836 * 837 * @param train The Train. 838 */ 839 public void setTrain(Train train) { 840 Train old = _train; 841 _train = train; 842 if (old != train) { 843 if (old != null) { 844 old.removePropertyChangeListener(this); 845 } 846 if (train != null) { 847 train.addPropertyChangeListener(this); 848 } 849 setDirtyAndFirePropertyChange(TRAIN_CHANGED_PROPERTY, old, train); 850 } 851 } 852 853 public Train getTrain() { 854 return _train; 855 } 856 857 public String getTrainName() { 858 if (getTrain() != null) { 859 return getTrain().getName(); 860 } 861 return NONE; 862 } 863 864 /** 865 * Sets the last train that serviced this rolling stock. 866 * 867 * @param train The last Train. 868 */ 869 public void setLastTrain(Train train) { 870 Train old = _lastTrain; 871 _lastTrain = train; 872 if (old != train) { 873 if (old != null) { 874 old.removePropertyChangeListener(this); 875 } 876 if (train != null) { 877 train.addPropertyChangeListener(this); 878 } 879 setDirtyAndFirePropertyChange(TRAIN_CHANGED_PROPERTY, old, train); 880 } 881 } 882 883 public Train getLastTrain() { 884 return _lastTrain; 885 } 886 887 public String getLastTrainName() { 888 if (getLastTrain() != null) { 889 return getLastTrain().getName(); 890 } 891 return NONE; 892 } 893 894 /** 895 * Sets the location where the rolling stock will be picked up by the train. 896 * 897 * @param routeLocation the pick up location for this rolling stock. 898 */ 899 public void setRouteLocation(RouteLocation routeLocation) { 900 // a couple of error checks before setting the route location 901 if (getLocation() == null && routeLocation != null) { 902 log.debug("WARNING rolling stock ({}) does not have an assigned location", this); // NOI18N 903 } else if (routeLocation != null && 904 getLocation() != null && 905 !routeLocation.getName().equals(getLocation().getName())) { 906 log.error("ERROR route location name({}) not equal to location name ({}) for rolling stock ({})", 907 routeLocation.getName(), getLocation().getName(), this); // NOI18N 908 } 909 RouteLocation old = _routeLocation; 910 _routeLocation = routeLocation; 911 if (old != routeLocation) { 912 setDirtyAndFirePropertyChange(ROUTE_LOCATION_CHANGED_PROPERTY, old, routeLocation); 913 } 914 } 915 916 /** 917 * Where in a train's route this car resides 918 * 919 * @return the location in a train's route 920 */ 921 public RouteLocation getRouteLocation() { 922 return _routeLocation; 923 } 924 925 public String getRouteLocationId() { 926 if (getRouteLocation() != null) { 927 return getRouteLocation().getId(); 928 } 929 return NONE; 930 } 931 932 /** 933 * Used to determine which train delivered a car to an interchange track. 934 * 935 * @return the route id of the last train delivering this car. 936 */ 937 public String getLastRouteId() { 938 return _routeId; 939 } 940 941 /** 942 * Sets the id of the route that was used to set out the rolling stock. Used 943 * to determine if the rolling stock can be pick ups from an interchange 944 * track. 945 * 946 * @param id The route id. 947 */ 948 public void setLastRouteId(String id) { 949 _routeId = id; 950 } 951 952 public String getValue() { 953 return _value; 954 } 955 956 /** 957 * Sets the value (cost, price) for this rolling stock. Currently has 958 * nothing to do with operations. But nice to have. 959 * 960 * @param value a string representing what this item is worth. 961 */ 962 public void setValue(String value) { 963 String old = _value; 964 _value = value; 965 if (!old.equals(value)) { 966 setDirtyAndFirePropertyChange("rolling stock value", old, value); // NOI18N 967 } 968 } 969 970 public String getRfid() { 971 return _rfid; 972 } 973 974 /** 975 * Sets the RFID for this rolling stock. 976 * 977 * @param id 12 character RFID string. 978 */ 979 public void setRfid(String id) { 980 String old = _rfid; 981 if (id != null && !id.equals(old)) { 982 log.debug("Setting IdTag for {} to {}", this, id); 983 _rfid = id; 984 if (!id.equals(NONE)) { 985 try { 986 IdTag tag = InstanceManager.getDefault(IdTagManager.class).provideIdTag(id); 987 setIdTag(tag); 988 } catch (IllegalArgumentException e) { 989 log.error("Exception recording tag {} - exception value {}", id, e.getMessage()); 990 } 991 } 992 setDirtyAndFirePropertyChange("rolling stock rfid", old, id); // NOI18N 993 } 994 } 995 996 public IdTag getIdTag() { 997 return _tag; 998 } 999 1000 /** 1001 * Sets the id tag for this rolling stock. The id tag isn't saved, between 1002 * session but the tag label is saved as _rfid. 1003 * 1004 * @param tag the id tag 1005 */ 1006 public void setIdTag(IdTag tag) { 1007 if (_tag != null) { 1008 _tag.removePropertyChangeListener(_tagListener); 1009 } 1010 _tag = tag; 1011 if (_tagListener == null) { 1012 // store the tag listener so we can reuse it and 1013 // dispose of it as necessary. 1014 _tagListener = new PropertyChangeListener() { 1015 @Override 1016 public void propertyChange(java.beans.PropertyChangeEvent e) { 1017 if (e.getPropertyName().equals("whereLastSeen")) { 1018 log.debug("Tag Reader Position update received for {}", this); 1019 // update the position of this piece of rolling 1020 // stock when its IdTag is seen, but only if 1021 // the actual location changes. 1022 if (e.getNewValue() != null) { 1023 // first, check to see if this reader is 1024 // associated with a track. 1025 Track newTrack = locationManager.getTrackByReporter((jmri.Reporter) e.getNewValue()); 1026 if (newTrack != null) { 1027 if (newTrack != getTrack()) { 1028 // set the car's location based on the track. 1029 setLocation(newTrack.getLocation(), newTrack); 1030 // also notify listeners that the last seen 1031 // location has changed. 1032 setDirtyAndFirePropertyChange("rolling stock whereLastSeen", _whereLastSeen, 1033 _whereLastSeen = newTrack.getLocation()); 1034 1035 } 1036 } else { 1037 // the reader isn't associated with a track, 1038 Location newLocation = locationManager 1039 .getLocationByReporter((jmri.Reporter) e.getNewValue()); 1040 if (newLocation != getLocation()) { 1041 // we really should be able to set the 1042 // location where we last saw the tag: 1043 // setLocation(newLocation,null); 1044 // for now, notify listeners that the 1045 // location changed. 1046 setDirtyAndFirePropertyChange("rolling stock whereLastSeen", _whereLastSeen, 1047 _whereLastSeen = newLocation); 1048 1049 } 1050 } 1051 } 1052 } 1053 if (e.getPropertyName().equals("whenLastSeen")) { 1054 log.debug("Tag Reader Time at Location update received for {}", this); 1055 // update the time when this car was last moved 1056 // stock when its IdTag is seen. 1057 if (e.getNewValue() != null) { 1058 LocalDateTime newDate = ((LocalDateTime) e.getNewValue()); 1059 setLastDate(newDate); 1060 // and notify listeners when last seen was updated. 1061 setDirtyAndFirePropertyChange("rolling stock whenLastSeen", _whenLastSeen, 1062 _whenLastSeen = newDate); 1063 } 1064 } 1065 } 1066 }; 1067 } 1068 if (_tag != null) { 1069 _tag.addPropertyChangeListener(_tagListener); 1070 setRfid(_tag.getSystemName()); 1071 } else { 1072 setRfid(NONE); 1073 } 1074 // initilize _whenLastSeen and _whereLastSeen for property 1075 // change notification. 1076 _whereLastSeen = getWhereLastSeen(); 1077 _whenLastSeen = getWhenLastSeen(); 1078 } 1079 1080 public String getWhereLastSeenName() { 1081 if (getWhereLastSeen() != null) { 1082 return getWhereLastSeen().getName(); 1083 } 1084 return NONE; 1085 } 1086 1087 public Location getWhereLastSeen() { 1088 if (_tag == null) { 1089 return null; 1090 } 1091 jmri.Reporter r = _tag.getWhereLastSeen(); 1092 Track t = locationManager.getTrackByReporter(r); 1093 if (t != null) { 1094 return t.getLocation(); 1095 } 1096 // the reader isn't associated with a track, return 1097 // the location it is associated with, which might be null. 1098 return locationManager.getLocationByReporter(r); 1099 } 1100 1101 public Track getTrackLastSeen() { 1102 if (_tag == null) { 1103 // there isn't a tag associated with this piece of rolling stock. 1104 return null; 1105 } 1106 jmri.Reporter r = _tag.getWhereLastSeen(); 1107 if (r == null) { 1108 // there is a tag associated with this piece 1109 // of rolling stock, but no reporter has seen it (or it was reset). 1110 return null; 1111 } 1112 // this return value will be null, if there isn't an associated track 1113 // for the last reporter. 1114 return locationManager.getTrackByReporter(r); 1115 } 1116 1117 public String getTrackLastSeenName() { 1118 // let getTrackLastSeen() find the track, if it exists. 1119 Track t = getTrackLastSeen(); 1120 if (t != null) { 1121 // if there is a track, return the name. 1122 return t.getName(); 1123 } 1124 // otherwise, there is no track to return the name of. 1125 return NONE; 1126 } 1127 1128 public LocalDateTime getWhenLastSeen() { 1129 if (_tag != null) { 1130 Date date = _tag.getWhenLastSeen(); 1131 if (date != null) { 1132 return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); 1133 } 1134 } 1135 return null; // never seen, so no date. 1136 } 1137 1138 /** 1139 * Provides the last date when this rolling stock was moved, or was reset 1140 * from a built train, as a string. 1141 * 1142 * @return date 1143 */ 1144 public String getWhenLastSeenDate() { 1145 if (getWhenLastSeen() == null) { 1146 return NONE; // return an empty string for the default date. 1147 } 1148 SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // NOI18N 1149 return format.format(getWhenLastSeen()); 1150 } 1151 1152 /** 1153 * Provides the last date when this rolling stock was moved 1154 * 1155 * @return String yyyy/MM/dd HH:mm:ss 1156 */ 1157 public String getSortDate() { 1158 if (_lastDate.equals(epoch)) { 1159 return NONE; // return an empty string for the default date. 1160 } 1161 DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); // NOI18N 1162 return _lastDate.format(format); 1163 } 1164 1165 public static final int DATE_TIME_LENGTH = 19; 1166 1167 /** 1168 * Provides the last date when this rolling stock was moved 1169 * 1170 * @return String MM/dd/yyyy HH:mm:ss 1171 */ 1172 public String getLastDate() { 1173 if (_lastDate.equals(epoch)) { 1174 return NONE; // return an empty string for the default date. 1175 } 1176 DateTimeFormatter format = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss"); // NOI18N 1177 return _lastDate.format(format); 1178 } 1179 1180 /** 1181 * Sets the last date when this rolling stock was moved 1182 * 1183 * @param date The Date when this rolling stock was last moved. 1184 */ 1185 public void setLastDate(LocalDateTime date) { 1186 LocalDateTime old = _lastDate; 1187 _lastDate = date; 1188 if (!old.equals(_lastDate)) { 1189 setDirtyAndFirePropertyChange("rolling stock date", old, date); // NOI18N 1190 } 1191 } 1192 1193 /** 1194 * Provides the last date when this rolling stock was moved 1195 * 1196 * @return date 1197 */ 1198 public LocalDateTime getLastMoveDate() { 1199 return _lastDate; 1200 } 1201 1202 /** 1203 * Sets the last date when this rolling stock was moved. This method is used 1204 * only for loading data from a file. Use setLastDate(Date) instead. 1205 * 1206 * @param date MM/dd/yyyy HH:mm:ss, MM/dd/yyyy hh:mm a, or MM/dd/yyyy HH:mm 1207 */ 1208 public void setLastDate(String date) { 1209 LocalDateTime d = TrainCommon.convertStringToLocalDateTime(date); 1210 if (d != null) { 1211 _lastDate = d; 1212 } 1213 } 1214 1215 public void setBlocking(int number) { 1216 int old = _blocking; 1217 _blocking = number; 1218 if (old != number) { 1219 setDirtyAndFirePropertyChange("rolling stock blocking changed", old, number); // NOI18N 1220 } 1221 } 1222 1223 public int getBlocking() { 1224 return _blocking; 1225 } 1226 1227 /** 1228 * Set where in a train's route this rolling stock will be set out. 1229 * 1230 * @param routeDestination the location where the rolling stock is to leave 1231 * the train. 1232 */ 1233 public void setRouteDestination(RouteLocation routeDestination) { 1234 if (routeDestination != null && 1235 getDestination() != null && 1236 !routeDestination.getName().equals(getDestination().getName())) { 1237 log.debug("WARNING route destination name ({}) not equal to destination name ({}) for rolling stock ({})", 1238 routeDestination.getName(), getDestination().getName(), this); // NOI18N 1239 } 1240 RouteLocation old = _routeDestination; 1241 _routeDestination = routeDestination; 1242 if (old != routeDestination) { 1243 setDirtyAndFirePropertyChange(ROUTE_DESTINATION_CHANGED_PROPERTY, old, routeDestination); 1244 } 1245 } 1246 1247 public RouteLocation getRouteDestination() { 1248 return _routeDestination; 1249 } 1250 1251 public String getRouteDestinationId() { 1252 if (getRouteDestination() != null) { 1253 return getRouteDestination().getId(); 1254 } 1255 return NONE; 1256 } 1257 1258 public void setRouteDestinationTiming(RouteLocation routeDestination) { 1259 _routeDestinationTiming = routeDestination; 1260 } 1261 1262 public RouteLocation getRouteDestinationTiming() { 1263 return _routeDestinationTiming; 1264 } 1265 1266 public void setOwnerName(String owner) { 1267 String old = _owner; 1268 _owner = owner; 1269 if (!old.equals(owner)) { 1270 setDirtyAndFirePropertyChange("rolling stock owner", old, owner); // NOI18N 1271 } 1272 } 1273 1274 public String getOwnerName() { 1275 return _owner; 1276 } 1277 1278 /** 1279 * Set the rolling stock location as unknown. 1280 * 1281 * @param unknown when true, the rolling stock location is unknown. 1282 */ 1283 public void setLocationUnknown(boolean unknown) { 1284 boolean old = _locationUnknown; 1285 _locationUnknown = unknown; 1286 if (!old == unknown) { 1287 setDirtyAndFirePropertyChange("car location known", old, unknown); // NOI18N 1288 } 1289 } 1290 1291 /** 1292 * @return true when car's location is unknown 1293 */ 1294 public boolean isLocationUnknown() { 1295 return _locationUnknown; 1296 } 1297 1298 /** 1299 * Sets the rolling stock service state. When true, rolling stock is out of 1300 * service. Normal state is false, the rolling stock is in service and 1301 * available. 1302 * 1303 * @param outOfService when true, out of service 1304 */ 1305 public void setOutOfService(boolean outOfService) { 1306 boolean old = _outOfService; 1307 _outOfService = outOfService; 1308 if (!old == outOfService) { 1309 setDirtyAndFirePropertyChange("car out of service", old, outOfService); // NOI18N 1310 } 1311 } 1312 1313 /** 1314 * @return true when rolling stock is out of service 1315 */ 1316 public boolean isOutOfService() { 1317 return _outOfService; 1318 } 1319 1320 public void setSelected(boolean selected) { 1321 boolean old = _selected; 1322 _selected = selected; 1323 if (!old == selected) { 1324 setDirtyAndFirePropertyChange("selected", old, selected); // NOI18N 1325 } 1326 } 1327 1328 /** 1329 * @return true when rolling stock is selected 1330 */ 1331 public boolean isSelected() { 1332 return _selected; 1333 } 1334 1335 public void setComment(String comment) { 1336 String old = _comment; 1337 _comment = comment; 1338 if (!old.equals(comment)) { 1339 setDirtyAndFirePropertyChange(COMMENT_CHANGED_PROPERTY, old, comment); 1340 } 1341 } 1342 1343 public String getComment() { 1344 return _comment; 1345 } 1346 1347 public void setPickupTime(String time) { 1348 String old = _pickupTime; 1349 _pickupTime = time; 1350 setDirtyAndFirePropertyChange("Pickup Time Changed", old, time); // NOI18N 1351 } 1352 1353 public String getPickupTime() { 1354 if (getTrain() != null) { 1355 return _pickupTime; 1356 } 1357 return NONE; 1358 } 1359 1360 public void setSetoutTime(String time) { 1361 String old = _setoutTime; 1362 _setoutTime = time; 1363 setDirtyAndFirePropertyChange("Setout Time Changed", old, time); // NOI18N 1364 } 1365 1366 public String getSetoutTime() { 1367 if (getTrain() != null && getTrain().isBuilding() && getRouteDestination() != null) { 1368 _setoutTime = getTrain().getExpectedArrivalTime(getRouteDestination(), true); 1369 } 1370 if (getTrain() != null) { 1371 return _setoutTime; 1372 } 1373 return NONE; 1374 } 1375 1376 /** 1377 * Used when building Manifest 1378 * @param order the order rolling stock is added to train 1379 */ 1380 public void setOrder(String order) { 1381 _order = order; 1382 } 1383 1384 public String getOrder() { 1385 return _order; 1386 } 1387 1388 protected void moveRollingStock(RouteLocation current, RouteLocation next) { 1389 if (current == getRouteLocation()) { 1390 setLastDate(LocalDateTime.now()); 1391 // Arriving at destination? 1392 if (getRouteLocation() == getRouteDestination() || next == null) { 1393 if (getRouteLocation() == getRouteDestination()) { 1394 log.debug("Rolling stock ({}) has arrived at destination ({})", this, getDestination()); 1395 } else { 1396 log.error("Rolling stock ({}) has a null route location for next", this); // NOI18N 1397 } 1398 setLocation(getDestination(), getDestinationTrack(), RollingStock.FORCE); // force RS to destination 1399 setDestination(null, null); // this also clears the route locations 1400 setLastTrain(getTrain()); // save the last train moving this rs 1401 setTrain(null); // this must come after setDestination (route id is set) 1402 setMoves(getMoves() + 1); // bump count 1403 setPickupTime(NONE); 1404 } else { 1405 log.debug("Rolling stock ({}) is in train ({}) leaves location ({}) destination ({})", this, 1406 getTrainName(), current.getName(), next.getName()); 1407 setLocation(next.getLocation(), null, RollingStock.FORCE); // force RS to location 1408 setRouteLocation(next); 1409 } 1410 } 1411 } 1412 1413 public void reset() { 1414 setPickupTime(NONE); 1415 // the order of the next two instructions is important, otherwise rs will have 1416 // train's route id 1417 setTrain(null); 1418 setDestination(null, null); 1419 } 1420 1421 /* 1422 * Clone has been reset and is in the process of being destroyed, move 1423 * original car back and restore the car's settings. 1424 */ 1425 protected void destroyCloneReset(RollingStock rs) { 1426 rs.setLocation(getLocation(), getTrack(), RollingStock.FORCE); 1427 rs.setRouteDestination(null); 1428 rs.setLastTrain(getLastTrain()); 1429 rs.setLastRouteId(getLastRouteId()); 1430 rs.setLastDate(getLastDate()); 1431 rs.setLastLocationId(getLastLocationId()); 1432 rs.setLastTrackId(getLastTrackId()); 1433 rs.setMoves(getMoves()); 1434 } 1435 1436 /** 1437 * Remove rolling stock. Releases all listeners. 1438 */ 1439 public void dispose() { 1440 setTrain(null); 1441 setDestination(null, null); 1442 setLocation(null, null); 1443 removePropertyChangeListeners(); 1444 if (getIdTag() != null) { 1445 getIdTag().removePropertyChangeListener(_tagListener); 1446 } 1447 } 1448 1449 /** 1450 * Construct this Entry from XML. 1451 * 1452 * @param e RollingStock XML element 1453 */ 1454 public RollingStock(org.jdom2.Element e) { 1455 this(); 1456 org.jdom2.Attribute a; 1457 if ((a = e.getAttribute(Xml.ID)) != null) { 1458 _id = a.getValue(); 1459 } else { 1460 log.warn("no id attribute in rolling stock element when reading operations"); 1461 } 1462 if ((a = e.getAttribute(Xml.ORDER)) != null) { 1463 _order = a.getValue(); 1464 } 1465 if ((a = e.getAttribute(Xml.ROAD_NUMBER)) != null) { 1466 _number = a.getValue(); 1467 } 1468 if ((a = e.getAttribute(Xml.ROAD_NAME)) != null) { 1469 _road = a.getValue(); 1470 } 1471 if (_id == null || !_id.equals(createId(_road, _number))) { 1472 _id = createId(_road, _number); 1473 } 1474 if ((a = e.getAttribute(Xml.TYPE)) != null) { 1475 _type = a.getValue(); 1476 } 1477 if ((a = e.getAttribute(Xml.LENGTH)) != null) { 1478 _length = a.getValue(); 1479 } 1480 if ((a = e.getAttribute(Xml.COLOR)) != null) { 1481 _color = a.getValue(); 1482 } 1483 if ((a = e.getAttribute(Xml.WEIGHT)) != null) { 1484 _weight = a.getValue(); 1485 } 1486 if ((a = e.getAttribute(Xml.WEIGHT_TONS)) != null) { 1487 _weightTons = a.getValue(); 1488 } 1489 if ((a = e.getAttribute(Xml.BUILT)) != null) { 1490 _built = a.getValue(); 1491 } 1492 if ((a = e.getAttribute(Xml.CLONE)) != null) { 1493 _clone = a.getValue().equals(Xml.TRUE); 1494 } 1495 Location location = null; 1496 Track track = null; 1497 if ((a = e.getAttribute(Xml.LOCATION_ID)) != null) { 1498 location = locationManager.getLocationById(a.getValue()); 1499 } 1500 if ((a = e.getAttribute(Xml.SEC_LOCATION_ID)) != null && location != null) { 1501 track = location.getTrackById(a.getValue()); 1502 } 1503 setLocation(location, track, RollingStock.FORCE); // force location 1504 1505 Location destination = null; 1506 track = null; 1507 if ((a = e.getAttribute(Xml.DESTINATION_ID)) != null) { 1508 destination = locationManager.getLocationById(a.getValue()); 1509 } 1510 if ((a = e.getAttribute(Xml.SEC_DESTINATION_ID)) != null && destination != null) { 1511 track = destination.getTrackById(a.getValue()); 1512 } 1513 setDestination(destination, track, RollingStock.FORCE); // force destination 1514 1515 if ((a = e.getAttribute(Xml.DIVISION_ID)) != null) { 1516 _division = InstanceManager.getDefault(DivisionManager.class).getDivisionById(a.getValue()); 1517 } 1518 // make xml error "DivisionId" backward compatible 1519 if ((a = e.getAttribute(Xml.DIVISION_ID_ERROR)) != null) { 1520 _division = InstanceManager.getDefault(DivisionManager.class).getDivisionById(a.getValue()); 1521 } 1522 if ((a = e.getAttribute(Xml.MOVES)) != null) { 1523 try { 1524 _moves = Integer.parseInt(a.getValue()); 1525 } catch (NumberFormatException nfe) { 1526 log.error("Move count ({}) for rollingstock ({}) isn't a valid number!", a.getValue(), toString()); 1527 } 1528 } 1529 if ((a = e.getAttribute(Xml.LAST_LOCATION_ID)) != null) { 1530 _lastLocationId = a.getValue(); 1531 } 1532 if ((a = e.getAttribute(Xml.LAST_TRACK_ID)) != null) { 1533 _lastTrackId = a.getValue(); 1534 } 1535 if ((a = e.getAttribute(Xml.TRAIN_ID)) != null) { 1536 setTrain(InstanceManager.getDefault(TrainManager.class).getTrainById(a.getValue())); 1537 } else if ((a = e.getAttribute(Xml.TRAIN)) != null) { 1538 setTrain(InstanceManager.getDefault(TrainManager.class).getTrainByName(a.getValue())); 1539 } 1540 if (getTrain() != null && 1541 getTrain().getRoute() != null && 1542 (a = e.getAttribute(Xml.ROUTE_LOCATION_ID)) != null) { 1543 _routeLocation = getTrain().getRoute().getRouteLocationById(a.getValue()); 1544 if ((a = e.getAttribute(Xml.ROUTE_DESTINATION_ID)) != null) { 1545 _routeDestination = getTrain().getRoute().getRouteLocationById(a.getValue()); 1546 } 1547 } 1548 if ((a = e.getAttribute(Xml.LAST_TRAIN_ID)) != null) { 1549 setLastTrain(InstanceManager.getDefault(TrainManager.class).getTrainById(a.getValue())); 1550 } 1551 if ((a = e.getAttribute(Xml.LAST_ROUTE_ID)) != null) { 1552 _routeId = a.getValue(); 1553 } 1554 if ((a = e.getAttribute(Xml.OWNER)) != null) { 1555 _owner = a.getValue(); 1556 } 1557 if ((a = e.getAttribute(Xml.COMMENT)) != null) { 1558 _comment = a.getValue(); 1559 } 1560 if ((a = e.getAttribute(Xml.VALUE)) != null) { 1561 _value = a.getValue(); 1562 } 1563 if ((a = e.getAttribute(Xml.RFID)) != null) { 1564 setRfid(a.getValue()); 1565 } 1566 if ((a = e.getAttribute(Xml.LOC_UNKNOWN)) != null) { 1567 _locationUnknown = a.getValue().equals(Xml.TRUE); 1568 } 1569 if ((a = e.getAttribute(Xml.OUT_OF_SERVICE)) != null) { 1570 _outOfService = a.getValue().equals(Xml.TRUE); 1571 } 1572 if ((a = e.getAttribute(Xml.SELECTED)) != null) { 1573 _selected = a.getValue().equals(Xml.TRUE); 1574 } 1575 if ((a = e.getAttribute(Xml.DATE)) != null) { 1576 setLastDate(a.getValue()); // uses the setLastDate(String) method. 1577 } 1578 if ((a = e.getAttribute(Xml.PICKUP_TIME)) != null) { 1579 _pickupTime = a.getValue(); 1580 } 1581 if ((a = e.getAttribute(Xml.SETOUT_TIME)) != null) { 1582 _setoutTime = a.getValue(); 1583 } 1584 if ((a = e.getAttribute(Xml.BLOCKING)) != null) { 1585 try { 1586 _blocking = Integer.parseInt(a.getValue()); 1587 } catch (NumberFormatException nfe) { 1588 log.error("Blocking ({}) for rollingstock ({}) isn't a valid number!", a.getValue(), toString()); 1589 } 1590 } 1591 // check for rolling stock without a track assignment 1592 if (getLocation() != null && getTrack() == null && getTrain() == null) { 1593 log.warn("Rollingstock ({}) at ({}) doesn't have a track assignment", this, getLocationName()); 1594 } 1595 addPropertyChangeListeners(); 1596 } 1597 1598 /** 1599 * Add XML elements to represent this Entry. 1600 * 1601 * @param e Element for car or engine store. 1602 * @return Contents in a JDOM Element 1603 */ 1604 protected org.jdom2.Element store(org.jdom2.Element e) { 1605 e.setAttribute(Xml.ID, getId()); 1606 e.setAttribute(Xml.ROAD_NAME, getRoadName()); 1607 e.setAttribute(Xml.ROAD_NUMBER, getNumber()); 1608 e.setAttribute(Xml.TYPE, getTypeName()); 1609 e.setAttribute(Xml.LENGTH, getLength()); 1610 if (!getColorName().equals(NONE)) { 1611 e.setAttribute(Xml.COLOR, getColorName()); 1612 } 1613 if (!getWeight().equals(DEFAULT_WEIGHT)) { 1614 e.setAttribute(Xml.WEIGHT, getWeight()); 1615 } 1616 if (!getWeightTons().equals(NONE)) { 1617 e.setAttribute(Xml.WEIGHT_TONS, getWeightTons()); 1618 } 1619 if (!getBuilt().equals(NONE)) { 1620 e.setAttribute(Xml.BUILT, getBuilt()); 1621 } 1622 if (!getLocationId().equals(NONE)) { 1623 e.setAttribute(Xml.LOCATION_ID, getLocationId()); 1624 } 1625 if (!getRouteLocationId().equals(NONE)) { 1626 e.setAttribute(Xml.ROUTE_LOCATION_ID, getRouteLocationId()); 1627 } 1628 if (!getTrackId().equals(NONE)) { 1629 e.setAttribute(Xml.SEC_LOCATION_ID, getTrackId()); 1630 } 1631 if (!getDestinationId().equals(NONE)) { 1632 e.setAttribute(Xml.DESTINATION_ID, getDestinationId()); 1633 } 1634 if (!getRouteDestinationId().equals(NONE)) { 1635 e.setAttribute(Xml.ROUTE_DESTINATION_ID, getRouteDestinationId()); 1636 } 1637 if (!getDestinationTrackId().equals(NONE)) { 1638 e.setAttribute(Xml.SEC_DESTINATION_ID, getDestinationTrackId()); 1639 } 1640 if (!getDivisionId().equals(NONE)) { 1641 e.setAttribute(Xml.DIVISION_ID, getDivisionId()); 1642 } 1643 if (!getLastRouteId().equals(NONE)) { 1644 e.setAttribute(Xml.LAST_ROUTE_ID, getLastRouteId()); 1645 } 1646 if (isClone()) { 1647 e.setAttribute(Xml.CLONE, isClone() ? Xml.TRUE : Xml.FALSE); 1648 } 1649 e.setAttribute(Xml.MOVES, Integer.toString(getMoves())); 1650 e.setAttribute(Xml.DATE, getLastDate()); 1651 e.setAttribute(Xml.SELECTED, isSelected() ? Xml.TRUE : Xml.FALSE); 1652 if (!getLastLocationId().equals(LOCATION_UNKNOWN)) { 1653 e.setAttribute(Xml.LAST_LOCATION_ID, getLastLocationId()); 1654 } 1655 if (!getLastTrackId().equals(LOCATION_UNKNOWN)) { 1656 e.setAttribute(Xml.LAST_TRACK_ID, getLastTrackId()); 1657 } 1658 if (!getTrainName().equals(NONE)) { 1659 e.setAttribute(Xml.TRAIN, getTrainName()); 1660 e.setAttribute(Xml.TRAIN_ID, getTrain().getId()); 1661 e.setAttribute(Xml.ORDER, getOrder()); 1662 } 1663 if (!getLastTrainName().equals(NONE)) { 1664 e.setAttribute(Xml.LAST_TRAIN, getLastTrainName()); 1665 e.setAttribute(Xml.LAST_TRAIN_ID, getLastTrain().getId()); 1666 } 1667 if (!getOwnerName().equals(NONE)) { 1668 e.setAttribute(Xml.OWNER, getOwnerName()); 1669 } 1670 if (!getValue().equals(NONE)) { 1671 e.setAttribute(Xml.VALUE, getValue()); 1672 } 1673 if (!getRfid().equals(NONE)) { 1674 e.setAttribute(Xml.RFID, getRfid()); 1675 } 1676 if (isLocationUnknown()) { 1677 e.setAttribute(Xml.LOC_UNKNOWN, isLocationUnknown() ? Xml.TRUE : Xml.FALSE); 1678 } 1679 if (isOutOfService()) { 1680 e.setAttribute(Xml.OUT_OF_SERVICE, isOutOfService() ? Xml.TRUE : Xml.FALSE); 1681 } 1682 if (!getPickupTime().equals(NONE)) { 1683 e.setAttribute(Xml.PICKUP_TIME, getPickupTime()); 1684 } 1685 if (!getSetoutTime().equals(NONE)) { 1686 e.setAttribute(Xml.SETOUT_TIME, getSetoutTime()); 1687 } 1688 if (getBlocking() != 0) { 1689 e.setAttribute(Xml.BLOCKING, Integer.toString(getBlocking())); 1690 } 1691 if (!getComment().equals(NONE)) { 1692 e.setAttribute(Xml.COMMENT, getComment()); 1693 } 1694 return e; 1695 } 1696 1697 protected void addPropertyChangeListeners() { 1698 InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this); 1699 InstanceManager.getDefault(CarOwners.class).addPropertyChangeListener(this); 1700 InstanceManager.getDefault(CarColors.class).addPropertyChangeListener(this); 1701 } 1702 1703 protected void removePropertyChangeListeners() { 1704 InstanceManager.getDefault(CarRoads.class).removePropertyChangeListener(this); 1705 InstanceManager.getDefault(CarOwners.class).removePropertyChangeListener(this); 1706 InstanceManager.getDefault(CarColors.class).removePropertyChangeListener(this); 1707 } 1708 1709 // rolling stock listens for changes in a location name or if a location is 1710 // deleted 1711 @Override 1712 public void propertyChange(PropertyChangeEvent e) { 1713 // log.debug("Property change for rolling stock: " + toString()+ " property 1714 // name: " 1715 // +e.getPropertyName()+ " old: "+e.getOldValue()+ " new: "+e.getNewValue()); 1716 // notify if track or location name changes 1717 if (e.getPropertyName().equals(Location.NAME_CHANGED_PROPERTY)) { 1718 log.debug("Property change for rolling stock: ({}) property name: ({}) old: ({}) new: ({})", this, 1719 e.getPropertyName(), e.getOldValue(), e.getNewValue()); 1720 setDirtyAndFirePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue()); 1721 } 1722 if (e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) { 1723 if (e.getSource() == getLocation()) { 1724 log.debug("delete location for rolling stock: ({})", this); 1725 setLocation(null, null); 1726 } 1727 if (e.getSource() == getDestination()) { 1728 log.debug("delete destination for rolling stock: ({})", this); 1729 setDestination(null, null); 1730 } 1731 } 1732 if (e.getPropertyName().equals(Track.DISPOSE_CHANGED_PROPERTY)) { 1733 if (e.getSource() == getTrack()) { 1734 log.debug("delete location for rolling stock: ({})", this); 1735 setLocation(getLocation(), null); 1736 } 1737 if (e.getSource() == getDestinationTrack()) { 1738 log.debug("delete destination for rolling stock: ({})", this); 1739 setDestination(getDestination(), null); 1740 } 1741 } 1742 if (e.getPropertyName().equals(Train.DISPOSE_CHANGED_PROPERTY) && e.getSource() == getTrain()) { 1743 log.debug("delete train for rolling stock: ({})", this); 1744 setTrain(null); 1745 } 1746 if (e.getPropertyName().equals(Train.TRAIN_LOCATION_CHANGED_PROPERTY) && e.getSource() == getTrain()) { 1747 log.debug("Rolling stock ({}) is serviced by train ({})", this, getTrainName()); 1748 moveRollingStock((RouteLocation) e.getOldValue(), (RouteLocation) e.getNewValue()); 1749 } 1750 if (e.getPropertyName().equals(Train.STATUS_CHANGED_PROPERTY) && 1751 e.getNewValue().equals(Train.TRAIN_RESET) && 1752 e.getSource() == getTrain()) { 1753 log.debug("Rolling stock ({}) is removed from train ({}) by reset", this, getTrainName()); // NOI18N 1754 reset(); 1755 } 1756 if (e.getPropertyName().equals(Train.NAME_CHANGED_PROPERTY)) { 1757 setDirtyAndFirePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue()); 1758 } 1759 if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) { 1760 if (e.getOldValue().equals(getRoadName())) { 1761 log.debug("Rolling stock ({}) sees road name change from ({}) to ({})", this, e.getOldValue(), 1762 e.getNewValue()); // NOI18N 1763 if (e.getNewValue() != null) { 1764 setRoadName((String) e.getNewValue()); 1765 } 1766 } 1767 } 1768 if (e.getPropertyName().equals(CarOwners.CAROWNERS_NAME_CHANGED_PROPERTY)) { 1769 if (e.getOldValue().equals(getOwnerName())) { 1770 log.debug("Rolling stock ({}) sees owner name change from ({}) to ({})", this, e.getOldValue(), 1771 e.getNewValue()); // NOI18N 1772 setOwnerName((String) e.getNewValue()); 1773 } 1774 } 1775 if (e.getPropertyName().equals(CarColors.CARCOLORS_NAME_CHANGED_PROPERTY)) { 1776 if (e.getOldValue().equals(getColorName())) { 1777 log.debug("Rolling stock ({}) sees color name change from ({}) to ({})", this, e.getOldValue(), 1778 e.getNewValue()); // NOI18N 1779 setColorName((String) e.getNewValue()); 1780 } 1781 } 1782 } 1783 1784 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 1785 firePropertyChange(p, old, n); 1786 } 1787 1788 private static final Logger log = LoggerFactory.getLogger(RollingStock.class); 1789 1790}