001package jmri.jmrit.operations.rollingstock.cars;
002
003import java.util.*;
004
005import javax.swing.JComboBox;
006
007import org.jdom2.Attribute;
008import org.jdom2.Element;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import jmri.InstanceManager;
013import jmri.InstanceManagerAutoDefault;
014import jmri.jmrit.operations.OperationsPanel;
015import jmri.jmrit.operations.rollingstock.RollingStockAttribute;
016import jmri.jmrit.operations.trains.TrainManifestHeaderText;
017import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
018
019/**
020 * Represents the loads that cars can have.
021 *
022 * @author Daniel Boudreau Copyright (C) 2008, 2014
023 */
024public class CarLoads extends RollingStockAttribute implements InstanceManagerAutoDefault {
025
026    protected Hashtable<String, List<CarLoad>> listCarLoads = new Hashtable<>();
027    protected String _emptyName = Bundle.getMessage("EmptyCar");
028    protected String _loadName = Bundle.getMessage("LoadedCar");
029
030    public static final String NONE = ""; // NOI18N
031
032    // for property change
033    public static final String LOAD_CHANGED_PROPERTY = "CarLoads_Load"; // NOI18N
034    public static final String LOAD_TYPE_CHANGED_PROPERTY = "CarLoads_Load_Type"; // NOI18N
035    public static final String LOAD_PRIORITY_CHANGED_PROPERTY = "CarLoads_Load_Priority"; // NOI18N
036    public static final String LOAD_NAME_CHANGED_PROPERTY = "CarLoads_Name"; // NOI18N
037    public static final String LOAD_COMMENT_CHANGED_PROPERTY = "CarLoads_Load_Comment"; // NOI18N
038    public static final String LOAD_HAZARDOUS_CHANGED_PROPERTY = "CarLoads_Load_Hazardous"; // NOI18N
039
040    public CarLoads() {
041    }
042
043    /**
044     * Add a car type with specific loads
045     *
046     * @param type car type
047     */
048    public void addType(String type) {
049        listCarLoads.put(type, new ArrayList<>());
050    }
051
052    /**
053     * Replace a car type. Transfers load type, priority, isHardous, drop and
054     * load comments.
055     *
056     * @param oldType old car type
057     * @param newType new car type
058     */
059    public void replaceType(String oldType, String newType) {
060        List<String> names = getNames(oldType);
061        addType(newType);
062        for (String name : names) {
063            addName(newType, name);
064            setLoadType(newType, name, getLoadType(oldType, name));
065            setPriority(newType, name, getPriority(oldType, name));
066            setHazardous(newType, name, isHazardous(oldType, name));
067            setDropComment(newType, name, getDropComment(oldType, name));
068            setPickupComment(newType, name, getPickupComment(oldType, name));
069        }
070        listCarLoads.remove(oldType);
071    }
072
073    /**
074     * Gets the appropriate car loads for the car's type.
075     *
076     * @param type Car type
077     * @return JComboBox with car loads starting with empty string.
078     */
079    public JComboBox<String> getSelectComboBox(String type) {
080        JComboBox<String> box = new JComboBox<>();
081        box.addItem(NONE);
082        for (String load : getNames(type)) {
083            box.addItem(load);
084        }
085        return box;
086    }
087
088    /**
089     * Gets the appropriate car loads for the car's type.
090     *
091     * @param type Car type
092     * @return JComboBox with car loads.
093     */
094    public JComboBox<String> getComboBox(String type) {
095        JComboBox<String> box = new JComboBox<>();
096        updateComboBox(type, box);
097        return box;
098
099    }
100
101    /**
102     * Gets a ComboBox with the available priorities
103     *
104     * @return JComboBox with car priorities.
105     */
106    public JComboBox<String> getPriorityComboBox() {
107        JComboBox<String> box = new JComboBox<>();
108        box.addItem(CarLoad.PRIORITY_LOW);
109        box.addItem(CarLoad.PRIORITY_MEDIUM);
110        box.addItem(CarLoad.PRIORITY_HIGH);
111        return box;
112    }
113
114    public JComboBox<String> getHazardousComboBox() {
115        JComboBox<String> box = new JComboBox<>();
116        box.addItem(Bundle.getMessage("ButtonNo"));
117        box.addItem(Bundle.getMessage("ButtonYes"));
118        return box;
119    }
120
121    /**
122     * Gets a ComboBox with the available load types: empty and load
123     *
124     * @return JComboBox with load types: LOAD_TYPE_EMPTY and LOAD_TYPE_LOAD
125     */
126    public JComboBox<String> getLoadTypesComboBox() {
127        JComboBox<String> box = new JComboBox<>();
128        box.addItem(CarLoad.LOAD_TYPE_EMPTY);
129        box.addItem(CarLoad.LOAD_TYPE_LOAD);
130        return box;
131    }
132
133    /**
134     * Gets a sorted list of load names for a given car type
135     *
136     * @param type car type
137     * @return list of load names
138     */
139    public List<String> getNames(String type) {
140        List<String> names = new ArrayList<>();
141        if (type == null) {
142            names.add(getDefaultEmptyName());
143            names.add(getDefaultLoadName());
144            return names;
145        }
146        List<CarLoad> loads = listCarLoads.get(type);
147        if (loads == null) {
148            addType(type);
149            loads = listCarLoads.get(type);
150        }
151        if (loads.isEmpty()) {
152            loads.add(new CarLoad(getDefaultEmptyName()));
153            loads.add(new CarLoad(getDefaultLoadName()));
154        }
155        for (CarLoad carLoad : loads) {
156            names.add(carLoad.getName());
157        }
158        java.util.Collections.sort(names);
159        return names;
160    }
161
162    /**
163     * Add a load name for the car type.
164     *
165     * @param type car type.
166     * @param name load name.
167     */
168    public void addName(String type, String name) {
169        // don't add if name already exists
170        if (containsName(type, name)) {
171            return;
172        }
173        List<CarLoad> loads = listCarLoads.get(type);
174        if (loads == null) {
175            log.debug("car type ({}) does not exist", type);
176            return;
177        }
178        loads.add(new CarLoad(name));
179        maxNameLength = 0; // reset maximum name length
180        setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, null, name);
181    }
182
183    public void deleteName(String type, String name) {
184        List<CarLoad> loads = listCarLoads.get(type);
185        if (loads == null) {
186            log.debug("car type ({}) does not exist", type);
187            return;
188        }
189        for (CarLoad cl : loads) {
190            if (cl.getName().equals(name)) {
191                loads.remove(cl);
192                break;
193            }
194        }
195        maxNameLength = 0; // reset maximum name length
196        setDirtyAndFirePropertyChange(LOAD_CHANGED_PROPERTY, name, null);
197    }
198
199    /**
200     * Determines if a car type can have a specific load name.
201     *
202     * @param type car type.
203     * @param name load name.
204     * @return true if car can have this load name.
205     */
206    public boolean containsName(String type, String name) {
207        List<String> names = getNames(type);
208        return names.contains(name);
209    }
210
211    public void updateComboBox(String type, JComboBox<String> box) {
212        box.removeAllItems();
213        List<String> names = getNames(type);
214        for (String name : names) {
215            box.addItem(name);
216        }
217        OperationsPanel.padComboBox(box, getMaxNameLength() + 1);
218    }
219
220    /**
221     * Update a JComboBox with all load names for every type of car.
222     *
223     * @param box the combo box to update
224     */
225    @Override
226    public void updateComboBox(JComboBox<String> box) {
227        box.removeAllItems();
228        List<String> names = new ArrayList<>();
229        for (String type : InstanceManager.getDefault(CarTypes.class).getNames()) {
230            for (String load : getNames(type)) {
231                if (!names.contains(load)) {
232                    names.add(load);
233                }
234            }
235        }
236        java.util.Collections.sort(names);
237        for (String load : names) {
238            box.addItem(load);
239        }
240    }
241
242    public void updateRweComboBox(String type, JComboBox<String> box) {
243        box.removeAllItems();
244        List<String> loads = getNames(type);
245        for (String name : loads) {
246            if (getLoadType(type, name).equals(CarLoad.LOAD_TYPE_EMPTY)) {
247                box.addItem(name);
248            }
249        }
250    }
251
252    public void updateRwlComboBox(String type, JComboBox<String> box) {
253        box.removeAllItems();
254        List<String> loads = getNames(type);
255        for (String name : loads) {
256            if (getLoadType(type, name).equals(CarLoad.LOAD_TYPE_LOAD)) {
257                box.addItem(name);
258            }
259        }
260    }
261
262    public void replaceName(String type, String oldName, String newName) {
263        addName(type, newName);
264        deleteName(type, oldName);
265        setDirtyAndFirePropertyChange(LOAD_NAME_CHANGED_PROPERTY, oldName, newName);
266    }
267
268    public String getDefaultLoadName() {
269        return _loadName;
270    }
271
272    public void setDefaultLoadName(String name) {
273        String old = _loadName;
274        _loadName = name;
275        setDirtyAndFirePropertyChange(LOAD_NAME_CHANGED_PROPERTY, old, name);
276    }
277
278    public String getDefaultEmptyName() {
279        return _emptyName;
280    }
281
282    public void setDefaultEmptyName(String name) {
283        String old = _emptyName;
284        _emptyName = name;
285        setDirtyAndFirePropertyChange(LOAD_NAME_CHANGED_PROPERTY, old, name);
286    }
287
288    /**
289     * Sets the load type, empty or load.
290     *
291     * @param type     car type.
292     * @param name     load name.
293     * @param loadType load type: LOAD_TYPE_EMPTY or LOAD_TYPE_LOAD.
294     */
295    public void setLoadType(String type, String name, String loadType) {
296        List<CarLoad> loads = listCarLoads.get(type);
297        if (loads != null) {
298            for (CarLoad cl : loads) {
299                if (cl.getName().equals(name)) {
300                    String oldType = cl.getLoadType();
301                    cl.setLoadType(loadType);
302                    if (!oldType.equals(loadType)) {
303                        setDirtyAndFirePropertyChange(LOAD_TYPE_CHANGED_PROPERTY, oldType, loadType);
304                    }
305                }
306            }
307        }
308    }
309
310    /**
311     * Get the load type, empty or load.
312     *
313     * @param type car type.
314     * @param name load name.
315     * @return load type, LOAD_TYPE_EMPTY or LOAD_TYPE_LOAD.
316     */
317    public String getLoadType(String type, String name) {
318        if (!containsName(type, name)) {
319            if (name != null && name.equals(getDefaultEmptyName())) {
320                return CarLoad.LOAD_TYPE_EMPTY;
321            }
322            return CarLoad.LOAD_TYPE_LOAD;
323        }
324        List<CarLoad> loads = listCarLoads.get(type);
325        for (CarLoad cl : loads) {
326            if (cl.getName().equals(name)) {
327                return cl.getLoadType();
328            }
329        }
330        return "error"; // NOI18N
331    }
332
333    /**
334     * Sets a loads priority.
335     *
336     * @param type     car type.
337     * @param name     load name.
338     * @param priority load priority, PRIORITY_LOW, PRIORITY_MEDIUM or
339     *                 PRIORITY_HIGH.
340     */
341    public void setPriority(String type, String name, String priority) {
342        List<CarLoad> loads = listCarLoads.get(type);
343        if (loads != null) {
344            for (CarLoad cl : loads) {
345                if (cl.getName().equals(name)) {
346                    String oldPriority = cl.getPriority();
347                    cl.setPriority(priority);
348                    if (!oldPriority.equals(priority)) {
349                        setDirtyAndFirePropertyChange(LOAD_PRIORITY_CHANGED_PROPERTY, oldPriority, priority);
350                    }
351                }
352            }
353        }
354    }
355
356    /**
357     * Get's a load's priority.
358     *
359     * @param type car type.
360     * @param name load name.
361     * @return load priority, PRIORITY_LOW, PRIORITY_MEDIUM or PRIORITY_HIGH.
362     */
363    public String getPriority(String type, String name) {
364        if (!containsName(type, name)) {
365            return CarLoad.PRIORITY_LOW;
366        }
367        List<CarLoad> loads = listCarLoads.get(type);
368        if (loads != null) {
369            for (CarLoad cl : loads) {
370                if (cl.getName().equals(name)) {
371                    return cl.getPriority();
372                }
373            }
374        }
375        return "error"; // NOI18N
376    }
377
378    public void setHazardous(String type, String name, boolean isHazardous) {
379        List<CarLoad> loads = listCarLoads.get(type);
380        if (loads != null) {
381            for (CarLoad cl : loads) {
382                if (cl.getName().equals(name)) {
383                    boolean oldIsHazardous = cl.isHazardous();
384                    cl.setHazardous(isHazardous);
385                    if (oldIsHazardous != isHazardous) {
386                        setDirtyAndFirePropertyChange(LOAD_HAZARDOUS_CHANGED_PROPERTY, oldIsHazardous, isHazardous);
387                    }
388                }
389            }
390        }
391    }
392
393    public boolean isHazardous(String type, String name) {
394        if (!containsName(type, name)) {
395            return false;
396        }
397        List<CarLoad> loads = listCarLoads.get(type);
398        for (CarLoad cl : loads) {
399            if (cl.getName().equals(name)) {
400                return cl.isHazardous();
401            }
402        }
403        return false;
404    }
405
406    /**
407     * Sets the comment for a car type's load
408     * 
409     * @param type    the car type
410     * @param name    the load name
411     * @param comment the comment
412     */
413    public void setPickupComment(String type, String name, String comment) {
414        if (!containsName(type, name)) {
415            return;
416        }
417        List<CarLoad> loads = listCarLoads.get(type);
418        if (loads != null) {
419            for (CarLoad cl : loads) {
420                if (cl.getName().equals(name)) {
421                    String oldComment = cl.getPickupComment();
422                    cl.setPickupComment(comment);
423                    if (!oldComment.equals(comment)) {
424                        maxCommentLength = 0;
425                        setDirtyAndFirePropertyChange(LOAD_COMMENT_CHANGED_PROPERTY, oldComment, comment);
426                    }
427                }
428            }
429        }
430    }
431
432    public String getPickupComment(String type, String name) {
433        if (!containsName(type, name)) {
434            return NONE;
435        }
436        List<CarLoad> loads = listCarLoads.get(type);
437        for (CarLoad cl : loads) {
438            if (cl.getName().equals(name)) {
439                return cl.getPickupComment();
440            }
441        }
442        return NONE;
443    }
444
445    public void setDropComment(String type, String name, String comment) {
446        if (!containsName(type, name)) {
447            return;
448        }
449        List<CarLoad> loads = listCarLoads.get(type);
450        if (loads != null) {
451            for (CarLoad cl : loads) {
452                if (cl.getName().equals(name)) {
453                    String oldComment = cl.getDropComment();
454                    cl.setDropComment(comment);
455                    if (!oldComment.equals(comment)) {
456                        maxCommentLength = 0;
457                        setDirtyAndFirePropertyChange(LOAD_COMMENT_CHANGED_PROPERTY, oldComment, comment);
458                    }
459                }
460            }
461        }
462    }
463
464    public String getDropComment(String type, String name) {
465        if (!containsName(type, name)) {
466            return NONE;
467        }
468        List<CarLoad> loads = listCarLoads.get(type);
469        for (CarLoad cl : loads) {
470            if (cl.getName().equals(name)) {
471                return cl.getDropComment();
472            }
473        }
474        return NONE;
475    }
476
477    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST",
478            justification = "I18N of Info Message")
479    @Override
480    public int getMaxNameLength() {
481        if (maxNameLength == 0) {
482            maxName = "";
483            maxNameLength = MIN_NAME_LENGTH;
484            String carTypeName = "";
485            Enumeration<String> en = listCarLoads.keys();
486            while (en.hasMoreElements()) {
487                String cartype = en.nextElement();
488                List<CarLoad> loads = listCarLoads.get(cartype);
489                for (CarLoad load : loads) {
490                    if (load.getName().split(TrainCommon.HYPHEN)[0].length() > maxNameLength) {
491                        maxName = load.getName().split(TrainCommon.HYPHEN)[0];
492                        maxNameLength = load.getName().split(TrainCommon.HYPHEN)[0].length();
493                        carTypeName = cartype;
494                    }
495                }
496            }
497            log.info(Bundle.getMessage("InfoMaxLoad", maxName, maxNameLength, carTypeName));
498        }
499        return maxNameLength;
500    }
501
502    int maxCommentLength = 0;
503
504    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST",
505            justification = "I18N of Info Message")
506    public int getMaxLoadCommentLength() {
507        if (maxCommentLength == 0) {
508            String maxComment = "";
509            String carTypeName = "";
510            String carLoadName = "";
511            Enumeration<String> en = listCarLoads.keys();
512            while (en.hasMoreElements()) {
513                String carType = en.nextElement();
514                List<CarLoad> loads = listCarLoads.get(carType);
515                for (CarLoad load : loads) {
516                    if (load.getDropComment().length() > maxCommentLength) {
517                        maxComment = load.getDropComment();
518                        maxCommentLength = load.getDropComment().length();
519                        carTypeName = carType;
520                        carLoadName = load.getName();
521                    }
522                    if (load.getPickupComment().length() > maxCommentLength) {
523                        maxComment = load.getPickupComment();
524                        maxCommentLength = load.getPickupComment().length();
525                        carTypeName = carType;
526                        carLoadName = load.getName();
527                    }
528                }
529            }
530            if (maxCommentLength < TrainManifestHeaderText.getStringHeader_Drop_Comment().length()) {
531                maxCommentLength = TrainManifestHeaderText.getStringHeader_Drop_Comment().length();
532            }
533            if (maxCommentLength < TrainManifestHeaderText.getStringHeader_Pickup_Comment().length()) {
534                maxCommentLength = TrainManifestHeaderText.getStringHeader_Pickup_Comment().length();
535            }
536            log.info(Bundle.getMessage("InfoMaxLoadMessage", maxComment, maxCommentLength,
537                    carTypeName, carLoadName));
538        }
539        return maxCommentLength;
540    }
541
542    private List<CarLoad> getSortedList(String type) {
543        List<CarLoad> loads = listCarLoads.get(type);
544        List<String> names = getNames(type);
545        List<CarLoad> out = new ArrayList<>();
546
547        // return a list sorted by load name
548        for (String name : names) {
549            for (CarLoad carLoad : loads) {
550                if (name.equals(carLoad.getName())) {
551                    out.add(carLoad);
552                    break;
553                }
554            }
555        }
556        return out;
557    }
558
559    @SuppressWarnings("unchecked")
560    public Hashtable<String, List<CarLoad>> getList() {
561        return (Hashtable<String, List<CarLoad>>) listCarLoads.clone();
562    }
563
564    @Override
565    public void dispose() {
566        listCarLoads.clear();
567        setDefaultEmptyName(Bundle.getMessage("EmptyCar"));
568        setDefaultLoadName(Bundle.getMessage("LoadedCar"));
569        super.dispose();
570    }
571
572    /**
573     * Create an XML element to represent this Entry. This member has to remain
574     * synchronized with the detailed DTD in operations-cars.dtd.
575     *
576     * @param root The common Element for operations-cars.dtd.
577     */
578    public void store(Element root) {
579        Element values = new Element(Xml.LOADS);
580        // store default load and empty
581        Element defaults = new Element(Xml.DEFAULTS);
582        defaults.setAttribute(Xml.EMPTY, getDefaultEmptyName());
583        defaults.setAttribute(Xml.LOAD, getDefaultLoadName());
584        values.addContent(defaults);
585        // store loads based on car types
586        String[] carTypeNames = InstanceManager.getDefault(CarTypes.class).getNames();
587        for (String carType : carTypeNames) {
588            if (!listCarLoads.containsKey(carType)) {
589                continue;
590            }
591            List<CarLoad> loads = getSortedList(carType);
592            Element xmlLoad = new Element(Xml.LOAD);
593            xmlLoad.setAttribute(Xml.TYPE, carType);
594            boolean mustStore = false; // only store loads that aren't the defaults
595            for (CarLoad load : loads) {
596                // don't store the defaults / low priority / not hazardous / no comment
597                if ((load.getName().equals(getDefaultEmptyName()) || load.getName().equals(getDefaultLoadName())) &&
598                        load.getPriority().equals(CarLoad.PRIORITY_LOW) &&
599                        !load.isHazardous() &&
600                        load.getPickupComment().equals(CarLoad.NONE) &&
601                        load.getDropComment().equals(CarLoad.NONE)) {
602                    continue;
603                }
604                Element xmlCarLoad = new Element(Xml.CAR_LOAD);
605                xmlCarLoad.setAttribute(Xml.NAME, load.getName());
606                if (!load.getPriority().equals(CarLoad.PRIORITY_LOW)) {
607                    xmlCarLoad.setAttribute(Xml.PRIORITY, load.getPriority());
608                    mustStore = true; // must store
609                }
610                if (load.isHazardous()) {
611                    xmlCarLoad.setAttribute(Xml.HAZARDOUS, load.isHazardous() ? Xml.TRUE : Xml.FALSE);
612                    mustStore = true; // must store
613                }
614                if (!load.getPickupComment().equals(CarLoad.NONE)) {
615                    xmlCarLoad.setAttribute(Xml.PICKUP_COMMENT, load.getPickupComment());
616                    mustStore = true; // must store
617                }
618                if (!load.getDropComment().equals(CarLoad.NONE)) {
619                    xmlCarLoad.setAttribute(Xml.DROP_COMMENT, load.getDropComment());
620                    mustStore = true; // must store
621                }
622                xmlCarLoad.setAttribute(Xml.LOAD_TYPE, load.getLoadType());
623                xmlLoad.addContent(xmlCarLoad);
624            }
625            if (loads.size() > 2 || mustStore) {
626                values.addContent(xmlLoad);
627            }
628        }
629        root.addContent(values);
630    }
631
632    public void load(Element e) {
633        if (e.getChild(Xml.LOADS) == null) {
634            return;
635        }
636        Attribute a;
637        Element defaults = e.getChild(Xml.LOADS).getChild(Xml.DEFAULTS);
638        if (defaults != null) {
639            if ((a = defaults.getAttribute(Xml.LOAD)) != null) {
640                _loadName = a.getValue();
641            }
642            if ((a = defaults.getAttribute(Xml.EMPTY)) != null) {
643                _emptyName = a.getValue();
644            }
645        }
646        List<Element> eLoads = e.getChild(Xml.LOADS).getChildren(Xml.LOAD);
647        log.debug("readFile sees {} car loads", eLoads.size());
648        for (Element eLoad : eLoads) {
649            if ((a = eLoad.getAttribute(Xml.TYPE)) != null) {
650                String type = a.getValue();
651                addType(type);
652                // old style had a list of names
653                if ((a = eLoad.getAttribute(Xml.NAMES)) != null) {
654                    String names = a.getValue();
655                    String[] loadNames = names.split("%%");// NOI18N
656                    Arrays.sort(loadNames);
657                    log.debug("Car load type: {} loads: {}", type, names);
658                    // addName puts new items at the start, so reverse load
659                    for (int j = loadNames.length; j > 0;) {
660                        addName(type, loadNames[--j]);
661                    }
662                }
663                // new style load and comments
664                List<Element> eCarLoads = eLoad.getChildren(Xml.CAR_LOAD);
665                log.debug("{} car loads for type: {}", eCarLoads.size(), type);
666                for (Element eCarLoad : eCarLoads) {
667                    if ((a = eCarLoad.getAttribute(Xml.NAME)) != null) {
668                        String name = a.getValue();
669                        addName(type, name);
670                        if ((a = eCarLoad.getAttribute(Xml.PRIORITY)) != null) {
671                            setPriority(type, name, a.getValue());
672                        }
673                        if ((a = eCarLoad.getAttribute(Xml.HAZARDOUS)) != null) {
674                            setHazardous(type, name, a.getValue().equals(Xml.TRUE));
675                        }
676                        if ((a = eCarLoad.getAttribute(Xml.PICKUP_COMMENT)) != null) {
677                            setPickupComment(type, name, a.getValue());
678                        }
679                        if ((a = eCarLoad.getAttribute(Xml.DROP_COMMENT)) != null) {
680                            setDropComment(type, name, a.getValue());
681                        }
682                        if ((a = eCarLoad.getAttribute(Xml.LOAD_TYPE)) != null) {
683                            setLoadType(type, name, a.getValue());
684                        }
685                    }
686                }
687            }
688        }
689    }
690
691    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
692        // Set dirty
693        InstanceManager.getDefault(CarManagerXml.class).setDirty(true);
694        super.firePropertyChange(p, old, n);
695    }
696
697    private final static Logger log = LoggerFactory.getLogger(CarLoads.class);
698
699}