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, curThread);
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, victim);
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, Thread monitor) {
927        long time = 0;
928        while (!eng.getState().equals(Thread.State.TERMINATED) && time < 100) {
929            try {
930                eng.stopRun(a, f); // releases throttle
931                monitor.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        clearWaitFlags(false);
1370        _overrun = true;    // allows doRestoreRunning to run at an OCCUPY state
1371        return restoreRunning(_engineer.getSpeedType(false));
1372    }
1373
1374    // returns true if block is owned and occupied by this warrant
1375    private boolean checkBlockForRunning(int idxBlockOrder) {
1376        BlockOrder bo = getBlockOrderAt(idxBlockOrder);
1377        if (bo == null) {
1378            _message = Bundle.getMessage("BlockNotInRoute", "?");
1379            return false;
1380        }
1381        OBlock block = bo.getBlock();
1382        if (!block.isOccupied()) {
1383            _message = Bundle.getMessage("blockUnoccupied", block.getDisplayName());
1384            return false;
1385        }
1386        return true;
1387    }
1388
1389    // User increases speed
1390    private boolean bumpSpeed() {
1391        // OK, will do as it long as you own it, and you are where you think you are.
1392        _engineer.setHalt(false);
1393        clearWaitFlags(false);
1394        float speedSetting = _engineer.getSpeedSetting();
1395        if (speedSetting < 0) { // may have done E-Stop
1396            speedSetting = 0.0f;
1397        }
1398        float bumpSpeed = Math.max(WarrantPreferences.getDefault().getSpeedAssistance(), _speedUtil.getRampThrottleIncrement());
1399        _engineer.setSpeed(speedSetting + bumpSpeed);
1400        return true;
1401    }
1402
1403    private boolean moveToBlock(BlockOrder bo, int idx) {
1404        _idxCurrentOrder = idx;
1405        _message = setPathAt(idx);    // no checks. Force path set and allocation
1406        if (_message != null) {
1407            return false;
1408        }
1409        OBlock block = bo.getBlock();
1410        if (block.equals(_stoppingBlock)) {
1411            clearStoppingBlock();
1412            _engineer.setHalt(false);
1413        }
1414        goingActive(block);
1415        return true;
1416    }
1417
1418    protected boolean debugInfo() {
1419        if ( !log.isInfoEnabled() ) {
1420            return true;
1421        }
1422        StringBuilder info = new StringBuilder("\""); info.append(getDisplayName());
1423        info.append("\" Train \""); info.append(getTrainName()); info.append("\" - Current Block \"");
1424        info.append(getBlockAt(_idxCurrentOrder).getDisplayName());
1425        info.append("\" BlockOrder idx= "); info.append(_idxCurrentOrder);
1426        info.append("\n\tWait flags: _waitForSignal= "); info.append(_waitForSignal);
1427        info.append(", _waitForBlock= "); info.append(_waitForBlock);
1428        info.append(", _waitForWarrant= "); info.append(_waitForWarrant);
1429        info.append("\n\tStatus flags: _overrun= "); info.append(_overrun); info.append(", _rampBlkOccupied= ");
1430        info.append(_rampBlkOccupied);info.append(", _lost= "); info.append(_lost);
1431        if (_protectSignal != null) {
1432            info.append("\n\tWait for Signal \"");info.append(_protectSignal.getDisplayName());info.append("\" protects block ");
1433            info.append(getBlockAt(_idxProtectSignal).getDisplayName()); info.append("\" from approch block \"");
1434            info.append(getBlockAt(_idxProtectSignal - 1).getDisplayName()); info.append("\". Shows aspect \"");
1435            info.append(getSignalSpeedType(_protectSignal)); info.append("\".");
1436        } else {
1437            info.append("\n\tNo signals ahead with speed restrictions");
1438        }
1439        if(_stoppingBlock != null) {
1440            if (_waitForWarrant) {
1441                info.append("\n\tWait for Warrant \"");
1442                Warrant w = getBlockingWarrant(); info.append((w != null?w.getDisplayName():"Unknown"));
1443                info.append("\" owns block \"");info.append(_stoppingBlock.getDisplayName()); info.append("\"");
1444            } else {
1445                Object what = _stoppingBlock.getValue();
1446                String who;
1447                if (what != null) {
1448                    who = what.toString();
1449                } else {
1450                    who = "Unknown Train";
1451                }
1452                info.append("\n\tWait for \""); info.append(who); info.append("\" occupying Block \"");
1453                info.append(_stoppingBlock.getDisplayName()); info.append("\"");
1454            }
1455        } else {
1456            info.append("\n\tNo occupied blocks ahead");
1457        }
1458        if (_message != null) {
1459            info.append("\n\tLast message = ");info.append(_message);
1460        } else {
1461            info.append("\n\tNo messages.");
1462        }
1463
1464        if (_engineer != null) {
1465            info.append("\""); info.append("\n\tEngineer Stack trace:");
1466            for (StackTraceElement elem : _engineer.getStackTrace()) {
1467                info.append("\n\t\t");
1468                info.append(elem.getClassName()); info.append("."); info.append(elem.getMethodName());
1469                info.append(", line "); info.append(elem.getLineNumber());
1470            }
1471            info.append(_engineer.debugInfo());
1472         } else {
1473            info.append("No engineer.");
1474        }
1475        log.info("\n Warrant: {}", info.toString());
1476        return true;
1477    }
1478
1479    protected void startupWarrant() {
1480        _idxCurrentOrder = 0;
1481        // set block state to show our train occupies the block
1482        BlockOrder bo = getBlockOrderAt(0);
1483        OBlock b = bo.getBlock();
1484        b.setValue(_trainName);
1485        b.setState(b.getState() | OBlock.RUNNING);
1486        firePropertyChange(PROPERTY_WARRANT_START, MODE_NONE, _runMode);
1487    }
1488
1489    private void runWarrant(DccThrottle throttle) {
1490        if (_runMode == MODE_LEARN) {
1491            synchronized (this) {
1492                // No Engineer. LearnControlPanel does throttle settings
1493                _student.notifyThrottleFound(throttle);
1494            }
1495        } else {
1496            if (_engineer != null) {    // should not happen
1497                killEngineer(_engineer, true, true);
1498            }
1499            _engineer = new Engineer(this, throttle);
1500
1501            _speedUtil.getBlockSpeedTimes(_commands, _orders);   // initialize SpeedUtil
1502            if (_tempRunBlind) {
1503                _engineer.setRunOnET(true);
1504            }
1505            if (_delayStart || _haltStart) {
1506                _engineer.setHalt(true);    // throttle already at 0
1507                // user must explicitly start train (resume) in a dark block
1508                fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0);   // ready to start msg
1509            }
1510            _delayStart = false;
1511            _engineer.start();
1512
1513            int runState = _engineer.getRunState();
1514            if (_trace || log.isDebugEnabled()) {
1515                log.info("Train \"{}\" on warrant \"{}\" launched. runState= {}", getTrainName(), getDisplayName(), RUN_STATE[runState]);
1516            }
1517            if (runState != HALT && runState != RAMP_HALT) {
1518                setMovement();
1519            }
1520        }
1521    }
1522
1523    private String setPathAt(int idx) {
1524        BlockOrder bo = _orders.get(idx);
1525        OBlock b = bo.getBlock();
1526        String msg = b.allocate(this);
1527        if (msg == null) {
1528            OPath path1 = bo.getPath();
1529            Portal exit = bo.getExitPortal();
1530            OBlock block = getBlockAt(idx+1);
1531            if (block != null) {
1532                Warrant w = block.getWarrant();
1533                if ((w != null && !w.equals(this)) || (w == null && block.isOccupied())) {
1534                    msg =  bo.pathsConnect(path1, exit, block);
1535                    if (msg == null) {
1536                        msg = bo.setPath(this);
1537                    }
1538                }
1539            }
1540            b.showAllocated(this, bo.getPathName());
1541        }
1542        return msg;
1543    }
1544
1545    /**
1546     * Allocate as many blocks as possible from the start of the warrant.
1547     * The first block must be allocated and all blocks of the route must
1548     * be in service. Otherwise partial success is OK.
1549     * Installs listeners for the entire route.
1550     * If occupation by another train is detected, a message will be
1551     * posted to the Warrant List Window. Note that warrants sharing their
1552     * clearance only allocate and set paths one block in advance.
1553     *
1554     * @param orders list of block orders
1555     * @param show _message for use ONLY to display a temporary route) continues to
1556     *  allocate skipping over blocks occupied or owned by another warrant.
1557     * @return error message, if unable to allocate first block or if any block
1558     *         is OUT_OF_SERVICE
1559     */
1560    public String allocateRoute(boolean show, List<BlockOrder> orders) {
1561        if (_totalAllocated && _runMode != MODE_NONE && _runMode != MODE_ABORT) {
1562            return null;
1563        }
1564        if (orders != null) {
1565            _orders = orders;
1566        }
1567        _allocated = false;
1568        _message = null;
1569
1570        int idxSpeedChange = 0;  // idxBlockOrder where speed changes
1571        do {
1572            TrainOrder to = getBlockOrderAt(idxSpeedChange).allocatePaths(this, true);
1573            switch (to._cause) {
1574                case NONE:
1575                    break;
1576               case WARRANT:
1577                   _waitForWarrant = true;
1578                   if (_message == null) {
1579                       _message = to._message;
1580                   }
1581                   if (!show && to._idxContrlBlock == 0) {
1582                       return _message;
1583                   }
1584                   break;
1585                case OCCUPY:
1586                    _waitForBlock = true;
1587                    if (_message == null) {
1588                        _message = to._message;
1589                    }
1590                    break;
1591                case SIGNAL:
1592                    if (Stop.equals(to._speedType)) {
1593                        _waitForSignal = true;
1594                        if (_message == null) {
1595                            _message = to._message;
1596                        }
1597                    }
1598                    break;
1599                default:
1600                    log.error("{}: allocateRoute at block \"{}\" setPath returns: {}",
1601                            getDisplayName(), getBlockAt(idxSpeedChange).getDisplayName(), to.toString());
1602                    if (_message == null) {
1603                        _message = to._message;
1604                    }
1605            }
1606            if (!show) {
1607                if (_message != null || (_shareRoute && idxSpeedChange > 1)) {
1608                    break;
1609                }
1610            }
1611            idxSpeedChange++;
1612        } while (idxSpeedChange < _orders.size());
1613
1614        if (log.isDebugEnabled()) {
1615            log.debug("{}: allocateRoute() _shareRoute= {} show= {}. Break at {} of {}. msg= {}",
1616                getDisplayName(), _shareRoute, show, idxSpeedChange, _orders.size(), _message);
1617        }
1618        _allocated = true; // start block allocated
1619        if (_message == null) {
1620            _totalAllocated = true;
1621            if (show && _shareRoute) {
1622                _message = Bundle.getMessage("sharedRoute");
1623            }
1624        }
1625        if (show) {
1626            return _message;
1627        }
1628        return null;
1629    }
1630
1631    /**
1632     * Deallocates blocks from the current BlockOrder list
1633     */
1634    public void deAllocate() {
1635        if (_runMode == MODE_NONE || _runMode == MODE_ABORT) {
1636            _allocated = false;
1637            _totalAllocated = false;
1638            _routeSet = false;
1639            for (int i = 0; i < _orders.size(); i++) {
1640                deAllocateBlock(_orders.get(i).getBlock());
1641            }
1642        }
1643    }
1644
1645    private boolean deAllocateBlock(OBlock block) {
1646        if (block.isAllocatedTo(this)) {
1647            block.deAllocate(this);
1648            if (block.equals(_stoppingBlock)){
1649                doStoppingBlockClear();
1650            }
1651            return true;
1652        }
1653        return false;
1654    }
1655
1656    /**
1657     * Convenience routine to use from Python to start a warrant.
1658     *
1659     * @param mode run mode
1660     */
1661    public void runWarrant(int mode) {
1662        setRunMode(mode, null, null, null, false);
1663    }
1664
1665    /**
1666     * Set the route paths and turnouts for the warrant. Only the first block
1667     * must be allocated and have its path set. Partial success is OK.
1668     * A message of the first subsequent block that fails allocation
1669     * or path setting is written to a field that is
1670     * displayed in the Warrant List window. When running with block
1671     * detection, occupation by another train or block 'not in use' or
1672     * Signals denying movement are reasons
1673     * for such a message, otherwise only allocation to another warrant
1674     * prevents total success. Note that warrants sharing their clearance
1675     * only allocate and set paths one block in advance.
1676     *
1677     * @param show If true allocateRoute returns messages for display.
1678     * @param orders  BlockOrder list of route. If null, use permanent warrant
1679     *            copy.
1680     * @return message if the first block fails allocation, otherwise null
1681     */
1682    public String setRoute(boolean show, List<BlockOrder> orders) {
1683        if (_shareRoute) { // full route of a shared warrant may be displayed
1684            deAllocate();   // clear route to allow sharing with another warrant
1685        }
1686
1687        // allocateRoute may set _message for status info, but return null msg
1688        _message = allocateRoute(show, orders);
1689        if (_message != null) {
1690            log.debug("{}: setRoute: {}", getDisplayName(), _message);
1691            return _message;
1692        }
1693        _routeSet = true;
1694        return null;
1695    } // setRoute
1696
1697    /**
1698     * Check start block for occupied for start of run
1699     *
1700     * @return error message, if any
1701     */
1702    public String checkStartBlock() {
1703        log.debug("{}: checkStartBlock.", getDisplayName());
1704        BlockOrder bo = _orders.get(0);
1705        OBlock block = bo.getBlock();
1706        String msg = block.allocate(this);
1707        if (msg != null) {
1708            return msg;
1709        }
1710        if (block.isDark() || _tempRunBlind) {
1711            msg = "BlockDark";
1712        } else if (!block.isOccupied()) {
1713            msg = "warnStart";
1714        }
1715        return msg;
1716    }
1717
1718    protected String checkforTrackers() {
1719        BlockOrder bo = _orders.get(0);
1720        OBlock block = bo.getBlock();
1721        log.debug("{}: checkforTrackers at block {}", getDisplayName(), block.getDisplayName());
1722        Tracker t = InstanceManager.getDefault(TrackerTableAction.class).findTrackerIn(block);
1723        if (t != null) {
1724            return Bundle.getMessage("blockInUse", t.getTrainName(), block.getDisplayName());
1725        }
1726        return null;
1727    }
1728
1729    /**
1730     * Report any occupied blocks in the route
1731     *
1732     * @return String
1733     */
1734    public String checkRoute() {
1735        log.debug("{}: checkRoute.", getDisplayName());
1736        if (_orders==null || _orders.isEmpty()) {
1737            return Bundle.getMessage("noBlockOrders");
1738        }
1739        OBlock startBlock = _orders.get(0).getBlock();
1740        for (int i = 1; i < _orders.size(); i++) {
1741            OBlock block = _orders.get(i).getBlock();
1742            if (block.isOccupied() && !startBlock.equals(block)) {
1743                return Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
1744            }
1745            Warrant w = block.getWarrant();
1746            if (w !=null && !this.equals(w)) {
1747                return Bundle.getMessage("AllocatedToWarrant",
1748                        w.getDisplayName(), block.getDisplayName(), w.getTrainName());
1749            }
1750        }
1751        return null;
1752    }
1753
1754    @Override
1755    public void propertyChange(java.beans.PropertyChangeEvent evt) {
1756        if (!(evt.getSource() instanceof NamedBean)) {
1757            return;
1758        }
1759        String property = evt.getPropertyName();
1760        if (log.isDebugEnabled()) {
1761            log.debug("{}: propertyChange \"{}\" new= {} source= {}", getDisplayName(),
1762                    property, evt.getNewValue(), ((NamedBean) evt.getSource()).getDisplayName());
1763        }
1764
1765        if (_protectSignal != null && _protectSignal == evt.getSource()) {
1766            if (property.equals("Aspect") || property.equals("Appearance")) {
1767                // signal controlling warrant has changed.
1768                readStoppingSignal();
1769            }
1770        } else if (property.equals("state")) {
1771            if (_stoppingBlock != null && _stoppingBlock.equals(evt.getSource())) {
1772                // starting block is allocated but not occupied
1773                int newState = ((Number) evt.getNewValue()).intValue();
1774                if ((newState & OBlock.OCCUPIED) != 0) {
1775                    if (_delayStart) { // wait for arrival of train to begin the run
1776                        // train arrived at starting block or last known block of lost train is found
1777                        clearStoppingBlock();
1778                        OBlock block = getBlockAt(0);
1779                        _idxCurrentOrder = 0;
1780                        if (_runMode == MODE_RUN && _engineer == null) {
1781                            _message = acquireThrottle();
1782                        } else if (_runMode == MODE_MANUAL) {
1783                            fireRunStatus(PROPERTY_READY_TO_RUN, -1, 0);   // ready to start msg
1784                            _delayStart = false;
1785                        }
1786                        block._entryTime = System.currentTimeMillis();
1787                        block.setValue(_trainName);
1788                        block.setState(block.getState() | OBlock.RUNNING);
1789                    } else if ((((Number) evt.getNewValue()).intValue() & OBlock.ALLOCATED) == 0) {
1790                        // blocking warrant has released allocation but train still occupies the block
1791                        clearStoppingBlock();
1792                        log.debug("\"{}\" cleared its wait. but block \"{}\" remains occupied", getDisplayName(),
1793                                (((Block)evt.getSource()).getDisplayName()));
1794                    }
1795                } else if ((((Number) evt.getNewValue()).intValue() & OBlock.UNOCCUPIED) != 0) {
1796                    //  blocking occupation has left the stopping block
1797                    clearStoppingBlock();
1798                }
1799            }
1800        }
1801    } //end propertyChange
1802
1803    private String getSignalSpeedType(@Nonnull NamedBean signal) {
1804        String speedType;
1805        if (signal instanceof SignalHead) {
1806            SignalHead head = (SignalHead) signal;
1807            int appearance = head.getAppearance();
1808            speedType = InstanceManager.getDefault(SignalSpeedMap.class)
1809                    .getAppearanceSpeed(head.getAppearanceName(appearance));
1810            if (log.isDebugEnabled()) {
1811                log.debug("{}: SignalHead {} sets appearance speed to {}",
1812                      getDisplayName(), signal.getDisplayName(), speedType);
1813            }
1814        } else {
1815            SignalMast mast = (SignalMast) signal;
1816            String aspect = mast.getAspect();
1817            speedType = InstanceManager.getDefault(SignalSpeedMap.class).getAspectSpeed(
1818                    (aspect== null ? "" : aspect), mast.getSignalSystem());
1819            if (log.isDebugEnabled()) {
1820                log.debug("{}: SignalMast {} sets aspect speed to {}",
1821                      getDisplayName(), signal.getDisplayName(), speedType);
1822            }
1823        }
1824        return speedType;
1825    }
1826
1827    /**
1828     * _protectSignal made an aspect change
1829     */
1830    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
1831    private void readStoppingSignal() {
1832        if (_idxProtectSignal < _idxCurrentOrder) { // signal is behind train. ignore
1833            changeSignalListener(null, _idxCurrentOrder);  // remove signal
1834            return;
1835        }
1836        // Signals may change after entry and while the train in the block.
1837        // Normally these changes are ignored.
1838        // However for the case of an overrun stop aspect, the train is waiting.
1839        if (_idxProtectSignal == _idxCurrentOrder && !_waitForSignal) { // not waiting
1840            changeSignalListener(null, _idxCurrentOrder);  // remove signal
1841            return; // normal case
1842        }// else Train previously overran stop aspect. Continue and respond to signal.
1843
1844        String speedType = getSignalSpeedType(_protectSignal);
1845        String curSpeedType;
1846        if (_waitForSignal) {
1847            curSpeedType = Stop;
1848        } else {
1849            curSpeedType = _engineer.getSpeedType(true);    // current or pending ramp completion
1850        }
1851        if (log.isDebugEnabled()) {
1852            log.debug("{}: Signal \"{}\" changed to aspect \"{}\" {} blocks ahead. curSpeedType= {}",
1853                    getDisplayName(), _protectSignal.getDisplayName(), speedType, _idxProtectSignal-_idxCurrentOrder, curSpeedType);
1854        }
1855
1856        if (curSpeedType.equals(speedType)) {
1857            return;
1858        }
1859        if (_idxProtectSignal > _idxCurrentOrder) {
1860            if (_speedUtil.secondGreaterThanFirst(speedType, curSpeedType)) {
1861                // change to slower speed. Check if speed change should occur now
1862                float availDist = getAvailableDistance(_idxProtectSignal);
1863                float changeDist = getChangeSpeedDistance(_idxProtectSignal, speedType);
1864                if (changeDist > availDist) {
1865                    // Not enough room in blocks ahead. start ramp in current block
1866                    availDist += getAvailableDistanceAt(_idxCurrentOrder);
1867                    if (speedType.equals(Warrant.Stop)) {
1868                        _waitForSignal = true;
1869                    }
1870                    int cmdStartIdx = _engineer.getCurrentCommandIndex(); // blkSpeedInfo.getFirstIndex();
1871                    if (!doDelayRamp(availDist, changeDist, _idxProtectSignal, speedType, cmdStartIdx)) {
1872                        log.info("No room for train {} to ramp to \"{}\" from \"{}\" for signal \"{}\"!. availDist={}, changeDist={} on warrant {}",
1873                                getTrainName(), speedType, curSpeedType, _protectSignal.getDisplayName(),
1874                                availDist,  changeDist, getDisplayName());
1875                    }   // otherwise will do ramp when entering a block ahead
1876                }
1877                return;
1878            }
1879        }
1880        if (!speedType.equals(Warrant.Stop)) {  // a moving aspect clears a signal wait
1881            if (_waitForSignal) {
1882                // signal protecting next block just released its hold
1883                _curSignalAspect = speedType;
1884                _waitForSignal = false;
1885                if (_trace || log.isDebugEnabled()) {
1886                    log.info(Bundle.getMessage("SignalCleared", _protectSignal.getDisplayName(), speedType, _trainName));
1887                }
1888                ThreadingUtil.runOnGUIDelayed(() -> {
1889                    restoreRunning(speedType);
1890                }, 2000);
1891            }
1892        }
1893    }
1894
1895
1896    /*
1897     * return distance from the exit of the current block "_idxCurrentOrder"
1898     * to the entrance of the "idxChange" block.
1899     */
1900    private float getAvailableDistance(int idxChange) {
1901        float availDist = 0;
1902        int idxBlockOrder = _idxCurrentOrder + 1;
1903        if (idxBlockOrder < _orders.size() - 1) {
1904            while (idxBlockOrder < idxChange) {
1905                availDist += getAvailableDistanceAt(idxBlockOrder++);   // distance to next block
1906            }
1907        }
1908        return availDist;
1909    }
1910
1911    /*
1912     * Get distance needed to ramp so the speed into the next block satisfies the speedType
1913     * @param idxBlockOrder blockOrder index of entrance block
1914     */
1915    private float getChangeSpeedDistance(int idxBlockOrder, String speedType) {
1916        float speedSetting = _engineer.getSpeedSetting();       // current speed
1917        // Estimate speed at start of ramp
1918        float enterSpeed;   // speed at start of ramp
1919        if (speedSetting > 0.1f && (_idxCurrentOrder == idxBlockOrder - 1)) {
1920            // if in the block immediately before the entrance block, use current speed
1921            enterSpeed = speedSetting;
1922        } else { // else use entrance speed of previous block
1923            String currentSpeedType = _engineer.getSpeedType(false); // current speed type
1924            float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder - 1).getEntranceSpeed();
1925            enterSpeed = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType);
1926        }
1927        float scriptSpeed = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getEntranceSpeed();
1928        float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType);
1929        // compare distance needed for script throttle at entrance to entrance speed,
1930        // to the distance needed for current throttle to entrance speed.
1931        float enterLen = _speedUtil.getRampLengthForEntry(enterSpeed, endSpeed);
1932        // add buffers for signal and safety clearance
1933        float bufDist = getEntranceBufferDist(idxBlockOrder);
1934//        log.debug("{}: getChangeSpeedDistance curSpeed= {} enterSpeed= {} endSpeed= {}", getDisplayName(), speedSetting, enterSpeed, endSpeed);
1935        return enterLen + bufDist;
1936    }
1937
1938    private void doStoppingBlockClear() {
1939        if (_stoppingBlock == null) {
1940            return;
1941        }
1942        _stoppingBlock.removePropertyChangeListener(this);
1943        _stoppingBlock = null;
1944        _idxStoppingBlock = -1;
1945    }
1946
1947    /**
1948     * Called when a rogue or warranted train has left a block.
1949     * Also called from propertyChange() to allow warrant to acquire a throttle
1950     * and launch an engineer. Also called by retry control command to help user
1951     * work out of an error condition.
1952     */
1953    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
1954    synchronized private void clearStoppingBlock() {
1955        if (_stoppingBlock == null) {
1956            return;
1957        }
1958        String name = _stoppingBlock.getDisplayName();
1959        doStoppingBlockClear();
1960
1961        if (_delayStart) {
1962            return;    // don't start. Let user resume start
1963        }
1964        if (_trace || log.isDebugEnabled()) {
1965            String reason;
1966            if (_waitForBlock) {
1967                reason = Bundle.getMessage("Occupancy");
1968            } else {
1969                reason = Bundle.getMessage("Warrant");
1970            }
1971            log.info(Bundle.getMessage("StopBlockCleared",
1972                    getTrainName(), getDisplayName(), reason, name));
1973        }
1974        cancelDelayRamp();
1975        int time = 1000;
1976        if (_waitForBlock) {
1977            _waitForBlock = false;
1978            time = 4000;
1979        }
1980        if (_waitForWarrant) {
1981            _waitForWarrant = false;
1982            time = 3000;
1983        }
1984        String speedType;
1985        if (_curSignalAspect != null) {
1986            speedType = _curSignalAspect;
1987        } else {
1988            speedType = _engineer.getSpeedType(false); // current speed type
1989        }
1990        ThreadingUtil.runOnGUIDelayed(() -> {
1991            restoreRunning(speedType);
1992        }, time);
1993    }
1994
1995    private String okToRun() {
1996        boolean cannot = false;
1997        StringBuilder sb = new StringBuilder();
1998        if (_waitForSignal) {
1999            sb.append(Bundle.getMessage("Signal"));
2000            cannot = true;
2001        }
2002        if (_waitForWarrant) {
2003            if (cannot) {
2004                sb.append(", ");
2005            } else {
2006                cannot = true;
2007            }
2008            Warrant w = getBlockingWarrant();
2009           if (w != null) {
2010                sb.append(Bundle.getMessage("WarrantWait",  w.getDisplayName()));
2011            } else {
2012                sb.append(Bundle.getMessage("WarrantWait", "Unknown"));
2013            }
2014        }
2015        if (_waitForBlock) {
2016            if (cannot) {
2017                sb.append(", ");
2018            } else {
2019                cannot = true;
2020            }
2021            sb.append(Bundle.getMessage("Occupancy"));
2022        }
2023
2024        if (_engineer != null) {
2025            int runState = _engineer.getRunState();
2026            if (runState == HALT || runState == RAMP_HALT) {
2027                if (cannot) {
2028                    sb.append(", ");
2029                } else {
2030                    cannot = true;
2031                }
2032                sb.append(Bundle.getMessage("userHalt"));
2033            }
2034        }
2035        if (cannot) {
2036            return sb.toString();
2037        }
2038        return null;
2039    }
2040
2041    /**
2042     * A layout condition that has restricted or stopped a train has been cleared.
2043     * i.e. Signal aspect, rogue occupied block, contesting warrant or user halt.
2044     * This may or may not be all the conditions restricting speed.
2045     * @return true if automatic restart is done
2046     */
2047    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2048    private boolean restoreRunning(String speedType) {
2049        _message = okToRun();
2050        boolean returnOK;
2051        if (_message == null) {
2052            BlockOrder bo = getBlockOrderAt(_idxCurrentOrder);
2053            TrainOrder to = bo.allocatePaths(this, true);
2054            OBlock block = bo.getBlock();
2055            if (log.isDebugEnabled()) {
2056                log.debug("{}: restoreRunning {}", getDisplayName(), to.toString());
2057            }
2058            switch (to._cause) {    // to._cause - precedence of checks is WARRANT, OCCUPY, SIGNAL
2059                case NONE:
2060                    returnOK = doRestoreRunning(block, speedType);
2061                    break;
2062                case WARRANT:
2063                   _waitForWarrant = true;
2064                   _message = to._message;
2065                   setStoppingBlock(to._idxContrlBlock);
2066                   returnOK = false;
2067                   break;
2068                case OCCUPY:
2069                    if (_overrun || _lost) {
2070                        _message = setPathAt(_idxCurrentOrder);
2071                        if (_message == null) {
2072                            returnOK = doRestoreRunning(block, speedType);
2073                        } else {
2074                            returnOK = false;
2075                        }
2076                        if (_lost && returnOK) {
2077                            _lost = false;
2078                        }
2079                        break;
2080                    }
2081                    returnOK = false;
2082                    _waitForBlock = true;
2083                    _message = to._message;
2084                    setStoppingBlock(to._idxContrlBlock);
2085                    break;
2086                case SIGNAL:
2087                    if (to._idxContrlBlock == _idxCurrentOrder) {
2088                        returnOK = doRestoreRunning(block, speedType);
2089                    } else {
2090                        returnOK = false;
2091                    }
2092                    if (returnOK && Stop.equals(to._speedType)) {
2093                        _waitForSignal = true;
2094                        _message = to._message;
2095                        setProtectingSignal(to._idxContrlBlock);
2096                        returnOK = false;
2097                        break;
2098                    }
2099                    speedType = to._speedType;
2100                    returnOK = doRestoreRunning(block, speedType);
2101                    break;
2102                default:
2103                    log.error("restoreRunning TrainOrder {}", to.toString());
2104                    _message = to._message;
2105                    returnOK = false;
2106            }
2107        } else {
2108            returnOK = false;
2109        }
2110        if (!returnOK) {
2111            String blockName = getBlockAt(_idxCurrentOrder).getDisplayName();
2112            if (_trace || log.isDebugEnabled()) {
2113                log.info(Bundle.getMessage("trainWaiting", getTrainName(), _message, blockName));
2114            }
2115            fireRunStatus(PROPERTY_CANNOT_RUN, blockName, _message);
2116        }
2117        return returnOK;
2118    }
2119
2120    private boolean doRestoreRunning(OBlock block, String speedType) {
2121        _overrun = false;
2122        _curSignalAspect = null;
2123        setPathAt(_idxCurrentOrder);    // show ownership and train Id
2124
2125        // It is highly likely an event to restart a speed increase occurs when the train
2126        // position is in the middle or end of the block. Since 'lookAheadforSpeedChange'
2127        // assumes the train is at the start of a block, don't ramp up if the
2128        // train may not enter the next block. No room for both ramp up and ramp down
2129        BlockOrder bo = getBlockOrderAt(_idxCurrentOrder+1);
2130        if (bo != null) {
2131            TrainOrder to = bo.allocatePaths(this, true);
2132            if (Warrant.Stop.equals(to._speedType)) {
2133                _message = to._message;
2134                switch (to._cause) {
2135                    case NONE:
2136                        break;
2137                   case WARRANT:
2138                       _waitForWarrant = true;
2139                       setStoppingBlock(to._idxContrlBlock);
2140                       break;
2141                    case OCCUPY:
2142                        _waitForBlock = true;
2143                        setStoppingBlock(to._idxContrlBlock);
2144                        break;
2145                    case SIGNAL:
2146                        _waitForSignal = true;
2147                        setProtectingSignal(to._idxContrlBlock);
2148                        break;
2149                    default:
2150                }
2151                return false;
2152            }
2153        }
2154        _engineer.clearWaitForSync(block);
2155        if (log.isDebugEnabled()) {
2156            log.debug("{}: restoreRunning(): rampSpeedTo to \"{}\"",
2157                    getDisplayName(), speedType);
2158        }
2159        rampSpeedTo(speedType, -1);
2160        // continue, there may be blocks ahead that need a speed decrease before entering them
2161        if (!_overrun && _idxCurrentOrder < _orders.size() - 1) {
2162            lookAheadforSpeedChange(speedType, speedType);
2163        } // else at last block, forget about speed changes
2164        return true;
2165    }
2166
2167    /**
2168     * Stopping block only used in MODE_RUN _stoppingBlock is an occupied OBlock
2169     * preventing the train from continuing the route OR another warrant
2170     * is preventing this warrant from allocating the block to continue.
2171     * <p>
2172     */
2173    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2174    private void setStoppingBlock(int idxBlock) {
2175        OBlock block = getBlockAt(idxBlock);
2176        if (block == null) {
2177            return;
2178        }
2179        // _idxCurrentOrder == 0 may be a delayed start waiting for loco.
2180        // Otherwise don't set _stoppingBlock for a block occupied by train
2181        if (idxBlock < 0 || (_idxCurrentOrder == idxBlock && !_lost)) {
2182            return;
2183        }
2184        OBlock prevBlk = _stoppingBlock;
2185        if (_stoppingBlock != null) {
2186            if (_stoppingBlock.equals(block)) {
2187                return;
2188            }
2189
2190            int idxStop = getIndexOfBlockAfter(_stoppingBlock, _idxCurrentOrder);
2191            if ((idxBlock < idxStop) || idxStop < 0) {
2192                prevBlk.removePropertyChangeListener(this);
2193            } else {
2194                if (idxStop < _idxCurrentOrder) {
2195                    log.error("{}: _stoppingBlock \"{}\" index {} < _idxCurrentOrder {}",
2196                            getDisplayName(), _stoppingBlock.getDisplayName(), idxStop, _idxCurrentOrder);
2197                }
2198                return;
2199            }
2200        }
2201        _stoppingBlock = block;
2202        _idxStoppingBlock = idxBlock;
2203        _stoppingBlock.addPropertyChangeListener(this);
2204        if ((_trace || log.isDebugEnabled()) && (_waitForBlock || _waitForWarrant)) {
2205            String reason;
2206            String cause;
2207            if (_waitForWarrant) {
2208                reason = Bundle.getMessage("Warrant");
2209                Warrant w = block.getWarrant();
2210                if (w != null) {
2211                    cause = w.getDisplayName();
2212                } else {
2213                    cause = Bundle.getMessage("Unknown");
2214                }
2215            } else if (_waitForBlock) {
2216                reason = Bundle.getMessage("Occupancy");
2217                cause = (String)block.getValue();
2218                if (cause == null) {
2219                    cause = Bundle.getMessage("unknownTrain");
2220                }
2221            } else if (_lost) {
2222                reason = Bundle.getMessage("Lost");
2223                cause = Bundle.getMessage("Occupancy");
2224            } else {
2225                reason = Bundle.getMessage("Start");
2226                cause = "";
2227            }
2228            log.info(Bundle.getMessage("StopBlockSet", _stoppingBlock.getDisplayName(), getTrainName(), reason, cause));
2229        }
2230    }
2231
2232    /**
2233     * set signal listening for aspect change for block at index.
2234     * return true if signal is set.
2235     */
2236    private boolean setProtectingSignal(int idx) {
2237        if (_idxProtectSignal == idx) {
2238            return true;
2239        }
2240        BlockOrder blkOrder = getBlockOrderAt(idx);
2241        NamedBean signal = blkOrder.getSignal();
2242
2243        if (_protectSignal != null && _protectSignal.equals(signal)) {
2244            // Must be the route coming back to the same block. Same signal, move index only.
2245            if (_idxProtectSignal < idx && idx >= 0) {
2246                _idxProtectSignal = idx;
2247            }
2248            return true;
2249        }
2250
2251        if (_protectSignal != null) {
2252            if (idx > _idxProtectSignal && _idxProtectSignal > _idxCurrentOrder) {
2253                return true;
2254            }
2255        }
2256
2257        return changeSignalListener(signal, idx);
2258    }
2259
2260    /**
2261     * if current listening signal is not at signalIndex, remove listener and
2262     * set new listening signal
2263     */
2264    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2265    private boolean changeSignalListener(NamedBean  signal,  int signalIndex) {
2266        if (signalIndex == _idxProtectSignal) {
2267            return true;
2268        }
2269//        StringBuilder sb = new StringBuilder(getDisplayName());
2270        if (_protectSignal != null) {
2271            _protectSignal.removePropertyChangeListener(this);
2272/*            if (log.isDebugEnabled()) {
2273                sb.append("Removes \"");
2274                sb.append(_protectSignal.getDisplayName());
2275                sb.append("\" at \"");
2276                sb.append(getBlockAt(_idxProtectSignal).getDisplayName());
2277                sb.append("\"");
2278            }*/
2279            _protectSignal = null;
2280            _idxProtectSignal = -1;
2281        }
2282        boolean ret = false;
2283        if (signal != null) {
2284            _protectSignal = signal;
2285            _idxProtectSignal = signalIndex;
2286            _protectSignal.addPropertyChangeListener(this);
2287            if (_trace || log.isDebugEnabled()) {
2288                log.info(Bundle.getMessage("ProtectSignalSet", getTrainName(),
2289                        _protectSignal.getDisplayName(), getBlockAt(_idxProtectSignal).getDisplayName()));
2290            }
2291            ret = true;
2292        }
2293        return ret;
2294    }
2295
2296    /**
2297     * Check if this is the next block of the train moving under the warrant
2298     * Learn mode assumes route is set and clear. Run mode update conditions.
2299     * <p>
2300     * Must be called on Layout thread.
2301     *
2302     * @param block Block in the route is going active.
2303     */
2304    @InvokeOnLayoutThread
2305    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2306    protected void goingActive(OBlock block) {
2307        if (log.isDebugEnabled()) {
2308            if (!ThreadingUtil.isLayoutThread()) {
2309                log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback"));
2310                stopWarrant(true, true);
2311                return;
2312            }
2313        }
2314
2315        if (_runMode == MODE_NONE) {
2316            return;
2317        }
2318        int activeIdx = getIndexOfBlockAfter(block, _idxCurrentOrder);
2319        if (log.isDebugEnabled()) {
2320            log.debug("{}: **Block \"{}\" goingActive. activeIdx= {}, _idxCurrentOrder= {}.",
2321                    getDisplayName(), block.getDisplayName(), activeIdx, _idxCurrentOrder);
2322        }
2323        Warrant w = block.getWarrant();
2324        if (w == null || !this.equals(w)) {
2325            if (log.isDebugEnabled()) {
2326                log.debug("{}: **Block \"{}\" owned by {}!",
2327                        getDisplayName(), block.getDisplayName(), (w==null?"NO One":w.getDisplayName()));
2328            }
2329            return;
2330        }
2331        if (_lost && !getBlockAt(_idxCurrentOrder).isOccupied()) {
2332            _idxCurrentOrder = activeIdx;
2333            log.info("Train \"{}\" found at block \"{}\" of warrant {}.",
2334                       getTrainName(), block.getDisplayName(),  getDisplayName());
2335            _lost = false;
2336            rampSpeedTo(_engineer.getSpeedType(false), - 1); // current speed type
2337            setMovement();
2338            return;
2339        }
2340        if (activeIdx <= 0) {
2341            // if _idxCurrentOrder == 0, (i.e. starting block) case 0 is handled as the _stoppingBlock
2342            return;
2343        }
2344        if (activeIdx == _idxCurrentOrder) {
2345            // unusual occurrence.  dirty track? sensor glitch?
2346            if (_trace || log.isDebugEnabled()) {
2347                log.info(Bundle.getMessage("RegainDetection", getTrainName(), block.getDisplayName()));
2348            }
2349        } else if (activeIdx == _idxCurrentOrder + 1) {
2350            if (_delayStart) {
2351                log.warn("{}: Rogue entered Block \"{}\" ahead of {}.",
2352                        getDisplayName(), block.getDisplayName(), getTrainName());
2353                _message = Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
2354                return;
2355            }
2356            // Since we are moving at speed we assume it is our train that entered the block
2357            // continue on.
2358            _idxCurrentOrder = activeIdx;
2359        } else if (activeIdx > _idxCurrentOrder + 1) {
2360            // if previous blocks are dark, this could be for our train
2361            // check from current (last known) block to this just activated block
2362            for (int idx = _idxCurrentOrder + 1; idx < activeIdx; idx++) {
2363                OBlock preBlock = getBlockAt(idx);
2364                if (!preBlock.isDark()) {
2365                    // not dark, therefore not our train
2366                    if (log.isDebugEnabled()) {
2367                        OBlock curBlock = getBlockAt(_idxCurrentOrder);
2368                        log.debug("Rogue train entered block \"{}\" ahead of train {} currently in block \"{}\"!",
2369                                block.getDisplayName(), _trainName, curBlock.getDisplayName());
2370                    }
2371                    return;
2372                }
2373                // we assume this is our train entering block
2374                _idxCurrentOrder = activeIdx;
2375            }
2376            // previous blocks were checked as UNDETECTED above
2377            // Indicate the previous dark block was entered
2378            OBlock prevBlock = getBlockAt(activeIdx - 1);
2379            prevBlock._entryTime = System.currentTimeMillis() - 5000; // arbitrary. Just say 5 seconds
2380            prevBlock.setValue(_trainName);
2381            prevBlock.setState(prevBlock.getState() | OBlock.RUNNING);
2382            if (log.isDebugEnabled()) {
2383                log.debug("{}: Train moving from UNDETECTED block \"{}\" now entering block\"{}\"",
2384                        getDisplayName(), prevBlock.getDisplayName(), block.getDisplayName());
2385            }
2386        } else if (_idxCurrentOrder > activeIdx) {
2387            // unusual occurrence.  dirty track, sensor glitch, too fast for goingInactive() for complete?
2388            log.info("Tail of Train {} regained detection behind Block= {} at block= {}",
2389                    getTrainName(), block.getDisplayName(), getBlockAt(activeIdx).getDisplayName());
2390            return;
2391        }
2392        // Since we are moving we assume it is our train entering the block
2393        // continue on.
2394        setHeadOfTrain(block);
2395        if (_engineer != null) {
2396            _engineer.clearWaitForSync(block); // Sync commands if train is faster than ET
2397        }
2398        if (_trace) {
2399            log.info(Bundle.getMessage("TrackerBlockEnter", getTrainName(),  block.getDisplayName()));
2400        }
2401        fireRunStatus("blockChange", getBlockAt(activeIdx - 1), block);
2402        if (_runMode == MODE_LEARN) {
2403            return;
2404        }
2405        // _idxCurrentOrder has been incremented. Warranted train has entered this block.
2406        // Do signals, speed etc.
2407        if (_idxCurrentOrder < _orders.size() - 1) {
2408            if (_engineer != null) {
2409                BlockOrder bo = _orders.get(_idxCurrentOrder + 1);
2410                if (bo.getBlock().isDark()) {
2411                    // can't detect next block, use ET
2412                    _engineer.setRunOnET(true);
2413                } else if (!_tempRunBlind) {
2414                    _engineer.setRunOnET(false);
2415                }
2416            }
2417        }
2418        if (log.isTraceEnabled()) {
2419            log.debug("{}: end of goingActive. leaving \"{}\" entered \"{}\"",
2420                    getDisplayName(), getBlockAt(activeIdx - 1).getDisplayName(), block.getDisplayName());
2421        }
2422        setMovement();
2423    } //end goingActive
2424
2425    private void setHeadOfTrain(OBlock block ) {
2426        block.setValue(_trainName);
2427        block.setState(block.getState() | OBlock.RUNNING);
2428        if (_runMode == MODE_RUN && _idxCurrentOrder > 0 && _idxCurrentOrder < _orders.size()) {
2429            _speedUtil.leavingBlock(_idxCurrentOrder - 1);
2430        }
2431    }
2432
2433    /**
2434     * @param block Block in the route is going Inactive
2435     */
2436    @InvokeOnLayoutThread
2437    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
2438    protected void goingInactive(OBlock block) {
2439        if (log.isDebugEnabled()) {
2440            if (!ThreadingUtil.isLayoutThread()) {
2441                log.error("{} invoked on wrong thread", getDisplayName(), new Exception("traceback"));
2442            }
2443        }
2444        if (_runMode == MODE_NONE) {
2445            return;
2446        }
2447
2448        int idx = getIndexOfBlockBefore(_idxCurrentOrder, block); // if idx >= 0, it is in this warrant
2449        if (log.isDebugEnabled()) {
2450            log.debug("{}: *Block \"{}\" goingInactive. idx= {}, _idxCurrentOrder= {}.",
2451                    getDisplayName(), block.getDisplayName(), idx, _idxCurrentOrder);
2452        }
2453        if (idx > _idxCurrentOrder) {
2454            return;
2455        }
2456        releaseBlock(block, idx);
2457        block.setValue(null);
2458        if (idx == _idxCurrentOrder) {
2459            // Train not visible if current block goes inactive. This is OK if the next block is Dark.
2460            if (_idxCurrentOrder + 1 < _orders.size()) {
2461                OBlock nextBlock = getBlockAt(_idxCurrentOrder + 1);
2462                if (nextBlock.isDark()) {
2463                    goingActive(nextBlock); // fake occupancy for dark block
2464                    return;
2465                }
2466                if (checkForOverrun(nextBlock)) {
2467                    return;
2468                }
2469            }
2470            _lost = true;
2471            if (_engineer != null) {
2472                setSpeedToType(Stop);   // set 0 throttle
2473                setStoppingBlock(_idxCurrentOrder);
2474            }
2475            if (_trace) {
2476                log.info(Bundle.getMessage("ChangedRoute", _trainName, block.getDisplayName(), getDisplayName()));
2477            }
2478            fireRunStatus("blockChange", block, null);  // train is lost
2479        }
2480    } // end goingInactive
2481
2482    /**
2483     * Deallocates all blocks prior to and including block at index idx
2484     * of _orders, if not needed again.
2485     * Comes from goingInactive, i.e. warrant has a listener on the block.
2486     * @param block warrant is releasing
2487     * @param idx index in BlockOrder list
2488     */
2489    private void releaseBlock(OBlock block, int idx) {
2490        /*
2491         * Deallocate block if train will not use the block again. Warrant
2492         * could loop back and re-enter blocks previously traversed. That is,
2493         * they will need to re-allocation of blocks ahead.
2494         * Previous Dark blocks also need deallocation and other trains or cars
2495         * dropped may have prevented previous blocks from going inactive.
2496         * Thus we must deallocate backward until we reach inactive detectable blocks
2497         * or blocks we no longer own.
2498         */
2499        for (int i = idx; i > -1; i--) {
2500            boolean neededLater = false;
2501            OBlock curBlock = getBlockAt(i);
2502            for (int j = i + 1; j < _orders.size(); j++) {
2503                if (curBlock.equals(getBlockAt(j))) {
2504                    neededLater = true;
2505                }
2506            }
2507            if (!neededLater) {
2508                if (deAllocateBlock(curBlock)) {
2509                    curBlock.setValue(null);
2510                    _totalAllocated = false;
2511                }
2512            } else {
2513                if (curBlock.isAllocatedTo(this)) {
2514                    // Can't deallocate, but must listen for followers
2515                    // who may be occupying the block
2516                    if (_idxCurrentOrder != idx + 1) {
2517                        curBlock.setValue(null);
2518                    }
2519                    if (curBlock.equals(_stoppingBlock)){
2520                        doStoppingBlockClear();
2521                    }
2522                }
2523                if (_shareRoute) { // don't deallocate if closer than 2 blocks, otherwise deallocate
2524                    int k = Math.min(3, _orders.size());
2525                    while (k > _idxCurrentOrder) {
2526                        if (!curBlock.equals(getBlockAt(k))) {
2527                            if (deAllocateBlock(curBlock)) {
2528                                curBlock.setValue(null);
2529                                _totalAllocated = false;
2530                            }
2531                        }
2532                        k--;
2533                    }
2534                }
2535            }
2536        }
2537    }
2538
2539    /*
2540     * This block is a possible overrun. If permitted, we may claim ownership.
2541     * BlockOrder index of block is _idxCurrentOrder + 1
2542     * return true, if warrant can claim occupation and ownership
2543     */
2544    private boolean checkForOverrun(OBlock block) {
2545        if (block.isOccupied() && (System.currentTimeMillis() - block._entryTime < 5000)) {
2546            // Went active within the last 5 seconds. Likely an overrun
2547            _overrun = true;
2548            _message = setPathAt(_idxCurrentOrder + 1);    //  no TrainOrder checks. allocates and sets path
2549            if (_message == null) {   // OK we own the block now.
2550                _idxCurrentOrder++;
2551                // insulate possible non-GUI thread making this call (e.g. Engineer)
2552                ThreadingUtil.runOnGUI(()-> goingActive(block));
2553                return true ;
2554            }
2555        }
2556        return false;
2557    }
2558
2559    @Override
2560    public void dispose() {
2561        if (_runMode != MODE_NONE) {
2562            stopWarrant(true, true);
2563        }
2564        super.dispose();
2565    }
2566
2567    @Override
2568    public String getBeanType() {
2569        return Bundle.getMessage("BeanNameWarrant");
2570    }
2571
2572    private class CommandDelay extends Thread {
2573
2574        String _speedType;
2575//        long _startTime = 0;
2576        long _waitTime = 0;
2577        float _waitSpeed;
2578        boolean quit = false;
2579        int _endBlockIdx;
2580
2581        CommandDelay(@Nonnull String speedType, long startWait, float waitSpeed, int endBlockIdx) {
2582            _speedType = speedType;
2583            _waitTime = startWait;
2584            _waitSpeed = waitSpeed;
2585            _endBlockIdx = endBlockIdx;
2586            setName("CommandDelay(" + getTrainName() + "-" + speedType +")");
2587        }
2588
2589        // check if request for a duplicate CommandDelay can be cancelled
2590        boolean isDuplicate(String speedType, long startWait, int endBlockIdx) {
2591            if (endBlockIdx == _endBlockIdx && speedType.equals(_speedType) ) { // &&
2592//                    (_waitTime - (System.currentTimeMillis() - _startTime)) < startWait) {
2593                return true;    // keeps this thread
2594            }
2595            return false;   // not a duplicate or does not shorten time wait. this thread will be cancelled
2596        }
2597
2598        @Override
2599        @SuppressFBWarnings(value = "WA_NOT_IN_LOOP", justification = "notify never called on this thread")
2600        public void run() {
2601            synchronized (this) {
2602//                _startTime = System.currentTimeMillis();
2603                boolean ramping = _engineer.isRamping();
2604                if (ramping) {
2605                    long time = 0;
2606                    while (time <= _waitTime) {
2607                        if (_engineer.getSpeedSetting() >= _waitSpeed) {
2608                            break; // stop ramping beyond this speed
2609                        }
2610                        try {
2611                            wait(100);
2612                        } catch (InterruptedException ie) {
2613                            if (log.isDebugEnabled() && quit) {
2614                                log.debug("CommandDelay interrupt. Ramp to {} not done. warrant {}",
2615                                        _speedType, getDisplayName());
2616                            }
2617                        }
2618                        time += 50;
2619                    }
2620                } else {
2621                    try {
2622                        wait(_waitTime);
2623                    } catch (InterruptedException ie) {
2624                        if (log.isDebugEnabled() && quit) {
2625                            log.debug("CommandDelay interrupt.  Ramp to {} not done. warrant {}",
2626                                    _speedType, getDisplayName());
2627                        }
2628                    }
2629                }
2630
2631                if (!quit && _engineer != null) {
2632                    if (_noRamp) {
2633                        setSpeedToType(_speedType);
2634                    } else {
2635                        _engineer.rampSpeedTo(_speedType, _endBlockIdx);
2636                    }
2637                }
2638            }
2639            endDelayCommand();
2640        }
2641    }
2642
2643    synchronized private void cancelDelayRamp() {
2644        if (_delayCommand != null) {
2645            log.debug("{}: cancelDelayRamp() called. _speedType= {}", getDisplayName(), _delayCommand._speedType);
2646            _delayCommand.quit = true;
2647            _delayCommand.interrupt();
2648            _delayCommand = null;
2649        }
2650    }
2651
2652    synchronized private void endDelayCommand() {
2653        _delayCommand = null;
2654    }
2655
2656    private void rampSpeedTo(String speedType, int idx) {
2657        cancelDelayRamp();
2658        if (_noRamp) {
2659            _engineer.setSpeedToType(speedType);
2660            _engineer.setWaitforClear(speedType.equals(Stop) || speedType.equals(EStop));
2661            if (log.isDebugEnabled()) {
2662                log.debug("{}: No Ramp to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName());
2663            }
2664            return;
2665        }
2666        if (log.isDebugEnabled()) {
2667            if (idx < 0) {
2668                log.debug("{}: Ramp up to \"{}\" from block \"{}\"", getDisplayName(), speedType, getCurrentBlockName());
2669            } else {
2670                log.debug("{}: Ramp down to \"{}\" before block \"{}\"", getDisplayName(), speedType, getBlockAt(idx).getDisplayName());
2671            }
2672        }
2673        if (_engineer != null) {
2674            _engineer.rampSpeedTo(speedType, idx);
2675        } else {
2676            log.error("{}: No Engineer!", getDisplayName());
2677        }
2678    }
2679
2680    private void setSpeedToType(String speedType) {
2681        cancelDelayRamp();
2682        _engineer.setSpeedToType(speedType);
2683    }
2684
2685    private void clearWaitFlags(boolean removeListeners) {
2686        if (log.isTraceEnabled()) {
2687            log.trace("{}: Flags cleared {}.", getDisplayName(), removeListeners?"and removed Listeners":"only");
2688        }
2689        _waitForBlock = false;
2690        _waitForSignal = false;
2691        _waitForWarrant = false;
2692        if (removeListeners) {
2693            if (_protectSignal != null) {
2694                _protectSignal.removePropertyChangeListener(this);
2695                _protectSignal = null;
2696                _idxProtectSignal = -1;
2697            }
2698            if (_stoppingBlock != null) {
2699                _stoppingBlock.removePropertyChangeListener(this);
2700                _stoppingBlock = null;
2701                _idxStoppingBlock = -1;
2702            }
2703        }
2704    }
2705
2706    /*
2707     * Return pathLength of the block.
2708     */
2709    private float getAvailableDistanceAt(int idxBlockOrder) {
2710        BlockOrder blkOrder = getBlockOrderAt(idxBlockOrder);
2711        float pathLength = blkOrder.getPathLength();
2712        if (idxBlockOrder == 0 || pathLength <= 20.0f) {
2713            // Position in block is unknown. use calculated distances instead
2714            float blkDist = _speedUtil.getBlockSpeedInfo(idxBlockOrder).getCalcLen();
2715            if (log.isDebugEnabled()) {
2716                log.debug("{}: getAvailableDistanceAt: block \"{}\" using calculated blkDist= {}, pathLength= {}",
2717                        getDisplayName(), blkOrder.getBlock().getDisplayName(), blkDist, pathLength);
2718            }
2719            return blkDist;
2720        } else {
2721            return pathLength;
2722        }
2723    }
2724
2725    private float getEntranceBufferDist(int idxBlockOrder) {
2726        float bufDist = BUFFER_DISTANCE;
2727        if (_waitForSignal) {        // signal restricting speed
2728            bufDist+= getBlockOrderAt(idxBlockOrder).getEntranceSpace(); // signal's adjustment
2729        }
2730        return bufDist;
2731    }
2732
2733    /**
2734     * Called to set the correct speed for the train when the scripted speed
2735     * must be modified due to a track condition (signaled speed or rogue
2736     * occupation). Also called to return to the scripted speed after the
2737     * condition is cleared. Assumes the train occupies the block of the current
2738     * block order.
2739     * <p>
2740     * Looks for speed requirements of this block and takes immediate action if
2741     * found. Otherwise looks ahead for future speed change needs. If speed
2742     * restriction changes are required to begin in this block, but the change
2743     * is not immediate, then determine the proper time delay to start the speed
2744     * change.
2745     */
2746    private void setMovement() {
2747        BlockOrder curBlkOrder = getBlockOrderAt(_idxCurrentOrder);
2748        OBlock curBlock = curBlkOrder.getBlock();
2749        String currentSpeedType = _engineer.getSpeedType(false); // current speed type
2750        String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block
2751        if (entrySpeedType == null) {
2752            entrySpeedType = currentSpeedType;
2753        }
2754        curBlkOrder.setPath(this);  // restore running
2755
2756        if (log.isDebugEnabled()) {
2757            SpeedState speedState = _engineer.getSpeedState();
2758            int runState = _engineer.getRunState();
2759            log.debug("{}: SET MOVEMENT Block \"{}\" runState= {}, speedState= {} for currentSpeedType= {}. entrySpeedType= {}.",
2760                    getDisplayName(), curBlock.getDisplayName(), RUN_STATE[runState], speedState.toString(),
2761                    currentSpeedType, entrySpeedType);
2762            log.debug("{}: Flags: _waitForBlock={}, _waitForSignal={}, _waitForWarrant={} curThrottle= {}.",
2763                    getDisplayName(), _waitForBlock, _waitForSignal, _waitForWarrant, _engineer.getSpeedSetting());
2764            if (_message != null) {
2765                log.debug("{}: _message ({}) ", getDisplayName(), _message);
2766            }
2767        }
2768
2769        // Check that flags and states agree with expected speed and position
2770        // A signal drop down can appear to be a speed violation, but only when a violation when expected
2771        if (_idxCurrentOrder > 0) {
2772            if (_waitForSignal) {
2773                if (_idxProtectSignal == _idxCurrentOrder) {
2774                    makeOverrunMessage(curBlkOrder);
2775                    setSpeedToType(Stop); // immediate decrease
2776                    return;
2777                }
2778            }
2779            if (_idxStoppingBlock == _idxCurrentOrder) {
2780                if (_waitForBlock || _waitForWarrant) {
2781                    makeOverrunMessage(curBlkOrder);
2782                    setSpeedToType(Stop); // immediate decrease
2783                    return;
2784                }
2785            }
2786
2787            if (_speedUtil.secondGreaterThanFirst(entrySpeedType, currentSpeedType)) {
2788                // signal or block speed entrySpeedType is less than currentSpeedType.
2789                // Speed for this block is violated so set end speed immediately
2790                NamedBean signal = curBlkOrder.getSignal();
2791                if (signal != null) {
2792                    log.info("Train {} moved past required {} speed for signal \"{}\" at block \"{}\" on warrant {}!",
2793                            getTrainName(), entrySpeedType, signal.getDisplayName(), curBlock.getDisplayName(), getDisplayName());
2794                } else {
2795                    log.info("Train {} moved past required \"{}\" speed at block \"{}\" on warrant {}!",
2796                            getTrainName(), entrySpeedType, curBlock.getDisplayName(), getDisplayName());
2797                }
2798                fireRunStatus("SignalOverrun", (signal!=null?signal.getDisplayName():curBlock.getDisplayName()),
2799                        entrySpeedType); // message of speed violation
2800               setSpeedToType(entrySpeedType); // immediate decrease
2801               currentSpeedType = entrySpeedType;
2802            }
2803        } else {    // at origin block and train has arrived,. ready to move
2804            if (Stop.equals(currentSpeedType)) {
2805                currentSpeedType = Normal;
2806            }
2807        }
2808
2809        if (_idxCurrentOrder < _orders.size() - 1) {
2810            lookAheadforSpeedChange(currentSpeedType, entrySpeedType);
2811        } // else at last block, forget about speed changes, return;
2812    }
2813
2814    /*
2815     * Looks for the need to reduce speed ahead. If one is found, mkes an estimate of the
2816     *distance needed to change speeds.  Find the available distance available, including
2817     * the full length of the current path. If the ramp to reduce speed should begin in the
2818     * current block, calls methods to calculate the time lapse before the ramp should begin.
2819     * entrySpeedType (expected type) will be either equal to or greater than currentSpeedType
2820     * for all blocks except rhe first.
2821     */
2822    private void lookAheadforSpeedChange(String currentSpeedType, String entrySpeedType) {
2823        clearWaitFlags(false);
2824        // look ahead for speed type slower than current type, refresh flags
2825        // entrySpeedType is the expected speed to be reached, if no speed change ahead
2826
2827        String speedType = currentSpeedType;    // first slower speedType ahead
2828        int idx = _idxCurrentOrder + 1;
2829        int idxSpeedChange = -1;  // idxBlockOrder where speed changes
2830        int idxContrlBlock = -1;
2831        int limit;
2832        if (_shareRoute) {
2833            limit = Math.min(_orders.size(), _idxCurrentOrder + 3);
2834        } else {
2835            limit = _orders.size();
2836        }
2837        boolean allocate = true;
2838        int numAllocated = 0;
2839        do {
2840            TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, allocate);
2841            if (log.isDebugEnabled()) {
2842                log.debug("{}: lookAheadforSpeedChange {}", getDisplayName(), to.toString());
2843            }
2844            switch (to._cause) {
2845                case NONE:
2846                    break;
2847               case WARRANT:
2848                   _waitForWarrant = true;
2849                   _message = to._message;
2850                   idxContrlBlock = to._idxContrlBlock;
2851                   idxSpeedChange = to._idxEnterBlock;
2852                   speedType = Stop;
2853                   break;
2854                case OCCUPY:
2855                    _waitForBlock = true;
2856                    _message = to._message;
2857                    idxContrlBlock = to._idxContrlBlock;
2858                    idxSpeedChange = to._idxEnterBlock;
2859                    speedType = Stop;
2860                    break;
2861                case SIGNAL:
2862                    speedType = to._speedType;
2863                    if (Stop.equals(speedType)) {
2864                        _waitForSignal = true;
2865                    }
2866                    idxContrlBlock = to._idxContrlBlock;
2867                    idxSpeedChange = to._idxEnterBlock;
2868                    _message = to._message;
2869                    break;
2870                default:
2871                    log.error("{}: lookAheadforSpeedChange at block \"{}\" setPath returns: {}",
2872                            getDisplayName(), getBlockAt(_idxCurrentOrder).getDisplayName(), to.toString());
2873                    _message = to._message;
2874                    setSpeedToType(Stop);
2875                    return;
2876            }
2877            numAllocated++;
2878            if (Stop.equals(speedType)) {
2879                break;
2880            }
2881            if (_shareRoute && numAllocated > 1 ) {
2882                allocate = false;
2883            }
2884            idx++;
2885
2886        } while ((idxSpeedChange < 0) && (idx < limit) &&
2887                !_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType));
2888
2889        if (!Stop.equals(speedType)) {
2890            while ((idx < limit)) { // allocate and set paths beyond speed change
2891                TrainOrder to = getBlockOrderAt(idx).allocatePaths(this, false);
2892                if (Stop.equals(to._speedType)) {
2893                    break;
2894                }
2895                idx++;
2896            }
2897        }
2898        if (idxSpeedChange < 0) {
2899            idxSpeedChange = _orders.size() - 1;
2900        }
2901
2902        float availDist = getAvailableDistance(idxSpeedChange);  // distance ahead (excluding current block
2903        float changeDist = getChangeSpeedDistance(idxSpeedChange, speedType);    // distance needed to change speed for speedType
2904
2905        if (_speedUtil.secondGreaterThanFirst(currentSpeedType, speedType)) {
2906            // speedType is greater than currentSpeedType. i.e. increase speed.
2907            rampSpeedTo(speedType, -1);
2908            return;
2909        }
2910        if (!currentSpeedType.equals(entrySpeedType)) {
2911            // entrySpeedType is greater than currentSpeedType. i.e. increase speed.
2912            rampSpeedTo(entrySpeedType, -1);
2913            // continue to interrupt ramp up with ramp down
2914        }
2915
2916        // set next signal after current block for aspect speed change
2917        for (int i = _idxCurrentOrder + 1; i < _orders.size(); i++) {
2918            if (setProtectingSignal(i)) {
2919               break;
2920           }
2921        }
2922
2923        OBlock block = getBlockAt(idxSpeedChange);
2924        if (log.isDebugEnabled()) {
2925            log.debug("{}: Speed \"{}\" at block \"{}\" until speed \"{}\" at block \"{}\", availDist={}, changeDist={}",
2926                    getDisplayName(), currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(), speedType,
2927                    block.getDisplayName(), availDist, changeDist);
2928        }
2929
2930        if (changeDist <= availDist) {
2931            cancelDelayRamp(); // interrupts down ramping
2932            clearWaitFlags(false);
2933            return;
2934        }
2935
2936        // Now set stopping condition of flags, if any. Not, if current block is also ahead.
2937        if (_waitForBlock) {
2938            if (!getBlockAt(_idxCurrentOrder).equals(block)) {
2939                setStoppingBlock(idxContrlBlock);
2940            }
2941        } else if (_waitForWarrant) {
2942            // if block is allocated and unoccupied, but cannot set path exit.
2943            if (_stoppingBlock == null) {
2944                setStoppingBlock(idxContrlBlock);
2945            }
2946        }
2947
2948        // Begin a ramp for speed change in this block. If due to a signal, watch that one
2949        if(_waitForSignal) {
2950            // Watch this signal. Should be the previous set signal above.
2951            // If not, then user has not configured signal system to allow room for speed changes.
2952            setProtectingSignal(idxContrlBlock);
2953        }
2954
2955        // either ramp in progress or no changes needed. Stopping conditions set, so move on.
2956        if (!_speedUtil.secondGreaterThanFirst(speedType, currentSpeedType)) {
2957            return;
2958        }
2959
2960        availDist += getAvailableDistanceAt(_idxCurrentOrder);   // Add available length in this block
2961
2962        int cmdStartIdx = _speedUtil.getBlockSpeedInfo(_idxCurrentOrder).getFirstIndex();
2963        if (!doDelayRamp(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx)) {
2964            log.warn("No room for train {} to ramp to \"{}\" from \"{}\" in block \"{}\"!. availDist={}, changeDist={} on warrant {}",
2965                    getTrainName(), speedType, currentSpeedType, getBlockAt(_idxCurrentOrder).getDisplayName(),
2966                    availDist,  changeDist, getDisplayName());
2967        }
2968    }
2969
2970    /*
2971     * if there is sufficient room calculate a wait time, otherwise ramp immediately.
2972     */
2973    synchronized private boolean doDelayRamp(float availDist, float changeDist, int idxSpeedChange, String speedType, int cmdStartIdx) {
2974        String pendingSpeedType = _engineer.getSpeedType(true); // current or pending speed type
2975        if (pendingSpeedType.equals(speedType)) {
2976            return true;
2977        }
2978        if (availDist < 10) {
2979            setSpeedToType(speedType);
2980            return false;
2981        } else {
2982            SpeedState speedState = _engineer.getSpeedState();
2983            switch (speedState) {
2984                case RAMPING_UP:
2985                    makeRampWait(availDist, idxSpeedChange, speedType);
2986                    break;
2987                case RAMPING_DOWN:
2988                    log.error("Already ramping to \"{}\" making ramp for \"{}\".", _engineer.getSpeedType(true), speedType);
2989                //$FALL-THROUGH$
2990                case STEADY_SPEED:
2991                //$FALL-THROUGH$
2992                default:
2993                    makeScriptWait(availDist, changeDist, idxSpeedChange, speedType, cmdStartIdx);
2994            }
2995        }
2996        return true;
2997    }
2998
2999    private void makeRampWait(float availDist, int idxSpeedChange, @Nonnull String speedType) {
3000        BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1);
3001        float speedSetting = info.getExitSpeed();
3002        float endSpeed = _speedUtil.modifySpeed(speedSetting, speedType);
3003
3004        speedSetting = _engineer.getSpeedSetting();       // current speed
3005        float prevSetting = speedSetting;
3006        String currentSpeedType = _engineer.getSpeedType(false); // current speed type
3007
3008        float changeDist = 0;
3009        if (log.isDebugEnabled()) {
3010            log.debug("{}: makeRampWait for speed change \"{}\" to \"{}\". Throttle from={}, to={}, availDist={}",
3011                    getDisplayName(), currentSpeedType, speedType, speedSetting, endSpeed, availDist);
3012            // command index numbers biased by 1
3013        }
3014        float bufDist = getEntranceBufferDist(idxSpeedChange);
3015        float accumTime = 0;    // accumulated time of commands up to ramp start
3016        float accumDist = 0;
3017        RampData ramp = _speedUtil.getRampForSpeedChange(speedSetting, 1.0f);
3018        int time = ramp.getRampTimeIncrement();
3019        ListIterator<Float> iter = ramp.speedIterator(true);
3020
3021        while (iter.hasNext()) {
3022            changeDist = _speedUtil.getRampLengthForEntry(speedSetting, endSpeed) + bufDist;
3023            accumDist += _speedUtil.getDistanceOfSpeedChange(prevSetting, speedSetting, time);
3024            accumTime += time;
3025            prevSetting = speedSetting;
3026            speedSetting = iter.next();
3027
3028            if (changeDist + accumDist >= availDist) {
3029                float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting);
3030                float remDist = changeDist + accumDist - availDist;
3031                if (curTrackSpeed > 0) {
3032                    accumTime -= remDist / curTrackSpeed;
3033                } else {
3034                    log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(),
3035                            speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed);
3036                }
3037                break;
3038            }
3039        }
3040        if (changeDist < accumDist) {
3041            float curTrackSpeed = _speedUtil.getTrackSpeed(speedSetting);
3042            if (curTrackSpeed > 0) {
3043                accumTime += (availDist - changeDist) / curTrackSpeed;
3044            } else {
3045                log.warn("{}: Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(),
3046                        speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed);
3047            }
3048        }
3049
3050        int waitTime = Math.round(accumTime);
3051
3052        if (log.isDebugEnabled()) {
3053            log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm",
3054                    getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist),
3055                    getBlockAt(idxSpeedChange).getDisplayName(), speedSetting, Math.round(availDist));
3056        }
3057        rampSpeedDelay(waitTime, speedType, speedSetting, idxSpeedChange);
3058    }
3059
3060    /**
3061     *  Must start the ramp in current block. ( at _idxCurrentOrder)
3062     *  find the time when ramp should start in this block, then use thread CommandDelay to start the ramp.
3063     *  Train must travel a deltaDist for a deltaTime to the start of the ramp.
3064     *  It travels at throttle settings of scriptSpeed(s) modified by currentSpeedType.
3065     *  trackSpeed(s) of these modified scriptSettings are computed from SpeedProfile
3066     *  waitThrottle is throttleSpeed when ramp is started. This may not be the scriptSpeed now
3067     *  Start with waitThrottle (modSetting) being at the entrance to the block.
3068     *  modSetting gives the current trackSpeed.
3069     *  accumulate the time and distance and determine the distance (changeDist) needed for entrance into
3070     *  block (at idxSpeedChange) requiring speed change to speedType
3071     *  final ramp should modify waitSpeed to endSpeed and must end at the exit of end block (endBlockIdx)
3072     *
3073     * @param availDist     distance available to make the ramp
3074     * @param changeDist    distance needed for the rmp
3075     * @param idxSpeedChange block order index of block to complete change before entry
3076     * @param speedType     speed aspect of speed change
3077     * @param cmdStartIdx   command index of delay
3078     */
3079    private void makeScriptWait(float availDist, float changeDist, int idxSpeedChange, @Nonnull String speedType, int cmdStartIdx) {
3080        BlockSpeedInfo info = _speedUtil.getBlockSpeedInfo(idxSpeedChange - 1);
3081        int cmdEndIdx = info.getLastIndex();
3082        float scriptSpeed = info.getExitSpeed();
3083        float endSpeed = _speedUtil.modifySpeed(scriptSpeed, speedType);
3084
3085        scriptSpeed = _engineer.getScriptSpeed();  // script throttle setting
3086        float speedSetting = _engineer.getSpeedSetting();       // current speed
3087        String currentSpeedType = _engineer.getSpeedType(false); // current speed type
3088
3089        float modSetting = speedSetting;      // _speedUtil.modifySpeed(scriptSpeed, currentSpeedType);
3090        float beginTrackSpeed = _speedUtil.getTrackSpeed(modSetting);   // mm/sec track speed at modSetting
3091        float curTrackSpeed = beginTrackSpeed;
3092        float prevTrackSpeed = beginTrackSpeed;
3093        if (_idxCurrentOrder == 0 && availDist > BUFFER_DISTANCE) {
3094            changeDist = 0;
3095        }
3096        if (log.isDebugEnabled()) {
3097            log.debug("{}: makespeedChange cmdIdx #{} to #{} at speedType \"{}\" to \"{}\". speedSetting={}, changeDist={}, availDist={}",
3098                    getDisplayName(), cmdStartIdx+1, cmdEndIdx+1, currentSpeedType, speedType, speedSetting, changeDist, availDist);
3099            // command index numbers biased by 1
3100        }
3101        float accumTime = 0;    // accumulated time of commands up to ramp start
3102        float accumDist = 0;
3103        Command cmd = _commands.get(cmdStartIdx).getCommand();
3104
3105        if (cmd.equals(Command.NOOP) && beginTrackSpeed > 0) {
3106            accumTime = (availDist - changeDist) / beginTrackSpeed;
3107        } else {
3108            float timeRatio; // time adjustment for current speed type
3109            if (curTrackSpeed > _speedUtil.getRampThrottleIncrement()) {
3110                timeRatio = _speedUtil.getTrackSpeed(scriptSpeed) / curTrackSpeed;
3111            } else {
3112                timeRatio = 1;
3113            }
3114            float bufDist = getEntranceBufferDist(idxSpeedChange);
3115
3116            for (int i = cmdStartIdx; i <= cmdEndIdx; i++) {
3117                ThrottleSetting ts = _commands.get(i);
3118                long time =  ts.getTime();
3119                accumDist += _speedUtil.getDistanceOfSpeedChange(prevTrackSpeed, curTrackSpeed, (int)(time * timeRatio));
3120                accumTime += time * timeRatio;
3121                cmd = ts.getCommand();
3122                if (cmd.equals(Command.SPEED)) {
3123                    prevTrackSpeed = curTrackSpeed;
3124                    CommandValue cmdVal = ts.getValue();
3125                    scriptSpeed = cmdVal.getFloat();
3126                    modSetting = _speedUtil.modifySpeed(scriptSpeed, currentSpeedType);
3127                    curTrackSpeed = _speedUtil.getTrackSpeed(modSetting);
3128                    changeDist = _speedUtil.getRampLengthForEntry(modSetting, endSpeed) + bufDist;
3129                    timeRatio = _speedUtil.getTrackSpeed(scriptSpeed) / curTrackSpeed;
3130                }
3131
3132                if (log.isDebugEnabled()) {
3133                    log.debug("{}: cmd#{} accumTime= {} accumDist= {} changeDist= {}, throttle= {}",
3134                            getDisplayName(), i+1, accumTime, accumDist, changeDist, modSetting);
3135                }
3136                if (changeDist + accumDist >= availDist) {
3137                    float remDist = changeDist + accumDist - availDist;
3138                    if (curTrackSpeed > 0) {
3139                        accumTime -= remDist / curTrackSpeed;
3140                    } else {
3141                        log.warn("{}: script unfit cmd#{}. Cannot compute wait time for \"{}\" ramp to block \"{}\". trackSpeed= {}", getDisplayName(),
3142                                i+1, speedType, getBlockAt(idxSpeedChange).getDisplayName(), curTrackSpeed);
3143                        if (prevTrackSpeed > 0) {
3144                            accumTime -= remDist / prevTrackSpeed;
3145                        }
3146                    }
3147                    break;
3148                }
3149                if (cmd.equals(Command.NOOP)) {
3150                    // speed change is supposed to start in current block
3151                    // start ramp in next block?
3152                    float remDist = availDist - changeDist - accumDist;
3153                    log.warn("{}: script unfit cmd#{}. Cannot compute wait time for \"{}\" ramp to block \"{}\". remDist= {}",
3154                            getDisplayName(), i+1, speedType, getBlockAt(idxSpeedChange).getDisplayName(), remDist);
3155                    accumTime -= _speedUtil.getTimeForDistance(modSetting, bufDist);
3156                    break;
3157                }
3158            }
3159        }
3160
3161        int waitTime = Math.round(accumTime);
3162
3163        if (log.isDebugEnabled()) {
3164            log.debug("{}: RAMP: Wait {}ms and travel {}mm until {}mm before entering block \"{}\" at throttle= {}. availDist={}mm",
3165                    getDisplayName(), waitTime, Math.round(accumDist), Math.round(changeDist),
3166                    getBlockAt(idxSpeedChange).getDisplayName(), modSetting, Math.round(availDist));
3167        }
3168
3169        rampSpeedDelay(waitTime, speedType, modSetting, idxSpeedChange);
3170    }
3171
3172    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
3173    synchronized private void rampSpeedDelay (long waitTime, String speedType, float waitSpeed, int idxSpeedChange) {
3174        int endBlockIdx = idxSpeedChange - 1;
3175        waitTime -= 50;     // Subtract a bit
3176        if( waitTime < 0) {
3177            rampSpeedTo(speedType, endBlockIdx);   // do it now on this thread.
3178            return;
3179        }
3180        String reason;
3181        if(_waitForSignal) {
3182            reason = Bundle.getMessage("Signal");
3183        } else if (_waitForWarrant) {
3184            reason = Bundle.getMessage("Warrant");
3185        } else if (_waitForBlock) {
3186            reason = Bundle.getMessage("Occupancy");
3187        } else {
3188            reason = Bundle.getMessage("Signal");
3189        }
3190
3191        if (_trace || log.isDebugEnabled()) {
3192            if (log.isDebugEnabled()) {
3193                log.info("Train \"{}\" needs speed decrease to \"{}\" from \"{}\" for {} before entering block \"{}\"",
3194                        getTrainName(), speedType, _engineer.getSpeedType(true), reason, getBlockAt(idxSpeedChange).getDisplayName());
3195           }
3196        }
3197        if (_delayCommand != null) {
3198            if (_delayCommand.isDuplicate(speedType, waitTime, endBlockIdx)) {
3199                return;
3200            }
3201            cancelDelayRamp();
3202        }
3203        _delayCommand = new CommandDelay(speedType, waitTime, waitSpeed, endBlockIdx);
3204        _delayCommand.start();
3205        if (log.isDebugEnabled()) {
3206            log.debug("{}: CommandDelay: will wait {}ms, then Ramp to {} in block {}.",
3207                    getDisplayName(), waitTime, speedType, getBlockAt(endBlockIdx).getDisplayName());
3208        }
3209        String blkName = getBlockAt(endBlockIdx).getDisplayName();
3210        if (_trace || log.isDebugEnabled()) {
3211            log.info(Bundle.getMessage("RampBegin", getTrainName(), reason, blkName, speedType, waitTime));
3212        }
3213    }
3214
3215    protected void downRampBegun(int endBlockIdx) {
3216        OBlock block = getBlockAt(endBlockIdx + 1);
3217        if (block != null) {
3218            _rampBlkOccupied = block.isOccupied();
3219        } else {
3220            _rampBlkOccupied = true;
3221        }
3222    }
3223
3224    protected void downRampDone(boolean stop, boolean halted, String speedType, int endBlockIdx) {
3225        if (_idxCurrentOrder < endBlockIdx) {
3226            return;     // overrun not possible.
3227        }
3228        // look for overruns
3229        int nextIdx = endBlockIdx + 1;
3230        if (nextIdx > 0 && nextIdx < _orders.size()) {
3231            BlockOrder bo = getBlockOrderAt(nextIdx);
3232            OBlock block = bo.getBlock();
3233            if (block.isOccupied() && !_rampBlkOccupied) {
3234                // Occupied now, but not occupied by another train at start of ramp.
3235                if (!checkForOverrun(block) ) {    // Not us. check if something should have us wait
3236                    Warrant w = block.getWarrant();
3237                    _overrun = true;    // endBlock occupied during ramp down. Speed overrun!
3238                    if (w != null && !w.equals(this)) { // probably redundant
3239                        _waitForWarrant = true;
3240                        setStoppingBlock(nextIdx);
3241                    } else if (Stop.equals(BlockOrder.getPermissibleSpeedAt(bo))) { // probably redundant
3242                        _waitForSignal = true;
3243                        setProtectingSignal(nextIdx);
3244                    } else {
3245                        _waitForBlock = true;
3246                    }
3247                }
3248                makeOverrunMessage(bo);
3249            }   // case where occupied at start of ramp is indeterminate
3250        }
3251    }
3252
3253    @SuppressFBWarnings(value="SLF4J_FORMAT_SHOULD_BE_CONST", justification="False assumption")
3254    private void makeOverrunMessage(BlockOrder curBlkOrder) {
3255        OBlock curBlock = curBlkOrder.getBlock();
3256        String name = null;
3257        if (_waitForSignal) {
3258            NamedBean signal = curBlkOrder.getSignal();
3259            if (signal!=null) {
3260                name = signal.getDisplayName();
3261            } else {
3262                name = curBlock.getDisplayName();
3263            }
3264            _overrun = true;
3265            String entrySpeedType = BlockOrder.getPermissibleSpeedAt(curBlkOrder); // expected speed type for this block
3266            log.info(Bundle.getMessage("SignalOverrun", getTrainName(), entrySpeedType, name));
3267            fireRunStatus("SignalOverrun", name, entrySpeedType); // message of speed violation
3268            return;
3269        }
3270        String bundleKey = null;
3271        if (_waitForWarrant) {
3272            bundleKey = PROPERTY_WARRANT_OVERRUN;
3273            Warrant w = curBlock.getWarrant();
3274            if (w != null) {
3275                name = w.getDisplayName();
3276            }
3277        } else if (_waitForBlock){
3278            bundleKey = PROPERTY_OCCUPY_OVERRUN;
3279            name = (String)curBlock.getValue();
3280        }
3281        if (name == null) {
3282            name = Bundle.getMessage("unknownTrain");
3283        }
3284        if (bundleKey != null) {
3285            _overrun = true;
3286            log.info(Bundle.getMessage(bundleKey, getTrainName(), curBlock.getDisplayName(), name));
3287            fireRunStatus(bundleKey, curBlock.getDisplayName(), name); // message of speed violation
3288        } else {
3289            log.error("Train \"{}\" entered stopping block \"{}\" for unknown reason on warrant {}!",
3290                getTrainName(), curBlock.getDisplayName(), getDisplayName());
3291        }
3292    }
3293
3294    /**
3295     * {@inheritDoc}
3296     * <p>
3297     * This implementation tests that
3298     * {@link jmri.NamedBean#getSystemName()}
3299     * is equal for this and obj.
3300     * To allow a warrant to run with sections, DccLocoAddress is included to test equality
3301     *
3302     * @param obj the reference object with which to compare.
3303     * @return {@code true} if this object is the same as the obj argument;
3304     *         {@code false} otherwise.
3305     */
3306    @Override
3307    public boolean equals(Object obj) {
3308        if (obj == null) return false; // by contract
3309
3310        if (obj instanceof Warrant) {  // NamedBeans are not equal to things of other types
3311            Warrant b = (Warrant) obj;
3312            DccLocoAddress addr = this._speedUtil.getDccAddress();
3313            if (addr == null) {
3314                if (b._speedUtil.getDccAddress() != null) {
3315                    return false;
3316                }
3317                return (this.getSystemName().equals(b.getSystemName()));
3318            }
3319            return (this.getSystemName().equals(b.getSystemName()) && addr.equals(b._speedUtil.getDccAddress()));
3320        }
3321        return false;
3322    }
3323
3324    /**
3325     * {@inheritDoc}
3326     *
3327     * @return hash code value is based on the system name and DccLocoAddress.
3328     */
3329    @Override
3330    public int hashCode() {
3331        return (getSystemName().concat(_speedUtil.getDccAddress().toString())).hashCode();
3332    }
3333
3334    @Override
3335    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
3336        List<NamedBeanUsageReport> report = new ArrayList<>();
3337        if (bean != null) {
3338            if (bean.equals(getBlockingWarrant())) {
3339                report.add(new NamedBeanUsageReport("WarrantBlocking"));
3340            }
3341            getBlockOrders().forEach((blockOrder) -> {
3342                if (bean.equals(blockOrder.getBlock())) {
3343                    report.add(new NamedBeanUsageReport("WarrantBlock"));
3344                }
3345                if (bean.equals(blockOrder.getSignal())) {
3346                    report.add(new NamedBeanUsageReport("WarrantSignal"));
3347                }
3348            });
3349        }
3350        return report;
3351    }
3352
3353    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Warrant.class);
3354}