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