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