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