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    public Car copy() {
091        Car car = new Car();
092        car.setBuilt(getBuilt());
093        car.setColor(getColor());
094        car.setLength(getLength());
095        car.setLoadName(getLoadName());
096        car.setWeightTons(getWeightTons());
097        car.setReturnWhenEmptyLoadName(getReturnWhenEmptyLoadName());
098        car.setReturnWhenLoadedLoadName(getReturnWhenLoadedLoadName());
099        car.setNumber(getNumber());
100        car.setOwnerName(getOwnerName());
101        car.setRoadName(getRoadName());
102        car.setTypeName(getTypeName());
103        car.setComment(getComment());
104        car.setCarHazardous(isCarHazardous());
105        car.setCaboose(isCaboose());
106        car.setFred(hasFred());
107        car.setPassenger(isPassenger());
108        car.setBlocking(getBlocking());
109        car.setLastTrain(getLastTrain());
110        car.setLastDate(getLastDate());
111        car.setLastLocationId(getLastLocationId());
112        car.setLastTrackId(getLastTrackId());
113        car.setLoadGeneratedFromStaging(isLoadGeneratedFromStaging());
114        car.setDivision(getDivision());
115        car.loaded = true;
116        return car;
117    }
118
119    public void setCarHazardous(boolean hazardous) {
120        boolean old = _hazardous;
121        _hazardous = hazardous;
122        if (!old == hazardous) {
123            setDirtyAndFirePropertyChange("car hazardous", old ? "true" : "false", hazardous ? "true" : "false"); // NOI18N
124        }
125    }
126
127    public boolean isCarHazardous() {
128        return _hazardous;
129    }
130
131    public boolean isCarLoadHazardous() {
132        return carLoads.isHazardous(getTypeName(), getLoadName());
133    }
134
135    /**
136     * Used to determine if the car is hazardous or the car's load is hazardous.
137     * 
138     * @return true if the car or car's load is hazardous.
139     */
140    public boolean isHazardous() {
141        return isCarHazardous() || isCarLoadHazardous();
142    }
143
144    public void setPassenger(boolean passenger) {
145        boolean old = _passenger;
146        _passenger = passenger;
147        if (!old == passenger) {
148            setDirtyAndFirePropertyChange("car passenger", old ? "true" : "false", passenger ? "true" : "false"); // NOI18N
149        }
150    }
151
152    public boolean isPassenger() {
153        return _passenger;
154    }
155
156    public void setFred(boolean fred) {
157        boolean old = _fred;
158        _fred = fred;
159        if (!old == fred) {
160            setDirtyAndFirePropertyChange("car has fred", old ? "true" : "false", fred ? "true" : "false"); // NOI18N
161        }
162    }
163
164    /**
165     * Used to determine if car has FRED (Flashing Rear End Device).
166     *
167     * @return true if car has FRED.
168     */
169    public boolean hasFred() {
170        return _fred;
171    }
172
173    public void setLoadName(String load) {
174        String old = _loadName;
175        _loadName = load;
176        if (!old.equals(load)) {
177            setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, old, load);
178        }
179    }
180
181    /**
182     * The load name assigned to this car.
183     *
184     * @return The load name assigned to this car.
185     */
186    public String getLoadName() {
187        return _loadName;
188    }
189
190    public void setReturnWhenEmptyLoadName(String load) {
191        String old = _rweLoadName;
192        _rweLoadName = load;
193        if (!old.equals(load)) {
194            setDirtyAndFirePropertyChange(RWE_LOAD_CHANGED_PROPERTY, old, load);
195        }
196    }
197
198    public String getReturnWhenEmptyLoadName() {
199        return _rweLoadName;
200    }
201
202    public void setReturnWhenLoadedLoadName(String load) {
203        String old = _rwlLoadName;
204        _rwlLoadName = load;
205        if (!old.equals(load)) {
206            setDirtyAndFirePropertyChange(RWL_LOAD_CHANGED_PROPERTY, old, load);
207        }
208    }
209
210    public String getReturnWhenLoadedLoadName() {
211        return _rwlLoadName;
212    }
213
214    /**
215     * Gets the car's load's priority.
216     * 
217     * @return The car's load priority.
218     */
219    public String getLoadPriority() {
220        return (carLoads.getPriority(getTypeName(), getLoadName()));
221    }
222
223    /**
224     * Gets the car load's type, empty or load.
225     *
226     * @return type empty or type load
227     */
228    public String getLoadType() {
229        return (carLoads.getLoadType(getTypeName(), getLoadName()));
230    }
231
232    public String getPickupComment() {
233        return carLoads.getPickupComment(getTypeName(), getLoadName());
234    }
235
236    public String getDropComment() {
237        return carLoads.getDropComment(getTypeName(), getLoadName());
238    }
239
240    public void setLoadGeneratedFromStaging(boolean fromStaging) {
241        _loadGeneratedByStaging = fromStaging;
242    }
243
244    public boolean isLoadGeneratedFromStaging() {
245        return _loadGeneratedByStaging;
246    }
247
248    /**
249     * Used to keep track of which item in a schedule was used for this car.
250     * 
251     * @param id The ScheduleItem id for this car.
252     */
253    public void setScheduleItemId(String id) {
254        log.debug("Set schedule item id ({}) for car ({})", id, toString());
255        String old = _scheduleId;
256        _scheduleId = id;
257        if (!old.equals(id)) {
258            setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id);
259        }
260    }
261
262    public String getScheduleItemId() {
263        return _scheduleId;
264    }
265
266    public ScheduleItem getScheduleItem(Track track) {
267        ScheduleItem si = null;
268        // arrived at spur?
269        if (track != null && track.isSpur() && !getScheduleItemId().equals(NONE)) {
270            Schedule sch = track.getSchedule();
271            if (sch == null) {
272                log.error("Schedule null for car ({}) at spur ({})", toString(), track.getName());
273            } else {
274                si = sch.getItemById(getScheduleItemId());
275            }
276        }
277        return si;
278    }
279
280    /**
281     * Only here for backwards compatibility before version 5.1.4. The next load
282     * name for this car. Normally set by a schedule.
283     * 
284     * @param load the next load name.
285     */
286    public void setNextLoadName(String load) {
287        String old = _nextLoadName;
288        _nextLoadName = load;
289        if (!old.equals(load)) {
290            setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, old, load);
291        }
292    }
293
294    public String getNextLoadName() {
295        return _nextLoadName;
296    }
297
298    @Override
299    public String getWeightTons() {
300        String weight = super.getWeightTons();
301        if (!_weightTons.equals(DEFAULT_WEIGHT)) {
302            return weight;
303        }
304        if (!isCaboose() && !isPassenger()) {
305            return weight;
306        }
307        // .9 tons/foot for caboose and passenger cars
308        try {
309            weight = Integer.toString((int) (Double.parseDouble(getLength()) * .9));
310        } catch (Exception e) {
311            log.debug("Car ({}) length not set for caboose or passenger car", toString());
312        }
313        return weight;
314    }
315
316    /**
317     * Returns a car's weight adjusted for load. An empty car's weight is 1/3
318     * the car's loaded weight.
319     */
320    @Override
321    public int getAdjustedWeightTons() {
322        int weightTons = 0;
323        try {
324            // get loaded weight
325            weightTons = Integer.parseInt(getWeightTons());
326            // adjust for empty weight if car is empty, 1/3 of loaded weight
327            if (!isCaboose() && !isPassenger() && getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
328                weightTons = weightTons / 3;
329            }
330        } catch (NumberFormatException e) {
331            log.debug("Car ({}) weight not set", toString());
332        }
333        return weightTons;
334    }
335
336    public void setWait(int count) {
337        int old = _wait;
338        _wait = count;
339        if (old != count) {
340            setDirtyAndFirePropertyChange(WAIT_CHANGED_PROPERTY, old, count);
341        }
342    }
343
344    public int getWait() {
345        return _wait;
346    }
347
348    /**
349     * Sets when this car will be picked up (day of the week)
350     *
351     * @param id See TrainSchedule.java
352     */
353    public void setPickupScheduleId(String id) {
354        String old = _pickupScheduleId;
355        _pickupScheduleId = id;
356        if (!old.equals(id)) {
357            setDirtyAndFirePropertyChange("car pickup schedule changes", old, id); // NOI18N
358        }
359    }
360
361    public String getPickupScheduleId() {
362        return _pickupScheduleId;
363    }
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 ? "true" : "false", caboose ? "true" : "false"); // 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 ? "true" : "false", utility ? "true" : "false"); // 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().equals(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().equals(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();
861        // now check to see if the track has a schedule
862        if (track != null && destinationTrack != track && loaded) {
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    public void loadCarFinalDestination(ScheduleItem scheduleItem) {
891        if (scheduleItem != 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    private void removeCarFinalDestination() {
904        if (getDestination() != null &&
905                getDestination().equals(getFinalDestination()) &&
906                getDestinationTrack() != null &&
907                (getDestinationTrack().equals(getFinalDestinationTrack()) ||
908                        getFinalDestinationTrack() == null)) {
909            setFinalDestination(null);
910            setFinalDestinationTrack(null);
911        }
912    }
913
914    /**
915     * Called when car is delivered to track. Updates the car's wait, pickup
916     * day, and load if spur. If staging, can swap default loads, force load to
917     * default empty, or replace custom loads with the default empty load. Can
918     * trigger RWE or RWL.
919     * 
920     * @param track the destination track for this car
921     */
922    public void loadNext(Track track) {
923        setLoadGeneratedFromStaging(false);
924        if (track != null) {
925            if (track.isSpur()) {
926                ScheduleItem si = getScheduleItem(track);
927                if (si == null) {
928                    log.debug("Schedule item ({}) is null for car ({}) at spur ({})", getScheduleItemId(), toString(),
929                            track.getName());
930                } else {
931                    setWait(si.getWait());
932                    setPickupScheduleId(si.getPickupTrainScheduleId());
933                }
934                updateLoad(track);
935            }
936            // update load optionally when car reaches staging
937            else if (track.isStaging()) {
938                if (track.isLoadSwapEnabled() && getLoadName().equals(carLoads.getDefaultEmptyName())) {
939                    setLoadLoaded();
940                } else if ((track.isLoadSwapEnabled() || track.isLoadEmptyEnabled()) &&
941                        getLoadName().equals(carLoads.getDefaultLoadName())) {
942                    setLoadEmpty();
943                } else if (track.isRemoveCustomLoadsEnabled() &&
944                        !getLoadName().equals(carLoads.getDefaultEmptyName()) &&
945                        !getLoadName().equals(carLoads.getDefaultLoadName())) {
946                    // remove this car's final destination if it has one
947                    setFinalDestination(null);
948                    setFinalDestinationTrack(null);
949                    if (getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && isRwlEnabled()) {
950                        setLoadLoaded();
951                        // car arriving into staging with the RWE load?
952                    } else if (getLoadName().equals(getReturnWhenEmptyLoadName())) {
953                        setLoadName(carLoads.getDefaultEmptyName());
954                    } else {
955                        setLoadEmpty(); // note that RWE sets the car's final
956                                        // destination
957                    }
958                }
959            }
960        }
961    }
962
963    /**
964     * Updates a car's load when placed at a spur. Load change delayed if wait
965     * count is greater than zero. 
966     * 
967     * @param track The spur the car is sitting on
968     */
969    public void updateLoad(Track track) {
970        if (track.isDisableLoadChangeEnabled()) {
971            return;
972        }
973        if (getWait() > 0) {
974            return; // change load name when wait count reaches 0
975        }
976        // arriving at spur with a schedule?
977        String loadName = NONE;
978        ScheduleItem si = getScheduleItem(track);
979        if (si != null) {
980            loadName = si.getShipLoadName(); // can be NONE
981        } else {
982            // for backwards compatibility before version 5.1.4
983            log.debug("Schedule item ({}) is null for car ({}) at spur ({}), using next load name", getScheduleItemId(),
984                    toString(), track.getName());
985            loadName = getNextLoadName();
986        }
987        setNextLoadName(NONE); // never used again
988        // car could be part of a kernel
989        if (getKernel() != null && !carLoads.containsName(getTypeName(), loadName)) {
990            loadName = NONE;
991        }
992        if (!loadName.equals(NONE)) {
993            setLoadName(loadName);
994            if (getLoadName().equals(getReturnWhenEmptyLoadName())) {
995                setReturnWhenEmpty();
996            } else if (getLoadName().equals(getReturnWhenLoadedLoadName())) {
997                setReturnWhenLoaded();
998            }
999        } else {
1000            // flip load names
1001            if (getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
1002                setLoadLoaded();
1003            } else {
1004                setLoadEmpty();
1005            }
1006        }
1007        loadCarFinalDestination(si);
1008        setScheduleItemId(Car.NONE);
1009    }
1010
1011    /**
1012     * Sets the car's load to empty, triggers RWE load and destination if
1013     * enabled.
1014     */
1015    private void setLoadEmpty() {
1016        if (!getLoadName().equals(getReturnWhenEmptyLoadName())) {
1017            setLoadName(getReturnWhenEmptyLoadName()); // default RWE load is
1018                                                       // the "E" load
1019            setReturnWhenEmpty();
1020        }
1021    }
1022
1023    /*
1024     * Don't set return address if in staging with the same RWE address and
1025     * don't set return address if at the RWE address
1026     */
1027    private void setReturnWhenEmpty() {
1028        if (getFinalDestination() == null &&
1029                getReturnWhenEmptyDestination() != null &&
1030                (getLocation() != getReturnWhenEmptyDestination() ||
1031                        (!getReturnWhenEmptyDestination().isStaging() &&
1032                                getTrack() != getReturnWhenEmptyDestTrack()))) {
1033            setFinalDestination(getReturnWhenEmptyDestination());
1034            setFinalDestinationTrack(getReturnWhenEmptyDestTrack());
1035            log.debug("Car ({}) has return when empty destination ({}, {}) load {}", toString(),
1036                    getFinalDestinationName(), getFinalDestinationTrackName(), getLoadName());
1037        }
1038    }
1039
1040    /**
1041     * Sets the car's load to loaded, triggers RWL load and destination if
1042     * enabled.
1043     */
1044    private void setLoadLoaded() {
1045        if (!getLoadName().equals(getReturnWhenLoadedLoadName())) {
1046            setLoadName(getReturnWhenLoadedLoadName()); // default RWL load is
1047                                                        // the "L" load
1048            setReturnWhenLoaded();
1049        }
1050    }
1051
1052    /*
1053     * Don't set return address if in staging with the same RWL address and
1054     * don't set return address if at the RWL address
1055     */
1056    private void setReturnWhenLoaded() {
1057        if (getFinalDestination() == null &&
1058                getReturnWhenLoadedDestination() != null &&
1059                (getLocation() != getReturnWhenLoadedDestination() ||
1060                        (!getReturnWhenLoadedDestination().isStaging() &&
1061                                getTrack() != getReturnWhenLoadedDestTrack()))) {
1062            setFinalDestination(getReturnWhenLoadedDestination());
1063            setFinalDestinationTrack(getReturnWhenLoadedDestTrack());
1064            log.debug("Car ({}) has return when loaded destination ({}, {}) load {}", toString(),
1065                    getFinalDestinationName(), getFinalDestinationTrackName(), getLoadName());
1066        }
1067    }
1068
1069    public String getTypeExtensions() {
1070        StringBuffer buf = new StringBuffer();
1071        if (isCaboose()) {
1072            buf.append(EXTENSION_REGEX + CABOOSE_EXTENSION);
1073        }
1074        if (hasFred()) {
1075            buf.append(EXTENSION_REGEX + FRED_EXTENSION);
1076        }
1077        if (isPassenger()) {
1078            buf.append(EXTENSION_REGEX + PASSENGER_EXTENSION + EXTENSION_REGEX + getBlocking());
1079        }
1080        if (isUtility()) {
1081            buf.append(EXTENSION_REGEX + UTILITY_EXTENSION);
1082        }
1083        if (isCarHazardous()) {
1084            buf.append(EXTENSION_REGEX + HAZARDOUS_EXTENSION);
1085        }
1086        return buf.toString();
1087    }
1088
1089    @Override
1090    public void reset() {
1091        setScheduleItemId(getPreviousScheduleId()); // revert to previous
1092        setNextLoadName(NONE);
1093        setFinalDestination(getPreviousFinalDestination());
1094        setFinalDestinationTrack(getPreviousFinalDestinationTrack());
1095        if (isLoadGeneratedFromStaging()) {
1096            setLoadGeneratedFromStaging(false);
1097            setLoadName(carLoads.getDefaultEmptyName());
1098        }
1099        super.reset();
1100        destroyClone();
1101    }
1102
1103    /*
1104     * This routine destroys the clone and restores the cloned car to its
1105     * original location and load. Note there can be multiple clones for a car.
1106     * Only the first clone created has the right info. A clone has creation
1107     * order number appended to the road number.
1108     */
1109    private void destroyClone() {
1110        if (isClone()) {
1111            // move cloned car back to original location
1112            CarManager carManager = InstanceManager.getDefault(CarManager.class);
1113            String[] number = getNumber().split(Car.CLONE_REGEX);
1114            Car car = carManager.getByRoadAndNumber(getRoadName(), number[0]);
1115            int cloneCreationNumber = Integer.parseInt(number[1]);
1116            if (cloneCreationNumber <= car.getCloneOrder()) {
1117                car.setLocation(getLocation(), getTrack(), Car.FORCE);
1118                car.setRouteDestination(null); // clear rd
1119                car.setLoadName(getLoadName());
1120                car.setLastTrain(getLastTrain());
1121                car.setLastRouteId(getLastRouteId());
1122                car.setLastDate(getLastDate());
1123                car.setFinalDestination(getPreviousFinalDestination());
1124                car.setFinalDestinationTrack(getPreviousFinalDestinationTrack());
1125                car.setPreviousFinalDestination(getPreviousFinalDestination());
1126                car.setPreviousFinalDestinationTrack(getPreviousFinalDestinationTrack());
1127                car.setScheduleItemId(getPreviousScheduleId());
1128                car.setWait(0);
1129                car.setMoves(getMoves());
1130                // remember the last clone destroyed
1131                car.setCloneOrder(cloneCreationNumber);
1132            }
1133            InstanceManager.getDefault(KernelManager.class).deleteKernel(getKernelName());
1134            carManager.deregister(this);
1135        }
1136    }
1137
1138    @Override
1139    public void dispose() {
1140        setKernel(null);
1141        setFinalDestination(null); // removes property change listener
1142        setFinalDestinationTrack(null); // removes property change listener
1143        InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this);
1144        InstanceManager.getDefault(CarLengths.class).removePropertyChangeListener(this);
1145        super.dispose();
1146    }
1147
1148    // used to stop a track's schedule from bumping when loading car database
1149    private boolean loaded = false;
1150
1151    /**
1152     * Construct this Entry from XML. This member has to remain synchronized
1153     * with the detailed DTD in operations-cars.dtd
1154     *
1155     * @param e Car XML element
1156     */
1157    public Car(org.jdom2.Element e) {
1158        super(e);
1159        loaded = true;
1160        org.jdom2.Attribute a;
1161        if ((a = e.getAttribute(Xml.PASSENGER)) != null) {
1162            _passenger = a.getValue().equals(Xml.TRUE);
1163        }
1164        if ((a = e.getAttribute(Xml.HAZARDOUS)) != null) {
1165            _hazardous = a.getValue().equals(Xml.TRUE);
1166        }
1167        if ((a = e.getAttribute(Xml.CABOOSE)) != null) {
1168            _caboose = a.getValue().equals(Xml.TRUE);
1169        }
1170        if ((a = e.getAttribute(Xml.FRED)) != null) {
1171            _fred = a.getValue().equals(Xml.TRUE);
1172        }
1173        if ((a = e.getAttribute(Xml.UTILITY)) != null) {
1174            _utility = a.getValue().equals(Xml.TRUE);
1175        }
1176        if ((a = e.getAttribute(Xml.KERNEL)) != null) {
1177            Kernel k = InstanceManager.getDefault(KernelManager.class).getKernelByName(a.getValue());
1178            if (k != null) {
1179                setKernel(k);
1180                if ((a = e.getAttribute(Xml.LEAD_KERNEL)) != null && a.getValue().equals(Xml.TRUE)) {
1181                    _kernel.setLead(this);
1182                }
1183            } else {
1184                log.error("Kernel {} does not exist", a.getValue());
1185            }
1186        }
1187        if ((a = e.getAttribute(Xml.LOAD)) != null) {
1188            _loadName = a.getValue();
1189        }
1190        if ((a = e.getAttribute(Xml.LOAD_FROM_STAGING)) != null && a.getValue().equals(Xml.TRUE)) {
1191            setLoadGeneratedFromStaging(true);
1192        }
1193        if ((a = e.getAttribute(Xml.WAIT)) != null) {
1194            try {
1195                _wait = Integer.parseInt(a.getValue());
1196            } catch (NumberFormatException nfe) {
1197                log.error("Wait count ({}) for car ({}) isn't a valid number!", a.getValue(), toString());
1198            }
1199        }
1200        if ((a = e.getAttribute(Xml.PICKUP_SCHEDULE_ID)) != null) {
1201            _pickupScheduleId = a.getValue();
1202        }
1203        if ((a = e.getAttribute(Xml.SCHEDULE_ID)) != null) {
1204            _scheduleId = a.getValue();
1205        }
1206        // for backwards compatibility before version 5.1.4
1207        if ((a = e.getAttribute(Xml.NEXT_LOAD)) != null) {
1208            _nextLoadName = a.getValue();
1209        }
1210        if ((a = e.getAttribute(Xml.NEXT_DEST_ID)) != null) {
1211            setFinalDestination(InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue()));
1212        }
1213        if (getFinalDestination() != null && (a = e.getAttribute(Xml.NEXT_DEST_TRACK_ID)) != null) {
1214            setFinalDestinationTrack(getFinalDestination().getTrackById(a.getValue()));
1215        }
1216        if ((a = e.getAttribute(Xml.PREVIOUS_NEXT_DEST_ID)) != null) {
1217            setPreviousFinalDestination(
1218                    InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue()));
1219        }
1220        if (getPreviousFinalDestination() != null && (a = e.getAttribute(Xml.PREVIOUS_NEXT_DEST_TRACK_ID)) != null) {
1221            setPreviousFinalDestinationTrack(getPreviousFinalDestination().getTrackById(a.getValue()));
1222        }
1223        if ((a = e.getAttribute(Xml.PREVIOUS_SCHEDULE_ID)) != null) {
1224            setPreviousScheduleId(a.getValue());
1225        }
1226        if ((a = e.getAttribute(Xml.RWE_DEST_ID)) != null) {
1227            _rweDestination = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue());
1228        }
1229        if (_rweDestination != null && (a = e.getAttribute(Xml.RWE_DEST_TRACK_ID)) != null) {
1230            _rweDestTrack = _rweDestination.getTrackById(a.getValue());
1231        }
1232        if ((a = e.getAttribute(Xml.RWE_LOAD)) != null) {
1233            _rweLoadName = a.getValue();
1234        }
1235        if ((a = e.getAttribute(Xml.RWL_DEST_ID)) != null) {
1236            _rwlDestination = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue());
1237        }
1238        if (_rwlDestination != null && (a = e.getAttribute(Xml.RWL_DEST_TRACK_ID)) != null) {
1239            _rwlDestTrack = _rwlDestination.getTrackById(a.getValue());
1240        }
1241        if ((a = e.getAttribute(Xml.RWL_LOAD)) != null) {
1242            _rwlLoadName = a.getValue();
1243        }
1244        if ((a = e.getAttribute(Xml.ROUTE_PATH)) != null) {
1245            _routePath = a.getValue();
1246        }
1247        addPropertyChangeListeners();
1248    }
1249
1250    /**
1251     * Create an XML element to represent this Entry. This member has to remain
1252     * synchronized with the detailed DTD in operations-cars.dtd.
1253     *
1254     * @return Contents in a JDOM Element
1255     */
1256    public org.jdom2.Element store() {
1257        org.jdom2.Element e = new org.jdom2.Element(Xml.CAR);
1258        super.store(e);
1259        if (isPassenger()) {
1260            e.setAttribute(Xml.PASSENGER, isPassenger() ? Xml.TRUE : Xml.FALSE);
1261        }
1262        if (isCarHazardous()) {
1263            e.setAttribute(Xml.HAZARDOUS, isCarHazardous() ? Xml.TRUE : Xml.FALSE);
1264        }
1265        if (isCaboose()) {
1266            e.setAttribute(Xml.CABOOSE, isCaboose() ? Xml.TRUE : Xml.FALSE);
1267        }
1268        if (hasFred()) {
1269            e.setAttribute(Xml.FRED, hasFred() ? Xml.TRUE : Xml.FALSE);
1270        }
1271        if (isUtility()) {
1272            e.setAttribute(Xml.UTILITY, isUtility() ? Xml.TRUE : Xml.FALSE);
1273        }
1274        if (getKernel() != null) {
1275            e.setAttribute(Xml.KERNEL, getKernelName());
1276            if (isLead()) {
1277                e.setAttribute(Xml.LEAD_KERNEL, Xml.TRUE);
1278            }
1279        }
1280
1281        e.setAttribute(Xml.LOAD, getLoadName());
1282
1283        if (isLoadGeneratedFromStaging()) {
1284            e.setAttribute(Xml.LOAD_FROM_STAGING, Xml.TRUE);
1285        }
1286
1287        if (getWait() != 0) {
1288            e.setAttribute(Xml.WAIT, Integer.toString(getWait()));
1289        }
1290
1291        if (!getPickupScheduleId().equals(NONE)) {
1292            e.setAttribute(Xml.PICKUP_SCHEDULE_ID, getPickupScheduleId());
1293        }
1294
1295        if (!getScheduleItemId().equals(NONE)) {
1296            e.setAttribute(Xml.SCHEDULE_ID, getScheduleItemId());
1297        }
1298
1299        // for backwards compatibility before version 5.1.4
1300        if (!getNextLoadName().equals(NONE)) {
1301            e.setAttribute(Xml.NEXT_LOAD, getNextLoadName());
1302        }
1303
1304        if (getFinalDestination() != null) {
1305            e.setAttribute(Xml.NEXT_DEST_ID, getFinalDestination().getId());
1306            if (getFinalDestinationTrack() != null) {
1307                e.setAttribute(Xml.NEXT_DEST_TRACK_ID, getFinalDestinationTrack().getId());
1308            }
1309        }
1310
1311        if (getPreviousFinalDestination() != null) {
1312            e.setAttribute(Xml.PREVIOUS_NEXT_DEST_ID, getPreviousFinalDestination().getId());
1313            if (getPreviousFinalDestinationTrack() != null) {
1314                e.setAttribute(Xml.PREVIOUS_NEXT_DEST_TRACK_ID, getPreviousFinalDestinationTrack().getId());
1315            }
1316        }
1317
1318        if (!getPreviousScheduleId().equals(NONE)) {
1319            e.setAttribute(Xml.PREVIOUS_SCHEDULE_ID, getPreviousScheduleId());
1320        }
1321
1322        if (getReturnWhenEmptyDestination() != null) {
1323            e.setAttribute(Xml.RWE_DEST_ID, getReturnWhenEmptyDestination().getId());
1324            if (getReturnWhenEmptyDestTrack() != null) {
1325                e.setAttribute(Xml.RWE_DEST_TRACK_ID, getReturnWhenEmptyDestTrack().getId());
1326            }
1327        }
1328        if (!getReturnWhenEmptyLoadName().equals(carLoads.getDefaultEmptyName())) {
1329            e.setAttribute(Xml.RWE_LOAD, getReturnWhenEmptyLoadName());
1330        }
1331
1332        if (getReturnWhenLoadedDestination() != null) {
1333            e.setAttribute(Xml.RWL_DEST_ID, getReturnWhenLoadedDestination().getId());
1334            if (getReturnWhenLoadedDestTrack() != null) {
1335                e.setAttribute(Xml.RWL_DEST_TRACK_ID, getReturnWhenLoadedDestTrack().getId());
1336            }
1337        }
1338        if (!getReturnWhenLoadedLoadName().equals(carLoads.getDefaultLoadName())) {
1339            e.setAttribute(Xml.RWL_LOAD, getReturnWhenLoadedLoadName());
1340        }
1341
1342        if (!getRoutePath().isEmpty()) {
1343            e.setAttribute(Xml.ROUTE_PATH, getRoutePath());
1344        }
1345
1346        return e;
1347    }
1348
1349    @Override
1350    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
1351        // Set dirty
1352        InstanceManager.getDefault(CarManagerXml.class).setDirty(true);
1353        super.setDirtyAndFirePropertyChange(p, old, n);
1354    }
1355
1356    private void addPropertyChangeListeners() {
1357        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
1358        InstanceManager.getDefault(CarLengths.class).addPropertyChangeListener(this);
1359    }
1360
1361    @Override
1362    public void propertyChange(PropertyChangeEvent e) {
1363        super.propertyChange(e);
1364        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
1365            if (e.getOldValue().equals(getTypeName())) {
1366                log.debug("Car ({}) sees type name change old: ({}) new: ({})", toString(), e.getOldValue(),
1367                        e.getNewValue()); // NOI18N
1368                setTypeName((String) e.getNewValue());
1369            }
1370        }
1371        if (e.getPropertyName().equals(CarLengths.CARLENGTHS_NAME_CHANGED_PROPERTY)) {
1372            if (e.getOldValue().equals(getLength())) {
1373                log.debug("Car ({}) sees length name change old: ({}) new: ({})", toString(), e.getOldValue(),
1374                        e.getNewValue()); // NOI18N
1375                setLength((String) e.getNewValue());
1376            }
1377        }
1378        if (e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) {
1379            if (e.getSource() == getFinalDestination()) {
1380                log.debug("delete final destination for car: ({})", toString());
1381                setFinalDestination(null);
1382            }
1383        }
1384        if (e.getPropertyName().equals(Track.DISPOSE_CHANGED_PROPERTY)) {
1385            if (e.getSource() == getFinalDestinationTrack()) {
1386                log.debug("delete final destination for car: ({})", toString());
1387                setFinalDestinationTrack(null);
1388            }
1389        }
1390    }
1391
1392    private final static Logger log = LoggerFactory.getLogger(Car.class);
1393
1394}