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