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().isOtherMenuEnabled()) {
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 (isActionRunning())
324            return Bundle.getMessage("Running");
325        if (!isActionRan())
326            return NONE;
327        if (getAction() != null)
328            return isActionSuccessful() ? getAction().getActionSuccessfulString() : getAction().getActionFailedString();
329        else
330            return "unknown"; // NOI18N
331    }
332    
333    public void reset() {
334        setActionRan(false);
335        setActionSuccessful(false);
336        setGotoBranched(false);
337    }
338
339    /**
340     * Copies item.
341     * @param item The item to copy.
342     */
343    public void copyItem(AutomationItem item) {
344        setAction(getActionByCode(item.getActionCode())); // must create a new action for each item
345        setAutomationToRun(item.getAutomationToRun());
346        setGotoAutomationItem(item.getGotoAutomationItem()); //needs an adjustment to work properly
347        setTrain(item.getTrain()); // must set train before route location
348        setRouteLocation(item.getRouteLocation());
349        setSequenceId(item.getSequenceId());
350        setTrainSchedule(item.getTrainSchedule());
351        setMessage(item.getMessage());
352        setMessageFail(item.getMessageFail());
353        setHaltFailureEnabled(item.isHaltFailureEnabled());
354    }
355    
356    public static Action getActionByCode(int code) {
357        for (Action action : getActionList()) {
358            if (action.getCode() == code)
359                return action;
360        }
361        return new NoAction(); // default if code not found
362    }
363
364    /**
365     * Gets a list of all known automation actions
366     * 
367     * @return list of automation actions
368     */
369    public static List<Action> getActionList() {
370        List<Action> list = new ArrayList<>();
371        list.add(new NoAction());
372        list.add(new BuildTrainAction());
373        list.add(new BuildTrainIfSelectedAction());
374        list.add(new PrintTrainManifestAction());
375        list.add(new PrintTrainManifestIfSelectedAction());
376        list.add(new PrintTrainBuildReportAction());
377        list.add(new RunTrainAction());
378        list.add(new MoveTrainAction());
379        list.add(new TerminateTrainAction());
380        list.add(new ResetTrainAction());
381        list.add(new IsTrainEnRouteAction());
382        list.add(new WaitTrainAction());
383        list.add(new WaitTrainTerminatedAction());
384        list.add(new ActivateTrainScheduleAction());
385        list.add(new ApplyTrainScheduleAction());
386        list.add(new SelectTrainAction());
387        list.add(new DeselectTrainAction());
388        list.add(new PrintSwitchListAction());
389        list.add(new UpdateSwitchListAction());
390        list.add(new WaitSwitchListAction());
391        list.add(new GenerateSwitchListAction());
392        list.add(new GenerateSwitchListChangesAction());
393        list.add(new ResetSwitchListsAction());
394        list.add(new RunSwitchListAction());
395        list.add(new RunSwitchListChangesAction());
396        list.add(new RunAutomationAction());
397        list.add(new ResumeAutomationAction());
398        list.add(new StopAutomationAction());
399        list.add(new CounterAction());
400        list.add(new MessageYesNoAction());
401        list.add(new GotoAction());
402        list.add(new GotoSuccessAction());
403        list.add(new GotoFailureAction());
404        list.add(new HaltAction());
405        return list;
406    }
407
408    public static JComboBox<Action> getActionComboBox() {
409        JComboBox<Action> box = new JComboBox<>();
410        for (Action action : getActionList())
411            box.addItem(action);
412        return box;
413    }
414
415    public void dispose() {
416        setDirtyAndFirePropertyChange(DISPOSE, null, DISPOSE);
417    }
418
419    /**
420     * Construct this Entry from XML. This member has to remain synchronized
421     * with the detailed DTD in operations-trains.xml
422     *
423     * @param e Consist XML element
424     */
425    public AutomationItem(Element e) {
426        org.jdom2.Attribute a;
427        if ((a = e.getAttribute(Xml.ID)) != null) {
428            _id = a.getValue();
429        } else {
430            log.warn("no id attribute in Automation Item element when reading operations");
431        }
432        if ((a = e.getAttribute(Xml.SEQUENCE_ID)) != null) {
433            _sequenceId = Integer.parseInt(a.getValue());
434        }
435        if ((a = e.getAttribute(Xml.ACTION_CODE)) != null) {
436            setAction(getActionByCode(Integer.decode(a.getValue())));
437        }
438        if ((a = e.getAttribute(Xml.HALT_FAIL)) != null) {
439            _haltFail = a.getValue().equals(Xml.TRUE);
440        }
441        if ((a = e.getAttribute(Xml.ACTION_RAN)) != null) {
442            _actionRan = a.getValue().equals(Xml.TRUE);
443        }
444        if ((a = e.getAttribute(Xml.ACTION_SUCCESSFUL)) != null) {
445            _actionSuccessful = a.getValue().equals(Xml.TRUE);
446        }
447        if ((a = e.getAttribute(Xml.TRAIN_ID)) != null) {
448            _train = InstanceManager.getDefault(TrainManager.class).getTrainById(a.getValue());
449        }
450        if ((a = e.getAttribute(Xml.ROUTE_LOCATION_ID)) != null && getTrain() != null) {
451            _routeLocation = getTrain().getRoute().getLocationById(a.getValue());
452        }
453        if ((a = e.getAttribute(Xml.AUTOMATION_ID)) != null) {
454            // in the process of loading automations, so we can't get them now, save id and get later.
455            _automationIdToRun = a.getValue();
456        }
457        if ((a = e.getAttribute(Xml.GOTO_AUTOMATION_ID)) != null) {
458            // in the process of loading automations, so we can't get them now, save id and get later.
459            _gotoAutomationItemId = a.getValue();
460        }
461        if ((a = e.getAttribute(Xml.GOTO_AUTOMATION_BRANCHED)) != null) {
462            _gotoAutomationBranched = a.getValue().equals(Xml.TRUE);
463        }
464        if ((a = e.getAttribute(Xml.TRAIN_SCHEDULE_ID)) != null) {
465            _trainScheduleId = a.getValue();
466        }
467        Element eMessages = e.getChild(Xml.MESSAGES);
468        if (eMessages != null) {
469            Element eMessageOk = eMessages.getChild(Xml.MESSAGE_OK);
470            if (eMessageOk != null && (a = eMessageOk.getAttribute(Xml.MESSAGE)) != null) {
471                _message = a.getValue();
472            }
473            Element eMessageFail = eMessages.getChild(Xml.MESSAGE_FAIL);
474            if (eMessageFail != null && (a = eMessageFail.getAttribute(Xml.MESSAGE)) != null) {
475                _messageFail = a.getValue();
476            }
477        }
478    }
479
480    /**
481     * Create an XML element to represent this Entry. This member has to remain
482     * synchronized with the detailed DTD in operations-trains.dtd.
483     *
484     * @return Contents in a JDOM Element
485     */
486    public Element store() {
487        Element e = new Element(Xml.ITEM);
488        e.setAttribute(Xml.ID, getId());
489        e.setAttribute(Xml.SEQUENCE_ID, Integer.toString(getSequenceId()));
490        e.setAttribute(Xml.NAME, getActionName());
491        e.setAttribute(Xml.ACTION_CODE, "0x" + Integer.toHexString(getActionCode())); // NOI18N
492        e.setAttribute(Xml.HALT_FAIL, isHaltFailureEnabled() ? Xml.TRUE : Xml.FALSE);
493        e.setAttribute(Xml.ACTION_RAN, isActionRan() ? Xml.TRUE : Xml.FALSE);
494        e.setAttribute(Xml.ACTION_SUCCESSFUL, isActionSuccessful() ? Xml.TRUE : Xml.FALSE);
495        if (getTrain() != null) {
496            e.setAttribute(Xml.TRAIN_ID, getTrain().getId());
497            if (getRouteLocation() != null) {
498                e.setAttribute(Xml.ROUTE_LOCATION_ID, getRouteLocation().getId());
499            }
500        }
501        if (getAutomationToRun() != null) {
502            e.setAttribute(Xml.AUTOMATION_ID, getAutomationToRun().getId());
503        }
504        if (getGotoAutomationItem() != null) {
505            e.setAttribute(Xml.GOTO_AUTOMATION_ID, getGotoAutomationItem().getId());
506            e.setAttribute(Xml.GOTO_AUTOMATION_BRANCHED, isGotoBranched() ? Xml.TRUE : Xml.FALSE);
507        }
508        if (getTrainSchedule() != null) {
509            e.setAttribute(Xml.TRAIN_SCHEDULE_ID, getTrainSchedule().getId());
510        }
511        if (!getMessage().equals(NONE) || !getMessageFail().equals(NONE)) {
512            Element eMessages = new Element(Xml.MESSAGES);
513            e.addContent(eMessages);
514            Element eMessageOk = new Element(Xml.MESSAGE_OK);
515            eMessageOk.setAttribute(Xml.MESSAGE, getMessage());
516            Element eMessageFail = new Element(Xml.MESSAGE_FAIL);
517            eMessageFail.setAttribute(Xml.MESSAGE, getMessageFail());
518            eMessages.addContent(eMessageOk);
519            eMessages.addContent(eMessageFail);
520        }
521        return e;
522    }
523
524    @Override
525    public void propertyChange(java.beans.PropertyChangeEvent e) {
526        if (Control.SHOW_PROPERTY) {
527            log.debug("AutomationItem id ({}) sees property change: ({}) old: ({}) new: ({})",
528                    getId(), e.getPropertyName(), e.getOldValue(), e.getNewValue()); // NOI18N
529        }
530    }
531
532    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
533        // set dirty
534        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
535        firePropertyChange(p, old, n);
536    }
537
538    private final static Logger log = LoggerFactory.getLogger(AutomationItem.class);
539
540}