001package jmri.jmrit.operations.locations.schedules;
002
003import java.beans.PropertyChangeListener;
004import java.util.*;
005
006import javax.swing.JComboBox;
007
008import org.jdom2.Element;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import jmri.*;
013import jmri.beans.PropertyChangeSupport;
014import jmri.jmrit.operations.locations.*;
015import jmri.jmrit.operations.rollingstock.cars.CarRoads;
016import jmri.jmrit.operations.rollingstock.cars.CarTypes;
017import jmri.jmrit.operations.setup.Control;
018
019/**
020 * Manages schedules.
021 *
022 * @author Bob Jacobsen Copyright (C) 2003
023 * @author Daniel Boudreau Copyright (C) 2008, 2013
024 */
025public class ScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
026
027    public static final String LISTLENGTH_CHANGED_PROPERTY = "scheduleListLength"; // NOI18N
028
029    public ScheduleManager() {
030    }
031
032    private int _id = 0;
033
034    public void dispose() {
035        _scheduleHashTable.clear();
036    }
037
038    // stores known Schedule instances by id
039    protected Hashtable<String, Schedule> _scheduleHashTable = new Hashtable<String, Schedule>();
040
041    /**
042     * @return Number of schedules
043     */
044    public int numEntries() {
045        return _scheduleHashTable.size();
046    }
047
048    /**
049     * @param name The string name for the schedule
050     * @return requested Schedule object or null if none exists
051     */
052    public Schedule getScheduleByName(String name) {
053        Schedule s;
054        Enumeration<Schedule> en = _scheduleHashTable.elements();
055        while (en.hasMoreElements()) {
056            s = en.nextElement();
057            if (s.getName().equals(name)) {
058                return s;
059            }
060        }
061        return null;
062    }
063
064    public Schedule getScheduleById(String id) {
065        return _scheduleHashTable.get(id);
066    }
067
068    /**
069     * Finds an existing schedule or creates a new schedule if needed requires
070     * schedule's name creates a unique id for this schedule
071     *
072     * @param name The string name for this schedule
073     *
074     *
075     * @return new schedule or existing schedule
076     */
077    public Schedule newSchedule(String name) {
078        Schedule schedule = getScheduleByName(name);
079        if (schedule == null) {
080            _id++;
081            schedule = new Schedule(Integer.toString(_id), name);
082            Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
083            _scheduleHashTable.put(schedule.getId(), schedule);
084            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable
085                    .size()));
086        }
087        return schedule;
088    }
089
090    /**
091     * Remember a NamedBean Object created outside the manager.
092     *
093     * @param schedule The Schedule to add.
094     */
095    public void register(Schedule schedule) {
096        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
097        _scheduleHashTable.put(schedule.getId(), schedule);
098        // find last id created
099        int id = Integer.parseInt(schedule.getId());
100        if (id > _id) {
101            _id = id;
102        }
103        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
104    }
105
106    /**
107     * Forget a NamedBean Object created outside the manager.
108     *
109     * @param schedule The Schedule to delete.
110     */
111    public void deregister(Schedule schedule) {
112        if (schedule == null) {
113            return;
114        }
115        schedule.dispose();
116        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
117        _scheduleHashTable.remove(schedule.getId());
118        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
119    }
120
121    /**
122     * Sort by schedule name
123     *
124     * @return list of schedules ordered by name
125     */
126    public List<Schedule> getSchedulesByNameList() {
127        List<Schedule> sortList = getList();
128        // now re-sort
129        List<Schedule> out = new ArrayList<Schedule>();
130        for (Schedule sch : sortList) {
131            for (int j = 0; j < out.size(); j++) {
132                if (sch.getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
133                    out.add(j, sch);
134                    break;
135                }
136            }
137            if (!out.contains(sch)) {
138                out.add(sch);
139            }
140        }
141        return out;
142
143    }
144
145    /**
146     * Sort by schedule id number
147     *
148     * @return list of schedules ordered by id number
149     */
150    public List<Schedule> getSchedulesByIdList() {
151        List<Schedule> sortList = getList();
152        // now re-sort
153        List<Schedule> out = new ArrayList<Schedule>();
154        for (Schedule sch : sortList) {
155            for (int j = 0; j < out.size(); j++) {
156                try {
157                    if (Integer.parseInt(sch.getId()) < Integer.parseInt(out.get(j).getId())) {
158                        out.add(j, sch);
159                        break;
160                    }
161                } catch (NumberFormatException e) {
162                    log.debug("list id number isn't a number");
163                }
164            }
165            if (!out.contains(sch)) {
166                out.add(sch);
167            }
168        }
169        return out;
170    }
171
172    private List<Schedule> getList() {
173        List<Schedule> out = new ArrayList<Schedule>();
174        Enumeration<Schedule> en = _scheduleHashTable.elements();
175        while (en.hasMoreElements()) {
176            out.add(en.nextElement());
177        }
178        return out;
179    }
180
181    public Schedule copySchedule(Schedule schedule, String newScheduleName) {
182        Schedule newSchedule = newSchedule(newScheduleName);
183        for (ScheduleItem si : schedule.getItemsBySequenceList()) {
184            ScheduleItem newSi = newSchedule.addItem(si.getTypeName());
185            newSi.copyScheduleItem(si);
186        }
187        return newSchedule;
188    }
189
190    public void resetHitCounts() {
191        for (Schedule schedule : getList()) {
192            schedule.resetHitCounts();
193        }
194    }
195
196    /**
197     * Gets a JComboBox loaded with schedules.
198     *
199     * @return JComboBox with a list of schedules.
200     */
201    public JComboBox<Schedule> getComboBox() {
202        JComboBox<Schedule> box = new JComboBox<>();
203        updateComboBox(box);
204        return box;
205    }
206
207    /**
208     * Update a JComboBox with the latest schedules.
209     *
210     * @param box the JComboBox needing an update.
211     */
212    public void updateComboBox(JComboBox<Schedule> box) {
213        box.removeAllItems();
214        box.addItem(null);
215        for (Schedule schedule : getSchedulesByNameList()) {
216            box.addItem(schedule);
217        }
218    }
219
220    /**
221     * Replaces car type in all schedules.
222     *
223     * @param oldType car type to be replaced.
224     * @param newType replacement car type.
225     */
226    public void replaceType(String oldType, String newType) {
227        for (Schedule sch : getSchedulesByIdList()) {
228            for (ScheduleItem si : sch.getItemsBySequenceList()) {
229                if (si.getTypeName().equals(oldType)) {
230                    si.setTypeName(newType);
231                }
232            }
233        }
234    }
235
236    /**
237     * Replaces car roads in all schedules.
238     *
239     * @param oldRoad car road to be replaced.
240     * @param newRoad replacement car road.
241     */
242    public void replaceRoad(String oldRoad, String newRoad) {
243        if (newRoad == null) {
244            return;
245        }
246        for (Schedule sch : getSchedulesByIdList()) {
247            for (ScheduleItem si : sch.getItemsBySequenceList()) {
248                if (si.getRoadName().equals(oldRoad)) {
249                    si.setRoadName(newRoad);
250                }
251            }
252        }
253    }
254
255    /**
256     * Replaces car loads in all schedules with specific car type.
257     *
258     * @param type    car type.
259     * @param oldLoad car load to be replaced.
260     * @param newLoad replacement car load.
261     */
262    public void replaceLoad(String type, String oldLoad, String newLoad) {
263        for (Schedule sch : getSchedulesByIdList()) {
264            for (ScheduleItem si : sch.getItemsBySequenceList()) {
265                if (si.getTypeName().equals(type) && si.getReceiveLoadName().equals(oldLoad)) {
266                    if (newLoad != null) {
267                        si.setReceiveLoadName(newLoad);
268                    } else {
269                        si.setReceiveLoadName(ScheduleItem.NONE);
270                    }
271                }
272                if (si.getTypeName().equals(type) && si.getShipLoadName().equals(oldLoad)) {
273                    if (newLoad != null) {
274                        si.setShipLoadName(newLoad);
275                    } else {
276                        si.setShipLoadName(ScheduleItem.NONE);
277                    }
278                }
279            }
280        }
281    }
282
283    public void replaceTrack(Track oldTrack, Track newTrack) {
284        for (Schedule sch : getSchedulesByIdList()) {
285            for (ScheduleItem si : sch.getItemsBySequenceList()) {
286                if (si.getDestinationTrack() == oldTrack) {
287                    si.setDestination(newTrack.getLocation());
288                    si.setDestinationTrack(newTrack);
289                }
290            }
291        }
292    }
293
294    /**
295     * Gets a JComboBox with a list of spurs that use this schedule.
296     *
297     * @param schedule The schedule for this JComboBox.
298     * @return JComboBox with a list of spurs using schedule.
299     */
300    public JComboBox<LocationTrackPair> getSpursByScheduleComboBox(Schedule schedule) {
301        JComboBox<LocationTrackPair> box = new JComboBox<>();
302        // search all spurs for that use schedule
303        for (Track spur : getListSpurs(schedule)) {
304            if (spur.getScheduleId().equals(schedule.getId())) {
305                LocationTrackPair ltp = new LocationTrackPair(spur);
306                box.addItem(ltp);
307            }
308        }
309        return box;
310    }
311
312    public List<Track> getListSpurs(Schedule schedule) {
313        List<Track> spurs = new ArrayList<Track>();
314        for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) {
315            for (Track spur : location.getTracksByNameList(Track.SPUR)) {
316                if (spur.getScheduleId().equals(schedule.getId())) {
317                    spurs.add(spur);
318                }
319            }
320        }
321        return spurs;
322    }
323
324    public void load(Element root) {
325        if (root.getChild(Xml.SCHEDULES) != null) {
326            List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE);
327            log.debug("readFile sees {} schedules", eSchedules.size());
328            for (Element eSchedule : eSchedules) {
329                register(new Schedule(eSchedule));
330            }
331        }
332    }
333
334    public void store(Element root) {
335        Element values;
336        root.addContent(values = new Element(Xml.SCHEDULES));
337        // add entries
338        for (Schedule schedule : getSchedulesByIdList()) {
339            values.addContent(schedule.store());
340        }
341    }
342
343    /**
344     * Check for car type and road name changes.
345     */
346    @Override
347    public void propertyChange(java.beans.PropertyChangeEvent e) {
348        if (Control.SHOW_PROPERTY) {
349            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
350                    .getNewValue());
351        }
352        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
353            replaceType((String) e.getOldValue(), (String) e.getNewValue());
354        }
355        if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) {
356            replaceRoad((String) e.getOldValue(), (String) e.getNewValue());
357        }
358    }
359
360    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
361        // set dirty
362        InstanceManager.getDefault(LocationManagerXml.class).setDirty(true);
363        firePropertyChange(p, old, n);
364    }
365
366    private final static Logger log = LoggerFactory.getLogger(ScheduleManager.class);
367
368    @Override
369    public void initialize() {
370        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
371        InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this);
372    }
373
374}