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