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