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