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