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