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