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