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