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.OperationsPanel;
015import jmri.jmrit.operations.locations.*;
016import jmri.jmrit.operations.rollingstock.cars.CarRoads;
017import jmri.jmrit.operations.rollingstock.cars.CarTypes;
018import jmri.jmrit.operations.setup.Control;
019
020/**
021 * Manages schedules.
022 *
023 * @author Bob Jacobsen Copyright (C) 2003
024 * @author Daniel Boudreau Copyright (C) 2008, 2013
025 */
026public class ScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
027
028    public static final String LISTLENGTH_CHANGED_PROPERTY = "scheduleListLength"; // NOI18N
029
030    public ScheduleManager() {
031    }
032
033    private int _id = 0;
034
035    public void dispose() {
036        _scheduleHashTable.clear();
037    }
038
039    // stores known Schedule instances by id
040    protected Hashtable<String, Schedule> _scheduleHashTable = new Hashtable<String, Schedule>();
041
042    /**
043     * @return Number of schedules
044     */
045    public int numEntries() {
046        return _scheduleHashTable.size();
047    }
048
049    /**
050     * @param name The string name for the schedule
051     * @return requested Schedule object or null if none exists
052     */
053    public Schedule getScheduleByName(String name) {
054        Schedule s;
055        Enumeration<Schedule> en = _scheduleHashTable.elements();
056        while (en.hasMoreElements()) {
057            s = en.nextElement();
058            if (s.getName().equals(name)) {
059                return s;
060            }
061        }
062        return null;
063    }
064
065    public Schedule getScheduleById(String id) {
066        return _scheduleHashTable.get(id);
067    }
068
069    /**
070     * Finds an existing schedule or creates a new schedule if needed requires
071     * schedule's name creates a unique id for this schedule
072     *
073     * @param name The string name for this schedule
074     *
075     *
076     * @return new schedule or existing schedule
077     */
078    public Schedule newSchedule(String name) {
079        Schedule schedule = getScheduleByName(name);
080        if (schedule == null && !name.isBlank()) {
081            _id++;
082            schedule = new Schedule(Integer.toString(_id), name);
083            int oldSize = _scheduleHashTable.size();
084            _scheduleHashTable.put(schedule.getId(), schedule);
085            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _scheduleHashTable.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        int oldSize = _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, _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        int oldSize = _scheduleHashTable.size();
117        _scheduleHashTable.remove(schedule.getId());
118        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _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        OperationsPanel.padComboBox(box, Control.max_len_string_location_name);
204        updateComboBox(box);
205        return box;
206    }
207
208    /**
209     * Update a JComboBox with the latest schedules.
210     *
211     * @param box the JComboBox needing an update.
212     */
213    public void updateComboBox(JComboBox<Schedule> box) {
214        box.removeAllItems();
215        box.addItem(null);
216        for (Schedule schedule : getSchedulesByNameList()) {
217            box.addItem(schedule);
218        }
219    }
220
221    /**
222     * Replaces car type in all schedules.
223     *
224     * @param oldType car type to be replaced.
225     * @param newType replacement car type.
226     */
227    public void replaceType(String oldType, String newType) {
228        for (Schedule sch : getSchedulesByIdList()) {
229            for (ScheduleItem si : sch.getItemsBySequenceList()) {
230                if (si.getTypeName().equals(oldType)) {
231                    si.setTypeName(newType);
232                }
233            }
234        }
235    }
236
237    /**
238     * Replaces car roads in all schedules.
239     *
240     * @param oldRoad car road to be replaced.
241     * @param newRoad replacement car road.
242     */
243    public void replaceRoad(String oldRoad, String newRoad) {
244        if (newRoad == null) {
245            return;
246        }
247        for (Schedule sch : getSchedulesByIdList()) {
248            for (ScheduleItem si : sch.getItemsBySequenceList()) {
249                if (si.getRoadName().equals(oldRoad)) {
250                    si.setRoadName(newRoad);
251                }
252            }
253        }
254    }
255
256    /**
257     * Replaces car loads in all schedules with specific car type.
258     *
259     * @param type    car type.
260     * @param oldLoad car load to be replaced.
261     * @param newLoad replacement car load.
262     */
263    public void replaceLoad(String type, String oldLoad, String newLoad) {
264        for (Schedule sch : getSchedulesByIdList()) {
265            for (ScheduleItem si : sch.getItemsBySequenceList()) {
266                if (si.getTypeName().equals(type) && si.getReceiveLoadName().equals(oldLoad)) {
267                    if (newLoad != null) {
268                        si.setReceiveLoadName(newLoad);
269                    } else {
270                        si.setReceiveLoadName(ScheduleItem.NONE);
271                    }
272                }
273                if (si.getTypeName().equals(type) && si.getShipLoadName().equals(oldLoad)) {
274                    if (newLoad != null) {
275                        si.setShipLoadName(newLoad);
276                    } else {
277                        si.setShipLoadName(ScheduleItem.NONE);
278                    }
279                }
280            }
281        }
282    }
283
284    public void replaceTrack(Track oldTrack, Track newTrack) {
285        for (Schedule sch : getSchedulesByIdList()) {
286            for (ScheduleItem si : sch.getItemsBySequenceList()) {
287                if (si.getDestinationTrack() == oldTrack) {
288                    si.setDestination(newTrack.getLocation());
289                    si.setDestinationTrack(newTrack);
290                }
291            }
292        }
293    }
294
295    /**
296     * Gets a JComboBox with a list of spurs that use this schedule.
297     *
298     * @param schedule The schedule for this JComboBox.
299     * @return JComboBox with a list of spurs using schedule.
300     */
301    public JComboBox<LocationTrackPair> getSpursByScheduleComboBox(Schedule schedule) {
302        JComboBox<LocationTrackPair> box = new JComboBox<>();
303        // search all spurs for that use schedule
304        for (Track spur : getListSpurs(schedule)) {
305            if (spur.getScheduleId().equals(schedule.getId())) {
306                LocationTrackPair ltp = new LocationTrackPair(spur);
307                box.addItem(ltp);
308            }
309        }
310        return box;
311    }
312
313    public List<Track> getListSpurs(Schedule schedule) {
314        List<Track> spurs = new ArrayList<Track>();
315        for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) {
316            for (Track spur : location.getTracksByNameList(Track.SPUR)) {
317                if (spur.getScheduleId().equals(schedule.getId())) {
318                    spurs.add(spur);
319                }
320            }
321        }
322        return spurs;
323    }
324
325    public void load(Element root) {
326        if (root.getChild(Xml.SCHEDULES) != null) {
327            List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE);
328            log.debug("readFile sees {} schedules", eSchedules.size());
329            for (Element eSchedule : eSchedules) {
330                register(new Schedule(eSchedule));
331            }
332        }
333    }
334
335    public void store(Element root) {
336        Element values;
337        root.addContent(values = new Element(Xml.SCHEDULES));
338        // add entries
339        for (Schedule schedule : getSchedulesByIdList()) {
340            values.addContent(schedule.store());
341        }
342    }
343
344    /**
345     * Check for car type and road name changes.
346     */
347    @Override
348    public void propertyChange(java.beans.PropertyChangeEvent e) {
349        if (Control.SHOW_PROPERTY) {
350            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
351                    .getNewValue());
352        }
353        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
354            replaceType((String) e.getOldValue(), (String) e.getNewValue());
355        }
356        if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) {
357            replaceRoad((String) e.getOldValue(), (String) e.getNewValue());
358        }
359    }
360
361    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
362        // set dirty
363        InstanceManager.getDefault(LocationManagerXml.class).setDirty(true);
364        firePropertyChange(p, old, n);
365    }
366
367    private static final Logger log = LoggerFactory.getLogger(ScheduleManager.class);
368
369    @Override
370    public void initialize() {
371        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
372        InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this);
373    }
374
375}