001package jmri.jmrit.operations.automation;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import javax.swing.JComboBox;
007
008import org.jdom2.Element;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import jmri.InstanceManager;
013import jmri.beans.PropertyChangeSupport;
014import jmri.jmrit.operations.automation.actions.*;
015import jmri.jmrit.operations.routes.RouteLocation;
016import jmri.jmrit.operations.setup.Control;
017import jmri.jmrit.operations.trains.*;
018import jmri.jmrit.operations.trains.schedules.TrainSchedule;
019import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
020
021/**
022 * Represents one automation item of a automation
023 *
024 * @author Daniel Boudreau Copyright (C) 2016
025 */
026public class AutomationItem extends PropertyChangeSupport implements java.beans.PropertyChangeListener {
027
028    public static final String NONE = ""; // NOI18N
029
030    protected String _id = NONE;
031    protected int _sequenceId = 0; // used to determine order in automation
032    
033    protected boolean _actionRunning = false; // when true action is running, for example waiting for a train
034    protected boolean _actionSuccessful = false;
035    protected boolean _actionRan = false;
036    protected boolean _haltFail = true;
037    
038    protected Action _action = null;
039    protected String _message = NONE;
040    protected String _messageFail = NONE;
041    
042    // the following are associated with actions
043    protected Train _train = null;
044    protected RouteLocation _routeLocation = null;
045    protected String _automationIdToRun = NONE;
046    protected String _gotoAutomationItemId = NONE; // the goto automationItem
047    protected boolean _gotoAutomationBranched = false;
048    protected String _trainScheduleId = NONE;
049
050    public static final String DISPOSE = "automationItemDispose"; // NOI18N
051
052    public AutomationItem(String id) {
053        log.debug("New automation item id: {}", id);
054        _id = id;
055        setAction(new NoAction()); // the default
056    }
057
058    public String getId() {
059        return _id;
060    }
061
062    @Override
063    public String toString() {
064        return getId(); // for property changes
065    }
066
067    public int getSequenceId() {
068        return _sequenceId;
069    }
070
071    public void setSequenceId(int sequence) {
072        // property change not needed
073        _sequenceId = sequence;
074    }
075
076    public void setAction(Action action) {
077        Action old = _action;
078        _action = action;
079        if (old != null) {
080            old.cancelAction();
081        }
082        if (action != null) {
083            action.setAutomationItem(this); // associate action with this item
084        }
085        if (old != action) {
086            setDirtyAndFirePropertyChange("AutomationItemActionChange", old, action); // NOI18N
087        }
088    }
089
090    public Action getAction() {
091        return _action;
092    }
093
094    public String getActionName() {
095        if (getAction() != null) {
096            return getAction().getName();
097        }
098        return NONE;
099    }
100
101    public int getActionCode() {
102        if (getAction() != null) {
103            return getAction().getCode();
104        }
105        return ActionCodes.NO_ACTION;
106    }
107
108    public void doAction() {
109        if (getAction() != null) {
110            getAction().doAction();
111        }
112    }
113
114    public void setTrain(Train train) {
115        Train old = _train;
116        _train = train;
117        if (old != train) {
118            setDirtyAndFirePropertyChange("AutomationItemTrainChange", old, train); // NOI18N
119            setRouteLocation(null);
120        }
121    }
122
123    public Train getTrain() {
124        if (getAction() != null && getAction().isTrainMenuEnabled()) {
125            return _train;
126        }
127        return null;
128    }
129
130    public void setRouteLocation(RouteLocation rl) {
131        RouteLocation old = _routeLocation;
132        _routeLocation = rl;
133        if (old != rl) {
134            setDirtyAndFirePropertyChange("AutomationItemRouteLocationChange", old, rl); // NOI18N
135        }
136    }
137
138    public RouteLocation getRouteLocation() {
139        if (getAction() != null && getAction().isRouteMenuEnabled()) {
140            return _routeLocation;
141        }
142        return null;
143    }
144
145    public void setOther(Object other) {
146        if (other != null && other.getClass().equals(Automation.class)) {
147            setAutomationToRun((Automation) other);
148        }
149        else if (other != null && other.getClass().equals(AutomationItem.class)) {
150            setGotoAutomationItem((AutomationItem) other);
151        }
152        else if (other == null || other.getClass().equals(TrainSchedule.class)) {
153            setTrainSchedule((TrainSchedule) other);
154        }
155    }
156
157    /**
158     * The automation for actions, not the automation associated with this item.
159     * @param automation the automation to run
160     *
161     */
162    public void setAutomationToRun(Automation automation) {
163        Automation old = InstanceManager.getDefault(AutomationManager.class).getAutomationById(_automationIdToRun);
164        if (automation != null)
165            _automationIdToRun = automation.getId();
166        else
167            _automationIdToRun = NONE;
168        if (old != automation) {
169            setDirtyAndFirePropertyChange("AutomationItemAutomationChange", old, automation); // NOI18N
170        }
171    }
172
173    /**
174     * The automation for actions, not the automation associated with this item.
175     * 
176     * @return Automation for this action
177     */
178    public Automation getAutomationToRun() {
179        if (getAction() != null && getAction().isAutomationMenuEnabled()) {
180            return InstanceManager.getDefault(AutomationManager.class).getAutomationById(_automationIdToRun);
181        }
182        return null;
183    }
184
185    /**
186     * The automation for action GOTO, not this automation item.
187     * @param automationItem which automation item to GOTO
188     *
189     */
190    public void setGotoAutomationItem(AutomationItem automationItem) {
191        AutomationItem oldItem = null;
192        if (automationItem != null) {
193            Automation automation = InstanceManager.getDefault(AutomationManager.class).getAutomationById(automationItem.getId().split(Automation.REGEX)[0]);
194            oldItem = automation.getItemById(_gotoAutomationItemId);
195            _gotoAutomationItemId = automationItem.getId();
196        } else {
197            _gotoAutomationItemId = NONE;
198        }
199        if (oldItem != automationItem) {
200            setDirtyAndFirePropertyChange("AutomationItemAutomationChange", oldItem, automationItem); // NOI18N
201        }
202    }
203
204    /**
205     * The automationItem for actions not this item.
206     * 
207     * @return AutomationItem for GOTO
208     */
209    public AutomationItem getGotoAutomationItem() {
210        if (getAction() != null && getAction().isGotoMenuEnabled()) {
211            Automation automation = InstanceManager.getDefault(AutomationManager.class).getAutomationById(_gotoAutomationItemId.split(Automation.REGEX)[0]);
212            if (automation != null) {
213                return automation.getItemById(_gotoAutomationItemId);
214            }
215        }
216        return null;
217    }
218    
219    public void setGotoBranched(boolean branched) {
220        _gotoAutomationBranched = branched;
221    }
222    
223    public boolean isGotoBranched() {
224        return _gotoAutomationBranched;
225    }
226
227    public void setTrainSchedule(TrainSchedule trainSchedule) {
228        String old = _trainScheduleId;
229        if (trainSchedule != null) {
230            _trainScheduleId = trainSchedule.getId();
231        } else {
232            _trainScheduleId = NONE;
233        }
234        if (!old.equals(_trainScheduleId)) {
235            setDirtyAndFirePropertyChange("AutomationItemTrainScheduleChange", old, _trainScheduleId); // NOI18N
236        }
237    }
238    
239    public TrainSchedule getTrainSchedule() {
240        if (getAction() != null && getAction().getCode() == ActionCodes.ACTIVATE_TRAIN_SCHEDULE) {
241            return InstanceManager.getDefault(TrainScheduleManager.class).getScheduleById(_trainScheduleId);
242        }
243        return null;
244    }
245    
246    public String getTrainScheduleId() {
247        return _trainScheduleId;
248    }
249
250    public void setMessage(String message) {
251        String old = _message;
252        _message = message;
253        if (!old.equals(message)) {
254            setDirtyAndFirePropertyChange("AutomationItemMessageChange", old, message); // NOI18N
255        }
256    }
257
258    public String getMessage() {
259        return _message;
260    }
261
262    public void setMessageFail(String message) {
263        String old = _messageFail;
264        _messageFail = message;
265        if (!old.equals(message)) {
266            setDirtyAndFirePropertyChange("AutomationItemMessageFailChange", old, message); // NOI18N
267        }
268    }
269
270    public String getMessageFail() {
271        return _messageFail;
272    }
273
274    public boolean isHaltFailureEnabled() {
275        return _haltFail;
276    }
277
278    public void setHaltFailureEnabled(boolean enable) {
279        boolean old = _haltFail;
280        _haltFail = enable;
281        if (old != enable) {
282            setDirtyAndFirePropertyChange("AutomationItemHaltFailureChange", old, enable); // NOI18N
283        }
284    }
285
286    public void setActionRunning(boolean actionRunning) {
287        boolean old = _actionRunning;
288        _actionRunning = actionRunning;
289        if (old != actionRunning) {
290            if (!actionRunning) {
291                setActionRan(true);
292            }
293            firePropertyChange("actionRunningChange", old, actionRunning); // NOI18N
294        }
295    }
296
297    public boolean isActionRunning() {
298        return _actionRunning;
299    }
300
301    public void setActionSuccessful(boolean successful) {
302        boolean old = _actionSuccessful;
303        _actionSuccessful = successful;
304        if (old != successful) {
305            setDirtyAndFirePropertyChange("actionSuccessful", old, successful); // NOI18N
306        }
307    }
308
309    public void setActionRan(boolean ran) {
310        _actionRan = ran;
311        firePropertyChange("actionRan", !ran, ran); // NOI18N
312    }
313
314    public boolean isActionRan() {
315        return _actionRan;
316    }
317
318    public boolean isActionSuccessful() {
319        return _actionSuccessful;
320    }
321
322    public String getStatus() {
323        if (getAction() != null) {
324            return getAction().getStatus();
325        }
326        return "unknown"; // NOI18N
327    }
328    
329    public void reset() {
330        setActionRan(false);
331        setActionSuccessful(false);
332        setGotoBranched(false);
333    }
334
335    /**
336     * Copies item.
337     * @param item The item to copy.
338     */
339    public void copyItem(AutomationItem item) {
340        setAction(getActionByCode(item.getActionCode())); // must create a new action for each item
341        setAutomationToRun(item.getAutomationToRun());
342        setGotoAutomationItem(item.getGotoAutomationItem()); //needs an adjustment to work properly
343        setTrain(item.getTrain()); // must set train before route location
344        setRouteLocation(item.getRouteLocation());
345        setSequenceId(item.getSequenceId());
346        setTrainSchedule(item.getTrainSchedule());
347        setMessage(item.getMessage());
348        setMessageFail(item.getMessageFail());
349        setHaltFailureEnabled(item.isHaltFailureEnabled());
350    }
351    
352    public static Action getActionByCode(int code) {
353        for (Action action : getActionList()) {
354            if (action.getCode() == code)
355                return action;
356        }
357        return new NoAction(); // default if code not found
358    }
359
360    /**
361     * Gets a list of all known automation actions
362     * 
363     * @return list of automation actions
364     */
365    public static List<Action> getActionList() {
366        List<Action> list = new ArrayList<>();
367        list.add(new NoAction());
368        list.add(new BuildTrainAction());
369        list.add(new BuildTrainIfSelectedAction());
370        list.add(new PrintTrainManifestAction());
371        list.add(new PrintTrainManifestIfSelectedAction());
372        list.add(new PrintTrainBuildReportAction());
373        list.add(new RunTrainAction());
374        list.add(new MoveTrainAction());
375        list.add(new TerminateTrainAction());
376        list.add(new ResetTrainAction());
377        list.add(new IsTrainEnRouteAction());
378        list.add(new WaitTrainAction());
379        list.add(new WaitTrainTerminatedAction());
380        list.add(new ActivateTrainScheduleAction());
381        list.add(new ApplyTrainScheduleAction());
382        list.add(new SelectTrainAction());
383        list.add(new DeselectTrainAction());
384        list.add(new PrintSwitchListAction());
385        list.add(new UpdateSwitchListAction());
386        list.add(new WaitSwitchListAction());
387        list.add(new GenerateSwitchListAction());
388        list.add(new GenerateSwitchListChangesAction());
389        list.add(new ResetSwitchListsAction());
390        list.add(new RunSwitchListAction());
391        list.add(new RunSwitchListChangesAction());
392        list.add(new RunAutomationAction());
393        list.add(new ResumeAutomationAction());
394        list.add(new StopAutomationAction());
395        list.add(new CounterAction());
396        list.add(new MessageYesNoAction());
397        list.add(new GotoAction());
398        list.add(new GotoSuccessAction());
399        list.add(new GotoFailureAction());
400        list.add(new HaltAction());
401        return list;
402    }
403
404    public static JComboBox<Action> getActionComboBox() {
405        JComboBox<Action> box = new JComboBox<>();
406        for (Action action : getActionList())
407            box.addItem(action);
408        return box;
409    }
410
411    public void dispose() {
412        setDirtyAndFirePropertyChange(DISPOSE, null, DISPOSE);
413    }
414
415    /**
416     * Construct this Entry from XML. This member has to remain synchronized
417     * with the detailed DTD in operations-trains.xml
418     *
419     * @param e Consist XML element
420     */
421    public AutomationItem(Element e) {
422        org.jdom2.Attribute a;
423        if ((a = e.getAttribute(Xml.ID)) != null) {
424            _id = a.getValue();
425        } else {
426            log.warn("no id attribute in Automation Item element when reading operations");
427        }
428        if ((a = e.getAttribute(Xml.SEQUENCE_ID)) != null) {
429            _sequenceId = Integer.parseInt(a.getValue());
430        }
431        if ((a = e.getAttribute(Xml.ACTION_CODE)) != null) {
432            setAction(getActionByCode(Integer.decode(a.getValue())));
433        }
434        if ((a = e.getAttribute(Xml.HALT_FAIL)) != null) {
435            _haltFail = a.getValue().equals(Xml.TRUE);
436        }
437        if ((a = e.getAttribute(Xml.ACTION_RAN)) != null) {
438            _actionRan = a.getValue().equals(Xml.TRUE);
439        }
440        if ((a = e.getAttribute(Xml.ACTION_SUCCESSFUL)) != null) {
441            _actionSuccessful = a.getValue().equals(Xml.TRUE);
442        }
443        if ((a = e.getAttribute(Xml.TRAIN_ID)) != null) {
444            _train = InstanceManager.getDefault(TrainManager.class).getTrainById(a.getValue());
445        }
446        if ((a = e.getAttribute(Xml.ROUTE_LOCATION_ID)) != null && getTrain() != null) {
447            _routeLocation = getTrain().getRoute().getLocationById(a.getValue());
448        }
449        if ((a = e.getAttribute(Xml.AUTOMATION_ID)) != null) {
450            // in the process of loading automations, so we can't get them now, save id and get later.
451            _automationIdToRun = a.getValue();
452        }
453        if ((a = e.getAttribute(Xml.GOTO_AUTOMATION_ID)) != null) {
454            // in the process of loading automations, so we can't get them now, save id and get later.
455            _gotoAutomationItemId = a.getValue();
456        }
457        if ((a = e.getAttribute(Xml.GOTO_AUTOMATION_BRANCHED)) != null) {
458            _gotoAutomationBranched = a.getValue().equals(Xml.TRUE);
459        }
460        if ((a = e.getAttribute(Xml.TRAIN_SCHEDULE_ID)) != null) {
461            _trainScheduleId = a.getValue();
462        }
463        Element eMessages = e.getChild(Xml.MESSAGES);
464        if (eMessages != null) {
465            Element eMessageOk = eMessages.getChild(Xml.MESSAGE_OK);
466            if (eMessageOk != null && (a = eMessageOk.getAttribute(Xml.MESSAGE)) != null) {
467                _message = a.getValue();
468            }
469            Element eMessageFail = eMessages.getChild(Xml.MESSAGE_FAIL);
470            if (eMessageFail != null && (a = eMessageFail.getAttribute(Xml.MESSAGE)) != null) {
471                _messageFail = a.getValue();
472            }
473        }
474    }
475
476    /**
477     * Create an XML element to represent this Entry. This member has to remain
478     * synchronized with the detailed DTD in operations-trains.dtd.
479     *
480     * @return Contents in a JDOM Element
481     */
482    public Element store() {
483        Element e = new Element(Xml.ITEM);
484        e.setAttribute(Xml.ID, getId());
485        e.setAttribute(Xml.SEQUENCE_ID, Integer.toString(getSequenceId()));
486        e.setAttribute(Xml.NAME, getActionName());
487        e.setAttribute(Xml.ACTION_CODE, "0x" + Integer.toHexString(getActionCode())); // NOI18N
488        e.setAttribute(Xml.HALT_FAIL, isHaltFailureEnabled() ? Xml.TRUE : Xml.FALSE);
489        e.setAttribute(Xml.ACTION_RAN, isActionRan() ? Xml.TRUE : Xml.FALSE);
490        e.setAttribute(Xml.ACTION_SUCCESSFUL, isActionSuccessful() ? Xml.TRUE : Xml.FALSE);
491        if (getTrain() != null) {
492            e.setAttribute(Xml.TRAIN_ID, getTrain().getId());
493            if (getRouteLocation() != null) {
494                e.setAttribute(Xml.ROUTE_LOCATION_ID, getRouteLocation().getId());
495            }
496        }
497        if (getAutomationToRun() != null) {
498            e.setAttribute(Xml.AUTOMATION_ID, getAutomationToRun().getId());
499        }
500        if (getGotoAutomationItem() != null) {
501            e.setAttribute(Xml.GOTO_AUTOMATION_ID, getGotoAutomationItem().getId());
502            e.setAttribute(Xml.GOTO_AUTOMATION_BRANCHED, isGotoBranched() ? Xml.TRUE : Xml.FALSE);
503        }
504        if (getTrainSchedule() != null) {
505            e.setAttribute(Xml.TRAIN_SCHEDULE_ID, getTrainSchedule().getId());
506        }
507        if (!getMessage().equals(NONE) || !getMessageFail().equals(NONE)) {
508            Element eMessages = new Element(Xml.MESSAGES);
509            e.addContent(eMessages);
510            Element eMessageOk = new Element(Xml.MESSAGE_OK);
511            eMessageOk.setAttribute(Xml.MESSAGE, getMessage());
512            Element eMessageFail = new Element(Xml.MESSAGE_FAIL);
513            eMessageFail.setAttribute(Xml.MESSAGE, getMessageFail());
514            eMessages.addContent(eMessageOk);
515            eMessages.addContent(eMessageFail);
516        }
517        return e;
518    }
519
520    @Override
521    public void propertyChange(java.beans.PropertyChangeEvent e) {
522        if (Control.SHOW_PROPERTY) {
523            log.debug("AutomationItem id ({}) sees property change: ({}) old: ({}) new: ({})",
524                    getId(), e.getPropertyName(), e.getOldValue(), e.getNewValue()); // NOI18N
525        }
526    }
527
528    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
529        // set dirty
530        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
531        firePropertyChange(p, old, n);
532    }
533
534    private final static Logger log = LoggerFactory.getLogger(AutomationItem.class);
535
536}