001package jmri.jmrit.operations.rollingstock.cars;
002
003import java.beans.PropertyChangeEvent;
004import java.util.ArrayList;
005import java.util.List;
006
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.InstanceManager;
011import jmri.jmrit.operations.locations.*;
012import jmri.jmrit.operations.locations.schedules.Schedule;
013import jmri.jmrit.operations.locations.schedules.ScheduleItem;
014import jmri.jmrit.operations.rollingstock.RollingStock;
015import jmri.jmrit.operations.routes.RouteLocation;
016import jmri.jmrit.operations.trains.schedules.TrainSchedule;
017import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
018import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
019
020/**
021 * Represents a car on the layout
022 *
023 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2012, 2013, 2014,
024 *         2015, 2023, 2025
025 */
026public class Car extends RollingStock {
027
028    CarLoads carLoads = InstanceManager.getDefault(CarLoads.class);
029
030    protected boolean _passenger = false;
031    protected boolean _hazardous = false;
032    protected boolean _caboose = false;
033    protected boolean _fred = false;
034    protected boolean _utility = false;
035    protected boolean _loadGeneratedByStaging = false;
036    protected Kernel _kernel = null;
037    protected String _loadName = carLoads.getDefaultEmptyName();
038    protected int _wait = 0;
039
040    protected Location _rweDestination = null; // return when empty destination
041    protected Track _rweDestTrack = null; // return when empty track
042    protected String _rweLoadName = carLoads.getDefaultEmptyName();
043
044    protected Location _rwlDestination = null; // return when loaded destination
045    protected Track _rwlDestTrack = null; // return when loaded track
046    protected String _rwlLoadName = carLoads.getDefaultLoadName();
047
048    // schedule items
049    protected String _scheduleId = NONE; // the schedule id assigned to this car
050    protected String _nextLoadName = NONE; // next load by schedule
051    protected Location _finalDestination = null; 
052    protected Track _finalDestTrack = null; // final track by schedule or router
053    protected Location _previousFinalDestination = null;
054    protected Track _previousFinalDestTrack = null;
055    protected String _previousScheduleId = NONE;
056    protected String _pickupScheduleId = NONE;
057
058    protected String _routePath = NONE;
059
060    public static final String EXTENSION_REGEX = " ";
061    public static final String CABOOSE_EXTENSION = Bundle.getMessage("(C)");
062    public static final String FRED_EXTENSION = Bundle.getMessage("(F)");
063    public static final String PASSENGER_EXTENSION = Bundle.getMessage("(P)");
064    public static final String UTILITY_EXTENSION = Bundle.getMessage("(U)");
065    public static final String HAZARDOUS_EXTENSION = Bundle.getMessage("(H)");
066
067    public static final String LOAD_CHANGED_PROPERTY = "Car load changed"; // NOI18N
068    public static final String RWE_LOAD_CHANGED_PROPERTY = "Car RWE load changed"; // NOI18N
069    public static final String RWL_LOAD_CHANGED_PROPERTY = "Car RWL load changed"; // NOI18N
070    public static final String WAIT_CHANGED_PROPERTY = "Car wait changed"; // NOI18N
071    public static final String FINAL_DESTINATION_CHANGED_PROPERTY = "Car final destination changed"; // NOI18N
072    public static final String FINAL_DESTINATION_TRACK_CHANGED_PROPERTY = "Car final destination track changed"; // NOI18N
073    public static final String RETURN_WHEN_EMPTY_CHANGED_PROPERTY = "Car return when empty changed"; // NOI18N
074    public static final String RETURN_WHEN_LOADED_CHANGED_PROPERTY = "Car return when loaded changed"; // NOI18N
075    public static final String SCHEDULE_ID_CHANGED_PROPERTY = "car schedule id changed"; // NOI18N
076    public static final String KERNEL_NAME_CHANGED_PROPERTY = "kernel name changed"; // NOI18N
077
078    public Car() {
079        super();
080        loaded = true;
081    }
082
083    public Car(String road, String number) {
084        super(road, number);
085        loaded = true;
086        log.debug("New car ({} {})", road, number);
087        addPropertyChangeListeners();
088    }
089
090    @Override
091    public Car copy() {
092        Car car = new Car();
093        super.copy(car);
094        car.setLoadName(getLoadName());
095        car.setReturnWhenEmptyLoadName(getReturnWhenEmptyLoadName());
096        car.setReturnWhenLoadedLoadName(getReturnWhenLoadedLoadName());
097        car.setCarHazardous(isCarHazardous());
098        car.setCaboose(isCaboose());
099        car.setFred(hasFred());
100        car.setPassenger(isPassenger());
101        car.setLoadGeneratedFromStaging(isLoadGeneratedFromStaging());
102        car.loaded = true;
103        return car;
104    }
105
106    public void setCarHazardous(boolean hazardous) {
107        boolean old = _hazardous;
108        _hazardous = hazardous;
109        if (!old == hazardous) {
110            setDirtyAndFirePropertyChange("car hazardous", old ? "true" : "false", hazardous ? "true" : "false"); // NOI18N
111        }
112    }
113
114    public boolean isCarHazardous() {
115        return _hazardous;
116    }
117
118    public boolean isCarLoadHazardous() {
119        return carLoads.isHazardous(getTypeName(), getLoadName());
120    }
121
122    /**
123     * Used to determine if the car is hazardous or the car's load is hazardous.
124     * 
125     * @return true if the car or car's load is hazardous.
126     */
127    public boolean isHazardous() {
128        return isCarHazardous() || isCarLoadHazardous();
129    }
130
131    public void setPassenger(boolean passenger) {
132        boolean old = _passenger;
133        _passenger = passenger;
134        if (!old == passenger) {
135            setDirtyAndFirePropertyChange("car passenger", old ? "true" : "false", passenger ? "true" : "false"); // NOI18N
136        }
137    }
138
139    public boolean isPassenger() {
140        return _passenger;
141    }
142
143    public void setFred(boolean fred) {
144        boolean old = _fred;
145        _fred = fred;
146        if (!old == fred) {
147            setDirtyAndFirePropertyChange("car has fred", old ? "true" : "false", fred ? "true" : "false"); // NOI18N
148        }
149    }
150
151    /**
152     * Used to determine if car has FRED (Flashing Rear End Device).
153     *
154     * @return true if car has FRED.
155     */
156    public boolean hasFred() {
157        return _fred;
158    }
159
160    public void setLoadName(String load) {
161        String old = _loadName;
162        _loadName = load;
163        if (!old.equals(load)) {
164            setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, old, load);
165        }
166    }
167
168    /**
169     * The load name assigned to this car.
170     *
171     * @return The load name assigned to this car.
172     */
173    public String getLoadName() {
174        return _loadName;
175    }
176
177    public void setReturnWhenEmptyLoadName(String load) {
178        String old = _rweLoadName;
179        _rweLoadName = load;
180        if (!old.equals(load)) {
181            setDirtyAndFirePropertyChange(RWE_LOAD_CHANGED_PROPERTY, old, load);
182        }
183    }
184
185    public String getReturnWhenEmptyLoadName() {
186        return _rweLoadName;
187    }
188
189    public void setReturnWhenLoadedLoadName(String load) {
190        String old = _rwlLoadName;
191        _rwlLoadName = load;
192        if (!old.equals(load)) {
193            setDirtyAndFirePropertyChange(RWL_LOAD_CHANGED_PROPERTY, old, load);
194        }
195    }
196
197    public String getReturnWhenLoadedLoadName() {
198        return _rwlLoadName;
199    }
200
201    /**
202     * Gets the car's load's priority.
203     * 
204     * @return The car's load priority.
205     */
206    public String getLoadPriority() {
207        return (carLoads.getPriority(getTypeName(), getLoadName()));
208    }
209
210    /**
211     * Gets the car load's type, empty or load.
212     *
213     * @return type empty or type load
214     */
215    public String getLoadType() {
216        return (carLoads.getLoadType(getTypeName(), getLoadName()));
217    }
218
219    public String getPickupComment() {
220        return carLoads.getPickupComment(getTypeName(), getLoadName());
221    }
222
223    public String getDropComment() {
224        return carLoads.getDropComment(getTypeName(), getLoadName());
225    }
226
227    public void setLoadGeneratedFromStaging(boolean fromStaging) {
228        _loadGeneratedByStaging = fromStaging;
229    }
230
231    public boolean isLoadGeneratedFromStaging() {
232        return _loadGeneratedByStaging;
233    }
234
235    /**
236     * Used to keep track of which item in a schedule was used for this car.
237     * 
238     * @param id The ScheduleItem id for this car.
239     */
240    public void setScheduleItemId(String id) {
241        log.debug("Set schedule item id ({}) for car ({})", id, toString());
242        String old = _scheduleId;
243        _scheduleId = id;
244        if (!old.equals(id)) {
245            setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id);
246        }
247    }
248
249    public String getScheduleItemId() {
250        return _scheduleId;
251    }
252
253    public ScheduleItem getScheduleItem(Track track) {
254        ScheduleItem si = null;
255        // arrived at spur?
256        if (track != null && track.isSpur() && !getScheduleItemId().equals(NONE)) {
257            Schedule sch = track.getSchedule();
258            if (sch == null) {
259                log.error("Schedule missing for car ({}) to spur ({}, {})", toString(), track.getLocation().getName(),
260                        track.getName());
261            } else {
262                si = sch.getItemById(getScheduleItemId());
263            }
264        }
265        return si;
266    }
267
268    /**
269     * Only here for backwards compatibility before version 5.1.4. The next load
270     * name for this car. Normally set by a schedule.
271     * 
272     * @param load the next load name.
273     */
274    public void setNextLoadName(String load) {
275        String old = _nextLoadName;
276        _nextLoadName = load;
277        if (!old.equals(load)) {
278            setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, old, load);
279        }
280    }
281
282    public String getNextLoadName() {
283        return _nextLoadName;
284    }
285
286    @Override
287    public String getWeightTons() {
288        String weight = super.getWeightTons();
289        if (!_weightTons.equals(DEFAULT_WEIGHT)) {
290            return weight;
291        }
292        if (!isCaboose() && !isPassenger()) {
293            return weight;
294        }
295        // .9 tons/foot for caboose and passenger cars
296        try {
297            weight = Integer.toString((int) (Double.parseDouble(getLength()) * .9));
298        } catch (Exception e) {
299            log.debug("Car ({}) length not set for caboose or passenger car", toString());
300        }
301        return weight;
302    }
303
304    /**
305     * Returns a car's weight adjusted for load. An empty car's weight is 1/3
306     * the car's loaded weight.
307     */
308    @Override
309    public int getAdjustedWeightTons() {
310        int weightTons = 0;
311        try {
312            // get loaded weight
313            weightTons = Integer.parseInt(getWeightTons());
314            // adjust for empty weight if car is empty, 1/3 of loaded weight
315            if (!isCaboose() && !isPassenger() && getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
316                weightTons = weightTons / 3;
317            }
318        } catch (NumberFormatException e) {
319            log.debug("Car ({}) weight not set", toString());
320        }
321        return weightTons;
322    }
323
324    public void setWait(int count) {
325        int old = _wait;
326        _wait = count;
327        if (old != count) {
328            setDirtyAndFirePropertyChange(WAIT_CHANGED_PROPERTY, old, count);
329        }
330    }
331
332    public int getWait() {
333        return _wait;
334    }
335
336    /**
337     * Sets when this car will be picked up (day of the week)
338     *
339     * @param id See TrainSchedule.java
340     */
341    public void setPickupScheduleId(String id) {
342        String old = _pickupScheduleId;
343        _pickupScheduleId = id;
344        if (!old.equals(id)) {
345            setDirtyAndFirePropertyChange("car pickup schedule changes", old, id); // NOI18N
346        }
347    }
348
349    public String getPickupScheduleId() {
350        return _pickupScheduleId;
351    }
352
353    public String getPickupScheduleName() {
354        if (getTrain() != null) {
355            return getPickupTime();
356        }
357        TrainSchedule sch = InstanceManager.getDefault(TrainScheduleManager.class)
358                .getScheduleById(getPickupScheduleId());
359        if (sch != null) {
360            return sch.getName();
361        }
362        return NONE;
363    }
364
365    /**
366     * Sets the final destination for a car.
367     *
368     * @param destination The final destination for this car.
369     */
370    public void setFinalDestination(Location destination) {
371        Location old = _finalDestination;
372        if (old != null) {
373            old.removePropertyChangeListener(this);
374        }
375        _finalDestination = destination;
376        if (_finalDestination != null) {
377            _finalDestination.addPropertyChangeListener(this);
378        }
379        if ((old != null && !old.equals(destination)) || (destination != null && !destination.equals(old))) {
380            setRoutePath(NONE);
381            setDirtyAndFirePropertyChange(FINAL_DESTINATION_CHANGED_PROPERTY, old, destination);
382        }
383    }
384
385    public Location getFinalDestination() {
386        return _finalDestination;
387    }
388    
389    public String getFinalDestinationName() {
390        if (getFinalDestination() != null) {
391            return getFinalDestination().getName();
392        }
393        return NONE;
394    }
395    
396    public String getSplitFinalDestinationName() {
397        return TrainCommon.splitString(getFinalDestinationName());
398    }
399
400    public void setFinalDestinationTrack(Track track) {
401        Track old = _finalDestTrack;
402        _finalDestTrack = track;
403        if ((old != null && !old.equals(track)) || (track != null && !track.equals(old))) {
404            if (old != null) {
405                old.removePropertyChangeListener(this);
406                old.deleteReservedInRoute(this);
407            }
408            if (_finalDestTrack != null) {
409                _finalDestTrack.addReservedInRoute(this);
410                _finalDestTrack.addPropertyChangeListener(this);
411            }
412            setDirtyAndFirePropertyChange(FINAL_DESTINATION_TRACK_CHANGED_PROPERTY, old, track);
413        }
414    }
415
416    public Track getFinalDestinationTrack() {
417        return _finalDestTrack;
418    }
419
420    public String getFinalDestinationTrackName() {
421        if (getFinalDestinationTrack() != null) {
422            return getFinalDestinationTrack().getName();
423        }
424        return NONE;
425    }
426    
427    public String getSplitFinalDestinationTrackName() {
428        return TrainCommon.splitString(getFinalDestinationTrackName());
429    }
430
431    public void setPreviousFinalDestination(Location location) {
432        _previousFinalDestination = location;
433    }
434
435    public Location getPreviousFinalDestination() {
436        return _previousFinalDestination;
437    }
438
439    public String getPreviousFinalDestinationName() {
440        if (getPreviousFinalDestination() != null) {
441            return getPreviousFinalDestination().getName();
442        }
443        return NONE;
444    }
445
446    public void setPreviousFinalDestinationTrack(Track track) {
447        _previousFinalDestTrack = track;
448    }
449
450    public Track getPreviousFinalDestinationTrack() {
451        return _previousFinalDestTrack;
452    }
453
454    public String getPreviousFinalDestinationTrackName() {
455        if (getPreviousFinalDestinationTrack() != null) {
456            return getPreviousFinalDestinationTrack().getName();
457        }
458        return NONE;
459    }
460
461    public void setPreviousScheduleId(String id) {
462        _previousScheduleId = id;
463    }
464
465    public String getPreviousScheduleId() {
466        return _previousScheduleId;
467    }
468
469    public void setReturnWhenEmptyDestination(Location destination) {
470        Location old = _rweDestination;
471        _rweDestination = destination;
472        if ((old != null && !old.equals(destination)) || (destination != null && !destination.equals(old))) {
473            setDirtyAndFirePropertyChange(RETURN_WHEN_EMPTY_CHANGED_PROPERTY, null, null);
474        }
475    }
476
477    public Location getReturnWhenEmptyDestination() {
478        return _rweDestination;
479    }
480
481    public String getReturnWhenEmptyDestinationName() {
482        if (getReturnWhenEmptyDestination() != null) {
483            return getReturnWhenEmptyDestination().getName();
484        }
485        return NONE;
486    }
487    
488    public String getSplitReturnWhenEmptyDestinationName() {
489        return TrainCommon.splitString(getReturnWhenEmptyDestinationName());
490    }
491    
492    public void setReturnWhenEmptyDestTrack(Track track) {
493        Track old = _rweDestTrack;
494        _rweDestTrack = track;
495        if ((old != null && !old.equals(track)) || (track != null && !track.equals(old))) {
496            setDirtyAndFirePropertyChange(RETURN_WHEN_EMPTY_CHANGED_PROPERTY, null, null);
497        }
498    }
499
500    public Track getReturnWhenEmptyDestTrack() {
501        return _rweDestTrack;
502    }
503
504    public String getReturnWhenEmptyDestTrackName() {
505        if (getReturnWhenEmptyDestTrack() != null) {
506            return getReturnWhenEmptyDestTrack().getName();
507        }
508        return NONE;
509    }
510    
511    public String getSplitReturnWhenEmptyDestinationTrackName() {
512        return TrainCommon.splitString(getReturnWhenEmptyDestTrackName());
513    }
514
515    public void setReturnWhenLoadedDestination(Location destination) {
516        Location old = _rwlDestination;
517        _rwlDestination = destination;
518        if ((old != null && !old.equals(destination)) || (destination != null && !destination.equals(old))) {
519            setDirtyAndFirePropertyChange(RETURN_WHEN_LOADED_CHANGED_PROPERTY, null, null);
520        }
521    }
522
523    public Location getReturnWhenLoadedDestination() {
524        return _rwlDestination;
525    }
526
527    public String getReturnWhenLoadedDestinationName() {
528        if (getReturnWhenLoadedDestination() != null) {
529            return getReturnWhenLoadedDestination().getName();
530        }
531        return NONE;
532    }
533
534    public void setReturnWhenLoadedDestTrack(Track track) {
535        Track old = _rwlDestTrack;
536        _rwlDestTrack = track;
537        if ((old != null && !old.equals(track)) || (track != null && !track.equals(old))) {
538            setDirtyAndFirePropertyChange(RETURN_WHEN_LOADED_CHANGED_PROPERTY, null, null);
539        }
540    }
541
542    public Track getReturnWhenLoadedDestTrack() {
543        return _rwlDestTrack;
544    }
545
546    public String getReturnWhenLoadedDestTrackName() {
547        if (getReturnWhenLoadedDestTrack() != null) {
548            return getReturnWhenLoadedDestTrack().getName();
549        }
550        return NONE;
551    }
552
553    /**
554     * Used to determine is car has been given a Return When Loaded (RWL)
555     * address or custom load
556     * 
557     * @return true if car has RWL
558     */
559    protected boolean isRwlEnabled() {
560        if (!getReturnWhenLoadedLoadName().equals(carLoads.getDefaultLoadName()) ||
561                getReturnWhenLoadedDestination() != null) {
562            return true;
563        }
564        return false;
565    }
566
567    public void setRoutePath(String routePath) {
568        String old = _routePath;
569        _routePath = routePath;
570        if (!old.equals(routePath)) {
571            setDirtyAndFirePropertyChange("Route path change", old, routePath);
572        }
573    }
574
575    public String getRoutePath() {
576        return _routePath;
577    }
578
579    public void setCaboose(boolean caboose) {
580        boolean old = _caboose;
581        _caboose = caboose;
582        if (!old == caboose) {
583            setDirtyAndFirePropertyChange("car is caboose", old ? "true" : "false", caboose ? "true" : "false"); // NOI18N
584        }
585    }
586
587    public boolean isCaboose() {
588        return _caboose;
589    }
590
591    public void setUtility(boolean utility) {
592        boolean old = _utility;
593        _utility = utility;
594        if (!old == utility) {
595            setDirtyAndFirePropertyChange("car is utility", old ? "true" : "false", utility ? "true" : "false"); // NOI18N
596        }
597    }
598
599    public boolean isUtility() {
600        return _utility;
601    }
602
603    /**
604     * Used to determine if car is performing a local move. A local move is when
605     * a car is moved to a different track at the same location.
606     * 
607     * @return true if local move
608     */
609    public boolean isLocalMove() {
610        if (getTrain() == null && getLocation() != null) {
611            return getSplitLocationName().equals(getSplitDestinationName());
612        }
613        if (getRouteLocation() == null || getRouteDestination() == null) {
614            return false;
615        }
616        if (getRouteLocation().equals(getRouteDestination()) && getTrack() != null) {
617            return true;
618        }
619        if (getTrain().isLocalSwitcher() &&
620                getRouteLocation().getSplitName()
621                        .equals(getRouteDestination().getSplitName()) &&
622                getTrack() != null) {
623            return true;
624        }
625        // look for sequential locations with the "same" name
626        if (getRouteLocation().getSplitName().equals(
627                getRouteDestination().getSplitName()) && getTrain().getRoute() != null) {
628            boolean foundRl = false;
629            for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) {
630                if (foundRl) {
631                    if (getRouteDestination().getSplitName()
632                            .equals(rl.getSplitName())) {
633                        // user can specify the "same" location two more more
634                        // times in a row
635                        if (getRouteDestination() != rl) {
636                            continue;
637                        } else {
638                            return true;
639                        }
640                    } else {
641                        return false;
642                    }
643                }
644                if (getRouteLocation().equals(rl)) {
645                    foundRl = true;
646                }
647            }
648        }
649        return false;
650    }
651
652    /**
653     * A kernel is a group of cars that are switched as a unit.
654     * 
655     * @param kernel The assigned Kernel for this car.
656     */
657    public void setKernel(Kernel kernel) {
658        if (_kernel == kernel) {
659            return;
660        }
661        String old = "";
662        if (_kernel != null) {
663            old = _kernel.getName();
664            _kernel.delete(this);
665        }
666        _kernel = kernel;
667        String newName = "";
668        if (_kernel != null) {
669            _kernel.add(this);
670            newName = _kernel.getName();
671        }
672        if (!old.equals(newName)) {
673            setDirtyAndFirePropertyChange(KERNEL_NAME_CHANGED_PROPERTY, old, newName); // NOI18N
674        }
675    }
676
677    public Kernel getKernel() {
678        return _kernel;
679    }
680
681    public String getKernelName() {
682        if (_kernel != null) {
683            return _kernel.getName();
684        }
685        return NONE;
686    }
687
688    /**
689     * Used to determine if car is lead car in a kernel
690     * 
691     * @return true if lead car in a kernel
692     */
693    public boolean isLead() {
694        if (getKernel() != null) {
695            return getKernel().isLead(this);
696        }
697        return false;
698    }
699
700    /**
701     * Updates all cars in a kernel. After the update, the cars will all have
702     * the same final destination, load, and route path.
703     */
704    public void updateKernel() {
705        if (isLead()) {
706            for (Car car : getKernel().getCars()) {
707                if (car != this) {
708                    car.setScheduleItemId(getScheduleItemId());
709                    car.setFinalDestination(getFinalDestination());
710                    car.setFinalDestinationTrack(getFinalDestinationTrack());
711                    car.setLoadGeneratedFromStaging(isLoadGeneratedFromStaging());
712                    car.setRoutePath(getRoutePath());
713                    car.setWait(getWait());
714                    if (carLoads.containsName(car.getTypeName(), getLoadName())) {
715                        car.setLoadName(getLoadName());
716                    } else {
717                        updateKernelCarCustomLoad(car);
718                    }
719                }
720            }
721        }
722    }
723
724    /**
725     * The non-lead car in a kernel can't use the custom load of the lead car.
726     * Determine if car has custom loads, and if the departure and arrival
727     * tracks allows one of the custom loads.
728     * 
729     * @param car the non-lead car in a kernel
730     */
731    private void updateKernelCarCustomLoad(Car car) {
732        // only update car's load if departing staging or spur
733        if (getTrack() != null) {
734            if (getTrack().isStaging() || getTrack().isSpur()) {
735                List<String> carLoadNames = carLoads.getNames(car.getTypeName());
736                List<String> okLoadNames = new ArrayList<>();
737                for (String name : carLoadNames) {
738                    if (getTrack().isLoadNameAndCarTypeShipped(name, car.getTypeName())) {
739                        if (getTrain() != null && !getTrain().isLoadNameAccepted(name, car.getTypeName())) {
740                            continue; // load not carried by train
741                        }
742                        if (getFinalDestinationTrack() != null &&
743                                getDestinationTrack() != null &&
744                                !getDestinationTrack().isSpur()) {
745                            if (getFinalDestinationTrack().isLoadNameAndCarTypeAccepted(name, car.getTypeName())) {
746                                okLoadNames.add(name);
747                            }
748                        } else if (getDestinationTrack() != null &&
749                                getDestinationTrack().isLoadNameAndCarTypeAccepted(name, car.getTypeName())) {
750                            okLoadNames.add(name);
751                        }
752                    }
753                }
754                // remove default names leaving only custom
755                okLoadNames.remove(carLoads.getDefaultEmptyName());
756                okLoadNames.remove(carLoads.getDefaultLoadName());
757                // randomly pick one of the available car loads
758                if (okLoadNames.size() > 0) {
759                    int rnd = (int) (Math.random() * okLoadNames.size());
760                    car.setLoadName(okLoadNames.get(rnd));
761                } else {
762                    log.debug("Car ({}) in kernel ({}) leaving staging ({}, {}) with load ({})", car.toString(),
763                            getKernelName(), getLocationName(), getTrackName(), car.getLoadName());
764                }
765            }
766        }
767    }
768
769    /**
770     * Returns the car length or the length of the car's kernel including
771     * couplers.
772     * 
773     * @return length of car or kernel
774     */
775    public int getTotalKernelLength() {
776        if (getKernel() != null) {
777            return getKernel().getTotalLength();
778        }
779        return getTotalLength();
780    }
781
782    /**
783     * Used to determine if a car can be set out at a destination (location).
784     * Track is optional. In addition to all of the tests that checkDestination
785     * performs, spurs with schedules are also checked.
786     *
787     * @return status OKAY, TYPE, ROAD, LENGTH, ERROR_TRACK, CAPACITY, SCHEDULE,
788     *         CUSTOM
789     */
790    @Override
791    public String checkDestination(Location destination, Track track) {
792        String status = super.checkDestination(destination, track);
793        if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
794            return status;
795        }
796        // now check to see if the track has a schedule
797        if (track == null) {
798            return status;
799        }
800        String statusSchedule = track.checkSchedule(this);
801        if (status.startsWith(Track.LENGTH) && statusSchedule.equals(Track.OKAY)) {
802            return status;
803        }
804        return statusSchedule;
805    }
806
807    /**
808     * Sets the car's destination on the layout
809     *
810     * @param track (yard, spur, staging, or interchange track)
811     * @return "okay" if successful, "type" if the rolling stock's type isn't
812     *         acceptable, or "length" if the rolling stock length didn't fit,
813     *         or Schedule if the destination will not accept the car because
814     *         the spur has a schedule and the car doesn't meet the schedule
815     *         requirements. Also changes the car load status when the car
816     *         reaches its destination.
817     */
818    @Override
819    public String setDestination(Location destination, Track track) {
820        return setDestination(destination, track, !Car.FORCE);
821    }
822
823    /**
824     * Sets the car's destination on the layout
825     *
826     * @param track (yard, spur, staging, or interchange track)
827     * @param force when true ignore track length, type, and road when setting
828     *              destination
829     * @return "okay" if successful, "type" if the rolling stock's type isn't
830     *         acceptable, or "length" if the rolling stock length didn't fit,
831     *         or Schedule if the destination will not accept the car because
832     *         the spur has a schedule and the car doesn't meet the schedule
833     *         requirements. Also changes the car load status when the car
834     *         reaches its destination. Removes car if clone.
835     */
836    @Override
837    public String setDestination(Location destination, Track track, boolean force) {
838        // save destination name and track in case car has reached its
839        // destination
840        String destinationName = getDestinationName();
841        Track destinationTrack = getDestinationTrack();
842        String status = super.setDestination(destination, track, force);
843        // return if not Okay
844        if (!status.equals(Track.OKAY)) {
845            return status;
846        }
847        // is car going to its final destination?
848        removeCarFinalDestination();
849        // now check to see if the track has a schedule
850        if (track != null && destinationTrack != track && loaded) {
851            status = track.scheduleNext(this);
852            if (!status.equals(Track.OKAY)) {
853                return status;
854            }
855        }
856        // done?
857        if (destinationName.equals(NONE) || (destination != null) || getTrain() == null) {
858            return status;
859        }
860        // car was in a train and has been dropped off, update load, RWE could
861        // set a new final destination
862        if (isClone()) {
863            // destroy clone
864            InstanceManager.getDefault(KernelManager.class).deleteKernel(getKernelName());
865            InstanceManager.getDefault(CarManager.class).deregister(this);
866        } else {
867            loadNext(destinationTrack);
868        }
869        return status;
870    }
871
872    /**
873     * Called when setting a car's destination to this spur. Loads the car with
874     * a final destination which is the ship address for the schedule item.
875     * 
876     * @param scheduleItem The schedule item to be applied this this car
877     */
878    public void loadCarFinalDestination(ScheduleItem scheduleItem) {
879        if (scheduleItem != null) {
880            // set the car's final destination and track
881            setFinalDestination(scheduleItem.getDestination());
882            setFinalDestinationTrack(scheduleItem.getDestinationTrack());
883            // set all cars in kernel same final destination
884            updateKernel();
885        } 
886    }
887    
888    /*
889     * remove the car's final destination if sent to that destination
890     */
891    private void removeCarFinalDestination() {
892        if (getDestination() != null &&
893                getDestination().equals(getFinalDestination()) &&
894                getDestinationTrack() != null &&
895                (getDestinationTrack().equals(getFinalDestinationTrack()) ||
896                        getFinalDestinationTrack() == null)) {
897            setFinalDestination(null);
898            setFinalDestinationTrack(null);
899        }
900    }
901
902    /**
903     * Called when car is delivered to track. Updates the car's wait, pickup
904     * day, and load if spur. If staging, can swap default loads, force load to
905     * default empty, or replace custom loads with the default empty load. Can
906     * trigger RWE or RWL.
907     * 
908     * @param track the destination track for this car
909     */
910    public void loadNext(Track track) {
911        setLoadGeneratedFromStaging(false);
912        if (track != null) {
913            if (track.isSpur()) {
914                ScheduleItem si = getScheduleItem(track);
915                if (si == null) {
916                    log.debug("Schedule item ({}) is null for car ({}) at spur ({})", getScheduleItemId(), toString(),
917                            track.getName());
918                } else {
919                    setWait(si.getWait());
920                    setPickupScheduleId(si.getPickupTrainScheduleId());
921                }
922                updateLoad(track);
923            }
924            // update load optionally when car reaches staging
925            else if (track.isStaging()) {
926                if (track.isLoadSwapEnabled() && getLoadName().equals(carLoads.getDefaultEmptyName())) {
927                    setLoadLoaded();
928                } else if ((track.isLoadSwapEnabled() || track.isLoadEmptyEnabled()) &&
929                        getLoadName().equals(carLoads.getDefaultLoadName())) {
930                    setLoadEmpty();
931                } else if (track.isRemoveCustomLoadsEnabled() &&
932                        !getLoadName().equals(carLoads.getDefaultEmptyName()) &&
933                        !getLoadName().equals(carLoads.getDefaultLoadName())) {
934                    // remove this car's final destination if it has one
935                    setFinalDestination(null);
936                    setFinalDestinationTrack(null);
937                    if (getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && isRwlEnabled()) {
938                        setLoadLoaded();
939                        // car arriving into staging with the RWE load?
940                    } else if (getLoadName().equals(getReturnWhenEmptyLoadName())) {
941                        setLoadName(carLoads.getDefaultEmptyName());
942                    } else {
943                        setLoadEmpty(); // note that RWE sets the car's final
944                                        // destination
945                    }
946                }
947            }
948        }
949    }
950
951    /**
952     * Updates a car's load when placed at a spur. Load change delayed if wait
953     * count is greater than zero. 
954     * 
955     * @param track The spur the car is sitting on
956     */
957    public void updateLoad(Track track) {
958        if (track.isDisableLoadChangeEnabled()) {
959            return;
960        }
961        if (getWait() > 0) {
962            return; // change load name when wait count reaches 0
963        }
964        // arriving at spur with a schedule?
965        String loadName = NONE;
966        ScheduleItem si = getScheduleItem(track);
967        if (si != null) {
968            loadName = si.getShipLoadName(); // can be NONE
969        } else {
970            // for backwards compatibility before version 5.1.4
971            log.debug("Schedule item ({}) is null for car ({}) at spur ({}), using next load name", getScheduleItemId(),
972                    toString(), track.getName());
973            loadName = getNextLoadName();
974        }
975        setNextLoadName(NONE); // never used again
976        // car could be part of a kernel
977        if (getKernel() != null && !carLoads.containsName(getTypeName(), loadName)) {
978            loadName = NONE;
979        }
980        if (!loadName.equals(NONE)) {
981            setLoadName(loadName);
982            if (getLoadName().equals(getReturnWhenEmptyLoadName())) {
983                setReturnWhenEmpty();
984            } else if (getLoadName().equals(getReturnWhenLoadedLoadName())) {
985                setReturnWhenLoaded();
986            }
987        } else {
988            // flip load names
989            if (getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
990                setLoadLoaded();
991            } else {
992                setLoadEmpty();
993            }
994        }
995        loadCarFinalDestination(si);
996        setScheduleItemId(Car.NONE);
997    }
998
999    /**
1000     * Sets the car's load to empty, triggers RWE load and destination if
1001     * enabled.
1002     */
1003    private void setLoadEmpty() {
1004        if (!getLoadName().equals(getReturnWhenEmptyLoadName())) {
1005            setLoadName(getReturnWhenEmptyLoadName()); // default RWE load is
1006                                                       // the "E" load
1007            setReturnWhenEmpty();
1008        }
1009    }
1010
1011    /*
1012     * Don't set return address if in staging with the same RWE address and
1013     * don't set return address if at the RWE address
1014     */
1015    private void setReturnWhenEmpty() {
1016        if (getFinalDestination() == null &&
1017                getReturnWhenEmptyDestination() != null &&
1018                (getLocation() != getReturnWhenEmptyDestination() ||
1019                        (!getReturnWhenEmptyDestination().isStaging() &&
1020                                getTrack() != getReturnWhenEmptyDestTrack()))) {
1021            setFinalDestination(getReturnWhenEmptyDestination());
1022            setFinalDestinationTrack(getReturnWhenEmptyDestTrack());
1023            log.debug("Car ({}) has return when empty destination ({}, {}) load {}", toString(),
1024                    getFinalDestinationName(), getFinalDestinationTrackName(), getLoadName());
1025        }
1026    }
1027
1028    /**
1029     * Sets the car's load to loaded, triggers RWL load and destination if
1030     * enabled.
1031     */
1032    private void setLoadLoaded() {
1033        if (!getLoadName().equals(getReturnWhenLoadedLoadName())) {
1034            setLoadName(getReturnWhenLoadedLoadName()); // default RWL load is
1035                                                        // the "L" load
1036            setReturnWhenLoaded();
1037        }
1038    }
1039
1040    /*
1041     * Don't set return address if in staging with the same RWL address and
1042     * don't set return address if at the RWL address
1043     */
1044    private void setReturnWhenLoaded() {
1045        if (getFinalDestination() == null &&
1046                getReturnWhenLoadedDestination() != null &&
1047                (getLocation() != getReturnWhenLoadedDestination() ||
1048                        (!getReturnWhenLoadedDestination().isStaging() &&
1049                                getTrack() != getReturnWhenLoadedDestTrack()))) {
1050            setFinalDestination(getReturnWhenLoadedDestination());
1051            setFinalDestinationTrack(getReturnWhenLoadedDestTrack());
1052            log.debug("Car ({}) has return when loaded destination ({}, {}) load {}", toString(),
1053                    getFinalDestinationName(), getFinalDestinationTrackName(), getLoadName());
1054        }
1055    }
1056
1057    public String getTypeExtensions() {
1058        StringBuffer buf = new StringBuffer();
1059        if (isCaboose()) {
1060            buf.append(EXTENSION_REGEX + CABOOSE_EXTENSION);
1061        }
1062        if (hasFred()) {
1063            buf.append(EXTENSION_REGEX + FRED_EXTENSION);
1064        }
1065        if (isPassenger()) {
1066            buf.append(EXTENSION_REGEX + PASSENGER_EXTENSION + EXTENSION_REGEX + getBlocking());
1067        }
1068        if (isUtility()) {
1069            buf.append(EXTENSION_REGEX + UTILITY_EXTENSION);
1070        }
1071        if (isCarHazardous()) {
1072            buf.append(EXTENSION_REGEX + HAZARDOUS_EXTENSION);
1073        }
1074        return buf.toString();
1075    }
1076
1077    @Override
1078    public void reset() {
1079        setScheduleItemId(getPreviousScheduleId()); // revert to previous
1080        setNextLoadName(NONE);
1081        setFinalDestination(getPreviousFinalDestination());
1082        setFinalDestinationTrack(getPreviousFinalDestinationTrack());
1083        if (isLoadGeneratedFromStaging()) {
1084            setLoadGeneratedFromStaging(false);
1085            setLoadName(carLoads.getDefaultEmptyName());
1086        }
1087        super.reset();
1088        destroyClone();
1089    }
1090
1091    /*
1092     * This routine destroys the clone and restores the cloned car to its
1093     * original location and settings. Note there can be multiple clones for a
1094     * car. A clone has uses the original car's road, number, and the creation
1095     * order number which is appended to the road number using the CLONE_REGEX
1096     */
1097    private void destroyClone() {
1098        if (isClone()) {
1099            // move cloned car back to original location
1100            CarManager carManager = InstanceManager.getDefault(CarManager.class);
1101            // get the original car's road and number
1102            String[] number = getNumber().split(Car.CLONE_REGEX);
1103            Car car = carManager.getByRoadAndNumber(getRoadName(), number[0]);
1104            if (car != null) {
1105                int cloneCreationNumber = Integer.parseInt(number[1]);
1106                if (cloneCreationNumber <= car.getCloneOrder()) {
1107                    // move car back and restore
1108                    destroyCloneReset(car);
1109                    car.setLoadName(getLoadName());
1110                    car.setFinalDestination(getPreviousFinalDestination());
1111                    car.setFinalDestinationTrack(getPreviousFinalDestinationTrack());
1112                    car.setPreviousFinalDestination(getPreviousFinalDestination());
1113                    car.setPreviousFinalDestinationTrack(getPreviousFinalDestinationTrack());
1114                    car.setScheduleItemId(getPreviousScheduleId());
1115                    car.setWait(0);
1116                    // remember the last clone destroyed
1117                    car.setCloneOrder(cloneCreationNumber);
1118                }
1119            } else {
1120                log.error("Not able to find and restore car ({}, {})", getRoadName(), number[0]);
1121            }
1122            InstanceManager.getDefault(KernelManager.class).deleteKernel(getKernelName());
1123            carManager.deregister(this);
1124        }
1125    }
1126
1127    @Override
1128    public void dispose() {
1129        setKernel(null);
1130        setFinalDestination(null); // removes property change listener
1131        setFinalDestinationTrack(null); // removes property change listener
1132        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
1133        InstanceManager.getDefault(CarLengths.class).removePropertyChangeListener(this);
1134        super.dispose();
1135    }
1136
1137    // used to stop a track's schedule from bumping when loading car database
1138    private boolean loaded = false;
1139
1140    /**
1141     * Construct this Entry from XML. This member has to remain synchronized
1142     * with the detailed DTD in operations-cars.dtd
1143     *
1144     * @param e Car XML element
1145     */
1146    public Car(org.jdom2.Element e) {
1147        super(e);
1148        loaded = true;
1149        org.jdom2.Attribute a;
1150        if ((a = e.getAttribute(Xml.PASSENGER)) != null) {
1151            _passenger = a.getValue().equals(Xml.TRUE);
1152        }
1153        if ((a = e.getAttribute(Xml.HAZARDOUS)) != null) {
1154            _hazardous = a.getValue().equals(Xml.TRUE);
1155        }
1156        if ((a = e.getAttribute(Xml.CABOOSE)) != null) {
1157            _caboose = a.getValue().equals(Xml.TRUE);
1158        }
1159        if ((a = e.getAttribute(Xml.FRED)) != null) {
1160            _fred = a.getValue().equals(Xml.TRUE);
1161        }
1162        if ((a = e.getAttribute(Xml.UTILITY)) != null) {
1163            _utility = a.getValue().equals(Xml.TRUE);
1164        }
1165        if ((a = e.getAttribute(Xml.KERNEL)) != null) {
1166            Kernel k = InstanceManager.getDefault(KernelManager.class).getKernelByName(a.getValue());
1167            if (k != null) {
1168                setKernel(k);
1169                if ((a = e.getAttribute(Xml.LEAD_KERNEL)) != null && a.getValue().equals(Xml.TRUE)) {
1170                    _kernel.setLead(this);
1171                }
1172            } else {
1173                log.error("Kernel {} does not exist", a.getValue());
1174            }
1175        }
1176        if ((a = e.getAttribute(Xml.LOAD)) != null) {
1177            _loadName = a.getValue();
1178        }
1179        if ((a = e.getAttribute(Xml.LOAD_FROM_STAGING)) != null && a.getValue().equals(Xml.TRUE)) {
1180            setLoadGeneratedFromStaging(true);
1181        }
1182        if ((a = e.getAttribute(Xml.WAIT)) != null) {
1183            try {
1184                _wait = Integer.parseInt(a.getValue());
1185            } catch (NumberFormatException nfe) {
1186                log.error("Wait count ({}) for car ({}) isn't a valid number!", a.getValue(), toString());
1187            }
1188        }
1189        if ((a = e.getAttribute(Xml.PICKUP_SCHEDULE_ID)) != null) {
1190            _pickupScheduleId = a.getValue();
1191        }
1192        if ((a = e.getAttribute(Xml.SCHEDULE_ID)) != null) {
1193            _scheduleId = a.getValue();
1194        }
1195        // for backwards compatibility before version 5.1.4
1196        if ((a = e.getAttribute(Xml.NEXT_LOAD)) != null) {
1197            _nextLoadName = a.getValue();
1198        }
1199        if ((a = e.getAttribute(Xml.NEXT_DEST_ID)) != null) {
1200            setFinalDestination(InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue()));
1201        }
1202        if (getFinalDestination() != null && (a = e.getAttribute(Xml.NEXT_DEST_TRACK_ID)) != null) {
1203            setFinalDestinationTrack(getFinalDestination().getTrackById(a.getValue()));
1204        }
1205        if ((a = e.getAttribute(Xml.PREVIOUS_NEXT_DEST_ID)) != null) {
1206            setPreviousFinalDestination(
1207                    InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue()));
1208        }
1209        if (getPreviousFinalDestination() != null && (a = e.getAttribute(Xml.PREVIOUS_NEXT_DEST_TRACK_ID)) != null) {
1210            setPreviousFinalDestinationTrack(getPreviousFinalDestination().getTrackById(a.getValue()));
1211        }
1212        if ((a = e.getAttribute(Xml.PREVIOUS_SCHEDULE_ID)) != null) {
1213            setPreviousScheduleId(a.getValue());
1214        }
1215        if ((a = e.getAttribute(Xml.RWE_DEST_ID)) != null) {
1216            _rweDestination = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue());
1217        }
1218        if (_rweDestination != null && (a = e.getAttribute(Xml.RWE_DEST_TRACK_ID)) != null) {
1219            _rweDestTrack = _rweDestination.getTrackById(a.getValue());
1220        }
1221        if ((a = e.getAttribute(Xml.RWE_LOAD)) != null) {
1222            _rweLoadName = a.getValue();
1223        }
1224        if ((a = e.getAttribute(Xml.RWL_DEST_ID)) != null) {
1225            _rwlDestination = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue());
1226        }
1227        if (_rwlDestination != null && (a = e.getAttribute(Xml.RWL_DEST_TRACK_ID)) != null) {
1228            _rwlDestTrack = _rwlDestination.getTrackById(a.getValue());
1229        }
1230        if ((a = e.getAttribute(Xml.RWL_LOAD)) != null) {
1231            _rwlLoadName = a.getValue();
1232        }
1233        if ((a = e.getAttribute(Xml.ROUTE_PATH)) != null) {
1234            _routePath = a.getValue();
1235        }
1236        addPropertyChangeListeners();
1237    }
1238
1239    /**
1240     * Create an XML element to represent this Entry. This member has to remain
1241     * synchronized with the detailed DTD in operations-cars.dtd.
1242     *
1243     * @return Contents in a JDOM Element
1244     */
1245    public org.jdom2.Element store() {
1246        org.jdom2.Element e = new org.jdom2.Element(Xml.CAR);
1247        super.store(e);
1248        if (isPassenger()) {
1249            e.setAttribute(Xml.PASSENGER, isPassenger() ? Xml.TRUE : Xml.FALSE);
1250        }
1251        if (isCarHazardous()) {
1252            e.setAttribute(Xml.HAZARDOUS, isCarHazardous() ? Xml.TRUE : Xml.FALSE);
1253        }
1254        if (isCaboose()) {
1255            e.setAttribute(Xml.CABOOSE, isCaboose() ? Xml.TRUE : Xml.FALSE);
1256        }
1257        if (hasFred()) {
1258            e.setAttribute(Xml.FRED, hasFred() ? Xml.TRUE : Xml.FALSE);
1259        }
1260        if (isUtility()) {
1261            e.setAttribute(Xml.UTILITY, isUtility() ? Xml.TRUE : Xml.FALSE);
1262        }
1263        if (getKernel() != null) {
1264            e.setAttribute(Xml.KERNEL, getKernelName());
1265            if (isLead()) {
1266                e.setAttribute(Xml.LEAD_KERNEL, Xml.TRUE);
1267            }
1268        }
1269
1270        e.setAttribute(Xml.LOAD, getLoadName());
1271
1272        if (isLoadGeneratedFromStaging()) {
1273            e.setAttribute(Xml.LOAD_FROM_STAGING, Xml.TRUE);
1274        }
1275
1276        if (getWait() != 0) {
1277            e.setAttribute(Xml.WAIT, Integer.toString(getWait()));
1278        }
1279
1280        if (!getPickupScheduleId().equals(NONE)) {
1281            e.setAttribute(Xml.PICKUP_SCHEDULE_ID, getPickupScheduleId());
1282        }
1283
1284        if (!getScheduleItemId().equals(NONE)) {
1285            e.setAttribute(Xml.SCHEDULE_ID, getScheduleItemId());
1286        }
1287
1288        // for backwards compatibility before version 5.1.4
1289        if (!getNextLoadName().equals(NONE)) {
1290            e.setAttribute(Xml.NEXT_LOAD, getNextLoadName());
1291        }
1292
1293        if (getFinalDestination() != null) {
1294            e.setAttribute(Xml.NEXT_DEST_ID, getFinalDestination().getId());
1295            if (getFinalDestinationTrack() != null) {
1296                e.setAttribute(Xml.NEXT_DEST_TRACK_ID, getFinalDestinationTrack().getId());
1297            }
1298        }
1299
1300        if (getPreviousFinalDestination() != null) {
1301            e.setAttribute(Xml.PREVIOUS_NEXT_DEST_ID, getPreviousFinalDestination().getId());
1302            if (getPreviousFinalDestinationTrack() != null) {
1303                e.setAttribute(Xml.PREVIOUS_NEXT_DEST_TRACK_ID, getPreviousFinalDestinationTrack().getId());
1304            }
1305        }
1306
1307        if (!getPreviousScheduleId().equals(NONE)) {
1308            e.setAttribute(Xml.PREVIOUS_SCHEDULE_ID, getPreviousScheduleId());
1309        }
1310
1311        if (getReturnWhenEmptyDestination() != null) {
1312            e.setAttribute(Xml.RWE_DEST_ID, getReturnWhenEmptyDestination().getId());
1313            if (getReturnWhenEmptyDestTrack() != null) {
1314                e.setAttribute(Xml.RWE_DEST_TRACK_ID, getReturnWhenEmptyDestTrack().getId());
1315            }
1316        }
1317        if (!getReturnWhenEmptyLoadName().equals(carLoads.getDefaultEmptyName())) {
1318            e.setAttribute(Xml.RWE_LOAD, getReturnWhenEmptyLoadName());
1319        }
1320
1321        if (getReturnWhenLoadedDestination() != null) {
1322            e.setAttribute(Xml.RWL_DEST_ID, getReturnWhenLoadedDestination().getId());
1323            if (getReturnWhenLoadedDestTrack() != null) {
1324                e.setAttribute(Xml.RWL_DEST_TRACK_ID, getReturnWhenLoadedDestTrack().getId());
1325            }
1326        }
1327        if (!getReturnWhenLoadedLoadName().equals(carLoads.getDefaultLoadName())) {
1328            e.setAttribute(Xml.RWL_LOAD, getReturnWhenLoadedLoadName());
1329        }
1330
1331        if (!getRoutePath().isEmpty()) {
1332            e.setAttribute(Xml.ROUTE_PATH, getRoutePath());
1333        }
1334
1335        return e;
1336    }
1337
1338    @Override
1339    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
1340        // Set dirty
1341        InstanceManager.getDefault(CarManagerXml.class).setDirty(true);
1342        super.setDirtyAndFirePropertyChange(p, old, n);
1343    }
1344
1345    private void addPropertyChangeListeners() {
1346        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
1347        InstanceManager.getDefault(CarLengths.class).addPropertyChangeListener(this);
1348    }
1349
1350    @Override
1351    public void propertyChange(PropertyChangeEvent e) {
1352        super.propertyChange(e);
1353        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
1354            if (e.getOldValue().equals(getTypeName())) {
1355                log.debug("Car ({}) sees type name change old: ({}) new: ({})", toString(), e.getOldValue(),
1356                        e.getNewValue()); // NOI18N
1357                setTypeName((String) e.getNewValue());
1358            }
1359        }
1360        if (e.getPropertyName().equals(CarLengths.CARLENGTHS_NAME_CHANGED_PROPERTY)) {
1361            if (e.getOldValue().equals(getLength())) {
1362                log.debug("Car ({}) sees length name change old: ({}) new: ({})", toString(), e.getOldValue(),
1363                        e.getNewValue()); // NOI18N
1364                setLength((String) e.getNewValue());
1365            }
1366        }
1367        if (e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) {
1368            if (e.getSource() == getFinalDestination()) {
1369                log.debug("delete final destination for car: ({})", toString());
1370                setFinalDestination(null);
1371            }
1372        }
1373        if (e.getPropertyName().equals(Track.DISPOSE_CHANGED_PROPERTY)) {
1374            if (e.getSource() == getFinalDestinationTrack()) {
1375                log.debug("delete final destination for car: ({})", toString());
1376                setFinalDestinationTrack(null);
1377            }
1378        }
1379    }
1380
1381    private final static Logger log = LoggerFactory.getLogger(Car.class);
1382
1383}