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            Integer oldSize = Integer.valueOf(_automationHashTable.size());
081            _automationHashTable.put(automation.getId(), automation);
082            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_automationHashTable
083                    .size()));
084        }
085        return automation;
086    }
087
088    /**
089     * Remember a NamedBean Object created outside the manager.
090     *
091     * @param automation The automation that is being registered.
092     */
093    public void register(Automation automation) {
094        Integer oldSize = Integer.valueOf(_automationHashTable.size());
095        _automationHashTable.put(automation.getId(), automation);
096        // find last id created
097        int id = Integer.parseInt(automation.getId());
098        if (id > _id) {
099            _id = id;
100        }
101        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize,
102                Integer.valueOf(_automationHashTable.size()));
103    }
104
105    /**
106     * Forget a NamedBean Object created outside the manager.
107     *
108     * @param automation The automation to be deleted.
109     */
110    public void deregister(Automation automation) {
111        if (automation == null) {
112            return;
113        }
114        automation.dispose();
115        Integer oldSize = Integer.valueOf(_automationHashTable.size());
116        _automationHashTable.remove(automation.getId());
117        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize,
118                Integer.valueOf(_automationHashTable.size()));
119    }
120
121    /**
122     * Sort by automation name
123     *
124     * @return list of automations ordered by name
125     */
126    public List<Automation> getAutomationsByNameList() {
127        List<Automation> sortList = getList();
128        // now re-sort
129        List<Automation> out = new ArrayList<>();
130        for (Automation automation : sortList) {
131            for (int j = 0; j < out.size(); j++) {
132                if (automation.getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
133                    out.add(j, automation);
134                    break;
135                }
136            }
137            if (!out.contains(automation)) {
138                out.add(automation);
139            }
140        }
141        return out;
142
143    }
144
145    /**
146     * Sort by automation id number
147     *
148     * @return list of automations ordered by id number
149     */
150    public List<Automation> getAutomationsByIdList() {
151        List<Automation> sortList = getList();
152        // now re-sort
153        List<Automation> out = new ArrayList<>();
154        for (Automation automation : sortList) {
155            for (int j = 0; j < out.size(); j++) {
156                try {
157                    if (Integer.parseInt(automation.getId()) < Integer.parseInt(out.get(j).getId())) {
158                        out.add(j, automation);
159                        break;
160                    }
161                } catch (NumberFormatException e) {
162                    log.debug("list id number isn't a number");
163                }
164            }
165            if (!out.contains(automation)) {
166                out.add(automation);
167            }
168        }
169        return out;
170    }
171
172    private List<Automation> getList() {
173        List<Automation> out = new ArrayList<>();
174        Enumeration<Automation> en = _automationHashTable.elements();
175        while (en.hasMoreElements()) {
176            out.add(en.nextElement());
177        }
178        return out;
179    }
180
181    /**
182     * Gets a JComboBox loaded with automations.
183     *
184     * @return JComboBox with a list of automations.
185     */
186    public JComboBox<Automation> getComboBox() {
187        JComboBox<Automation> box = new JComboBox<>();
188        updateComboBox(box);
189        return box;
190    }
191
192    /**
193     * Update a JComboBox with the latest automations.
194     *
195     * @param box the JComboBox needing an update.
196     */
197    public void updateComboBox(JComboBox<Automation> box) {
198        box.removeAllItems();
199        box.addItem(null);
200        for (Automation automation : getAutomationsByNameList()) {
201            box.addItem(automation);
202        }
203    }
204
205    /**
206     * Restarts all automations that were running when the operations program
207     * was last saved.
208     */
209    public void resumeAutomations() {
210        for (Automation automation : getAutomationsByNameList()) {
211            if (!automation.isActionRunning() && !automation.isReadyToRun()) {
212                automation.resume();
213            }
214        }
215    }
216
217    /**
218     * Makes a new copy of automation
219     *
220     * @param automation the automation to copy
221     * @param newName    name for the copy of automation
222     * @return new copy of automation
223     */
224    public Automation copyAutomation(Automation automation, String newName) {
225        Automation newAutomation = newAutomation(newName);
226        newAutomation.copyAutomation(automation);
227        return newAutomation;
228    }
229
230    public Automation getStartupAutomation() {
231        return _startupAutomation;
232    }
233    
234    protected String getStartupAutomationId() {
235        String id = "";
236        if (getStartupAutomation() != null) {
237            id = getStartupAutomation().getId();
238        }
239        return id;
240    }
241    
242    public void setStartupAutomation(Automation automation) {
243        Automation old = _startupAutomation;
244        _startupAutomation = automation;
245        setDirtyAndFirePropertyChange("automationStartupIdChanged", old, automation);
246    }
247
248    public void runStartupAutomation() {
249        Automation startup = getStartupAutomation();
250        if (startup != null) {
251            log.debug("Run automation: {}", startup.getName());
252            startup.run();
253        }
254    }
255
256    public void dispose() {
257        _automationHashTable.clear();
258        _id = 0;
259    }
260
261    /**
262     * Construct this Entry from XML. This member has to remain synchronized
263     * with the detailed DTD in operations-trains.dtd
264     *
265     * @param root Consist XML element
266     */
267    public void load(Element root) {
268        if (root.getChild(Xml.AUTOMATIONS) != null) {
269            List<Element> eAutomations = root.getChild(Xml.AUTOMATIONS).getChildren(Xml.AUTOMATION);
270            log.debug("readFile sees {} automations", eAutomations.size());
271            for (Element eAutomation : eAutomations) {
272                register(new Automation(eAutomation));
273            }
274        }
275        // get startup automation after all of the automations have been loaded
276        Element e = root.getChild(Xml.AUTOMATION_OPTIONS);
277        Attribute a;
278        if (e != null) {
279            if ((a = e.getAttribute(Xml.AUTOMATION_STARTUP_ID)) != null) {
280                _startupAutomation = getAutomationById(a.getValue());
281            }
282        }
283    }
284
285    /**
286     * Create an XML element to represent this Entry. This member has to remain
287     * synchronized with the detailed DTD in operations-trains.dtd.
288     *
289     * @param root Contents in a JDOM Element
290     */
291    public void store(Element root) {
292        Element e = new Element(Xml.AUTOMATION_OPTIONS);
293        e.setAttribute(Xml.AUTOMATION_STARTUP_ID, getStartupAutomationId());
294        root.addContent(e);
295        Element values;
296        root.addContent(values = new Element(Xml.AUTOMATIONS));
297        // add entries
298        for (Automation automation : getAutomationsByNameList()) {
299            values.addContent(automation.store());
300        }
301    }
302
303    @Override
304    public void propertyChange(java.beans.PropertyChangeEvent e) {
305        if (Control.SHOW_PROPERTY) {
306            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
307                    .getNewValue());
308        }
309    }
310
311    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
312        // set dirty
313        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
314        firePropertyChange(p, old, n);
315    }
316
317    @Override
318    public void initialize() {
319        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
320        InstanceManager.getDefault(CarManagerXml.class); // load cars
321        InstanceManager.getDefault(EngineManagerXml.class); // load engines
322        InstanceManager.getDefault(TrainManagerXml.class); // load trains
323    }
324
325    private final static Logger log = LoggerFactory.getLogger(AutomationManager.class);
326
327}