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