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