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