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