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