001package jmri.jmrit.operations.trains.schedules;
002
003import java.beans.PropertyChangeListener;
004import java.util.*;
005
006import javax.swing.JComboBox;
007
008import org.jdom2.Attribute;
009import org.jdom2.Element;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import jmri.*;
014import jmri.beans.PropertyChangeSupport;
015import jmri.jmrit.operations.OperationsPanel;
016import jmri.jmrit.operations.locations.Location;
017import jmri.jmrit.operations.locations.LocationManager;
018import jmri.jmrit.operations.setup.Setup;
019import jmri.jmrit.operations.trains.*;
020import jmri.jmrit.operations.trains.csv.TrainCsvSwitchLists;
021
022/**
023 * Manages train schedules. The default is the days of the week, but can be
024 * anything the user wants when defining when trains will run.
025 *
026 * @author Bob Jacobsen Copyright (C) 2003
027 * @author Daniel Boudreau Copyright (C) 2010
028 */
029public class TrainScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
030
031    public TrainScheduleManager() {
032    }
033
034    public static final String NONE = "";
035    private String _trainScheduleActiveId = NONE;
036    private int _id = 0;
037    
038    public static final String LISTLENGTH_CHANGED_PROPERTY = "trainScheduleListLength"; // NOI18N
039    public static final String SCHEDULE_ID_CHANGED_PROPERTY = "ActiveTrainScheduleId"; // NOI18N
040
041    public void dispose() {
042        _scheduleHashTable.clear();
043    }
044
045    // stores known TrainSchedule instances by id
046    protected Hashtable<String, TrainSchedule> _scheduleHashTable = new Hashtable<>();
047
048    /**
049     * @return Number of schedules
050     */
051    public int numEntries() {
052        return _scheduleHashTable.size();
053    }
054    
055    /**
056     * Sets the selected schedule id
057     *
058     * @param id Selected schedule id
059     */
060    public void setTrainScheduleActiveId(String id) {
061        String old = _trainScheduleActiveId;
062        _trainScheduleActiveId = id;
063        if (!old.equals(id)) {
064            setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id);
065        }
066    }
067
068    public String getTrainScheduleActiveId() {
069        return _trainScheduleActiveId;
070    }
071    
072    public TrainSchedule getActiveSchedule() {
073        return getScheduleById(getTrainScheduleActiveId());
074    }
075
076    /**
077     * @param name The schedule string name to search for.
078     * @return requested TrainSchedule object or null if none exists
079     */
080    public TrainSchedule getScheduleByName(String name) {
081        TrainSchedule s;
082        Enumeration<TrainSchedule> en = _scheduleHashTable.elements();
083        while (en.hasMoreElements()) {
084            s = en.nextElement();
085            if (s.getName().equals(name)) {
086                return s;
087            }
088        }
089        return null;
090    }
091
092    public TrainSchedule getScheduleById(String id) {
093        return _scheduleHashTable.get(id);
094    }
095
096    /**
097     * Finds an existing schedule or creates a new schedule if needed requires
098     * schedule's name creates a unique id for this schedule
099     *
100     * @param name The string name of the schedule.
101     *
102     *
103     * @return new TrainSchedule or existing TrainSchedule
104     */
105    public TrainSchedule newSchedule(String name) {
106        TrainSchedule schedule = getScheduleByName(name);
107        if (schedule == null) {
108            _id++;
109            schedule = new TrainSchedule(Integer.toString(_id), name);
110            Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
111            _scheduleHashTable.put(schedule.getId(), schedule);
112            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize,
113                    Integer.valueOf(_scheduleHashTable.size()));
114        }
115        return schedule;
116    }
117
118    /**
119     * Remember a NamedBean Object created outside the manager.
120     *
121     * @param schedule The TrainSchedule to add.
122     */
123    public void register(TrainSchedule schedule) {
124        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
125        _scheduleHashTable.put(schedule.getId(), schedule);
126        // find last id created
127        int id = Integer.parseInt(schedule.getId());
128        if (id > _id) {
129            _id = id;
130        }
131        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
132    }
133
134    /**
135     * Forget a NamedBean Object created outside the manager.
136     *
137     * @param schedule The TrainSchedule to delete.
138     */
139    public void deregister(TrainSchedule schedule) {
140        if (schedule == null) {
141            return;
142        }
143        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
144        _scheduleHashTable.remove(schedule.getId());
145        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
146    }
147
148    /**
149     * Sort by train schedule name
150     *
151     * @return list of train schedules ordered by name
152     */
153    public List<TrainSchedule> getSchedulesByNameList() {
154        List<TrainSchedule> sortList = getList();
155        // now re-sort
156        List<TrainSchedule> out = new ArrayList<>();
157        for (int i = 0; i < sortList.size(); i++) {
158            for (int j = 0; j < out.size(); j++) {
159                if (sortList.get(i).getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
160                    out.add(j, sortList.get(i));
161                    break;
162                }
163            }
164            if (!out.contains(sortList.get(i))) {
165                out.add(sortList.get(i));
166            }
167        }
168        return out;
169    }
170
171    /**
172     * Sort by train schedule id numbers
173     *
174     * @return list of train schedules ordered by id numbers
175     */
176    public List<TrainSchedule> getSchedulesByIdList() {
177        List<TrainSchedule> sortList = getList();
178        // now re-sort
179        List<TrainSchedule> out = new ArrayList<>();
180        for (int i = 0; i < sortList.size(); i++) {
181            for (int j = 0; j < out.size(); j++) {
182                try {
183                    if (Integer.parseInt(sortList.get(i).getId()) < Integer.parseInt(out.get(j).getId())) {
184                        out.add(j, sortList.get(i));
185                        break;
186                    }
187                } catch (NumberFormatException e) {
188                    log.debug("list id number isn't a number");
189                }
190            }
191            if (!out.contains(sortList.get(i))) {
192                out.add(sortList.get(i));
193            }
194        }
195        return out;
196    }
197
198    private List<TrainSchedule> getList() {
199        // no schedules? then load defaults
200        if (numEntries() == 0) {
201            createDefaultSchedules();
202        }
203        List<TrainSchedule> out = new ArrayList<>();
204        Enumeration<TrainSchedule> en = _scheduleHashTable.elements();
205        while (en.hasMoreElements()) {
206            out.add(en.nextElement());
207        }
208        return out;
209    }
210
211    /**
212     * Gets a JComboBox loaded with schedules starting with null.
213     *
214     * @return JComboBox with a list of schedules.
215     */
216    public JComboBox<TrainSchedule> getComboBox() {
217        JComboBox<TrainSchedule> box = new JComboBox<>();
218        updateComboBox(box);
219        OperationsPanel.padComboBox(box);
220        return box;
221    }
222
223    /**
224     * Gets a JComboBox loaded with schedules starting with null.
225     *
226     * @return JComboBox with a list of schedules starting with null.
227     */
228    public JComboBox<TrainSchedule> getSelectComboBox() {
229        JComboBox<TrainSchedule> box = new JComboBox<>();
230        box.addItem(null);
231        for (TrainSchedule sch : getSchedulesByIdList()) {
232            box.addItem(sch);
233        }
234        OperationsPanel.padComboBox(box);
235        return box;
236    }
237
238    /**
239     * Update a JComboBox with the latest schedules.
240     *
241     * @param box the JComboBox needing an update.
242     * @throws IllegalArgumentException if box is null
243     */
244    public void updateComboBox(JComboBox<TrainSchedule> box) {
245        if (box == null) {
246            throw new IllegalArgumentException("Attempt to update non-existant comboBox");
247        }
248        box.removeAllItems();
249        for (TrainSchedule sch : getSchedulesByNameList()) {
250            box.addItem(sch);
251        }
252    }
253
254    public void buildSwitchLists() {
255        TrainSwitchLists trainSwitchLists = new TrainSwitchLists();
256        TrainCsvSwitchLists trainCsvSwitchLists = new TrainCsvSwitchLists();
257        String locationName = ""; // only create switch lists once for locations with similar names
258        for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) {
259            if (location.isSwitchListEnabled() && !locationName.equals(location.getSplitName())) {
260                trainCsvSwitchLists.buildSwitchList(location);
261                trainSwitchLists.buildSwitchList(location);
262                locationName = location.getSplitName();
263                // print switch lists for locations that have changes
264                if (Setup.isSwitchListRealTime() && location.getStatus().equals(Location.UPDATED)) {
265                    trainSwitchLists.printSwitchList(location, InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled());
266                }
267            }
268        }
269        // set trains switch lists printed
270        InstanceManager.getDefault(TrainManager.class).setTrainsSwitchListStatus(Train.PRINTED);
271    }
272
273    /**
274     * Create an XML element to represent this Entry. This member has to remain
275     * synchronized with the detailed DTD in operations-trains.dtd.
276     *
277     * @param root The common Element for operations-trains.dtd.
278     *
279     */
280    public void store(Element root) {
281        Element e = new Element(Xml.TRAIN_SCHEDULE_OPTIONS);
282        e.setAttribute(Xml.ACTIVE_ID, InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId());
283        root.addContent(e);
284        Element values = new Element(Xml.SCHEDULES);
285        // add entries
286        List<TrainSchedule> schedules = getSchedulesByIdList();
287        for (TrainSchedule schedule : schedules) {
288            values.addContent(schedule.store());
289        }
290        root.addContent(values);
291    }
292
293    public void load(Element root) {
294        Element e = root.getChild(Xml.TRAIN_SCHEDULE_OPTIONS);
295        Attribute a;
296        if (e != null) {
297            if ((a = e.getAttribute(Xml.ACTIVE_ID)) != null) {
298                setTrainScheduleActiveId(a.getValue());
299            }
300        }
301
302        e = root.getChild(Xml.SCHEDULES);
303        if (e != null) {
304            List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE);
305            log.debug("TrainScheduleManager sees {} train schedules", eSchedules.size());
306            for (Element eSchedule : eSchedules) {
307                register(new TrainSchedule(eSchedule));
308            }
309        }
310    }
311
312    public void createDefaultSchedules() {
313        log.debug("creating default schedules");
314        newSchedule(Bundle.getMessage("Sunday"));
315        newSchedule(Bundle.getMessage("Monday"));
316        newSchedule(Bundle.getMessage("Tuesday"));
317        newSchedule(Bundle.getMessage("Wednesday"));
318        newSchedule(Bundle.getMessage("Thursday"));
319        newSchedule(Bundle.getMessage("Friday"));
320        newSchedule(Bundle.getMessage("Saturday"));
321    }
322
323    @Override
324    public void propertyChange(java.beans.PropertyChangeEvent e) {
325        log.debug("ScheduleManager sees property change: ({}) old: ({}) new ({})",
326                e.getPropertyName(), e.getOldValue(), e.getNewValue());
327    }
328
329    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
330        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
331        firePropertyChange(p, old, n);
332    }
333
334    private final static Logger log = LoggerFactory.getLogger(TrainScheduleManager.class);
335
336    @Override
337    public void initialize() {
338        InstanceManager.getDefault(TrainManagerXml.class); // load trains
339    }
340
341}