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