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