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