001package jmri.jmrit.operations.automation;
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.rollingstock.cars.CarManagerXml;
016import jmri.jmrit.operations.rollingstock.engines.EngineManagerXml;
017import jmri.jmrit.operations.setup.Control;
018import jmri.jmrit.operations.setup.OperationsSetupXml;
019import jmri.jmrit.operations.trains.TrainManagerXml;
020
021/**
022 * Manages automations.
023 *
024 * @author Bob Jacobsen Copyright (C) 2003
025 * @author Daniel Boudreau Copyright (C) 2016
026 */
027public class AutomationManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
028
029    public static final String LISTLENGTH_CHANGED_PROPERTY = "automationListLength"; // NOI18N
030    private int _id = 0; // retain highest automation Id seen to ensure no Id
031                         // collisions
032
033    public AutomationManager() {
034    }
035
036    // stores known Automation instances by id
037    protected Hashtable<String, Automation> _automationHashTable = new Hashtable<>();
038
039    protected Automation _startupAutomation;
040
041    /**
042     * @return Number of automations
043     */
044    public int getSize() {
045        return _automationHashTable.size();
046    }
047
048    /**
049     * @param name The string name of the automation to be returned.
050     * @return requested Automation object or null if none exists
051     */
052    public Automation getAutomationByName(String name) {
053        Automation automation;
054        Enumeration<Automation> en = _automationHashTable.elements();
055        while (en.hasMoreElements()) {
056            automation = en.nextElement();
057            if (automation.getName().equals(name)) {
058                return automation;
059            }
060        }
061        return null;
062    }
063
064    public Automation getAutomationById(String id) {
065        return _automationHashTable.get(id);
066    }
067
068    /**
069     * Finds an existing automation or creates a new automation if needed
070     * requires automation's name creates a unique id for this automation
071     *
072     * @param name The string name of the automation.
073     * @return new automation or existing automation
074     */
075    public Automation newAutomation(String name) {
076        Automation automation = getAutomationByName(name);
077        if (automation == null) {
078            _id++;
079            automation = new Automation(Integer.toString(_id), name);
080            int oldSize = _automationHashTable.size();
081            _automationHashTable.put(automation.getId(), automation);
082            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _automationHashTable.size());
083        }
084        return automation;
085    }
086
087    /**
088     * Remember a NamedBean Object created outside the manager.
089     *
090     * @param automation The automation that is being registered.
091     */
092    public void register(Automation automation) {
093        int oldSize = _automationHashTable.size();
094        _automationHashTable.put(automation.getId(), automation);
095        // find last id created
096        int id = Integer.parseInt(automation.getId());
097        if (id > _id) {
098            _id = id;
099        }
100        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _automationHashTable.size());
101    }
102
103    /**
104     * Forget a NamedBean Object created outside the manager.
105     *
106     * @param automation The automation to be deleted.
107     */
108    public void deregister(Automation automation) {
109        if (automation == null) {
110            return;
111        }
112        automation.dispose();
113        int oldSize = _automationHashTable.size();
114        _automationHashTable.remove(automation.getId());
115        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _automationHashTable.size());
116    }
117
118    /**
119     * Sort by automation name
120     *
121     * @return list of automations ordered by name
122     */
123    public List<Automation> getAutomationsByNameList() {
124        List<Automation> sortList = getList();
125        // now re-sort
126        List<Automation> out = new ArrayList<>();
127        for (Automation automation : sortList) {
128            for (int j = 0; j < out.size(); j++) {
129                if (automation.getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
130                    out.add(j, automation);
131                    break;
132                }
133            }
134            if (!out.contains(automation)) {
135                out.add(automation);
136            }
137        }
138        return out;
139
140    }
141
142    /**
143     * Sort by automation id number
144     *
145     * @return list of automations ordered by id number
146     */
147    public List<Automation> getAutomationsByIdList() {
148        List<Automation> sortList = getList();
149        // now re-sort
150        List<Automation> out = new ArrayList<>();
151        for (Automation automation : sortList) {
152            for (int j = 0; j < out.size(); j++) {
153                try {
154                    if (Integer.parseInt(automation.getId()) < Integer.parseInt(out.get(j).getId())) {
155                        out.add(j, automation);
156                        break;
157                    }
158                } catch (NumberFormatException e) {
159                    log.debug("list id number isn't a number");
160                }
161            }
162            if (!out.contains(automation)) {
163                out.add(automation);
164            }
165        }
166        return out;
167    }
168
169    private List<Automation> getList() {
170        List<Automation> out = new ArrayList<>();
171        Enumeration<Automation> en = _automationHashTable.elements();
172        while (en.hasMoreElements()) {
173            out.add(en.nextElement());
174        }
175        return out;
176    }
177
178    /**
179     * Gets a JComboBox loaded with automations.
180     *
181     * @return JComboBox with a list of automations.
182     */
183    public JComboBox<Automation> getComboBox() {
184        JComboBox<Automation> box = new JComboBox<>();
185        updateComboBox(box);
186        return box;
187    }
188
189    /**
190     * Update a JComboBox with the latest automations.
191     *
192     * @param box the JComboBox needing an update.
193     */
194    public void updateComboBox(JComboBox<Automation> box) {
195        box.removeAllItems();
196        box.addItem(null);
197        for (Automation automation : getAutomationsByNameList()) {
198            box.addItem(automation);
199        }
200    }
201
202    /**
203     * Restarts all automations that were running when the operations program
204     * was last saved.
205     */
206    public void resumeAutomations() {
207        for (Automation automation : getAutomationsByNameList()) {
208            if (!automation.isActionRunning() && !automation.isReadyToRun()) {
209                automation.resume();
210            }
211        }
212    }
213
214    /**
215     * Makes a new copy of automation
216     *
217     * @param automation the automation to copy
218     * @param newName    name for the copy of automation
219     * @return new copy of automation
220     */
221    public Automation copyAutomation(Automation automation, String newName) {
222        Automation newAutomation = newAutomation(newName);
223        newAutomation.copyAutomation(automation);
224        return newAutomation;
225    }
226
227    public Automation getStartupAutomation() {
228        return _startupAutomation;
229    }
230    
231    protected String getStartupAutomationId() {
232        String id = "";
233        if (getStartupAutomation() != null) {
234            id = getStartupAutomation().getId();
235        }
236        return id;
237    }
238    
239    public void setStartupAutomation(Automation automation) {
240        Automation old = _startupAutomation;
241        _startupAutomation = automation;
242        setDirtyAndFirePropertyChange("automationStartupIdChanged", old, automation);
243    }
244
245    public void runStartupAutomation() {
246        Automation startup = getStartupAutomation();
247        if (startup != null) {
248            log.debug("Run automation: {}", startup.getName());
249            startup.run();
250        }
251    }
252
253    public void dispose() {
254        _automationHashTable.clear();
255        _id = 0;
256    }
257
258    /**
259     * Construct this Entry from XML. This member has to remain synchronized
260     * with the detailed DTD in operations-trains.dtd
261     *
262     * @param root Consist XML element
263     */
264    public void load(Element root) {
265        if (root.getChild(Xml.AUTOMATIONS) != null) {
266            List<Element> eAutomations = root.getChild(Xml.AUTOMATIONS).getChildren(Xml.AUTOMATION);
267            log.debug("readFile sees {} automations", eAutomations.size());
268            for (Element eAutomation : eAutomations) {
269                register(new Automation(eAutomation));
270            }
271        }
272        // get startup automation after all of the automations have been loaded
273        Element e = root.getChild(Xml.AUTOMATION_OPTIONS);
274        Attribute a;
275        if (e != null) {
276            if ((a = e.getAttribute(Xml.AUTOMATION_STARTUP_ID)) != null) {
277                _startupAutomation = getAutomationById(a.getValue());
278            }
279        }
280    }
281
282    /**
283     * Create an XML element to represent this Entry. This member has to remain
284     * synchronized with the detailed DTD in operations-trains.dtd.
285     *
286     * @param root Contents in a JDOM Element
287     */
288    public void store(Element root) {
289        Element e = new Element(Xml.AUTOMATION_OPTIONS);
290        e.setAttribute(Xml.AUTOMATION_STARTUP_ID, getStartupAutomationId());
291        root.addContent(e);
292        Element values;
293        root.addContent(values = new Element(Xml.AUTOMATIONS));
294        // add entries
295        for (Automation automation : getAutomationsByNameList()) {
296            values.addContent(automation.store());
297        }
298    }
299
300    @Override
301    public void propertyChange(java.beans.PropertyChangeEvent e) {
302        if (Control.SHOW_PROPERTY) {
303            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
304                    .getNewValue());
305        }
306    }
307
308    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
309        // set dirty
310        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
311        firePropertyChange(p, old, n);
312    }
313
314    @Override
315    public void initialize() {
316        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
317        InstanceManager.getDefault(CarManagerXml.class); // load cars
318        InstanceManager.getDefault(EngineManagerXml.class); // load engines
319        InstanceManager.getDefault(TrainManagerXml.class); // load trains
320    }
321
322    private static final Logger log = LoggerFactory.getLogger(AutomationManager.class);
323
324}