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