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