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