001package jmri.jmrit.operations.rollingstock.cars;
002
003import java.beans.PropertyChangeEvent;
004import java.text.NumberFormat;
005import java.util.*;
006
007import org.jdom2.Element;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import jmri.*;
012import jmri.jmrit.operations.locations.Track;
013import jmri.jmrit.operations.rollingstock.RollingStock;
014import jmri.jmrit.operations.rollingstock.RollingStockManager;
015import jmri.jmrit.operations.routes.Route;
016import jmri.jmrit.operations.routes.RouteLocation;
017import jmri.jmrit.operations.setup.OperationsSetupXml;
018import jmri.jmrit.operations.setup.Setup;
019import jmri.jmrit.operations.trains.Train;
020import jmri.jmrit.operations.trains.TrainManifestHeaderText;
021
022/**
023 * Manages the cars.
024 *
025 * @author Daniel Boudreau Copyright (C) 2008
026 */
027public class CarManager extends RollingStockManager<Car>
028        implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize {
029
030    public CarManager() {
031    }
032
033    /**
034     * Finds an existing Car or creates a new Car if needed requires car's road and
035     * number
036     *
037     * @param road   car road
038     * @param number car number
039     * @return new car or existing Car
040     */
041    @Override
042    public Car newRS(String road, String number) {
043        Car car = getByRoadAndNumber(road, number);
044        if (car == null) {
045            car = new Car(road, number);
046            register(car);
047        }
048        return car;
049    }
050
051    @Override
052    public void deregister(Car car) {
053        super.deregister(car);
054        InstanceManager.getDefault(CarManagerXml.class).setDirty(true);
055    }
056
057    /**
058     * Sort by rolling stock location
059     *
060     * @return list of cars ordered by the Car's location
061     */
062    @Override
063    public List<Car> getByLocationList() {
064        List<Car> byFinal = getByList(getByNumberList(), BY_FINAL_DEST);
065        List<Car> byKernel = getByList(byFinal, BY_KERNEL);
066        return getByList(byKernel, BY_LOCATION);
067    }
068
069    /**
070     * Sort by car kernel names
071     *
072     * @return list of cars ordered by car kernel
073     */
074    public List<Car> getByKernelList() {
075        return getByList(getByList(getByNumberList(), BY_BLOCKING), BY_KERNEL);
076    }
077
078    /**
079     * Sort by car loads
080     *
081     * @return list of cars ordered by car loads
082     */
083    public List<Car> getByLoadList() {
084        return getByList(getByLocationList(), BY_LOAD);
085    }
086
087    /**
088     * Sort by car return when empty location and track
089     *
090     * @return list of cars ordered by car return when empty
091     */
092    public List<Car> getByRweList() {
093        return getByList(getByLocationList(), BY_RWE);
094    }
095
096    public List<Car> getByRwlList() {
097        return getByList(getByLocationList(), BY_RWL);
098    }
099
100    public List<Car> getByRouteList() {
101        return getByList(getByLocationList(), BY_ROUTE);
102    }
103
104    public List<Car> getByDivisionList() {
105        return getByList(getByLocationList(), BY_DIVISION);
106    }
107
108    public List<Car> getByFinalDestinationList() {
109        return getByList(getByDestinationList(), BY_FINAL_DEST);
110    }
111
112    /**
113     * Sort by car wait count
114     *
115     * @return list of cars ordered by wait count
116     */
117    public List<Car> getByWaitList() {
118        return getByList(getByIdList(), BY_WAIT);
119    }
120
121    @Override
122    public List<Car> getByPickupList() {
123        return getByList(getByDestinationList(), BY_PICKUP);
124    }
125
126    // The special sort options for cars
127    private static final int BY_LOAD = 30;
128    private static final int BY_KERNEL = 31;
129    private static final int BY_RWE = 32; // Return When Empty
130    private static final int BY_FINAL_DEST = 33;
131    private static final int BY_WAIT = 34;
132    private static final int BY_PICKUP = 35;
133    private static final int BY_HAZARD = 36;
134    private static final int BY_RWL = 37; // Return When loaded
135    private static final int BY_ROUTE = 38;
136    private static final int BY_DIVISION = 39;
137    
138    // the name of the location and track is "split"
139    private static final int BY_SPLIT_FINAL_DEST = 40;
140    private static final int BY_SPLIT_LOCATION = 41;
141    private static final int BY_SPLIT_DESTINATION = 42;
142
143    // add car options to sort comparator
144    @Override
145    protected java.util.Comparator<Car> getComparator(int attribute) {
146        switch (attribute) {
147            case BY_LOAD:
148                return (c1, c2) -> (c1.getLoadName().compareToIgnoreCase(c2.getLoadName()));
149            case BY_KERNEL:
150                return (c1, c2) -> (c1.getKernelName().compareToIgnoreCase(c2.getKernelName()));
151            case BY_RWE:
152                return (c1, c2) -> (c1.getReturnWhenEmptyDestinationName() + c1.getReturnWhenEmptyDestTrackName())
153                        .compareToIgnoreCase(
154                                c2.getReturnWhenEmptyDestinationName() + c2.getReturnWhenEmptyDestTrackName());
155            case BY_RWL:
156                return (c1, c2) -> (c1.getReturnWhenLoadedDestinationName() + c1.getReturnWhenLoadedDestTrackName())
157                        .compareToIgnoreCase(
158                                c2.getReturnWhenLoadedDestinationName() + c2.getReturnWhenLoadedDestTrackName());
159            case BY_FINAL_DEST:
160                return (c1, c2) -> (c1.getFinalDestinationName() + c1.getFinalDestinationTrackName())
161                        .compareToIgnoreCase(c2.getFinalDestinationName() + c2.getFinalDestinationTrackName());
162            case BY_ROUTE:
163                return (c1, c2) -> (c1.getRoutePath().compareToIgnoreCase(c2.getRoutePath()));
164            case BY_DIVISION:
165                return (c1, c2) -> (c1.getDivisionName().compareToIgnoreCase(c2.getDivisionName()));
166            case BY_WAIT:
167                return (c1, c2) -> (c1.getWait() - c2.getWait());
168            case BY_PICKUP:
169                return (c1, c2) -> (c1.getPickupScheduleName().compareToIgnoreCase(c2.getPickupScheduleName()));
170            case BY_HAZARD:
171                return (c1, c2) -> ((c1.isHazardous() ? 1 : 0) - (c2.isHazardous() ? 1 : 0));
172            case BY_SPLIT_FINAL_DEST:
173                return (c1, c2) -> (c1.getSplitFinalDestinationName() + c1.getSplitFinalDestinationTrackName())
174                        .compareToIgnoreCase(
175                                c2.getSplitFinalDestinationName() + c2.getSplitFinalDestinationTrackName());
176            case BY_SPLIT_LOCATION:
177                return (c1, c2) -> (c1.getStatus() + c1.getSplitLocationName() + c1.getSplitTrackName())
178                        .compareToIgnoreCase(c2.getStatus() + c2.getSplitLocationName() + c2.getSplitTrackName());
179            case BY_SPLIT_DESTINATION:
180                return (c1, c2) -> (c1.getSplitDestinationName() + c1.getSplitDestinationTrackName())
181                        .compareToIgnoreCase(c2.getSplitDestinationName() + c2.getSplitDestinationTrackName());
182            default:
183                return super.getComparator(attribute);
184        }
185    }
186
187    /**
188     * Return a list available cars (no assigned train or car already assigned
189     * to this train) on a route, cars are ordered least recently moved to most
190     * recently moved. Note that it is possible for a car to have a location,
191     * but no track assignment.
192     *
193     * @param train The Train to use.
194     * @return List of cars with no assigned train on a route
195     */
196    public List<Car> getAvailableTrainList(Train train) {
197        List<Car> out = new ArrayList<>();
198        Route route = train.getRoute();
199        if (route == null) {
200            return out;
201        }
202        // get a list of locations served by this route
203        List<RouteLocation> routeList = route.getLocationsBySequenceList();
204        // don't include Car at route destination
205        RouteLocation destination = null;
206        if (routeList.size() > 1) {
207            destination = routeList.get(routeList.size() - 1);
208            // However, if the destination is visited more than once, must
209            // include all cars
210            for (int i = 0; i < routeList.size() - 1; i++) {
211                if (destination.getName().equals(routeList.get(i).getName())) {
212                    destination = null; // include cars at destination
213                    break;
214                }
215            }
216            // pickup allowed at destination? Don't include cars in staging
217            if (destination != null &&
218                    destination.isPickUpAllowed() &&
219                    destination.getLocation() != null &&
220                    !destination.getLocation().isStaging()) {
221                destination = null; // include cars at destination
222            }
223        }
224        // get rolling stock by track priority, load priority and then by moves
225        List<Car> sortByPriority = sortByTrackPriority(sortByLoadPriority(getByMovesList()));
226        // now build list of available Car for this route
227        for (Car car : sortByPriority) {
228            // only use Car with a location
229            if (car.getLocation() == null) {
230                continue;
231            }
232            RouteLocation rl = route.getLastLocationByName(car.getLocationName());
233            // get Car that don't have an assigned train, or the
234            // assigned train is this one
235            if (rl != null && rl != destination && (car.getTrain() == null || train.equals(car.getTrain()))) {
236                out.add(car);
237            }
238        }
239        return out;
240    }
241
242    // sorts the high priority cars to the start of the list
243    protected List<Car> sortByLoadPriority(List<Car> list) {
244        List<Car> out = new ArrayList<>();
245        // move high priority cars to the start
246        for (Car car : list) {
247            if (car.getLoadPriority().equals(CarLoad.PRIORITY_HIGH)) {
248                out.add(car);
249            }
250        }
251        for (Car car : list) {
252            if (car.getLoadPriority().equals(CarLoad.PRIORITY_MEDIUM)) {
253                out.add(car);
254            }
255        }
256        // now load all of the remaining low priority cars
257        for (Car car : list) {
258            if (!out.contains(car)) {
259                out.add(car);
260            }
261        }
262        return out;
263    }
264
265    /**
266     * Provides a very sorted list of cars assigned to the train. Note that this
267     * isn't the final sort as the cars must be sorted by each location the
268     * train visits.
269     * <p>
270     * The sort priority is as follows:
271     * <ol>
272     * <li>Caboose or car with FRED to the end of the list, unless passenger.
273     * <li>Passenger cars have blocking numbers which places them relative to
274     * each other. Passenger cars with positive blocking numbers to the end of
275     * the list, but before cabooses or car with FRED. Passenger cars with
276     * negative blocking numbers are placed at the front of the train.
277     * <li>Car's destination (alphabetical by location and track name or by
278     * track blocking order)
279     * <li>Car is hazardous (hazardous placed after a non-hazardous car)
280     * <li>Car's current location (alphabetical by location and track name)
281     * <li>Car's final destination (alphabetical by location and track name)
282     * </ol>
283     * <p>
284     * Cars in a kernel are placed together by their kernel blocking numbers,
285     * except if they are type passenger. The kernel's position in the list is
286     * based on the lead car in the kernel.
287     * <p>
288     * If the train is to be blocked by track blocking order, all of the tracks
289     * at that location need a blocking number greater than 0.
290     *
291     * @param train The selected Train.
292     * @return Ordered list of cars assigned to the train
293     */
294    public List<Car> getByTrainDestinationList(Train train) {
295        List<Car> byFinal = getByList(getList(train), BY_SPLIT_FINAL_DEST);
296        List<Car> byLocation = getByList(byFinal, BY_SPLIT_LOCATION);
297        List<Car> byHazard = getByList(byLocation, BY_HAZARD);
298        List<Car> byDestination = getByList(byHazard, BY_SPLIT_DESTINATION);
299        // now place cabooses, cars with FRED, and passenger cars at the rear of the
300        // train
301        List<Car> out = new ArrayList<>();
302        int lastCarsIndex = 0; // incremented each time a car is added to the end of the list
303        for (Car car : byDestination) {
304            if (car.getKernel() != null && !car.isLead() && !car.isPassenger()) {
305                continue; // not the lead car, skip for now.
306            }
307            if (!car.isCaboose() && !car.hasFred() && !car.isPassenger()) {
308                // sort order based on train direction when serving track, low to high if West
309                // or North bound trains
310                if (car.getDestinationTrack() != null && car.getDestinationTrack().getBlockingOrder() > 0) {
311                    for (int j = 0; j < out.size(); j++) {
312                        if (out.get(j).getDestinationTrack() == null) {
313                            continue;
314                        }
315                        if (car.getRouteDestination() != null &&
316                                (car.getRouteDestination().getTrainDirectionString().equals(RouteLocation.WEST_DIR) ||
317                                        car.getRouteDestination().getTrainDirectionString()
318                                                .equals(RouteLocation.NORTH_DIR))) {
319                            if (car.getDestinationTrack().getBlockingOrder() < out.get(j).getDestinationTrack()
320                                    .getBlockingOrder()) {
321                                out.add(j, car);
322                                break;
323                            }
324                            // Train is traveling East or South when setting out the car
325                        } else {
326                            if (car.getDestinationTrack().getBlockingOrder() > out.get(j).getDestinationTrack()
327                                    .getBlockingOrder()) {
328                                out.add(j, car);
329                                break;
330                            }
331                        }
332                    }
333                }
334                if (!out.contains(car)) {
335                    out.add(out.size() - lastCarsIndex, car);
336                }
337            } else if (car.isPassenger()) {
338                if (car.getBlocking() < 0) {
339                    // block passenger cars with negative blocking numbers at
340                    // front of train
341                    int index;
342                    for (index = 0; index < out.size(); index++) {
343                        Car carTest = out.get(index);
344                        if (!carTest.isPassenger() || carTest.getBlocking() > car.getBlocking()) {
345                            break;
346                        }
347                    }
348                    out.add(index, car);
349                } else {
350                    // block passenger cars at end of list, but before cabooses
351                    // or car with FRED
352                    int index;
353                    for (index = 0; index < lastCarsIndex; index++) {
354                        Car carTest = out.get(out.size() - 1 - index);
355                        log.debug("Car ({}) has blocking number: {}", carTest.toString(), carTest.getBlocking());
356                        if (carTest.isPassenger() &&
357                                !carTest.isCaboose() &&
358                                !carTest.hasFred() &&
359                                carTest.getBlocking() < car.getBlocking()) {
360                            break;
361                        }
362                    }
363                    out.add(out.size() - index, car);
364                    lastCarsIndex++;
365                }
366            } else if (car.isCaboose() || car.hasFred()) {
367                out.add(car); // place at end of list
368                lastCarsIndex++;
369            }
370            // group the cars in the kernel together, except passenger
371            if (car.isLead()) {
372                int index = out.indexOf(car);
373                int numberOfCars = 1; // already added the lead car to the list
374                for (Car kcar : car.getKernel().getCars()) {
375                    if (car != kcar && !kcar.isPassenger()) {
376                        // Block cars in kernel
377                        for (int j = 0; j < numberOfCars; j++) {
378                            if (kcar.getBlocking() < out.get(index + j).getBlocking()) {
379                                out.add(index + j, kcar);
380                                break;
381                            }
382                        }
383                        if (!out.contains(kcar)) {
384                            out.add(index + numberOfCars, kcar);
385                        }
386                        numberOfCars++;
387                        if (car.hasFred() || car.isCaboose() || car.isPassenger() && car.getBlocking() > 0) {
388                            lastCarsIndex++; // place entire kernel at the end of list
389                        }
390                    }
391                }
392            }
393        }
394        return out;
395    }
396
397    /**
398     * Get a list of car road names where the car was flagged as a caboose.
399     *
400     * @return List of caboose road names.
401     */
402    public List<String> getCabooseRoadNames() {
403        List<String> names = new ArrayList<>();
404        Enumeration<String> en = _hashTable.keys();
405        while (en.hasMoreElements()) {
406            Car car = getById(en.nextElement());
407            if (car.isCaboose() && !names.contains(car.getRoadName())) {
408                names.add(car.getRoadName());
409            }
410        }
411        java.util.Collections.sort(names);
412        return names;
413    }
414
415    /**
416     * Get a list of car road names where the car was flagged with FRED
417     *
418     * @return List of road names of cars with FREDs
419     */
420    public List<String> getFredRoadNames() {
421        List<String> names = new ArrayList<>();
422        Enumeration<String> en = _hashTable.keys();
423        while (en.hasMoreElements()) {
424            Car car = getById(en.nextElement());
425            if (car.hasFred() && !names.contains(car.getRoadName())) {
426                names.add(car.getRoadName());
427            }
428        }
429        java.util.Collections.sort(names);
430        return names;
431    }
432
433    /**
434     * Replace car loads
435     *
436     * @param type        type of car
437     * @param oldLoadName old load name
438     * @param newLoadName new load name
439     */
440    public void replaceLoad(String type, String oldLoadName, String newLoadName) {
441        List<Car> cars = getList();
442        for (Car car : cars) {
443            if (car.getTypeName().equals(type) && car.getLoadName().equals(oldLoadName)) {
444                if (newLoadName != null) {
445                    car.setLoadName(newLoadName);
446                } else {
447                    car.setLoadName(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName());
448                }
449            }
450            if (car.getTypeName().equals(type) && car.getReturnWhenEmptyLoadName().equals(oldLoadName)) {
451                if (newLoadName != null) {
452                    car.setReturnWhenEmptyLoadName(newLoadName);
453                } else {
454                    car.setReturnWhenEmptyLoadName(InstanceManager.getDefault(CarLoads.class).getDefaultEmptyName());
455                }
456            }
457            if (car.getTypeName().equals(type) && car.getReturnWhenLoadedLoadName().equals(oldLoadName)) {
458                if (newLoadName != null) {
459                    car.setReturnWhenLoadedLoadName(newLoadName);
460                } else {
461                    car.setReturnWhenLoadedLoadName(InstanceManager.getDefault(CarLoads.class).getDefaultLoadName());
462                }
463            }
464        }
465    }
466
467    public List<Car> getCarsLocationUnknown() {
468        List<Car> mias = new ArrayList<>();
469        for (Car car : getByIdList()) {
470            if (car.isLocationUnknown()) {
471                mias.add(car); // return unknown location car
472            }
473        }
474        return mias;
475    }
476
477    /**
478     * Determines a car's weight in ounces based on car's scale length
479     * 
480     * @param carLength Car's scale length
481     * @return car's weight in ounces
482     * @throws NumberFormatException if length isn't a number
483     */
484    public static String calculateCarWeight(String carLength) throws NumberFormatException {
485        double doubleCarLength = Double.parseDouble(carLength) * 12 / Setup.getScaleRatio();
486        double doubleCarWeight = (Setup.getInitalWeight() + doubleCarLength * Setup.getAddWeight()) / 1000;
487        NumberFormat nf = NumberFormat.getNumberInstance();
488        nf.setMaximumFractionDigits(1);
489        return nf.format(doubleCarWeight); // car weight in ounces.
490    }
491    
492    /**
493     * Used to determine if any car has been assigned a division
494     * 
495     * @return true if any car has been assigned a division, otherwise false
496     */
497    public boolean isThereDivisions() {
498        for (Car car : getList()) {
499            if (car.getDivision() != null) {
500                return true;
501            }
502        }
503        return false;
504    }
505    
506    /**
507     * Used to determine if there are clone cars.
508     * 
509     * @return true if there are clone cars, otherwise false.
510     */
511    public boolean isThereClones() {
512        for (Car car : getList()) {
513            if (car.isClone()) {
514                return true;
515            }
516        }
517        return false;
518    }
519
520    /**
521     * Creates a clone for the car, and clones if the car is part of a kernel.
522     * Note that a car have have multiple clones.
523     * 
524     * @param car       The car to clone
525     * @param track     The destination track for the clones
526     * @param train     The train transporting the clones
527     * @param startTime The date and time the clones were moved
528     * @return clone for this car
529     */
530    public Car createClone(Car car, Track track, Train train, Date startTime) {
531        int cloneCreationOrder = getCloneCreationOrder();
532        String creationOrder = padNumber(cloneCreationOrder);
533        Car cloneCar = car.copy();
534        cloneCar.setNumber(car.getNumber() + Car.CLONE + creationOrder);
535        cloneCar.setClone(true);
536        // register car before setting location so the car gets logged
537        register(cloneCar);
538        cloneCar.setLocation(car.getLocation(), car.getTrack(), RollingStock.FORCE);
539        // for reset
540        cloneCar.setPreviousFinalDestination(car.getPreviousFinalDestination());
541        cloneCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
542        cloneCar.setPreviousScheduleId(car.getScheduleItemId());
543        cloneCar.setLastRouteId(car.getLastRouteId());
544        cloneCar.setMoves(car.getMoves());
545        if (car.getKernel() != null) {
546            String kernelName = car.getKernelName() + Car.CLONE + creationOrder;
547            Kernel kernel = InstanceManager.getDefault(KernelManager.class).newKernel(kernelName);
548            cloneCar.setKernel(kernel);
549            for (Car kar : car.getKernel().getCars()) {
550                if (kar != car) {
551                    Car nCar = kar.copy();
552                    nCar.setNumber(kar.getNumber() + Car.CLONE + creationOrder);
553                    nCar.setClone(true);
554                    nCar.setKernel(kernel);
555                    nCar.setMoves(kar.getMoves());
556                    register(nCar);
557                    nCar.setLocation(car.getLocation(), car.getTrack(), RollingStock.FORCE);
558                    // for reset
559                    nCar.setPreviousFinalDestination(car.getPreviousFinalDestination());
560                    nCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
561                    // move car to new location for later pick up
562                    kar.setLocation(track.getLocation(), track, RollingStock.FORCE);
563                    kar.setLastTrain(train);
564                    kar.setLastLocationId(car.getLocationId());
565                    kar.setLastTrackId(car.getTrackId());
566                    kar.setLastDate(startTime);
567                    kar.setMoves(kar.getMoves() + 1); // bump count
568                    kar.setCloneOrder(cloneCreationOrder); // for reset
569                }
570            }
571        }
572        // move car to new location for later pick up
573        car.setLocation(track.getLocation(), track, RollingStock.FORCE);
574        car.setLastTrain(train);
575        car.setLastLocationId(cloneCar.getLocationId());
576        car.setLastTrackId(cloneCar.getTrackId());
577        car.setLastRouteId(train.getRoute().getId());
578        // this car was moved during the build process
579        car.setLastDate(startTime);
580        car.setMoves(car.getMoves() + 1); // bump count
581        car.setCloneOrder(cloneCreationOrder); // for reset
582        car.setDestination(null, null);    
583        return cloneCar;
584    }
585
586    int _commentLength = 0;
587    
588    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
589            justification="I18N of Info Message")
590    public int getMaxCommentLength() {
591        if (_commentLength == 0) {
592            _commentLength = TrainManifestHeaderText.getStringHeader_Comment().length();
593            String comment = "";
594            Car carMax = null;
595            for (Car car : getList()) {
596                if (car.getComment().length() > _commentLength) {
597                    _commentLength = car.getComment().length();
598                    comment = car.getComment();
599                    carMax = car;
600                }
601            }
602            if (carMax != null) {
603                log.info(Bundle.getMessage("InfoMaxComment", carMax.toString(), comment, _commentLength));
604            }
605        }
606        return _commentLength;
607    }
608
609    public void load(Element root) {
610        if (root.getChild(Xml.CARS) != null) {
611            List<Element> eCars = root.getChild(Xml.CARS).getChildren(Xml.CAR);
612            log.debug("readFile sees {} cars", eCars.size());
613            for (Element eCar : eCars) {
614                register(new Car(eCar));
615            }
616        }
617    }
618
619    /**
620     * Create an XML element to represent this Entry. This member has to remain
621     * synchronized with the detailed DTD in operations-cars.dtd.
622     *
623     * @param root The common Element for operations-cars.dtd.
624     */
625    public void store(Element root) {
626        // nothing to save under options
627        root.addContent(new Element(Xml.OPTIONS));
628        
629        Element values;
630        root.addContent(values = new Element(Xml.CARS));
631        // add entries
632        List<Car> carList = getByIdList();
633        for (Car rs : carList) {
634            Car car = rs;
635            values.addContent(car.store());
636        }
637    }
638
639    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
640        // Set dirty
641        InstanceManager.getDefault(CarManagerXml.class).setDirty(true);
642        super.firePropertyChange(p, old, n);
643    }
644    
645    @Override
646    public void propertyChange(PropertyChangeEvent evt) {
647        if (evt.getPropertyName().equals(Car.COMMENT_CHANGED_PROPERTY)) {
648            _commentLength = 0;
649        }
650        super.propertyChange(evt);
651    }
652
653    private final static Logger log = LoggerFactory.getLogger(CarManager.class);
654
655    @Override
656    public void initialize() {
657        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
658        // create manager to load cars and their attributes
659        InstanceManager.getDefault(CarManagerXml.class);
660    }
661
662}