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