001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.util.ArrayList;
006import java.util.List;
007import java.util.ListIterator;
008
009import javax.annotation.concurrent.GuardedBy;
010import javax.annotation.CheckForNull;
011import javax.annotation.Nonnull;
012
013import jmri.*;
014import jmri.implementation.SignalSpeedMap;
015import jmri.util.ThreadingUtil;
016import jmri.jmrit.logix.ThrottleSetting.Command;
017import jmri.jmrit.logix.ThrottleSetting.CommandValue;
018import jmri.jmrit.logix.ThrottleSetting.ValueType;
019import jmri.util.swing.JmriJOptionPane;
020
021/**
022 * A Warrant contains the operating permissions and directives needed for a
023 * train to proceed from an Origin to a Destination.
024 * There are three modes that a Warrant may execute;
025 * <p>
026 * MODE_LEARN - Warrant is created or edited in WarrantFrame and then launched
027 * from WarrantFrame who records throttle commands from "_student" throttle.
028 * Warrant fires PropertyChanges for WarrantFrame to record when blocks are
029 * entered. "_engineer" thread is null.
030 * <p>
031 * MODE_RUN - Warrant may be launched from several places. An array of
032 * BlockOrders, _savedOrders, and corresponding _throttleCommands allow an
033 * "_engineer" thread to execute the throttle commands. The blockOrders
034 * establish the route for the Warrant to acquire and reserve OBlocks. The
035 * Warrant monitors block activity (entrances and exits, signals, rogue
036 * occupancy etc) and modifies speed as needed.
037 * <p>
038 * MODE_MANUAL - Warrant may be launched from several places. The Warrant to
039 * acquires and reserves the route from the array of BlockOrders. Throttle
040 * commands are done by a human operator. "_engineer" and "_throttleCommands"
041 * are not used. Warrant monitors block activity but does not set _stoppingBlock
042 * or _protectSignal since it cannot control speed. It does attempt to realign
043 * the route as needed, but can be thwarted.
044 * <p>
045 * Version 1.11 - remove setting of SignalHeads
046 *
047 * @author Pete Cressman Copyright (C) 2009, 2010, 2022
048 */
049public class Warrant extends jmri.implementation.AbstractNamedBean implements ThrottleListener, java.beans.PropertyChangeListener {
050
051    public static final String Stop = InstanceManager.getDefault(SignalSpeedMap.class).getNamedSpeed(0.0f); // aspect name
052    public static final String EStop = Bundle.getMessage("EStop");
053    public static final String Normal ="Normal";    // Cannot determine which SignalSystem(s) and their name(s) for "Clear"
054
055    /**
056     * String constant for property warrant start.
057     */
058    public static final String PROPERTY_WARRANT_START = "WarrantStart";
059
060    /**
061     * String constant for property stop warrant.
062     */
063    public static final String PROPERTY_STOP_WARRANT = "StopWarrant";
064
065    /**
066     * String constant for property throttle fail.
067     */
068    public static final String PROPERTY_THROTTLE_FAIL = "throttleFail";
069
070    /**
071     * String constant for property abort learn.
072     */
073    public static final String PROPERTY_ABORT_LEARN = "abortLearn";
074
075    /**
076     * String constant for property control change.
077     */
078    public static final String PROPERTY_CONTROL_CHANGE = "controlChange";
079
080    /**
081     * String constant for property control failed.
082     */
083    public static final String PROPERTY_CONTROL_FAILED = "controlFailed";
084
085    /**
086     * String constant for property ready to run.
087     */
088    public static final String PROPERTY_READY_TO_RUN = "ReadyToRun";
089
090    /**
091     * String constant for property cannot run.
092     */
093    public static final String PROPERTY_CANNOT_RUN = "cannotRun";
094
095    /**
096     * String constant for property block change.
097     */
098    public static final String PROPERTY_BLOCK_CHANGE = "blockChange";
099
100    /**
101     * String constant for property signal overrun.
102     */
103    public static final String PROPERTY_SIGNAL_OVERRUN = "SignalOverrun";
104
105    /**
106     * String constant for property warrant overrun.
107     */
108    public static final String PROPERTY_WARRANT_OVERRUN = "WarrantOverrun";
109
110    /**
111     * String constant for property warrant start.
112     */
113    public static final String PROPERTY_OCCUPY_OVERRUN = "OccupyOverrun";
114
115    // permanent members.
116    private List<BlockOrder> _orders;
117    private BlockOrder _viaOrder;
118    private BlockOrder _avoidOrder;
119    private List<ThrottleSetting> _commands = new ArrayList<>();
120    protected String _trainName; // User train name for icon
121    private SpeedUtil _speedUtil;
122    private boolean _runBlind; // Unable to use block detection, must run on et only
123    private boolean _shareRoute;// only allocate one block at a time for sharing route.
124    private boolean _addTracker;    // start tracker when warrant ends normally.
125    private boolean _haltStart;     // Hold train in Origin block until Resume command
126    private boolean _noRamp; // do not ramp speed changes. make immediate speed change when entering approach block.
127    private boolean _nxWarrant = false;
128
129    // transient members
130    private LearnThrottleFrame _student; // need to callback learning throttle in learn mode
131    private boolean _tempRunBlind; // run mode flag to allow running on ET only
132    private boolean _delayStart; // allows start block unoccupied and wait for train
133    private boolean _lost;      // helps recovery if _idxCurrentOrder block goes inactive
134    private boolean _overrun;   // train overran a signal or warrant stop
135    private boolean _rampBlkOccupied;  // test for overruns when speed change block occupied by another train
136    private int _idxCurrentOrder; // Index of block at head of train (if running)
137
138    protected int _runMode = MODE_NONE;
139    private Engineer _engineer; // thread that runs the train
140    @GuardedBy("this")
141    private CommandDelay _delayCommand; // thread for delayed ramp down
142    private boolean _allocated; // initial Blocks of _orders have been allocated
143    private boolean _totalAllocated; // All Blocks of _orders have been allocated
144    private boolean _routeSet; // all allocated Blocks of _orders have paths set for route
145    protected OBlock _stoppingBlock; // Block occupied by rogue train or halted
146    private int _idxStoppingBlock;      // BlockOrder index of _stoppingBlock
147    private NamedBean _protectSignal; // Signal stopping train movement
148    private int _idxProtectSignal;      // BlockOrder index of _protectSignal
149
150    private boolean _waitForSignal; // train may not move until false
151    private boolean _waitForBlock; // train may not move until false
152    private boolean _waitForWarrant;
153    private String _curSignalAspect;   // speed type to restore when flags are cleared;
154    protected String _message; // last message returned from an action
155    private final ThrottleManager tm;
156
157    // Running modes
158    public static final int MODE_NONE = 0;
159    public static final int MODE_LEARN = 1; // Record a command list
160    public static final int MODE_RUN = 2;   // Autorun, playback the command list
161    public static final int MODE_MANUAL = 3; // block detection of manually run train
162    static final String[] MODES = {"none", "LearnMode", "RunAuto", "RunManual", "Abort"};
163    public static final int MODE_ABORT = 4; // used to set status string in WarrantTableFrame
164
165    // control states
166    public static final int STOP = 0;
167    public static final int HALT = 1;
168    public static final int RESUME = 2;
169    public static final int ABORT = 3;
170    public static final int RETRY_FWD = 4;
171    public static final int ESTOP = 5;
172    protected static final int RAMP_HALT = 6;  // used only to distinguish User halt from speed change halts
173    public static final int SPEED_UP = 7;
174    public static final int RETRY_BKWD = 8;
175    public static final int DEBUG = 9;
176    static final String[] CNTRL_CMDS = {"Stop", "Halt", "Resume", "Abort", "MoveToNext",
177          "EStop", "ramp", "SpeedUp", "MoveToPrevious","Debug"};  // RAMP_HALT is not a control command
178
179    // engineer running states
180    protected static final int RUNNING = 7;
181    protected static final int SPEED_RESTRICTED = 8;
182    protected static final int WAIT_FOR_CLEAR = 9;
183    protected static final int WAIT_FOR_SENSOR = 10;
184    protected static final int WAIT_FOR_TRAIN = 11;
185    protected static final int WAIT_FOR_DELAYED_START = 12;
186    protected static final int LEARNING = 13;
187    protected static final int STOP_PENDING = 14;
188    static final String[] RUN_STATE = {"HaltStart", "atHalt", "Resumed", "Aborts", "Retried",
189            "EStop", "HaltPending", "Running", "changeSpeed", "WaitingForClear", "WaitingForSensor",
190            "RunningLate", "WaitingForStart", "RecordingScript", "StopPending"};
191
192    static final float BUFFER_DISTANCE = 50*12*25.4F / WarrantPreferences.getDefault().getLayoutScale(); // 50 scale feet for safety distance
193    protected static boolean _trace = WarrantPreferences.getDefault().getTrace();
194
195    // Speed states: steady, increasing, decreasing
196    static final int AT_SPEED = 1;
197    static final int RAMP_DOWN = 2;
198    static final int RAMP_UP = 3;
199    public enum SpeedState {
200        STEADY_SPEED(AT_SPEED, "SteadySpeed"),
201        RAMPING_DOWN(RAMP_DOWN, "RampingDown"),
202        RAMPING_UP(RAMP_UP, "RampingUp");
203
204        int _speedStateId;  // state id
205        String _bundleKey; // key to get state display name
206
207        SpeedState(int id, String bundleName) {
208            _speedStateId = id;
209            _bundleKey = bundleName;
210        }
211
212        public int getIntId() {
213            return _speedStateId;
214        }
215
216        @Override
217        public String toString() {
218            return Bundle.getMessage(_bundleKey);
219        }
220    }
221
222    /**
223     * Create an object with no route defined. The list of BlockOrders is the
224     * route from an Origin to a Destination
225     *
226     * @param sName system name
227     * @param uName user name
228     */
229    public Warrant(String sName, String uName) {
230        super(sName, uName);
231        _idxCurrentOrder = -1;
232        _idxProtectSignal = -1;
233        _orders = new ArrayList<>();
234        _runBlind = false;
235        _speedUtil = new SpeedUtil();
236        tm = InstanceManager.getNullableDefault(ThrottleManager.class);
237    }
238
239    protected void setNXWarrant(boolean set) {
240        _nxWarrant = set;
241    }
242    protected boolean isNXWarrant() {
243        return _nxWarrant;
244    }
245
246    @Override
247    public int getState() {
248        if (_engineer != null) {
249            return _engineer.getRunState();
250        }
251        if (_delayStart) {
252            return WAIT_FOR_DELAYED_START;
253        }
254        if (_runMode == MODE_LEARN) {
255            return LEARNING;
256        }
257        if (_runMode != MODE_NONE) {
258            return RUNNING;
259        }
260        return -1;
261    }
262
263    @Override
264    public void setState(int state) {
265        // warrant state is computed from other values
266    }
267
268    public SpeedUtil getSpeedUtil() {
269        return _speedUtil;
270    }
271
272    public void setSpeedUtil(SpeedUtil su) {
273        _speedUtil = su;
274    }
275
276    /**
277     * Return BlockOrders.
278     *
279     * @return list of block orders
280     */
281    public List<BlockOrder> getBlockOrders() {
282        return _orders;
283    }
284
285    /**
286     * Add permanently saved BlockOrder.
287     *
288     * @param order block order
289     */
290    public void addBlockOrder(BlockOrder order) {
291        _orders.add(order);
292    }
293
294    public void setBlockOrders(List<BlockOrder> orders) {
295        _orders = orders;
296    }
297
298    /**
299     * Return permanently saved Origin.
300     *
301     * @return origin block order
302     */
303    public BlockOrder getfirstOrder() {
304        if (_orders.isEmpty()) {
305            return null;
306        }
307        return new BlockOrder(_orders.get(0));
308    }
309
310    /**
311     * Return permanently saved Destination.
312     *
313     * @return destination block order
314     */
315    public BlockOrder getLastOrder() {
316        int size = _orders.size();
317        if (size < 2) {
318            return null;
319        }
320        return new BlockOrder(_orders.get(size - 1));
321    }
322
323    /**
324     * Return permanently saved BlockOrder that must be included in the route.
325     *
326     * @return via block order
327     */
328    public BlockOrder getViaOrder() {
329        if (_viaOrder == null) {
330            return null;
331        }
332        return new BlockOrder(_viaOrder);
333    }
334
335    public void setViaOrder(BlockOrder order) {
336        _viaOrder = order;
337    }
338
339    public BlockOrder getAvoidOrder() {
340        if (_avoidOrder == null) {
341            return null;
342        }
343        return new BlockOrder(_avoidOrder);
344    }
345
346    public void setAvoidOrder(BlockOrder order) {
347        _avoidOrder = order;
348    }
349
350    /**
351     * @return block order currently at the train position
352     */
353    public final BlockOrder getCurrentBlockOrder() {
354        return getBlockOrderAt(_idxCurrentOrder);
355    }
356
357    /**
358     * @return index of block order currently at the train position
359     */
360    public final int getCurrentOrderIndex() {
361        return _idxCurrentOrder;
362    }
363
364    protected int getNumOrders() {
365        return _orders.size();
366    }
367    /*
368     * Used only by SCWarrant
369     * SCWarrant overrides goingActive
370     */
371    protected void incrementCurrentOrderIndex() {
372        _idxCurrentOrder++;
373    }
374
375    /**
376     * Find index of a block AFTER BlockOrder index.
377     *
378     * @param block used by the warrant
379     * @param idx start index of search
380     * @return index of block after of block order index, -1 if not found
381     */
382    protected int getIndexOfBlockAfter(OBlock block, int idx) {
383        for (int i = idx; i < _orders.size(); i++) {
384            if (_orders.get(i).getBlock().equals(block)) {
385                return i;
386            }
387        }
388        return -1;
389    }
390
391    /**
392     * Find index of block BEFORE BlockOrder index.
393     *
394     * @param idx start index of search
395     * @param block used by the warrant
396     * @return index of block before of block order index, -1 if not found
397     */
398    protected int getIndexOfBlockBefore(int idx, OBlock block) {
399        for (int i = idx; i >= 0; i--) {
400            if (_orders.get(i).getBlock().equals(block)) {
401                return i;
402            }
403        }
404        return -1;
405    }
406
407    /**
408     * Call is only valid when in MODE_LEARN and MODE_RUN.
409     *
410     * @param index index of block order
411     * @return block order or null if not found
412     */
413    protected BlockOrder getBlockOrderAt(int index) {
414        if (index >= 0 && index < _orders.size()) {
415            return _orders.get(index);
416        }
417        return null;
418    }
419
420    /**
421     * Call is only valid when in MODE_LEARN and MODE_RUN.
422     *
423     * @param idx index of block order
424     * @return block of the block order
425     */
426    protected OBlock getBlockAt(int idx) {
427
428        BlockOrder bo = getBlockOrderAt(idx);
429        if (bo != null) {
430            return bo.getBlock();
431        }
432        return null;
433    }
434
435    /**
436     * Call is only valid when in MODE_LEARN and MODE_RUN.
437     *
438     * @return Name of OBlock currently occupied
439     */
440    public String getCurrentBlockName() {
441        OBlock block = getBlockAt(_idxCurrentOrder);
442        if (block == null || !block.isOccupied()) {
443            return Bundle.getMessage("Unknown");
444        } else {
445            return block.getDisplayName();
446        }
447    }
448
449    /**
450     * @return throttle commands
451     */
452    public List<ThrottleSetting> getThrottleCommands() {
453        return _commands;
454    }
455
456    public void setThrottleCommands(List<ThrottleSetting> list) {
457        _commands = list;
458    }
459
460    public void addThrottleCommand(ThrottleSetting ts) {
461        if (ts == null) {
462            log.error("warrant {} cannot add null ThrottleSetting", getDisplayName());
463        } else {
464            _commands.add(ts);
465        }
466    }
467
468    public void setTrackSpeeds() {
469        float speed = 0.0f;
470        for (ThrottleSetting ts :_commands) {
471            CommandValue cmdVal = ts.getValue();
472            ValueType valType = cmdVal.getType();
473            switch (valType) {
474                case VAL_FLOAT:
475                    speed = _speedUtil.getTrackSpeed(cmdVal.getFloat());
476                    break;
477                case VAL_TRUE:
478                    _speedUtil.setIsForward(true);
479                    break;
480                case VAL_FALSE:
481                    _speedUtil.setIsForward(false);
482                    break;
483                default:
484            }
485            ts.setTrackSpeed(speed);
486        }
487    }
488
489    public void setNoRamp(boolean set) {
490        _noRamp = set;
491    }
492
493    public void setShareRoute(boolean set) {
494        _shareRoute = set;
495    }
496
497    public void setAddTracker (boolean set) {
498        _addTracker = set;
499    }
500
501    public void setHaltStart (boolean set) {
502        _haltStart = set;
503    }
504
505    public boolean getNoRamp() {
506        return _noRamp;
507    }
508
509    public boolean getShareRoute() {
510        return _shareRoute;
511    }
512
513    public boolean getAddTracker() {
514        return _addTracker;
515    }
516
517    public boolean getHaltStart() {
518        return _haltStart;
519    }
520
521    public String getTrainName() {
522        return _trainName;
523    }
524
525    public void setTrainName(String name) {
526        if (_runMode == MODE_NONE) {
527            _trainName = name;
528        }
529    }
530
531    public boolean getRunBlind() {
532        return _runBlind;
533    }
534
535    public void setRunBlind(boolean runBlind) {
536        _runBlind = runBlind;
537    }
538
539    /*
540     * Engineer reports its status
541     */
542    protected void fireRunStatus(String property, Object old, Object status) {
543//        jmri.util.ThreadingUtil.runOnLayout(() -> {   // Will hang GUI!
544        ThreadingUtil.runOnGUIEventually(() -> { // OK but can be quite late in reporting speed changes
545            firePropertyChange(property, old, status);
546        });
547    }
548
549    /**
550     * ****************************** state queries ****************
551     */
552    /**
553     * @return true if listeners are installed enough to run
554     */
555    public boolean isAllocated() {
556        return _allocated;
557    }
558
559    /**
560     * @return true if listeners are installed for entire route
561     */
562    public boolean isTotalAllocated() {
563        return _totalAllocated;
564    }
565
566    /**
567     * Turnouts are set for the route
568     *
569     * @return true if turnouts are set
570     */
571    public boolean hasRouteSet() {
572        return _routeSet;
573    }
574
575    /**
576     * Test if the permanent saved blocks of this warrant are free (unoccupied
577     * and unallocated)
578     *
579     * @return true if route is free
580     */
581    public boolean routeIsFree() {
582        for (int i = 0; i < _orders.size(); i++) {
583            OBlock block = _orders.get(i).getBlock();
584            if (!block.isFree()) {
585                return false;
586            }
587        }
588        return true;
589    }
590
591    /**
592     * Test if the permanent saved blocks of this warrant are occupied
593     *
594     * @return true if any block is occupied
595     */
596    public boolean routeIsOccupied() {
597        for (int i = 1; i < _orders.size(); i++) {
598            OBlock block = _orders.get(i).getBlock();
599            if ((block.getState() & Block.OCCUPIED) != 0) {
600                return true;
601            }
602        }
603        return false;
604    }
605
606    public String getMessage() {
607        return _message;
608    }
609
610    /* ************* Methods for running trains *****************/
611/*
612    protected void setWaitingForSignal(Boolean set) {
613        _waitForSignal = set;
614    }
615    protected void setWaitingForBlock(Boolean set) {
616        _waitForBlock = set;
617    }
618    protected void setWaitingForWarrant(Boolean set) {
619        _waitForWarrant = set;
620    }
621    */
622    protected boolean isWaitingForSignal() {
623        return _waitForSignal;
624    }
625    protected boolean isWaitingForBlock() {
626        return _waitForBlock;
627    }
628    protected boolean isWaitingForWarrant() {
629        return _waitForWarrant;
630    }
631    protected Warrant getBlockingWarrant() {
632        if (_stoppingBlock != null && !this.equals(_stoppingBlock.getWarrant())) {
633            return _stoppingBlock.getWarrant();
634        }
635        return null;
636    }
637
638    /**
639      *  @return ID of run mode
640     */
641    public int getRunMode() {
642        return _runMode;
643    }
644
645    protected String getRunModeMessage() {
646        String modeDesc = null;
647        switch (_runMode) {
648            case MODE_NONE:
649                return Bundle.getMessage("NotRunning", getDisplayName());
650            case MODE_LEARN:
651                modeDesc = Bundle.getMessage("Recording");
652                break;
653            case MODE_RUN:
654                modeDesc = Bundle.getMessage("AutoRun");
655                break;
656            case MODE_MANUAL:
657                modeDesc = Bundle.getMessage("ManualRun");
658                break;
659            default:
660        }
661        return Bundle.getMessage("WarrantInUse", modeDesc, getDisplayName());
662
663    }
664
665    /**
666     * Get the warrant speed message for the current throttle speed setting.
667     * This is public to provide access for scripts and LogixNG formulas.
668     * @return the current speed message or "Not available".
669     */
670    public String getWarrantSpeedMessage() {
671        var msg = Bundle.getMessage("SpeedNotAvailable");
672        if (_runMode == Warrant.MODE_RUN && _engineer != null) {
673            msg = getSpeedMessage(_engineer.getSpeedType(true));
674        }
675        return msg;
676    }
677
678    @SuppressWarnings("fallthrough")
679    @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH")
680    protected synchronized String getRunningMessage() {
681        if (_delayStart) {
682            return Bundle.getMessage("waitForDelayStart", _trainName, getBlockAt(0).getDisplayName());
683        }
684        switch (_runMode) {
685            case Warrant.MODE_NONE:
686                _message = null;
687            case Warrant.MODE_ABORT:
688                if (getBlockOrders().isEmpty()) {
689                    return Bundle.getMessage("BlankWarrant");
690                }
691                if (_speedUtil.getAddress() == null) {
692                    return Bundle.getMessage("NoLoco");
693                }
694                if (!(this instanceof SCWarrant) && _commands.size() <= _orders.size()) {
695                    return Bundle.getMessage("NoCommands", getDisplayName());
696                }
697                if (_message != null) {
698                    if (_lost) {
699                        return Bundle.getMessage("locationUnknown", _trainName, getCurrentBlockName()) + _message;
700                    } else {
701                        return Bundle.getMessage("Idle", _message);
702                    }
703                 }
704                return Bundle.getMessage("Idle");
705            case Warrant.MODE_LEARN:
706                return Bundle.getMessage("Learning", getCurrentBlockName());
707            case Warrant.MODE_RUN:
708                if (_engineer == null) {
709                    return Bundle.getMessage("engineerGone", getCurrentBlockName());
710                }
711                String speedMsg = getSpeedMessage(_engineer.getSpeedType(true)); // current or pending
712                int runState = _engineer.getRunState();
713
714                int cmdIdx = _engineer.getCurrentCommandIndex();
715                if (cmdIdx >= _commands.size()) {
716                    cmdIdx = _commands.size() - 1;
717                }
718                cmdIdx++;   // display is 1-based
719                OBlock block = getBlockAt(_idxCurrentOrder);
720                if ((block.getState() & (Block.OCCUPIED | Block.UNDETECTED)) == 0) {
721                    return Bundle.getMessage("TrackerNoCurrentBlock", _trainName, block.getDisplayName());
722                }
723                String blockName = block.getDisplayName();
724
725                switch (runState) {
726                    case Warrant.ABORT:
727                        if (cmdIdx == _commands.size() - 1) {
728                            return Bundle.getMessage("endOfScript", _trainName);
729                        }
730                        return Bundle.getMessage("Aborted", blockName, cmdIdx);
731
732                    case Warrant.HALT:
733                        return Bundle.getMessage("RampHalt", getTrainName(), blockName);
734                    case Warrant.WAIT_FOR_CLEAR:
735                        SpeedState ss = _engineer.getSpeedState();
736                        if (ss.equals(SpeedState.STEADY_SPEED)) {
737                            return  makeWaitMessage(blockName, cmdIdx);
738                        } else {
739                            return Bundle.getMessage("Ramping", ss.toString(), speedMsg, blockName);
740                        }
741                    case Warrant.WAIT_FOR_TRAIN:
742                        if (_engineer.getSpeedSetting() <= 0) {
743                            return makeWaitMessage(blockName, cmdIdx);
744                        } else {
745                            return Bundle.getMessage("WaitForTrain", cmdIdx,
746                                    _engineer.getSynchBlock().getDisplayName(), speedMsg);
747                        }
748                    case Warrant.WAIT_FOR_SENSOR:
749                        return Bundle.getMessage("WaitForSensor",
750                                cmdIdx, _engineer.getWaitSensor().getDisplayName(),
751                                blockName, speedMsg);
752
753                    case Warrant.RUNNING:
754                        return Bundle.getMessage("WhereRunning", blockName, cmdIdx, speedMsg);
755                    case Warrant.SPEED_RESTRICTED:
756                        return Bundle.getMessage("changeSpeed", blockName, cmdIdx, speedMsg);
757
758                    case Warrant.RAMP_HALT:
759                        return Bundle.getMessage("HaltPending", speedMsg, blockName);
760
761                    case Warrant.STOP_PENDING:
762                        return Bundle.getMessage("StopPending", speedMsg, blockName, (_waitForSignal
763                                ? Bundle.getMessage("Signal") : (_waitForWarrant
764                                        ? Bundle.getMessage("Warrant") :Bundle.getMessage("Occupancy"))));
765
766                    default:
767                        return _message;
768                }
769
770            case Warrant.MODE_MANUAL:
771                BlockOrder bo = getCurrentBlockOrder();
772                if (bo != null) {
773                    return Bundle.getMessage("ManualRunning", bo.getBlock().getDisplayName());
774                }
775                return Bundle.getMessage("ManualRun");
776
777            default:
778        }
779        return "ERROR mode= " + MODES[_runMode];
780    }
781
782    /**
783     * Calculates the scale speed of the current throttle setting for display
784     * @param speedType name of current speed
785     * @return text message
786     */
787    private String getSpeedMessage(String speedType) {
788        float speed = 0;
789        String units;
790        SignalSpeedMap speedMap = InstanceManager.getDefault(SignalSpeedMap.class);
791        switch (speedMap.getInterpretation()) {
792            case SignalSpeedMap.PERCENT_NORMAL:
793                speed = _engineer.getSpeedSetting() * 100;
794                float scriptSpeed = _engineer.getScriptSpeed();
795                scriptSpeed = (scriptSpeed > 0 ? (speed/scriptSpeed) : 0);
796                units = Bundle.getMessage("percentNormalScript", Math.round(scriptSpeed));
797                break;
798            case SignalSpeedMap.PERCENT_THROTTLE:
799                units = Bundle.getMessage("percentThrottle");
800                speed = _engineer.getSpeedSetting() * 100;
801                break;
802            case SignalSpeedMap.SPEED_MPH:
803                units = Bundle.getMessage("mph");
804                speed = _speedUtil.getTrackSpeed(_engineer.getSpeedSetting()) * speedMap.getLayoutScale();
805                speed *= 2.2369363f;
806                break;
807            case SignalSpeedMap.SPEED_KMPH:
808                units = Bundle.getMessage("kph");
809                speed = _speedUtil.getTrackSpeed(_engineer.getSpeedSetting()) * speedMap.getLayoutScale();
810                speed *= 3.6f;
811                break;
812            default:
813                log.error("{} Unknown speed interpretation {}", getDisplayName(), speedMap.getInterpretation());
814                throw new java.lang.IllegalArgumentException("Unknown speed interpretation " + speedMap.getInterpretation());
815        }
816        return Bundle.getMessage("atSpeed", speedType, Math.round(speed), units);
817    }
818
819    private String makeWaitMessage(String blockName, int cmdIdx) {
820        String which = null;
821        String where = null;
822        if (_waitForSignal) {
823            which = Bundle.getMessage("Signal");
824            OBlock protectedBlock = getBlockAt(_idxProtectSignal);
825            if (protectedBlock != null) {
826                where = protectedBlock.getDisplayName();
827            }
828        } else if (_waitForWarrant) {
829            Warrant w = getBlockingWarrant();
830            which = Bundle.getMessage("WarrantWait",
831                w==null ? "Unknown" : w.getDisplayName());
832            if (_stoppingBlock != null) {
833                where = _stoppingBlock.getDisplayName();
834            }
835        } else if (_waitForBlock) {
836            which = Bundle.getMessage("Occupancy");
837            if (_stoppingBlock != null) {
838                where = _stoppingBlock.getDisplayName();
839            }
840        }
841        int runState = _engineer.getRunState();
842        if (which == null && (runState == HALT || runState == RAMP_HALT)) {
843            which = Bundle.getMessage("Halt");
844            where = blockName;
845        }
846        if (_engineer.isRamping() && runState != RAMP_HALT) {
847            String speedMsg = getSpeedMessage(_engineer.getSpeedType(true));
848            return Bundle.getMessage("changeSpeed", blockName, cmdIdx, speedMsg);
849        }
850
851        if (where == null) {
852            // flags can't identify cause.
853            if (_message == null) {
854                _message = Bundle.getMessage(RUN_STATE[runState], blockName);
855            }
856            return Bundle.getMessage("trainWaiting", getTrainName(), _message, blockName);
857        }
858        return Bundle.getMessage("WaitForClear", blockName, which, where);
859    }
860
861    @InvokeOnLayoutThread
862    private void startTracker() {
863        ThreadingUtil.runOnGUIEventually(() -> {
864            new Tracker(getCurrentBlockOrder().getBlock(), _trainName,
865                    null, InstanceManager.getDefault(TrackerTableAction.class));
866        });
867    }
868
869    // Get engineer thread to TERMINATED state - didn't answer CI test problem, but let it be.
870    private void killEngineer(Engineer engineer, boolean abort, boolean functionFlag) {
871        engineer.stopRun(abort, functionFlag); // releases throttle
872        engineer.interrupt();
873        if (!engineer.getState().equals(Thread.State.TERMINATED)) {
874            Thread curThread = Thread.currentThread();
875            if (!curThread.equals(_engineer)) {
876                kill( engineer, abort, functionFlag, curThread);
877            } else {   // can't join yourself if called by _engineer
878                class Killer implements Runnable {
879                    Engineer victim;
880                    boolean abortFlag;
881                    boolean functionFlag;
882                    Killer (Engineer v, boolean a, boolean f) {
883                        victim = v;
884                        abortFlag = a;
885                        functionFlag = f;
886                    }
887                    @Override
888                    public void run() {
889                        kill(victim, abortFlag, functionFlag, victim);
890                    }
891                }
892                final Runnable killer = new Killer(engineer, abort, functionFlag);
893                synchronized (killer) {
894                    Thread hit = ThreadingUtil.newThread(killer,
895                            getDisplayName()+" Killer");
896                    hit.start();
897                }
898            }
899        }
900    }
901
902    private void kill(Engineer eng, boolean a, boolean f, Thread monitor) {
903        long time = 0;
904        while (!eng.getState().equals(Thread.State.TERMINATED) && time < 100) {
905            try {
906                eng.stopRun(a, f); // releases throttle
907                monitor.join(10);
908            } catch (InterruptedException ex) {
909                log.info("victim.join() interrupted. warrant {}", getDisplayName());
910            }
911            time += 10;
912        }
913        _engineer = null;
914        log.debug("{}: engineer state {} after {}ms", getDisplayName(), eng.getState().toString(), time);
915    }
916
917    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
918    public void stopWarrant(boolean abort, boolean turnOffFunctions) {
919        _delayStart = false;
920        clearWaitFlags(true);
921        if (_student != null) {
922            _student.dispose(); // releases throttle
923            _student = null;
924        }
925        _curSignalAspect = null;
926        cancelDelayRamp();
927
928        if (_engineer != null) {
929            if (!_engineer.getState().equals(Thread.State.TERMINATED)) {
930                killEngineer(_engineer, abort, turnOffFunctions);
931            }
932            if (_trace || log.isDebugEnabled()) {
933                if (abort) {
934                    log.info("{} at block {}", Bundle.getMessage("warrantAbort", getTrainName(), getDisplayName()),
935                            getBlockAt(_idxCurrentOrder).getDisplayName());
936                } else {
937                    log.info(Bundle.getMessage("warrantComplete",
938                            getTrainName(), getDisplayName(), getBlockAt(_idxCurrentOrder).getDisplayName()));
939                }
940            }
941        } else {
942            _runMode = MODE_NONE;
943        }
944
945        if (_addTracker && _idxCurrentOrder == _orders.size()-1) { // run was complete to end
946            startTracker();
947        }
948        _addTracker = false;
949
950        // insulate possible non-GUI thread making this call (e.g. Engineer)
951        ThreadingUtil.runOnGUI(this::deAllocate);
952
953        String bundleKey;
954        String blockName;
955        if (abort) {
956            blockName = null;
957            if (_idxCurrentOrder <= 0) {
958                bundleKey = "warrantAnnull";
959            } else {
960                bundleKey = "warrantAbort";
961            }
962        } else {
963            blockName = getCurrentBlockName();
964            if (_idxCurrentOrder == _orders.size() - 1) {
965                bundleKey = "warrantComplete";
966            } else {
967                bundleKey = "warrantEnd";
968            }
969        }
970        fireRunStatus(PROPERTY_STOP_WARRANT, blockName, bundleKey);
971    }
972
973    /**
974     * Sets up recording and playing back throttle commands - also cleans up
975     * afterwards. MODE_LEARN and MODE_RUN sessions must end by calling again
976     * with MODE_NONE. It is important that the route be deAllocated (remove
977     * listeners).
978     * <p>
979     * Rule for (auto) MODE_RUN: 1. At least the Origin block must be owned
980     * (allocated) by this warrant. (block._warrant == this) and path set for
981     * Run Mode Rule for (auto) LEARN_RUN: 2. Entire Route must be allocated and
982     * Route Set for Learn Mode. i.e. this warrant has listeners on all block
983     * sensors in the route. Rule for MODE_MANUAL The Origin block must be
984     * allocated to this warrant and path set for the route
985     *
986     * @param mode run mode
987     * @param address DCC loco address
988     * @param student throttle frame for learn mode parameters
989     * @param commands list of throttle commands
990     * @param runBlind true if occupancy should be ignored
991     * @return error message, if any
992     */
993    public String setRunMode(int mode, DccLocoAddress address,
994            LearnThrottleFrame student,
995            List<ThrottleSetting> commands, boolean runBlind) {
996        if (log.isDebugEnabled()) {
997            log.debug("{}: setRunMode({}) ({}) called with _runMode= {}.",
998                  getDisplayName(), mode, MODES[mode], MODES[_runMode]);
999        }
1000        _message = null;
1001        if (_runMode != MODE_NONE) {
1002            _message = getRunModeMessage();
1003            log.error("{} called setRunMode when mode= {}. {}", getDisplayName(), MODES[_runMode],  _message);
1004            return _message;
1005        }
1006        _delayStart = false;
1007        _lost = false;
1008        _overrun = false;
1009        clearWaitFlags(true);
1010        if (address != null) {
1011            _speedUtil.setDccAddress(address);
1012        }
1013        _message = setPathAt(0);
1014        if (_message != null) {
1015            return _message;
1016        }
1017
1018        if (mode == MODE_LEARN) {
1019            // Cannot record if block 0 is not occupied or not dark. If dark, user is responsible for occupation
1020            if (student == null) {
1021                _message = Bundle.getMessage("noLearnThrottle", getDisplayName());
1022                log.error("{} called setRunMode for mode= {}. {}", getDisplayName(), MODES[mode],  _message);
1023                return _message;
1024            }
1025            synchronized (this) {
1026                _student = student;
1027            }
1028            // set mode before notifyThrottleFound is called
1029            _runMode = mode;
1030        } else if (mode == MODE_RUN) {
1031            if (commands != null && commands.size() > 1) {
1032                _commands = commands;
1033            }
1034            // set mode before setStoppingBlock and callback to notifyThrottleFound are called
1035            _idxCurrentOrder = 0;
1036            _runMode = mode;
1037            OBlock b = getBlockAt(0);
1038            if (b.isDark()) {
1039                _haltStart = true;
1040            } else if (!b.isOccupied()) {
1041                // continuing with no occupation of starting block
1042                _idxCurrentOrder = -1;
1043                setStoppingBlock(0);
1044                _delayStart = true;
1045            }
1046        } else if (mode == MODE_MANUAL) {
1047            if (commands != null) {
1048                _commands = commands;
1049            }
1050        } else {
1051            deAllocate();
1052            return _message;
1053        }
1054        getBlockAt(0)._entryTime = System.currentTimeMillis();
1055        _tempRunBlind = runBlind;
1056        if (!_delayStart) {
1057            if (mode != MODE_MANUAL) {
1058                _message = acquireThrottle();
1059            } else {
1060                startupWarrant(); // assuming manual operator will go to start block
1061            }
1062        }
1063        return _message;
1064    } // end setRunMode
1065
1066    /////////////// start warrant run - end of create/edit/setup methods //////////////////
1067
1068    /**
1069     * @return error message if any
1070     */
1071    @CheckForNull
1072    protected String acquireThrottle() {
1073        String msg = null;
1074        DccLocoAddress dccAddress = _speedUtil.getDccAddress();
1075        if (log.isDebugEnabled()) {
1076            log.debug("{}: acquireThrottle request at {}",
1077                    getDisplayName(), dccAddress);
1078        }
1079        if (dccAddress == null) {
1080            msg = Bundle.getMessage("NoAddress", getDisplayName());
1081        } else {
1082            if (tm == null) {
1083                msg = Bundle.getMessage("noThrottle", _speedUtil.getDccAddress().getNumber());
1084            } else {
1085                if (!tm.requestThrottle(dccAddress, this, false)) {
1086                    msg = Bundle.getMessage("trainInUse", dccAddress.getNumber());
1087                }
1088            }
1089        }
1090        if (msg != null) {
1091            fireRunStatus(PROPERTY_THROTTLE_FAIL, null, msg);
1092            abortWarrant(msg);
1093            return msg;
1094        }
1095        return null;
1096    }
1097
1098    @Override
1099    public void notifyThrottleFound(DccThrottle throttle) {
1100        if (throttle == null) {
1101            _message = Bundle.getMessage("noThrottle", getDisplayName());
1102            fireRunStatus(PROPERTY_THROTTLE_FAIL, null, _message);
1103            abortWarrant(_message);
1104            return;
1105        }
1106        if (log.isDebugEnabled()) {
1107            log.debug("{}: notifyThrottleFound for address= {}, class= {},",
1108                  getDisplayName(), throttle.getLocoAddress(), throttle.getClass().getName());
1109        }
1110        _speedUtil.setThrottle(throttle);
1111        startupWarrant();
1112        runWarrant(throttle);
1113    } //end notifyThrottleFound
1114
1115    @Override
1116    public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
1117        _message = Bundle.getMessage("noThrottle",
1118                (reason + " " + (address != null ? address.getNumber() : getDisplayName())));
1119        fireRunStatus(PROPERTY_THROTTLE_FAIL, null, reason);
1120        abortWarrant(_message);
1121    }
1122
1123    /**
1124     * No steal or share decisions made locally
1125     * <p>
1126     * {@inheritDoc}
1127     */
1128    @Override
1129    public void notifyDecisionRequired(LocoAddress address, DecisionType question) {
1130    }
1131
1132    protected void releaseThrottle(DccThrottle throttle) {
1133        if (throttle != null) {
1134            if (tm != null) {
1135                tm.releaseThrottle(throttle, this);
1136            } else {
1137                log.error("{} releaseThrottle. {} on thread {}",
1138                        getDisplayName(), Bundle.getMessage("noThrottle", throttle.getLocoAddress()),
1139                        Thread.currentThread().getName());
1140            }
1141            _runMode = MODE_NONE;
1142        }
1143    }
1144
1145    protected void abortWarrant(String msg) {
1146        log.error("Abort warrant \"{}\" - {} ", getDisplayName(), msg);
1147        stopWarrant(true, true);
1148    }
1149
1150    /**
1151     * Pause and resume auto-running train or abort any allocation state User
1152     * issued overriding commands during run time of warrant _engineer.abort()
1153     * calls setRunMode(MODE_NONE,...) which calls deallocate all.
1154     *
1155     * @param idx index of control command
1156     * @return false if command cannot be given
1157     */
1158    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
1159    public boolean controlRunTrain(int idx) {
1160        if (idx < 0) {
1161            return false;
1162        }
1163        boolean ret = false;
1164        if (_engineer == null) {
1165            if (log.isDebugEnabled()) {
1166                log.debug("{}: controlRunTrain({})= \"{}\" for train {} runMode= {}",
1167                      getDisplayName(), idx, CNTRL_CMDS[idx], getTrainName(), MODES[_runMode]);
1168            }
1169            switch (idx) {
1170                case HALT:
1171                case RESUME:
1172                case RETRY_FWD:
1173                case RETRY_BKWD:
1174                case SPEED_UP:
1175                    break;
1176                case STOP:
1177                case ABORT:
1178                    if (_runMode == Warrant.MODE_LEARN) {
1179                        // let WarrantFrame do the abort. (WarrantFrame listens for "abortLearn")
1180                        fireRunStatus(PROPERTY_ABORT_LEARN, -MODE_LEARN, _idxCurrentOrder);
1181                    } else {
1182                        stopWarrant(true, true);
1183                    }
1184                    break;
1185                case DEBUG:
1186                    debugInfo();
1187                    break;
1188                default:
1189            }
1190            return true;
1191        }
1192        int runState = _engineer.getRunState();
1193        if (_trace || log.isDebugEnabled()) {
1194            log.info(Bundle.getMessage("controlChange",
1195                    getTrainName(), Bundle.getMessage(Warrant.CNTRL_CMDS[idx]),
1196                    getCurrentBlockName()));
1197        }
1198        synchronized (this) {
1199            switch (idx) {
1200                case HALT:
1201                    rampSpeedTo(Warrant.Stop, -1);  // ramp down
1202                    _engineer.setHalt(true);
1203                    ret = true;
1204                    break;
1205                case RESUME:
1206                    BlockOrder bo = getBlockOrderAt(_idxCurrentOrder);
1207                    OBlock block = bo.getBlock();
1208                    String msg = null;
1209                    if (checkBlockForRunning(_idxCurrentOrder)) {
1210                        if (_waitForSignal || _waitForBlock || _waitForWarrant) {
1211                            msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder);
1212                        } else {
1213                            if (runState == WAIT_FOR_CLEAR) {
1214                                TrainOrder to = bo.allocatePaths(this, true);
1215                                if (to._cause == null) {
1216                                       _engineer.setWaitforClear(false);
1217                                } else {
1218                                    msg = to._message;
1219                                }
1220                            }
1221                            String train = (String)block.getValue();
1222                            if (train == null) {
1223                                train = Bundle.getMessage("unknownTrain");
1224                            }
1225                            if (block.isOccupied() && !_trainName.equals(train)) {
1226                                msg = Bundle.getMessage("blockInUse", train, block.getDisplayName());
1227                            }
1228                        }
1229                    }
1230                    if (msg != null) {
1231                        ret = askResumeQuestion(block, msg);
1232                        if (ret) {
1233                            ret = reStartTrain();
1234                        }
1235                    } else {
1236                        ret = reStartTrain();
1237                    }
1238                    if (!ret) {
1239//                        _engineer.setHalt(true);
1240                        if (_message.equals(Bundle.getMessage("blockUnoccupied", block.getDisplayName()))) {
1241                            ret = askResumeQuestion(block, _message);
1242                            if (ret) {
1243                                ret = reStartTrain();
1244                            }
1245                        }
1246                    }
1247                    break;
1248                case SPEED_UP:
1249                    // user wants to increase throttle of stalled train slowly
1250                    if (checkBlockForRunning(_idxCurrentOrder)) {
1251                        if ((_waitForSignal || _waitForBlock || _waitForWarrant) ||
1252                                (runState != RUNNING && runState != SPEED_RESTRICTED)) {
1253                            block = getBlockAt(_idxCurrentOrder);
1254                            msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder);
1255                            ret = askResumeQuestion(block, msg);
1256                            if (ret) {
1257                                ret = bumpSpeed();
1258                            }
1259                        } else {
1260                            ret = bumpSpeed();
1261                        }
1262                    }
1263                    break;
1264                case RETRY_FWD: // Force move into next block
1265                    if (checkBlockForRunning(_idxCurrentOrder + 1)) {
1266                        bo = getBlockOrderAt(_idxCurrentOrder + 1);
1267                        block = bo.getBlock();
1268                        if ((_waitForSignal || _waitForBlock || _waitForWarrant) ||
1269                                (runState != RUNNING && runState != SPEED_RESTRICTED)) {
1270                            msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder);
1271                            ret = askResumeQuestion(block, msg);
1272                            if (ret) {
1273                                ret = moveToBlock(bo, _idxCurrentOrder + 1);
1274                            }
1275                        } else {
1276                            ret = moveToBlock(bo, _idxCurrentOrder + 1);
1277                        }
1278                    }
1279                    break;
1280                case RETRY_BKWD: // Force move into previous block - Not enabled.
1281                    if (checkBlockForRunning(_idxCurrentOrder - 1)) {
1282                        bo = getBlockOrderAt(_idxCurrentOrder - 1);
1283                        block = bo.getBlock();
1284                        if ((_waitForSignal || _waitForBlock || _waitForWarrant) ||
1285                                (runState != RUNNING && runState != SPEED_RESTRICTED)) {
1286                            msg = makeWaitMessage(block.getDisplayName(), _idxCurrentOrder);
1287                            ret = askResumeQuestion(block, msg);
1288                            if (ret) {
1289                                ret = moveToBlock(bo, _idxCurrentOrder - 1);
1290                            }
1291                        } else {
1292                            ret = moveToBlock(bo, _idxCurrentOrder - 1);
1293                        }
1294                    }
1295                    break;
1296                case ABORT:
1297                    stopWarrant(true, true);
1298                    ret = true;
1299                    break;
1300//                case HALT:
1301                case STOP:
1302                    setSpeedToType(Stop); // sets _halt
1303                    _engineer.setHalt(true);
1304                    ret = true;
1305                    break;
1306                case ESTOP:
1307                    setSpeedToType(EStop); // E-stop & halt
1308                    _engineer.setHalt(true);
1309                    ret = true;
1310                    break;
1311                case DEBUG:
1312                    ret = debugInfo();
1313                    break;
1314                default:
1315            }
1316        }
1317        if (ret) {
1318            fireRunStatus(PROPERTY_CONTROL_CHANGE, runState, idx);
1319        } else {
1320            if (_trace || log.isDebugEnabled()) {
1321                log.info(Bundle.getMessage("controlFailed",
1322                        getTrainName(), _message,
1323                        Bundle.getMessage(Warrant.CNTRL_CMDS[idx])));
1324            }
1325            fireRunStatus(PROPERTY_CONTROL_FAILED, _message, idx);
1326        }
1327        return ret;
1328    }
1329
1330    private boolean askResumeQuestion(OBlock block, String reason) {
1331        String msg = Bundle.getMessage("ResumeQuestion", reason);
1332        return ThreadingUtil.runOnGUIwithReturn(() -> {
1333            int result = JmriJOptionPane.showConfirmDialog(WarrantTableFrame.getDefault(),
1334                msg, Bundle.getMessage("ResumeTitle"),
1335                JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE);
1336            return result==JmriJOptionPane.YES_OPTION;
1337        });
1338    }
1339
1340    // User insists to run train
1341    private boolean reStartTrain() {
1342        BlockOrder bo = getBlockOrderAt(_idxCurrentOrder);
1343        OBlock block = bo.getBlock();
1344        if (!block.isOccupied() && !block.isDark()) {
1345            _message = Bundle.getMessage("blockUnoccupied", block.getDisplayName());
1346            return false;
1347        }
1348        // OK, will do it as it long as you own it, and you are where you think you are there.
1349        block.setValue(_trainName); // indicate position
1350        block.setState(block.getState());
1351        _engineer.setHalt(false);
1352        clearWaitFlags(false);
1353        _overrun = true;    // allows doRestoreRunning to run at an OCCUPY state
1354        return restoreRunning(_engineer.getSpeedType(false));
1355    }
1356
1357    // returns true if block is owned and occupied by this warrant
1358    private boolean checkBlockForRunning(int idxBlockOrder) {
1359        BlockOrder bo = getBlockOrderAt(idxBlockOrder);
1360        if (bo == null) {
1361            _message = Bundle.getMessage("BlockNotInRoute", "?");
1362            return false;
1363        }
1364        OBlock block = bo.getBlock();
1365        if (!block.isOccupied()) {
1366            _message = Bundle.getMessage("blockUnoccupied", block.getDisplayName());
1367            return false;
1368        }
1369        return true;
1370    }
1371
1372    // User increases speed
1373    private boolean bumpSpeed() {
1374        // OK, will do as it long as you own it, and you are where you think you are.
1375        _engineer.setHalt(false);
1376        clearWaitFlags(false);
1377        float speedSetting = _engineer.getSpeedSetting();
1378        if (speedSetting < 0) { // may have done E-Stop
1379            speedSetting = 0.0f;
1380        }
1381        float bumpSpeed = Math.max(WarrantPreferences.getDefault().getSpeedAssistance(), _speedUtil.getRampThrottleIncrement());
1382        _engineer.setSpeed(speedSetting + bumpSpeed);
1383        return true;
1384    }
1385
1386    private boolean moveToBlock(BlockOrder bo, int idx) {
1387        _idxCurrentOrder = idx;
1388        _message = setPathAt(idx);    // no checks. Force path set and allocation
1389        if (_message != null) {
1390            return false;
1391        }
1392        OBlock block = bo.getBlock();
1393        if (block.equals(_stoppingBlock)) {
1394            clearStoppingBlock();
1395            _engineer.setHalt(false);
1396        }
1397        goingActive(block);
1398        return true;
1399    }
1400
1401    protected boolean debugInfo() {
1402        if ( !log.isInfoEnabled() ) {
1403            return true;
1404        }
1405        StringBuilder info = new StringBuilder("\""); info.append(getDisplayName());
1406        info.append("\" Train \""); info.append(getTrainName()); info.append("\" - Current Block \"");
1407        info.append(getBlockAt(_idxCurrentOrder).getDisplayName());
1408        info.append("\" BlockOrder idx= "); info.append(_idxCurrentOrder);
1409        info.append("\n\tWait flags: _waitForSignal= "); info.append(_waitForSignal);
1410        info.append(", _waitForBlock= "); info.append(_waitForBlock);
1411        info.append(", _waitForWarrant= "); info.append(_waitForWarrant);
1412        info.append("\n\tStatus flags: _overrun= "); info.append(_overrun); info.append(", _rampBlkOccupied= ");
1413        info.append(_rampBlkOccupied);info.append(", _lost= "); info.append(_lost);
1414        if (_protectSignal != null) {
1415            info.append("\n\tWait for Signal \"");info.append(_protectSignal.getDisplayName());info.append("\" protects block ");
1416            info.append(getBlockAt(_idxProtectSignal).getDisplayName()); info.append("\" from approch block \"");
1417            info.append(getBlockAt(_idxProtectSignal - 1).getDisplayName()); info.append("\". Shows aspect \"");
1418            info.append(getSignalSpeedType(_protectSignal)); info.append("\".");
1419        } else {
1420            info.append("\n\tNo signals ahead with speed restrictions");
1421        }
1422        if(_stoppingBlock != null) {
1423            if (_waitForWarrant) {
1424                info.append("\n\tWait for Warrant \"");
1425                Warrant w = getBlockingWarrant(); info.append((w != null?w.getDisplayName():"Unknown"));
1426                info.append("\" owns block \"");info.append(_stoppingBlock.getDisplayName()); info.append("\"");
1427            } else {
1428                Object what = _stoppingBlock.getValue();
1429                String who;
1430                if (what != null) {
1431                    who = what.toString();
1432                } else {
1433                    who = "Unknown Train";
1434                }
1435                info.append("\n\tWait for \""); info.append(who); info.append("\" occupying Block \"");
1436                info.append(_stoppingBlock.getDisplayName()); info.append("\"");
1437            }
1438        } else {
1439            info.append("\n\tNo occupied blocks ahead");
1440        }
1441        if (_message != null) {
1442            info.append("\n\tLast message = ");info.append(_message);
1443        } else {
1444            info.append("\n\tNo messages.");
1445        }
1446
1447        if (_engineer != null) {
1448            info.append("\""); info.append("\n\tEngineer Stack trace:");
1449            for (StackTraceElement elem : _engineer.getStackTrace()) {
1450                info.append("\n\t\t");
1451                info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName());
1452                info.append(", line "); info.append(elem.getLineNumber());
1453            }
1454            info.append(_engineer.debugInfo());
1455         } else {
1456            info.append("No engineer.");
1457        }
1458        log.info("\n Warrant: {}", info.toString());
1459        return true;
1460    }
1461
1462    protected void startupWarrant() {
1463        _idxCurrentOrder = 0;
1464        // set block state to show our train occupies the block
1465        BlockOrder bo = getBlockOrderAt(0);
1466        OBlock b = bo.getBlock();
1467        b.setValue(_trainName);
1468        b.setState(b.getState() | OBlock.RUNNING);
1469        firePropertyChange(PROPERTY_WARRANT_START, MODE_NONE, _runMode);
1470    }
1471
1472    private void runWarrant(DccThrottle throttle) {
1473        if (_runMode == MODE_LEARN) {
1474            synchronized (this) {
1475                // No Engineer. LearnControlPanel does throttle settings
1476                _student.notifyThrottleFound(throttle);
1477            }
1478        } else {
1479            if (_engineer != null) {    // should not happen
1480                killEngineer(_engineer, true, true);
1481            }
1482            _engineer = new Engineer(this, throttle);
1483
1484            _speedUtil.getBlockSpeedTimes(_commands, _orders);   // initialize SpeedUtil
1485            if (_tempRunBlind) {
1486                _engineer.setRunOnET(true);
1487            }
1488            if (_delayStart || _haltStart) {
1489                _engineer.setHalt(true);    // throttle already at 0
1490                // user must explicitly start train (resume) in a dark block
1491                fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0);   // ready to start msg
1492            }
1493            _delayStart = false;
1494            _engineer.start();
1495
1496            int runState = _engineer.getRunState();
1497            if (_trace || log.isDebugEnabled()) {
1498                log.info("Train \"{}\" on warrant \"{}\" launched. runState= {}", getTrainName(), getDisplayName(), RUN_STATE[runState]);
1499            }
1500            if (runState != HALT && runState != RAMP_HALT) {
1501                setMovement();
1502            }
1503        }
1504    }
1505
1506    private String setPathAt(int idx) {
1507        BlockOrder bo = _orders.get(idx);
1508        OBlock b = bo.getBlock();
1509        String msg = b.allocate(this);
1510        if (msg == null) {
1511            OPath path1 = bo.getPath();
1512            Portal exit = bo.getExitPortal();
1513            OBlock block = getBlockAt(idx+1);
1514            if (block != null) {
1515                Warrant w = block.getWarrant();
1516                if ((w != null && !w.equals(this)) || (w == null && block.isOccupied())) {
1517                    msg =  bo.pathsConnect(path1, exit, block);
1518                    if (msg == null) {
1519                        msg = bo.setPath(this);
1520                    }
1521                }
1522            }
1523            b.showAllocated(this, bo.getPathName());
1524        }
1525        return msg;
1526    }
1527
1528    /**
1529     * Allocate as many blocks as possible from the start of the warrant.
1530     * The first block must be allocated and all blocks of the route must
1531     * be in service. Otherwise partial success is OK.
1532     * Installs listeners for the entire route.
1533     * If occupation by another train is detected, a message will be
1534     * posted to the Warrant List Window. Note that warrants sharing their
1535     * clearance only allocate and set paths one block in advance.
1536     *
1537     * @param orders list of block orders
1538     * @param show _message for use ONLY to display a temporary route) continues to
1539     *  allocate skipping over blocks occupied or owned by another warrant.
1540     * @return error message, if unable to allocate first block or if any block
1541     *         is OUT_OF_SERVICE
1542     */
1543    public String allocateRoute(boolean show, List<BlockOrder> orders) {
1544        if (_totalAllocated && _runMode != MODE_NONE && _runMode != MODE_ABORT) {
1545            return null;
1546        }
1547        if (orders != null) {
1548            _orders = orders;
1549        }
1550        _allocated = false;
1551        _message = null;
1552
1553        int idxSpeedChange = 0;  // idxBlockOrder where speed changes
1554        do {
1555            TrainOrder to = getBlockOrderAt(idxSpeedChange).allocatePaths(this, true);
1556            switch (to._cause) {
1557                case NONE:
1558                    break;
1559               case WARRANT:
1560                   _waitForWarrant = true;
1561                   if (_message == null) {
1562                       _message = to._message;
1563                   }
1564                   if (!show && to._idxContrlBlock == 0) {
1565                       return _message;
1566                   }
1567                   break;
1568                case OCCUPY:
1569                    _waitForBlock = true;
1570                    if (_message == null) {
1571                        _message = to._message;
1572                    }
1573                    break;
1574                case SIGNAL:
1575                    if (Stop.equals(to._speedType)) {
1576                        _waitForSignal = true;
1577                        if (_message == null) {
1578                            _message = to._message;
1579                        }
1580                    }
1581                    break;
1582                default:
1583                    log.error("{}: allocateRoute at block \"{}\" setPath returns: {}",
1584                            getDisplayName(), getBlockAt(idxSpeedChange).getDisplayName(), to.toString());
1585                    if (_message == null) {
1586                        _message = to._message;
1587                    }
1588            }
1589            if (!show) {
1590                if (_message != null || (_shareRoute && idxSpeedChange > 1)) {
1591                    break;
1592                }
1593            }
1594            idxSpeedChange++;
1595        } while (idxSpeedChange < _orders.size());
1596
1597        if (log.isDebugEnabled()) {
1598            log.debug("{}: allocateRoute() _shareRoute= {} show= {}. Break at {} of {}. msg= {}",
1599                getDisplayName(), _shareRoute, show, idxSpeedChange, _orders.size(), _message);
1600        }
1601        _allocated = true; // start block allocated
1602        if (_message == null) {
1603            _totalAllocated = true;
1604            if (show && _shareRoute) {
1605                _message = Bundle.getMessage("sharedRoute");
1606            }
1607        }
1608        if (show) {
1609            return _message;
1610        }
1611        return null;
1612    }
1613
1614    /**
1615     * Deallocates blocks from the current BlockOrder list
1616     */
1617    public void deAllocate() {
1618        if (_runMode == MODE_NONE || _runMode == MODE_ABORT) {
1619            _allocated = false;
1620            _totalAllocated = false;
1621            _routeSet = false;
1622            for (int i = 0; i < _orders.size(); i++) {
1623                deAllocateBlock(_orders.get(i).getBlock());
1624            }
1625        }
1626    }
1627
1628    private boolean deAllocateBlock(OBlock block) {
1629        if (block.isAllocatedTo(this)) {
1630            block.deAllocate(this);
1631            if (block.equals(_stoppingBlock)){
1632                doStoppingBlockClear();
1633            }
1634            return true;
1635        }
1636        return false;
1637    }
1638
1639    /**
1640     * Convenience routine to use from Python to start a warrant.
1641     *
1642     * @param mode run mode
1643     */
1644    public void runWarrant(int mode) {
1645        setRunMode(mode, null, null, null, false);
1646    }
1647
1648    /**
1649     * Set the route paths and turnouts for the warrant. Only the first block
1650     * must be allocated and have its path set. Partial success is OK.
1651     * A message of the first subsequent block that fails allocation
1652     * or path setting is written to a field that is
1653     * displayed in the Warrant List window. When running with block
1654     * detection, occupation by another train or block 'not in use' or
1655     * Signals denying movement are reasons
1656     * for such a message, otherwise only allocation to another warrant
1657     * prevents total success. Note that warrants sharing their clearance
1658     * only allocate and set paths one block in advance.
1659     *
1660     * @param show If true allocateRoute returns messages for display.
1661     * @param orders  BlockOrder list of route. If null, use permanent warrant
1662     *            copy.
1663     * @return message if the first block fails allocation, otherwise null
1664     */
1665    public String setRoute(boolean show, List<BlockOrder> orders) {
1666        if (_shareRoute) { // full route of a shared warrant may be displayed
1667            deAllocate();   // clear route to allow sharing with another warrant
1668        }
1669
1670        // allocateRoute may set _message for status info, but return null msg
1671        _message = allocateRoute(show, orders);
1672        if (_message != null) {
1673            log.debug("{}: setRoute: {}", getDisplayName(), _message);
1674            return _message;
1675        }
1676        _routeSet = true;
1677        return null;
1678    } // setRoute
1679
1680    /**
1681     * Check start block for occupied for start of run
1682     *
1683     * @return error message, if any
1684     */
1685    public String checkStartBlock() {
1686        log.debug("{}: checkStartBlock.", getDisplayName());
1687        BlockOrder bo = _orders.get(0);
1688        OBlock block = bo.getBlock();
1689        String msg = block.allocate(this);
1690        if (msg != null) {
1691            return msg;
1692        }
1693        if (block.isDark() || _tempRunBlind) {
1694            msg = "BlockDark";
1695        } else if (!block.isOccupied()) {
1696            msg = "warnStart";
1697        }
1698        return msg;
1699    }
1700
1701    protected String checkforTrackers() {
1702        BlockOrder bo = _orders.get(0);
1703        OBlock block = bo.getBlock();
1704        log.debug("{}: checkforTrackers at block {}", getDisplayName(), block.getDisplayName());
1705        Tracker t = InstanceManager.getDefault(TrackerTableAction.class).findTrackerIn(block);
1706        if (t != null) {
1707            return Bundle.getMessage("blockInUse", t.getTrainName(), block.getDisplayName());
1708        }
1709        return null;
1710    }
1711
1712    /**
1713     * Report any occupied blocks in the route
1714     *
1715     * @return String
1716     */
1717    public String checkRoute() {
1718        log.debug("{}: checkRoute.", getDisplayName());
1719        if (_orders==null || _orders.isEmpty()) {
1720            return Bundle.getMessage("noBlockOrders");
1721        }
1722        OBlock startBlock = _orders.get(0).getBlock();
1723        for (int i = 1; i < _orders.size(); i++) {
1724            OBlock block = _orders.get(i).getBlock();
1725            if (block.isOccupied() && !startBlock.equals(block)) {
1726                return Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
1727            }
1728            Warrant w = block.getWarrant();
1729            if (w !=null && !this.equals(w)) {
1730                return Bundle.getMessage("AllocatedToWarrant",
1731                        w.getDisplayName(), block.getDisplayName(), w.getTrainName());
1732            }
1733        }
1734        return null;
1735    }
1736
1737    @Override
1738    public void propertyChange(java.beans.PropertyChangeEvent evt) {
1739        if (!(evt.getSource() instanceof NamedBean)) {
1740            return;
1741        }
1742        String property = evt.getPropertyName();
1743        if (log.isDebugEnabled()) {
1744            log.debug("{}: propertyChange \"{}\" new= {} source= {}", getDisplayName(),
1745                    property, evt.getNewValue(), ((NamedBean) evt.getSource()).getDisplayName());
1746        }
1747
1748        if (_protectSignal != null && _protectSignal == evt.getSource()) {
1749            if (property.equals("Aspect") || property.equals("Appearance")) {
1750                // signal controlling warrant has changed.
1751                readStoppingSignal();
1752            }
1753        } else if (property.equals("state")) {
1754            if (_stoppingBlock != null && _stoppingBlock.equals(evt.getSource())) {
1755                // starting block is allocated but not occupied
1756                int newState = ((Number) evt.getNewValue()).intValue();
1757                if ((newState & OBlock.OCCUPIED) != 0) {
1758                    if (_delayStart) { // wait for arrival of train to begin the run
1759                        // train arrived at starting block or last known block of lost train is found
1760                        clearStoppingBlock();
1761                        OBlock block = getBlockAt(0);
1762                        _idxCurrentOrder = 0;
1763                        if (_runMode == MODE_RUN && _engineer == null) {
1764                            _message = acquireThrottle();
1765                        } else if (_runMode == MODE_MANUAL) {
1766                            fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0);   // ready to start msg
1767                            _delayStart = false;
1768                        }
1769                        block._entryTime = System.currentTimeMillis();
1770                        block.setValue(_trainName);
1771                        block.setState(block.getState() | OBlock.RUNNING);
1772                    } else if ((((Number) evt.getNewValue()).intValue() & OBlock.ALLOCATED) == 0) {
1773                        // blocking warrant has released allocation but train still occupies the block
1774                        clearStoppingBlock();
1775                        log.debug("\"{}\" cleared its wait. but block \"{}\" remains occupied", getDisplayName(),
1776                                (((Block)evt.getSource()).getDisplayName()));
1777                    }
1778                } else if ((((Number) evt.getNewValue()).intValue() & OBlock.UNOCCUPIED) != 0) {
1779                    //  blocking occupation has left the stopping block
1780                    clearStoppingBlock();
1781                }
1782            }
1783        }
1784    } //end propertyChange
1785
1786    private String getSignalSpeedType(@Nonnull NamedBean signal) {
1787        String speedType;
1788        if (signal instanceof SignalHead) {
1789            SignalHead head = (SignalHead) signal;
1790            int appearance = head.getAppearance();
1791            speedType = InstanceManager.getDefault(SignalSpeedMap.class)
1792                    .getAppearanceSpeed(head.getAppearanceName(appearance));
1793            if (log.isDebugEnabled()) {
1794                log.debug("{}: SignalHead {} sets appearance speed to {}",
1795                      getDisplayName(), signal.getDisplayName(), speedType);
1796            }
1797        } else {
1798            SignalMast mast = (SignalMast) signal;
1799            String aspect = mast.getAspect();
1800            speedType = InstanceManager.getDefault(SignalSpeedMap.class).getAspectSpeed(
1801                    (aspect== null ? "" : aspect), mast.getSignalSystem());
1802            if (log.isDebugEnabled()) {
1803                log.debug("{}: SignalMast {} sets aspect speed to {}",
1804                      getDisplayName(), signal.getDisplayName(), speedType);
1805            }
1806        }
1807        return speedType;
1808    }
1809
1810    /**
1811     * _protectSignal made an aspect change
1812     */
1813    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
1814    private void readStoppingSignal() {
1815        if (_idxProtectSignal < _idxCurrentOrder) { // signal is behind train. ignore
1816            changeSignalListener(null, _idxCurrentOrder);  // remove signal
1817            return;
1818        }
1819        // Signals may change after entry and while the train in the block.
1820        // Normally these changes are ignored.
1821        // However for the case of an overrun stop aspect, the train is waiting.
1822        if (_idxProtectSignal == _idxCurrentOrder && !_waitForSignal) { // not waiting
1823            changeSignalListener(null, _idxCurrentOrder);  // remove signal
1824            return; // normal case
1825        }// else Train previously overran stop aspect. Continue and respond to signal.
1826
1827        String speedType = getSignalSpeedType(_protectSignal);
1828        String curSpeedType;
1829        if (_waitForSignal) {
1830            curSpeedType = Stop;
1831        } else {
1832            curSpeedType = _engineer.getSpeedType(true);    // current or pending ramp completion
1833        }
1834        if (log.isDebugEnabled()) {
1835            log.debug("{}: Signal \"{}\" changed to aspect \"{}\" {} blocks ahead. curSpeedType= {}",
1836                    getDisplayName(), _protectSignal.getDisplayName(), speedType, _idxProtectSignal-_idxCurrentOrder, curSpeedType);
1837        }
1838
1839        if (curSpeedType.equals(speedType)) {
1840            return;
1841        }
1842        if (_idxProtectSignal > _idxCurrentOrder) {
1843            if (_speedUtil.secondGreaterThanFirst(speedType, curSpeedType)) {
1844                // change to slower speed. Check if speed change should occur now
1845                float availDist = getAvailableDistance(_idxProtectSignal);
1846                float changeDist = getChangeSpeedDistance(_idxProtectSignal, speedType);
1847                if (changeDist > availDist) {
1848                    // Not enough room in blocks ahead. start ramp in current block
1849                    availDist += getAvailableDistanceAt(_idxCurrentOrder);
1850                    if (speedType.equals(Warrant.Stop)) {
1851                        _waitForSignal = true;
1852                    }
1853                    int cmdStartIdx = _engineer.getCurrentCommandIndex(); // blkSpeedInfo.getFirstIndex();
1854                    if (!doDelayRamp(availDist, changeDist, _idxProtectSignal, speedType, cmdStartIdx)) {
1855                        log.info("No room for train {} to ramp to \"{}\" from \"{}\" for signal \"{}\"!. availDist={}, changeDist={} on warrant {}",
1856                                getTrainName(), speedType, curSpeedType, _protectSignal.getDisplayName(),
1857                                availDist,  changeDist, getDisplayName());
1858                    }   // otherwise will do ramp when entering a block ahead
1859                }
1860                return;
1861            }
1862        }
1863        if (!speedType.equals(Warrant.Stop)) {  // a moving aspect clears a signal wait
1864            if (_waitForSignal) {
1865                // signal protecting next block just released its hold
1866                _curSignalAspect = speedType;
1867                _waitForSignal = false;
1868                if (_trace || log.isDebugEnabled()) {
1869                    log.info(Bundle.getMessage("SignalCleared", _protectSignal.getDisplayName(), speedType, _trainName));
1870                }
1871                ThreadingUtil.runOnGUIDelayed(() -> {
1872                    restoreRunning(speedType);
1873                }, 2000);
1874            }
1875        }
1876    }
1877
1878
1879    /*
1880     * return distance from the exit of the current block "_idxCurrentOrder"
1881     * to the entrance of the "idxChange" block.
1882     */
1883    private float getAvailableDistance(int idxChange) {
1884        float availDist = 0;
1885        int idxBlockOrder = _idxCurrentOrder + 1;
1886        if (idxBlockOrder < _orders.size() - 1) {
1887            while (idxBlockOrder < idxChange) {
1888                availDist += getAvailableDistanceAt(idxBlockOrder++);   // distance to next block
1889            }
1890        }
1891        return availDist;
1892    }
1893
1894    /*
1895     * Get distance needed to ramp so the speed into the next block satisfies the speedType
1896     * @param idxBlockOrder blockOrder index of entrance block
1897     */
1898    private float getChangeSpeedDistance(int idxBlockOrder, String speedType) {
1899        float speedSetting = _engineer.getSpeedSetting();       // current speed
1900        // Estimate speed at start of ramp
1901        float enterSpeed;   // speed at start of ramp
1902        if (speedSetting > 0.1f && (_idxCurrentOrder == idxBlockOrder - 1)) {
1903            // if in the block immediately before the entrance block, use current speed
1904            enterSpeed = speedSetting;
1905        } else { // else use entrance speed of previous block
1906            String currentSpeedType = _engineer.getSpeedType(false); // current speed type
1907            float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder - 1).getEntranceSpeed();
1908            enterSpeed = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType);
1909        }
1910        float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getEntranceSpeed();
1911        float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType);
1912        // compare distance needed for script throttle at entrance to entrance speed,
1913        // to the distance needed for current throttle to entrance speed.
1914        float enterLen = _speedUtil.getRampLengthForEntry(enterSpeed, endSpeed);
1915        // add buffers for signal and safety clearance
1916        float bufDist = getEntranceBufferDist(idxBlockOrder);
1917//        log.debug("{}: getChangeSpeedDistance curSpeed= {} enterSpeed= {} endSpeed= {}", getDisplayName(), speedSetting, enterSpeed, endSpeed);
1918        return enterLen + bufDist;
1919    }
1920
1921    private void doStoppingBlockClear() {
1922        if (_stoppingBlock == null) {
1923            return;
1924        }
1925        _stoppingBlock.removePropertyChangeListener(this);
1926        _stoppingBlock = null;
1927        _idxStoppingBlock = -1;
1928    }
1929
1930    /**
1931     * Called when a rogue or warranted train has left a block.
1932     * Also called from propertyChange() to allow warrant to acquire a throttle
1933     * and launch an engineer. Also called by retry control command to help user
1934     * work out of an error condition.
1935     */
1936    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
1937    synchronized private void clearStoppingBlock() {
1938        if (_stoppingBlock == null) {
1939            return;
1940        }
1941        String name = _stoppingBlock.getDisplayName();
1942        doStoppingBlockClear();
1943
1944        if (_delayStart) {
1945            return;    // don't start. Let user resume start
1946        }
1947        if (_trace || log.isDebugEnabled()) {
1948            String reason;
1949            if (_waitForBlock) {
1950                reason = Bundle.getMessage("Occupancy");
1951            } else {
1952                reason = Bundle.getMessage("Warrant");
1953            }
1954            log.info(Bundle.getMessage("StopBlockCleared",
1955                    getTrainName(), getDisplayName(), reason, name));
1956        }
1957        cancelDelayRamp();
1958        int time = 1000;
1959        if (_waitForBlock) {
1960            _waitForBlock = false;
1961            time = 4000;
1962        }
1963        if (_waitForWarrant) {
1964            _waitForWarrant = false;
1965            time = 3000;
1966        }
1967        String speedType;
1968        if (_curSignalAspect != null) {
1969            speedType = _curSignalAspect;
1970        } else {
1971            speedType = _engineer.getSpeedType(false); // current speed type
1972        }
1973        ThreadingUtil.runOnGUIDelayed(() -> {
1974            restoreRunning(speedType);
1975        }, time);
1976    }
1977
1978    private String okToRun() {
1979        boolean cannot = false;
1980        StringBuilder sb = new StringBuilder();
1981        if (_waitForSignal) {
1982            sb.append(Bundle.getMessage("Signal"));
1983            cannot = true;
1984        }
1985        if (_waitForWarrant) {
1986            if (cannot) {
1987                sb.append(", ");
1988            } else {
1989                cannot = true;
1990            }
1991            Warrant w = getBlockingWarrant();
1992           if (w != null) {
1993                sb.append(Bundle.getMessage("WarrantWait",  w.getDisplayName()));
1994            } else {
1995                sb.append(Bundle.getMessage("WarrantWait", "Unknown"));
1996            }
1997        }
1998        if (_waitForBlock) {
1999            if (cannot) {
2000                sb.append(", ");
2001            } else {
2002                cannot = true;
2003            }
2004            sb.append(Bundle.getMessage("Occupancy"));
2005        }
2006
2007        if (_engineer != null) {
2008            int runState = _engineer.getRunState();
2009            if (runState == HALT || runState == RAMP_HALT) {
2010                if (cannot) {
2011                    sb.append(", ");
2012                } else {
2013                    cannot = true;
2014                }
2015                sb.append(Bundle.getMessage("userHalt"));
2016            }
2017        }
2018        if (cannot) {
2019            return sb.toString();
2020        }
2021        return null;
2022    }
2023
2024    /**
2025     * A layout condition that has restricted or stopped a train has been cleared.
2026     * i.e. Signal aspect, rogue occupied block, contesting warrant or user halt.
2027     * This may or may not be all the conditions restricting speed.
2028     * @return true if automatic restart is done
2029     */
2030    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2031    private boolean restoreRunning(String speedType) {
2032        _message = okToRun();
2033        boolean returnOK;
2034        if (_message == null) {
2035            BlockOrder bo = getBlockOrderAt(_idxCurrentOrder);
2036            TrainOrder to = bo.allocatePaths(this, true);
2037            OBlock block = bo.getBlock();
2038            if (log.isDebugEnabled()) {
2039                log.debug("{}: restoreRunning {}", getDisplayName(), to.toString());
2040            }
2041            switch (to._cause) {    // to._cause - precedence of checks is WARRANT, OCCUPY, SIGNAL
2042                case NONE:
2043                    returnOK = doRestoreRunning(block, speedType);
2044                    break;
2045                case WARRANT:
2046                   _waitForWarrant = true;
2047                   _message = to._message;
2048                   setStoppingBlock(to._idxContrlBlock);
2049                   returnOK = false;
2050                   break;
2051                case OCCUPY:
2052                    if (_overrun || _lost) {
2053                        _message = setPathAt(_idxCurrentOrder);
2054                        if (_message == null) {
2055                            returnOK = doRestoreRunning(block, speedType);
2056                        } else {
2057                            returnOK = false;
2058                        }
2059                        if (_lost && returnOK) {
2060                            _lost = false;
2061                        }
2062                        break;
2063                    }
2064                    returnOK = false;
2065                    _waitForBlock = true;
2066                    _message = to._message;
2067                    setStoppingBlock(to._idxContrlBlock);
2068                    break;
2069                case SIGNAL:
2070                    if (to._idxContrlBlock == _idxCurrentOrder) {
2071                        returnOK = doRestoreRunning(block, speedType);
2072                    } else {
2073                        returnOK = false;
2074                    }
2075                    if (returnOK && Stop.equals(to._speedType)) {
2076                        _waitForSignal = true;
2077                        _message = to._message;
2078                        setProtectingSignal(to._idxContrlBlock);
2079                        returnOK = false;
2080                        break;
2081                    }
2082                    speedType = to._speedType;
2083                    returnOK = doRestoreRunning(block, speedType);
2084                    break;
2085                default:
2086                    log.error("restoreRunning TrainOrder {}", to.toString());
2087                    _message = to._message;
2088                    returnOK = false;
2089            }
2090        } else {
2091            returnOK = false;
2092        }
2093        if (!returnOK) {
2094            String blockName = getBlockAt(_idxCurrentOrder).getDisplayName();
2095            if (_trace || log.isDebugEnabled()) {
2096                log.info(Bundle.getMessage("trainWaiting", getTrainName(), _message, blockName));
2097            }
2098            fireRunStatus(PROPERTY_CANNOT_RUN, blockName, _message);
2099        }
2100        return returnOK;
2101    }
2102
2103    private boolean doRestoreRunning(OBlock block, String speedType) {
2104        _overrun = false;
2105        _curSignalAspect = null;
2106        setPathAt(_idxCurrentOrder);    // show ownership and train Id
2107
2108        // It is highly likely an event to restart a speed increase occurs when the train
2109        // position is in the middle or end of the block. Since 'lookAheadforSpeedChange'
2110        // assumes the train is at the start of a block, don't ramp up if the
2111        // train may not enter the next block. No room for both ramp up and ramp down
2112        BlockOrder bo = getBlockOrderAt(_idxCurrentOrder+1);
2113        if (bo != null) {
2114            TrainOrder to = bo.allocatePaths(this, true);
2115            if (Warrant.Stop.equals(to._speedType)) {
2116                _message = to._message;
2117                switch (to._cause) {
2118                    case NONE:
2119                        break;
2120                   case WARRANT:
2121                       _waitForWarrant = true;
2122                       setStoppingBlock(to._idxContrlBlock);
2123                       break;
2124                    case OCCUPY:
2125                        _waitForBlock = true;
2126                        setStoppingBlock(to._idxContrlBlock);
2127                        break;
2128                    case SIGNAL:
2129                        _waitForSignal = true;
2130                        setProtectingSignal(to._idxContrlBlock);
2131                        break;
2132                    default:
2133                }
2134                return false;
2135            }
2136        }
2137        _engineer.clearWaitForSync(block);
2138        if (log.isDebugEnabled()) {
2139            log.debug("{}: restoreRunning(): rampSpeedTo to \"{}\"",
2140                    getDisplayName(), speedType);
2141        }
2142        rampSpeedTo(speedType, -1);
2143        // continue, there may be blocks ahead that need a speed decrease before entering them
2144        if (!_overrun && _idxCurrentOrder < _orders.size() - 1) {
2145            lookAheadforSpeedChange(speedType, speedType);
2146        } // else at last block, forget about speed changes
2147        return true;
2148    }
2149
2150    /**
2151     * Stopping block only used in MODE_RUN _stoppingBlock is an occupied OBlock
2152     * preventing the train from continuing the route OR another warrant
2153     * is preventing this warrant from allocating the block to continue.
2154     * <p>
2155     */
2156    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2157    private void setStoppingBlock(int idxBlock) {
2158        OBlock block = getBlockAt(idxBlock);
2159        if (block == null) {
2160            return;
2161        }
2162        // _idxCurrentOrder == 0 may be a delayed start waiting for loco.
2163        // Otherwise don't set _stoppingBlock for a block occupied by train
2164        if (idxBlock < 0 || (_idxCurrentOrder == idxBlock && !_lost)) {
2165            return;
2166        }
2167        OBlock prevBlk = _stoppingBlock;
2168        if (_stoppingBlock != null) {
2169            if (_stoppingBlock.equals(block)) {
2170                return;
2171            }
2172
2173            int idxStop = getIndexOfBlockAfter(_stoppingBlock, _idxCurrentOrder);
2174            if ((idxBlock < idxStop) || idxStop < 0) {
2175                prevBlk.removePropertyChangeListener(this);
2176            } else {
2177                if (idxStop < _idxCurrentOrder) {
2178                    log.error("{}: _stoppingBlock \"{}\" index {} < _idxCurrentOrder {}",
2179                            getDisplayName(), _stoppingBlock.getDisplayName(), idxStop, _idxCurrentOrder);
2180                }
2181                return;
2182            }
2183        }
2184        _stoppingBlock = block;
2185        _idxStoppingBlock = idxBlock;
2186        _stoppingBlock.addPropertyChangeListener(this);
2187        if ((_trace || log.isDebugEnabled()) && (_waitForBlock || _waitForWarrant)) {
2188            String reason;
2189            String cause;
2190            if (_waitForWarrant) {
2191                reason = Bundle.getMessage("Warrant");
2192                Warrant w = block.getWarrant();
2193                if (w != null) {
2194                    cause = w.getDisplayName();
2195                } else {
2196                    cause = Bundle.getMessage("Unknown");
2197                }
2198            } else if (_waitForBlock) {
2199                reason = Bundle.getMessage("Occupancy");
2200                cause = (String)block.getValue();
2201                if (cause == null) {
2202                    cause = Bundle.getMessage("unknownTrain");
2203                }
2204            } else if (_lost) {
2205                reason = Bundle.getMessage("Lost");
2206                cause = Bundle.getMessage("Occupancy");
2207            } else {
2208                reason = Bundle.getMessage("Start");
2209                cause = "";
2210            }
2211            log.info(Bundle.getMessage("StopBlockSet", _stoppingBlock.getDisplayName(), getTrainName(), reason, cause));
2212        }
2213    }
2214
2215    /**
2216     * set signal listening for aspect change for block at index.
2217     * return true if signal is set.
2218     */
2219    private boolean setProtectingSignal(int idx) {
2220        if (_idxProtectSignal == idx) {
2221            return true;
2222        }
2223        BlockOrder blkOrder = getBlockOrderAt(idx);
2224        NamedBean signal = blkOrder.getSignal();
2225
2226        if (_protectSignal != null && _protectSignal.equals(signal)) {
2227            // Must be the route coming back to the same block. Same signal, move index only.
2228            if (_idxProtectSignal < idx && idx >= 0) {
2229                _idxProtectSignal = idx;
2230            }
2231            return true;
2232        }
2233
2234        if (_protectSignal != null) {
2235            if (idx > _idxProtectSignal && _idxProtectSignal > _idxCurrentOrder) {
2236                return true;
2237            }
2238        }
2239
2240        return changeSignalListener(signal, idx);
2241    }
2242
2243    /**
2244     * if current listening signal is not at signalIndex, remove listener and
2245     * set new listening signal
2246     */
2247    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2248    private boolean changeSignalListener(NamedBean  signal,  int signalIndex) {
2249        if (signalIndex == _idxProtectSignal) {
2250            return true;
2251        }
2252//        StringBuilder sb = new StringBuilder(getDisplayName());
2253        if (_protectSignal != null) {
2254            _protectSignal.removePropertyChangeListener(this);
2255/*            if (log.isDebugEnabled()) {
2256                sb.append("Removes \"");
2257                sb.append(_protectSignal.getDisplayName());
2258                sb.append("\" at \"");
2259                sb.append(getBlockAt(_idxProtectSignal).getDisplayName());
2260                sb.append("\"");
2261            }*/
2262            _protectSignal = null;
2263            _idxProtectSignal = -1;
2264        }
2265        boolean ret = false;
2266        if (signal != null) {
2267            _protectSignal = signal;
2268            _idxProtectSignal = signalIndex;
2269            _protectSignal.addPropertyChangeListener(this);
2270            if (_trace || log.isDebugEnabled()) {
2271                log.info(Bundle.getMessage("ProtectSignalSet", getTrainName(),
2272                        _protectSignal.getDisplayName(), getBlockAt(_idxProtectSignal).getDisplayName()));
2273            }
2274            ret = true;
2275        }
2276        return ret;
2277    }
2278
2279    /**
2280     * Check if this is the next block of the train moving under the warrant
2281     * Learn mode assumes route is set and clear. Run mode update conditions.
2282     * <p>
2283     * Must be called on Layout thread.
2284     *
2285     * @param block Block in the route is going active.
2286     */
2287    @InvokeOnLayoutThread
2288    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2289    protected void goingActive(OBlock block) {
2290        if (log.isDebugEnabled()) {
2291            if (!ThreadingUtil.isLayoutThread()) {
2292                log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback"));
2293                stopWarrant(true, true);
2294                return;
2295            }
2296        }
2297
2298        if (_runMode == MODE_NONE) {
2299            return;
2300        }
2301        int activeIdx = getIndexOfBlockAfter(block, _idxCurrentOrder);
2302        if (log.isDebugEnabled()) {
2303            log.debug("{}: **Block \"{}\" goingActive. activeIdx= {}, _idxCurrentOrder= {}.",
2304                    getDisplayName(), block.getDisplayName(), activeIdx, _idxCurrentOrder);
2305        }
2306        Warrant w = block.getWarrant();
2307        if (w == null || !this.equals(w)) {
2308            if (log.isDebugEnabled()) {
2309                log.debug("{}: **Block \"{}\" owned by {}!",
2310                        getDisplayName(), block.getDisplayName(), (w==null?"NO One":w.getDisplayName()));
2311            }
2312            return;
2313        }
2314        if (_lost && !getBlockAt(_idxCurrentOrder).isOccupied()) {
2315            _idxCurrentOrder = activeIdx;
2316            log.info("Train \"{}\" found at block \"{}\" of warrant {}.",
2317                       getTrainName(), block.getDisplayName(),  getDisplayName());
2318            _lost = false;
2319            rampSpeedTo(_engineer.getSpeedType(false), - 1); // current speed type
2320            setMovement();
2321            return;
2322        }
2323        if (activeIdx <= 0) {
2324            // if _idxCurrentOrder == 0, (i.e. starting block) case 0 is handled as the _stoppingBlock
2325            return;
2326        }
2327        if (activeIdx == _idxCurrentOrder) {
2328            // unusual occurrence.  dirty track? sensor glitch?
2329            if (_trace || log.isDebugEnabled()) {
2330                log.info(Bundle.getMessage("RegainDetection", getTrainName(), block.getDisplayName()));
2331            }
2332        } else if (activeIdx == _idxCurrentOrder + 1) {
2333            if (_delayStart) {
2334                log.warn("{}: Rogue entered Block \"{}\" ahead of {}.",
2335                        getDisplayName(), block.getDisplayName(), getTrainName());
2336                _message = Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
2337                return;
2338            }
2339            // Since we are moving at speed we assume it is our train that entered the block
2340            // continue on.
2341            _idxCurrentOrder = activeIdx;
2342        } else if (activeIdx > _idxCurrentOrder + 1) {
2343            // if previous blocks are dark, this could be for our train
2344            // check from current (last known) block to this just activated block
2345            for (int idx = _idxCurrentOrder + 1; idx < activeIdx; idx++) {
2346                OBlock preBlock = getBlockAt(idx);
2347                if (!preBlock.isDark()) {
2348                    // not dark, therefore not our train
2349                    if (log.isDebugEnabled()) {
2350                        OBlock curBlock = getBlockAt(_idxCurrentOrder);
2351                        log.debug("Rogue train entered block \"{}\" ahead of train {} currently in block \"{}\"!",
2352                                block.getDisplayName(), _trainName, curBlock.getDisplayName());
2353                    }
2354                    return;
2355                }
2356                // we assume this is our train entering block
2357                _idxCurrentOrder = activeIdx;
2358            }
2359            // previous blocks were checked as UNDETECTED above
2360            // Indicate the previous dark block was entered
2361            OBlock prevBlock = getBlockAt(activeIdx - 1);
2362            prevBlock._entryTime = System.currentTimeMillis() - 5000; // arbitrary. Just say 5 seconds
2363            prevBlock.setValue(_trainName);
2364            prevBlock.setState(prevBlock.getState() | OBlock.RUNNING);
2365            if (log.isDebugEnabled()) {
2366                log.debug("{}: Train moving from UNDETECTED block \"{}\" now entering block\"{}\"",
2367                        getDisplayName(), prevBlock.getDisplayName(), block.getDisplayName());
2368            }
2369        } else if (_idxCurrentOrder > activeIdx) {
2370            // unusual occurrence.  dirty track, sensor glitch, too fast for goingInactive() for complete?
2371            log.info("Tail of Train {} regained detection behind Block= {} at block= {}",
2372                    getTrainName(), block.getDisplayName(), getBlockAt(activeIdx).getDisplayName());
2373            return;
2374        }
2375        // Since we are moving we assume it is our train entering the block
2376        // continue on.
2377        setHeadOfTrain(block);
2378        if (_engineer != null) {
2379            _engineer.clearWaitForSync(block); // Sync commands if train is faster than ET
2380        }
2381        if (_trace) {
2382            log.info(Bundle.getMessage("TrackerBlockEnter", getTrainName(),  block.getDisplayName()));
2383        }
2384        fireRunStatus("blockChange", getBlockAt(activeIdx - 1), block);
2385        if (_runMode == MODE_LEARN) {
2386            return;
2387        }
2388        // _idxCurrentOrder has been incremented. Warranted train has entered this block.
2389        // Do signals, speed etc.
2390        if (_idxCurrentOrder < _orders.size() - 1) {
2391            if (_engineer != null) {
2392                BlockOrder bo = _orders.get(_idxCurrentOrder + 1);
2393                if (bo.getBlock().isDark()) {
2394                    // can't detect next block, use ET
2395                    _engineer.setRunOnET(true);
2396                } else if (!_tempRunBlind) {
2397                    _engineer.setRunOnET(false);
2398                }
2399            }
2400        }
2401        if (log.isTraceEnabled()) {
2402            log.debug("{}: end of goingActive. leaving \"{}\" entered \"{}\"",
2403                    getDisplayName(), getBlockAt(activeIdx - 1).getDisplayName(), block.getDisplayName());
2404        }
2405        setMovement();
2406    } //end goingActive
2407
2408    private void setHeadOfTrain(OBlock block ) {
2409        block.setValue(_trainName);
2410        block.setState(block.getState() | OBlock.RUNNING);
2411        if (_runMode == MODE_RUN && _idxCurrentOrder > 0 && _idxCurrentOrder < _orders.size()) {
2412            _speedUtil.leavingBlock(_idxCurrentOrder - 1);
2413        }
2414    }
2415
2416    /**
2417     * @param block Block in the route is going Inactive
2418     */
2419    @InvokeOnLayoutThread
2420    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2421    protected void goingInactive(OBlock block) {
2422        if (log.isDebugEnabled()) {
2423            if (!ThreadingUtil.isLayoutThread()) {
2424                log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback"));
2425            }
2426        }
2427        if (_runMode == MODE_NONE) {
2428            return;
2429        }
2430
2431        int idx = getIndexOfBlockBefore(_idxCurrentOrder, block); // if idx >= 0, it is in this warrant
2432        if (log.isDebugEnabled()) {
2433            log.debug("{}: *Block \"{}\" goingInactive. idx= {}, _idxCurrentOrder= {}.",
2434                    getDisplayName(), block.getDisplayName(), idx, _idxCurrentOrder);
2435        }
2436        if (idx > _idxCurrentOrder) {
2437            return;
2438        }
2439        releaseBlock(block, idx);
2440        block.setValue(null);
2441        if (idx == _idxCurrentOrder) {
2442            // Train not visible if current block goes inactive. This is OK if the next block is Dark.
2443            if (_idxCurrentOrder + 1 < _orders.size()) {
2444                OBlock nextBlock = getBlockAt(_idxCurrentOrder + 1);
2445                if (nextBlock.isDark()) {
2446                    goingActive(nextBlock); // fake occupancy for dark block
2447                    return;
2448                }
2449                if (checkForOverrun(nextBlock)) {
2450                    return;
2451                }
2452            }
2453            _lost = true;
2454            if (_engineer != null) {
2455                setSpeedToType(Stop);   // set 0 throttle
2456                setStoppingBlock(_idxCurrentOrder);
2457            }
2458            if (_trace) {
2459                log.info(Bundle.getMessage("ChangedRoute", _trainName, block.getDisplayName(), getDisplayName()));
2460            }
2461            fireRunStatus("blockChange", block, null);  // train is lost
2462        }
2463    } // end goingInactive
2464
2465    /**
2466     * Deallocates all blocks prior to and including block at index idx
2467     * of _orders, if not needed again.
2468     * Comes from goingInactive, i.e. warrant has a listener on the block.
2469     * @param block warrant is releasing
2470     * @param idx index in BlockOrder list
2471     */
2472    private void releaseBlock(OBlock block, int idx) {
2473        /*
2474         * Deallocate block if train will not use the block again. Warrant
2475         * could loop back and re-enter blocks previously traversed. That is,
2476         * they will need to re-allocation of blocks ahead.
2477         * Previous Dark blocks also need deallocation and other trains or cars
2478         * dropped may have prevented previous blocks from going inactive.
2479         * Thus we must deallocate backward until we reach inactive detectable blocks
2480         * or blocks we no longer own.
2481         */
2482        for (int i = idx; i > -1; i--) {
2483            boolean neededLater = false;
2484            OBlock curBlock = getBlockAt(i);
2485            for (int j = i + 1; j < _orders.size(); j++) {
2486                if (curBlock.equals(getBlockAt(j))) {
2487                    neededLater = true;
2488                }
2489            }
2490            if (!neededLater) {
2491                if (deAllocateBlock(curBlock)) {
2492                    curBlock.setValue(null);
2493                    _totalAllocated = false;
2494                }
2495            } else {
2496                if (curBlock.isAllocatedTo(this)) {
2497                    // Can't deallocate, but must listen for followers
2498                    // who may be occupying the block
2499                    if (_idxCurrentOrder != idx + 1) {
2500                        curBlock.setValue(null);
2501                    }
2502                    if (curBlock.equals(_stoppingBlock)){
2503                        doStoppingBlockClear();
2504                    }
2505                }
2506                if (_shareRoute) { // don't deallocate if closer than 2 blocks, otherwise deallocate
2507                    int k = Math.min(3, _orders.size());
2508                    while (k > _idxCurrentOrder) {
2509                        if (!curBlock.equals(getBlockAt(k))) {
2510                            if (deAllocateBlock(curBlock)) {
2511                                curBlock.setValue(null);
2512                                _totalAllocated = false;
2513                            }
2514                        }
2515                        k--;
2516                    }
2517                }
2518            }
2519        }
2520    }
2521
2522    /*
2523     * This block is a possible overrun. If permitted, we may claim ownership.
2524     * BlockOrder index of block is _idxCurrentOrder + 1
2525     * return true, if warrant can claim occupation and ownership
2526     */
2527    private boolean checkForOverrun(OBlock block) {
2528        if (block.isOccupied() && (System.currentTimeMillis() - block._entryTime < 5000)) {
2529            // Went active within the last 5 seconds. Likely an overrun
2530            _overrun = true;
2531            _message = setPathAt(_idxCurrentOrder + 1);    //  no TrainOrder checks. allocates and sets path
2532            if (_message == null) {   // OK we own the block now.
2533                _idxCurrentOrder++;
2534                // insulate possible non-GUI thread making this call (e.g. Engineer)
2535                ThreadingUtil.runOnGUI(()-> goingActive(block));
2536                return true ;
2537            }
2538        }
2539        return false;
2540    }
2541
2542    @Override
2543    public void dispose() {
2544        if (_runMode != MODE_NONE) {
2545            stopWarrant(true, true);
2546        }
2547        super.dispose();
2548    }
2549
2550    @Override
2551    public String getBeanType() {
2552        return Bundle.getMessage("BeanNameWarrant");
2553    }
2554
2555    private class CommandDelay extends Thread {
2556
2557        String _speedType;
2558//        long _startTime = 0;
2559        long _waitTime = 0;
2560        float _waitSpeed;
2561        boolean quit = false;
2562        int _endBlockIdx;
2563
2564        CommandDelay(@Nonnull String speedType, long startWait, float waitSpeed, int endBlockIdx) {
2565            _speedType = speedType;
2566            _waitTime = startWait;
2567            _waitSpeed = waitSpeed;
2568            _endBlockIdx = endBlockIdx;
2569            setName("CommandDelay(" + getTrainName() + "-" + speedType +")");
2570        }
2571
2572        // check if request for a duplicate CommandDelay can be cancelled
2573        boolean isDuplicate(String speedType, long startWait, int endBlockIdx) {
2574            if (endBlockIdx == _endBlockIdx && speedType.equals(_speedType) ) { // &&
2575//                    (_waitTime - (System.currentTimeMillis() - _startTime)) < startWait) {
2576                return true;    // keeps this thread
2577            }
2578            return false;   // not a duplicate or does not shorten time wait. this thread will be cancelled
2579        }
2580
2581        @Override
2582        @SuppressFBWarnings(value = "WA_NOT_IN_LOOP", justification = "notify never called on this thread")
2583        public void run() {
2584            synchronized (this) {
2585//                _startTime = System.currentTimeMillis();
2586                boolean ramping = _engineer.isRamping();
2587                if (ramping) {
2588                    long time = 0;
2589                    while (time <= _waitTime) {
2590                        if (_engineer.getSpeedSetting() >= _waitSpeed) {
2591                            break; // stop ramping beyond this speed
2592                        }
2593                        try {
2594                            wait(100);
2595                        } catch (InterruptedException ie) {
2596                            if (log.isDebugEnabled() && quit) {
2597                                log.debug("CommandDelay interrupt. Ramp to {} not done. warrant {}",
2598                                        _speedType, getDisplayName());
2599                            }
2600                        }
2601                        time += 50;
2602                    }
2603                } else {
2604                    try {
2605                        wait(_waitTime);
2606                    } catch (InterruptedException ie) {
2607                        if (log.isDebugEnabled() && quit) {
2608                            log.debug("CommandDelay interrupt.  Ramp to {} not done. warrant {}",
2609                                    _speedType, getDisplayName());
2610                        }
2611                    }
2612                }
2613
2614                if (!quit && _engineer != null) {
2615                    if (_noRamp) {
2616                        setSpeedToType(_speedType);
2617                    } else {
2618                        _engineer.rampSpeedTo(_speedType, _endBlockIdx);
2619                    }
2620                }
2621            }
2622            endDelayCommand();
2623        }
2624    }
2625
2626    synchronized private void cancelDelayRamp() {
2627        if (_delayCommand != null) {
2628            log.debug("{}: cancelDelayRamp() called. _speedType= {}", getDisplayName(), _delayCommand._speedType);
2629            _delayCommand.quit = true;
2630            _delayCommand.interrupt();
2631            _delayCommand = null;
2632        }
2633    }
2634
2635    synchronized private void endDelayCommand() {
2636        _delayCommand = null;
2637    }
2638
2639    private void rampSpeedTo(String speedType, int idx) {
2640        cancelDelayRamp();
2641        if (_noRamp) {
2642            _engineer.setSpeedToType(speedType);
2643            _engineer.setWaitforClear(speedType.equals(Stop) || speedType.equals(EStop));
2644            if (log.isDebugEnabled()) {
2645                log.debug("{}: No Ramp to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName());
2646            }
2647            return;
2648        }
2649        if (log.isDebugEnabled()) {
2650            if (idx < 0) {
2651                log.debug("{}: Ramp up to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName());
2652            } else {
2653                log.debug("{}: Ramp down to \"{}\" before block \"{}\"", getDisplayName(), speedType, getBlockAt(idx).getDisplayName());
2654            }
2655        }
2656        if (_engineer != null) {
2657            _engineer.rampSpeedTo(speedType, idx);
2658        } else {
2659            log.error("{}: No Engineer!", getDisplayName());
2660        }
2661    }
2662
2663    private void setSpeedToType(String speedType) {
2664        cancelDelayRamp();
2665        _engineer.setSpeedToType(speedType);
2666    }
2667
2668    private void clearWaitFlags(boolean removeListeners) {
2669        if (log.isTraceEnabled()) {
2670            log.trace("{}: Flags cleared {}.", getDisplayName(), removeListeners?"and removed Listeners":"only");
2671        }
2672        _waitForBlock = false;
2673        _waitForSignal = false;
2674        _waitForWarrant = false;
2675        if (removeListeners) {
2676            if (_protectSignal != null) {
2677                _protectSignal.removePropertyChangeListener(this);
2678                _protectSignal = null;
2679                _idxProtectSignal = -1;
2680            }
2681            if (_stoppingBlock != null) {
2682                _stoppingBlock.removePropertyChangeListener(this);
2683                _stoppingBlock = null;
2684                _idxStoppingBlock = -1;
2685            }
2686        }
2687    }
2688
2689    /*
2690     * Return pathLength of the block.
2691     */
2692    private float getAvailableDistanceAt(int idxBlockOrder) {
2693        BlockOrder blkOrder = getBlockOrderAt(idxBlockOrder);
2694        float pathLength = blkOrder.getPathLength();
2695        if (idxBlockOrder == 0 || pathLength <= 20.0f) {
2696            // Position in block is unknown. use calculated distances instead
2697            float blkDist = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getCalcLen();
2698            if (log.isDebugEnabled()) {
2699                log.debug("{}: getAvailableDistanceAt: block \"{}\" using calculated blkDist= {}, pathLength= {}",
2700                        getDisplayName(), blkOrder.getBlock().getDisplayName(), blkDist, pathLength);
2701            }
2702            return blkDist;
2703        } else {
2704            return pathLength;
2705        }
2706    }
2707
2708    private float getEntranceBufferDist(int idxBlockOrder) {
2709        float bufDist = BUFFER_DISTANCE;
2710        if (_waitForSignal) {        // signal restricting speed
2711            bufDist+= getBlockOrderAt(idxBlockOrder).getEntranceSpace(); // signal's adjustment
2712        }
2713        return bufDist;
2714    }
2715
2716    /**
2717     * Called to set the correct speed for the train when the scripted speed
2718     * must be modified due to a track condition (signaled speed or rogue
2719     * occupation). Also called to return to the scripted speed after the
2720     * condition is cleared. Assumes the train occupies the block of the current
2721     * block order.
2722     * <p>
2723     * Looks for speed requirements of this block and takes immediate action if
2724     * found. Otherwise looks ahead for future speed change needs. If speed
2725     * restriction changes are required to begin in this block, but the change
2726     * is not immediate, then determine the proper time delay to start the speed
2727     * change.
2728     */
2729    private void setMovement() {
2730        BlockOrder curBlkOrder = getBlockOrderAt(_idxCurrentOrder);
2731        OBlock curBlock = curBlkOrder.getBlock();
2732        String currentSpeedType = _engineer.getSpeedType(false); // current speed type
2733        String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block
2734        if (entrySpeedType == null) {
2735            entrySpeedType = currentSpeedType;
2736        }
2737        curBlkOrder.setPath(this);  // restore running
2738
2739        if (log.isDebugEnabled()) {
2740            SpeedState speedState = _engineer.getSpeedState();
2741            int runState = _engineer.getRunState();
2742            log.debug("{}: SET MOVEMENT Block \"{}\" runState= {}, speedState= {} for currentSpeedType= {}. entrySpeedType= {}.",
2743                    getDisplayName(), curBlock.getDisplayName(), RUN_STATE[runState], speedState.toString(),
2744                    currentSpeedType, entrySpeedType);
2745            log.debug("{}: Flags: _waitForBlock={}, _waitForSignal={}, _waitForWarrant={} curThrottle= {}.",
2746                    getDisplayName(), _waitForBlock, _waitForSignal, _waitForWarrant, _engineer.getSpeedSetting());
2747            if (_message != null) {
2748                log.debug("{}: _message ({}) ", getDisplayName(), _message);
2749            }
2750        }
2751
2752        // Check that flags and states agree with expected speed and position
2753        // A signal drop down can appear to be a speed violation, but only when a violation when expected
2754        if (_idxCurrentOrder > 0) {
2755            if (_waitForSignal) {
2756                if (_idxProtectSignal == _idxCurrentOrder) {
2757                    makeOverrunMessage(curBlkOrder);
2758                    setSpeedToType(Stop); // immediate decrease
2759                    return;
2760                }
2761            }
2762            if (_idxStoppingBlock == _idxCurrentOrder) {
2763                if (_waitForBlock || _waitForWarrant) {
2764                    makeOverrunMessage(curBlkOrder);
2765                    setSpeedToType(Stop); // immediate decrease
2766                    return;
2767                }
2768            }
2769
2770            if (_speedUtil.secondGreaterThanFirst(entrySpeedType, currentSpeedType)) {
2771                // signal or block speed entrySpeedType is less than currentSpeedType.
2772                // Speed for this block is violated so set end speed immediately
2773                NamedBean signal = curBlkOrder.getSignal();
2774                if (signal != null) {
2775                    log.info("Train {} moved past required {} speed for signal \"{}\" at block \"{}\" on warrant {}!",
2776                            getTrainName(), entrySpeedType, signal.getDisplayName(), curBlock.getDisplayName(), getDisplayName());
2777                } else {
2778                    log.info("Train {} moved past required \"{}\" speed at block \"{}\" on warrant {}!",
2779                            getTrainName(), entrySpeedType, curBlock.getDisplayName(), getDisplayName());
2780                }
2781                fireRunStatus("SignalOverrun", (signal!=null?signal.getDisplayName():curBlock.getDisplayName()),
2782                        entrySpeedType); // message of speed violation
2783               setSpeedToType(entrySpeedType); // immediate decrease
2784               currentSpeedType = entrySpeedType;
2785            }
2786        } else {    // at origin block and train has arrived,. ready to move
2787            if (Stop.equals(currentSpeedType)) {
2788                currentSpeedType = Normal;
2789            }
2790        }
2791
2792        if (_idxCurrentOrder < _orders.size() - 1) {
2793            lookAheadforSpeedChange(currentSpeedType, entrySpeedType);
2794        } // else at last block, forget about speed changes, return;
2795    }
2796
2797    /*
2798     * Looks for the need to reduce speed ahead. If one is found, mkes an estimate of the
2799     *distance needed to change speeds.  Find the available distance available, including
2800     * the full length of the current path. If the ramp to reduce speed should begin in the
2801     * current block, calls methods to calculate the time lapse before the ramp should begin.
2802     * entrySpeedType (expected type) will be either equal to or greater than currentSpeedType
2803     * for all blocks except rhe first.
2804     */
2805    private void lookAheadforSpeedChange(String currentSpeedType, String entrySpeedType) {
2806        clearWaitFlags(false);
2807        // look ahead for speed type slower than current type, refresh flags
2808        // entrySpeedType is the expected speed to be reached, if no speed change ahead
2809
2810        String speedType = currentSpeedType;    // first slower speedType ahead
2811        int idx = _idxCurrentOrder + 1;
2812        int idxSpeedChange = -1;  // idxBlockOrder where speed changes
2813        int idxContrlBlock = -1;
2814        int limit;
2815        if (_shareRoute) {
2816            limit = Math.min(_orders.size(), _idxCurrentOrder + 3);
2817        } else {
2818            limit = _orders.size();
2819        }
2820        boolean allocate = true;
2821        int numAllocated = 0;
2822        do {
2823            TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, allocate);
2824            if (log.isDebugEnabled()) {
2825                log.debug("{}: lookAheadforSpeedChange {}", getDisplayName(), to.toString());
2826            }
2827            switch (to._cause) {
2828                case NONE:
2829                    break;
2830               case WARRANT:
2831                   _waitForWarrant = true;
2832                   _message = to._message;
2833                   idxContrlBlock = to._idxContrlBlock;
2834                   idxSpeedChange = to._idxEnterBlock;
2835                   speedType = Stop;
2836                   break;
2837                case OCCUPY:
2838                    _waitForBlock = true;
2839                    _message = to._message;
2840                    idxContrlBlock = to._idxContrlBlock;
2841                    idxSpeedChange = to._idxEnterBlock;
2842                    speedType = Stop;
2843                    break;
2844                case SIGNAL:
2845                    speedType = to._speedType;
2846                    if (Stop.equals(speedType)) {
2847                        _waitForSignal = true;
2848                    }
2849                    idxContrlBlock = to._idxContrlBlock;
2850                    idxSpeedChange = to._idxEnterBlock;
2851                    _message = to._message;
2852                    break;
2853                default:
2854                    log.error("{}: lookAheadforSpeedChange at block \"{}\" setPath returns: {}",
2855                            getDisplayName(), getBlockAt(_idxCurrentOrder).getDisplayName(), to.toString());
2856                    _message = to._message;
2857                    setSpeedToType(Stop);
2858                    return;
2859            }
2860            numAllocated++;
2861            if (Stop.equals(speedType)) {
2862                break;
2863            }
2864            if (_shareRoute && numAllocated > 1 ) {
2865                allocate = false;
2866            }
2867            idx++;
2868
2869        } while ((idxSpeedChange < 0) && (idx < limit) &&
2870                !_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType));
2871
2872        if (!Stop.equals(speedType)) {
2873            while ((idx < limit)) { // allocate and set paths beyond speed change
2874                TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, false);
2875                if (Stop.equals(to._speedType)) {
2876                    break;
2877                }
2878                idx++;
2879            }
2880        }
2881        if (idxSpeedChange < 0) {
2882            idxSpeedChange = _orders.size() - 1;
2883        }
2884
2885        float availDist = getAvailableDistance(idxSpeedChange);  // distance ahead (excluding current block
2886        float changeDist = getChangeSpeedDistance(idxSpeedChange, speedType);    // distance needed to change speed for speedType
2887
2888        if (_speedUtil.secondGreaterThanFirst(currentSpeedType, speedType)) {
2889            // speedType is greater than currentSpeedType. i.e. increase speed.
2890            rampSpeedTo(speedType, -1);
2891            return;
2892        }
2893        if (!currentSpeedType.equals(entrySpeedType)) {
2894            // entrySpeedType is greater than currentSpeedType. i.e. increase speed.
2895            rampSpeedTo(entrySpeedType, -1);
2896            // continue to interrupt ramp up with ramp down
2897        }
2898
2899        // set next signal after current block for aspect speed change
2900        for (int i = _idxCurrentOrder + 1; i < _orders.size(); i++) {
2901            if (setProtectingSignal(i)) {
2902               break;
2903           }
2904        }
2905
2906        OBlock block = getBlockAt(idxSpeedChange);
2907        if (log.isDebugEnabled()) {
2908            log.debug("{}: Speed \"{}\" at block \"{}\" until speed \"{}\" at block \"{}\", availDist={}, changeDist={}",
2909                    getDisplayName(), currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(), speedType,
2910                    block.getDisplayName(), availDist, changeDist);
2911        }
2912
2913        if (changeDist <= availDist) {
2914            cancelDelayRamp(); // interrupts down ramping
2915            clearWaitFlags(false);
2916            return;
2917        }
2918
2919        // Now set stopping condition of flags, if any. Not, if current block is also ahead.
2920        if (_waitForBlock) {
2921            if (!getBlockAt(_idxCurrentOrder).equals(block)) {
2922                setStoppingBlock(idxContrlBlock);
2923            }
2924        } else if (_waitForWarrant) {
2925            // if block is allocated and unoccupied, but cannot set path exit.
2926            if (_stoppingBlock == null) {
2927                setStoppingBlock(idxContrlBlock);
2928            }
2929        }
2930
2931        // Begin a ramp for speed change in this block. If due to a signal, watch that one
2932        if(_waitForSignal) {
2933            // Watch this signal. Should be the previous set signal above.
2934            // If not, then user has not configured signal system to allow room for speed changes.
2935            setProtectingSignal(idxContrlBlock);
2936        }
2937
2938        // either ramp in progress or no changes needed. Stopping conditions set, so move on.
2939        if (!_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType)) {
2940            return;
2941        }
2942
2943        availDist += getAvailableDistanceAt(_idxCurrentOrder);   // Add available length in this block
2944
2945        int cmdStartIdx = _speedUtil.getBlockSpeedInfo(_idxCurrentOrder).getFirstIndex();
2946        if (!doDelayRamp(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx)) {
2947            log.warn("No room for train {} to ramp to \"{}\" from \"{}\" in block \"{}\"!. availDist={}, changeDist={} on warrant {}",
2948                    getTrainName(), speedType, currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(),
2949                    availDist,  changeDist, getDisplayName());
2950        }
2951    }
2952
2953    /*
2954     * if there is sufficient room calculate a wait time, otherwise ramp immediately.
2955     */
2956    @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH", justification="Write unexpected error and fall through")
2957    synchronized private boolean doDelayRamp(float availDist, float changeDist, int idxSpeedChange, String speedType, int cmdStartIdx) {
2958        String pendingSpeedType = _engineer.getSpeedType(true); // current or pending speed type
2959        if (pendingSpeedType.equals(speedType)) {
2960            return true;
2961        }
2962        if (availDist < 10) {
2963            setSpeedToType(speedType);
2964            return false;
2965        } else {
2966            SpeedState speedState = _engineer.getSpeedState();
2967            switch (speedState) {
2968                case RAMPING_UP:
2969                    makeRampWait(availDist, idxSpeedChange, speedType);
2970                    break;
2971                case RAMPING_DOWN:
2972                    log.error("Already ramping to \"{}\" making ramp for \"{}\".", _engineer.getSpeedType(true), speedType);
2973                //$FALL-THROUGH$
2974                case STEADY_SPEED:
2975                //$FALL-THROUGH$
2976                default:
2977                    makeScriptWait(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx);
2978            }
2979        }
2980        return true;
2981    }
2982
2983    private void makeRampWait(float availDist, int idxSpeedChange, @Nonnull String speedType) {
2984        BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1);
2985        float speedSetting = info.getExitSpeed();
2986        float endSpeed = _speedUtil.modifySpeed(speedSetting, speedType);
2987
2988        speedSetting = _engineer.getSpeedSetting();       // current speed
2989        float prevSetting = speedSetting;
2990        String currentSpeedType = _engineer.getSpeedType(false); // current speed type
2991
2992        float changeDist = 0;
2993        if (log.isDebugEnabled()) {
2994            log.debug("{}: makeRampWait for speed change \"{}\" to \"{}\". Throttle from={}, to={}, availDist={}",
2995                    getDisplayName(), currentSpeedType, speedType, speedSetting, endSpeed, availDist);
2996            // command index numbers biased by 1
2997        }
2998        float bufDist = getEntranceBufferDist(idxSpeedChange);
2999        float accumTime = 0;    // accumulated time of commands up to ramp start
3000        float accumDist = 0;
3001        RampData ramp = _speedUtil.getRampForSpeedChange(speedSetting, 1.0f);
3002        int time = ramp.getRampTimeIncrement();
3003        ListIterator<Float> iter = ramp.speedIterator(true);
3004
3005        while (iter.hasNext()) {
3006            changeDist = _speedUtil.getRampLengthForEntry(speedSetting, endSpeed) + bufDist;
3007            accumDist += _speedUtil.getDistanceOfSpeedChange(prevSetting, speedSetting, time);
3008            accumTime += time;
3009            prevSetting = speedSetting;
3010            speedSetting = iter.next();
3011
3012            if (changeDist + accumDist >= availDist) {
3013                float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting);
3014                float remDist = changeDist + accumDist - availDist;
3015                if (curTrackSpeed > 0) {
3016                    accumTime -= remDist / curTrackSpeed;
3017                } else {
3018                    log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(),
3019                            speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed);
3020                }
3021                break;
3022            }
3023        }
3024        if (changeDist < accumDist) {
3025            float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting);
3026            if (curTrackSpeed > 0) {
3027                accumTime += (availDist - changeDist) / curTrackSpeed;
3028            } else {
3029                log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(),
3030                        speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed);
3031            }
3032        }
3033
3034        int waitTime = Math.round(accumTime);
3035
3036        if (log.isDebugEnabled()) {
3037            log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm",
3038                    getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist),
3039                    getBlockAt(idxSpeedChange).getDisplayName(), speedSetting, Math.round(availDist));
3040        }
3041        rampSpeedDelay(waitTime, speedType, speedSetting, idxSpeedChange);
3042    }
3043
3044    /**
3045     *  Must start the ramp in current block. ( at _idxCurrentOrder)
3046     *  find the time when ramp should start in this block, then use thread CommandDelay to start the ramp.
3047     *  Train must travel a deltaDist for a deltaTime to the start of the ramp.
3048     *  It travels at throttle settings of scriptSpeed(s) modified by currentSpeedType.
3049     *  trackSpeed(s) of these modified scriptSettings are computed from SpeedProfile
3050     *  waitThrottle is throttleSpeed when ramp is started. This may not be the scriptSpeed now
3051     *  Start with waitThrottle (modSetting) being at the entrance to the block.
3052     *  modSetting gives the current trackSpeed.
3053     *  accumulate the time and distance and determine the distance (changeDist) needed for entrance into
3054     *  block (at idxSpeedChange) requiring speed change to speedType
3055     *  final ramp should modify waitSpeed to endSpeed and must end at the exit of end block (endBlockIdx)
3056     *
3057     * @param availDist     distance available to make the ramp
3058     * @param changeDist    distance needed for the rmp
3059     * @param idxSpeedChange block order index of block to complete change before entry
3060     * @param speedType     speed aspect of speed change
3061     * @param cmdStartIdx   command index of delay
3062     */
3063    private void makeScriptWait(float availDist, float changeDist, int idxSpeedChange, @Nonnull String speedType, int cmdStartIdx) {
3064        BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1);
3065        int cmdEndIdx = info.getLastIndex();
3066        float scriptSpeed = info.getExitSpeed();
3067        float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType);
3068
3069        scriptSpeed = _engineer.getScriptSpeed();  // script throttle setting
3070        float speedSetting = _engineer.getSpeedSetting();       // current speed
3071        String currentSpeedType = _engineer.getSpeedType(false); // current speed type
3072
3073        float modSetting = speedSetting;      // _speedUtil.modifySpeed(scriptSpeed, currentSpeedType);
3074        float beginTrackSpeed = _speedUtil.getTrackSpeed(modSetting);   // mm/sec track speed at modSetting
3075        float curTrackSpeed = beginTrackSpeed;
3076        float prevTrackSpeed = beginTrackSpeed;
3077        if (_idxCurrentOrder == 0 && availDist > BUFFER_DISTANCE) {
3078            changeDist = 0;
3079        }
3080        if (log.isDebugEnabled()) {
3081            log.debug("{}: makespeedChange cmdIdx #{} to #{} at speedType \"{}\" to \"{}\". speedSetting={}, changeDist={}, availDist={}",
3082                    getDisplayName(), cmdStartIdx+1, cmdEndIdx+1, currentSpeedType, speedType, speedSetting, changeDist, availDist);
3083            // command index numbers biased by 1
3084        }
3085        float accumTime = 0;    // accumulated time of commands up to ramp start
3086        float accumDist = 0;
3087        Command cmd = _commands.get(cmdStartIdx).getCommand();
3088
3089        if (cmd.equals(Command.NOOP) && beginTrackSpeed > 0) {
3090            accumTime = (availDist - changeDist) / beginTrackSpeed;
3091        } else {
3092            float timeRatio; // time adjustment for current speed type
3093            if (curTrackSpeed > _speedUtil.getRampThrottleIncrement()) {
3094                timeRatio = _speedUtil.getTrackSpeed(scriptSpeed) / curTrackSpeed;
3095            } else {
3096                timeRatio = 1;
3097            }
3098            float bufDist = getEntranceBufferDist(idxSpeedChange);
3099
3100            for (int i = cmdStartIdx; i <= cmdEndIdx; i++) {
3101                ThrottleSetting ts = _commands.get(i);
3102                long time =  ts.getTime();
3103                accumDist += _speedUtil.getDistanceOfSpeedChange(prevTrackSpeed, curTrackSpeed, (int)(time * timeRatio));
3104                accumTime += time * timeRatio;
3105                cmd = ts.getCommand();
3106                if (cmd.equals(Command.SPEED)) {
3107                    prevTrackSpeed = curTrackSpeed;
3108                    CommandValue cmdVal = ts.getValue();
3109                    scriptSpeed = cmdVal.getFloat();
3110                    modSetting = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType);
3111                    curTrackSpeed = _speedUtil.getTrackSpeed(modSetting);
3112                    changeDist = _speedUtil.getRampLengthForEntry(modSetting, endSpeed) + bufDist;
3113                    timeRatio = _speedUtil.getTrackSpeed(scriptSpeed) / curTrackSpeed;
3114                }
3115
3116                if (log.isDebugEnabled()) {
3117                    log.debug("{}: cmd#{} accumTime= {} accumDist= {} changeDist= {}, throttle= {}",
3118                            getDisplayName(), i+1, accumTime, accumDist, changeDist, modSetting);
3119                }
3120                if (changeDist + accumDist >= availDist) {
3121                    float remDist = changeDist + accumDist - availDist;
3122                    if (curTrackSpeed > 0) {
3123                        accumTime -= remDist / curTrackSpeed;
3124                    } else {
3125                        log.warn("{}: script unfit cmd#{}. Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(),
3126                                i+1, speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed);
3127                        if (prevTrackSpeed > 0) {
3128                            accumTime -= remDist / prevTrackSpeed;
3129                        }
3130                    }
3131                    break;
3132                }
3133                if (cmd.equals(Command.NOOP)) {
3134                    // speed change is supposed to start in current block
3135                    // start ramp in next block?
3136                    float remDist = availDist - changeDist - accumDist;
3137                    log.warn("{}: script unfit cmd#{}. Cannot compute wait time for \"{}\" ramp to block \"{}\". remDist= {}",
3138                            getDisplayName(), i+1, speedType, getBlockAt(idxSpeedChange).getDisplayName(), remDist);
3139                    accumTime -= _speedUtil.getTimeForDistance(modSetting, bufDist);
3140                    break;
3141                }
3142            }
3143        }
3144
3145        int waitTime = Math.round(accumTime);
3146
3147        if (log.isDebugEnabled()) {
3148            log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm",
3149                    getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist),
3150                    getBlockAt(idxSpeedChange).getDisplayName(), modSetting, Math.round(availDist));
3151        }
3152
3153        rampSpeedDelay(waitTime, speedType, modSetting, idxSpeedChange);
3154    }
3155
3156    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
3157    synchronized private void rampSpeedDelay (long waitTime, String speedType, float waitSpeed, int idxSpeedChange) {
3158        int endBlockIdx = idxSpeedChange - 1;
3159        waitTime -= 50;     // Subtract a bit
3160        if( waitTime < 0) {
3161            rampSpeedTo(speedType, endBlockIdx);   // do it now on this thread.
3162            return;
3163        }
3164        String reason;
3165        if(_waitForSignal) {
3166            reason = Bundle.getMessage("Signal");
3167        } else if (_waitForWarrant) {
3168            reason = Bundle.getMessage("Warrant");
3169        } else if (_waitForBlock) {
3170            reason = Bundle.getMessage("Occupancy");
3171        } else {
3172            reason = Bundle.getMessage("Signal");
3173        }
3174
3175        if (_trace || log.isDebugEnabled()) {
3176            if (log.isDebugEnabled()) {
3177                log.info("Train \"{}\" needs speed decrease to \"{}\" from \"{}\" for {} before entering block \"{}\"",
3178                        getTrainName(), speedType, _engineer.getSpeedType(true), reason, getBlockAt(idxSpeedChange).getDisplayName());
3179           }
3180        }
3181        if (_delayCommand != null) {
3182            if (_delayCommand.isDuplicate(speedType, waitTime, endBlockIdx)) {
3183                return;
3184            }
3185            cancelDelayRamp();
3186        }
3187        _delayCommand = new CommandDelay(speedType, waitTime, waitSpeed, endBlockIdx);
3188        _delayCommand.start();
3189        if (log.isDebugEnabled()) {
3190            log.debug("{}: CommandDelay: will wait {}ms, then Ramp to {} in block {}.",
3191                    getDisplayName(), waitTime, speedType, getBlockAt(endBlockIdx).getDisplayName());
3192        }
3193        String blkName = getBlockAt(endBlockIdx).getDisplayName();
3194        if (_trace || log.isDebugEnabled()) {
3195            log.info(Bundle.getMessage("RampBegin", getTrainName(), reason, blkName, speedType, waitTime));
3196        }
3197    }
3198
3199    protected void downRampBegun(int endBlockIdx) {
3200        OBlock block = getBlockAt(endBlockIdx + 1);
3201        if (block != null) {
3202            _rampBlkOccupied = block.isOccupied();
3203        } else {
3204            _rampBlkOccupied = true;
3205        }
3206    }
3207
3208    protected void downRampDone(boolean stop, boolean halted, String speedType, int endBlockIdx) {
3209        if (_idxCurrentOrder < endBlockIdx) {
3210            return;     // overrun not possible.
3211        }
3212        // look for overruns
3213        int nextIdx = endBlockIdx + 1;
3214        if (nextIdx > 0 && nextIdx < _orders.size()) {
3215            BlockOrder bo = getBlockOrderAt(nextIdx);
3216            OBlock block = bo.getBlock();
3217            if (block.isOccupied() && !_rampBlkOccupied) {
3218                // Occupied now, but not occupied by another train at start of ramp.
3219                if (!checkForOverrun(block) ) {    // Not us. check if something should have us wait
3220                    Warrant w = block.getWarrant();
3221                    _overrun = true;    // endBlock occupied during ramp down. Speed overrun!
3222                    if (w != null && !w.equals(this)) { // probably redundant
3223                        _waitForWarrant = true;
3224                        setStoppingBlock(nextIdx);
3225                    } else if (Stop.equals(BlockOrder.getPermissibleSpeedAt(bo))) { // probably redundant
3226                        _waitForSignal = true;
3227                        setProtectingSignal(nextIdx);
3228                    } else {
3229                        _waitForBlock = true;
3230                    }
3231                }
3232                makeOverrunMessage(bo);
3233            }   // case where occupied at start of ramp is indeterminate
3234        }
3235    }
3236
3237    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
3238    private void makeOverrunMessage(BlockOrder curBlkOrder) {
3239        OBlock curBlock = curBlkOrder.getBlock();
3240        String name = null;
3241        if (_waitForSignal) {
3242            NamedBean signal = curBlkOrder.getSignal();
3243            if (signal!=null) {
3244                name = signal.getDisplayName();
3245            } else {
3246                name = curBlock.getDisplayName();
3247            }
3248            _overrun = true;
3249            String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block
3250            log.info(Bundle.getMessage("SignalOverrun", getTrainName(), entrySpeedType, name));
3251            fireRunStatus("SignalOverrun", name, entrySpeedType); // message of speed violation
3252            return;
3253        }
3254        String bundleKey = null;
3255        if (_waitForWarrant) {
3256            bundleKey = PROPERTY_WARRANT_OVERRUN;
3257            Warrant w = curBlock.getWarrant();
3258            if (w != null) {
3259                name = w.getDisplayName();
3260            }
3261        } else if (_waitForBlock){
3262            bundleKey = PROPERTY_OCCUPY_OVERRUN;
3263            name = (String)curBlock.getValue();
3264        }
3265        if (name == null) {
3266            name = Bundle.getMessage("unknownTrain");
3267        }
3268        if (bundleKey != null) {
3269            _overrun = true;
3270            log.info(Bundle.getMessage(bundleKey, getTrainName(), curBlock.getDisplayName(), name));
3271            fireRunStatus(bundleKey, curBlock.getDisplayName(), name); // message of speed violation
3272        } else {
3273            log.error("Train \"{}\" entered stopping block \"{}\" for unknown reason on warrant {}!",
3274                getTrainName(), curBlock.getDisplayName(), getDisplayName());
3275        }
3276    }
3277
3278    /**
3279     * {@inheritDoc}
3280     * <p>
3281     * This implementation tests that
3282     * {@link jmri.NamedBean#getSystemName()}
3283     * is equal for this and obj.
3284     * To allow a warrant to run with sections, DccLocoAddress is included to test equality
3285     *
3286     * @param obj the reference object with which to compare.
3287     * @return {@code true} if this object is the same as the obj argument;
3288     *         {@code false} otherwise.
3289     */
3290    @Override
3291    public boolean equals(Object obj) {
3292        if (obj == null) return false; // by contract
3293
3294        if (obj instanceof Warrant) {  // NamedBeans are not equal to things of other types
3295            Warrant b = (Warrant) obj;
3296            DccLocoAddress addr = this._speedUtil.getDccAddress();
3297            if (addr == null) {
3298                if (b._speedUtil.getDccAddress() != null) {
3299                    return false;
3300                }
3301                return (this.getSystemName().equals(b.getSystemName()));
3302            }
3303            return (this.getSystemName().equals(b.getSystemName()) && addr.equals(b._speedUtil.getDccAddress()));
3304        }
3305        return false;
3306    }
3307
3308    /**
3309     * {@inheritDoc}
3310     *
3311     * @return hash code value is based on the system name and DccLocoAddress.
3312     */
3313    @Override
3314    public int hashCode() {
3315        return (getSystemName().concat(_speedUtil.getDccAddress().toString())).hashCode();
3316    }
3317
3318    @Override
3319    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
3320        List<NamedBeanUsageReport> report = new ArrayList<>();
3321        if (bean != null) {
3322            if (bean.equals(getBlockingWarrant())) {
3323                report.add(new NamedBeanUsageReport("WarrantBlocking"));
3324            }
3325            getBlockOrders().forEach((blockOrder) -> {
3326                if (bean.equals(blockOrder.getBlock())) {
3327                    report.add(new NamedBeanUsageReport("WarrantBlock"));
3328                }
3329                if (bean.equals(blockOrder.getSignal())) {
3330                    report.add(new NamedBeanUsageReport("WarrantSignal"));
3331                }
3332            });
3333        }
3334        return report;
3335    }
3336
3337    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Warrant.class);
3338}