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