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