001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.ArrayList;
005import java.util.List;
006import javax.annotation.concurrent.GuardedBy;
007
008import jmri.*;
009import jmri.implementation.SignalSpeedMap;
010import jmri.util.ThreadingUtil;
011import jmri.jmrit.logix.ThrottleSetting.Command;
012import jmri.jmrit.logix.ThrottleSetting.CommandValue;
013import jmri.jmrit.logix.ThrottleSetting.ValueType;
014
015/**
016 * An Warrant contains the operating permissions and directives needed for a
017 * train to proceed from an Origin to a Destination. There are three modes that
018 * a Warrant may execute;
019 * <p>
020 * MODE_LEARN - Warrant is created or edited in WarrantFrame and then launched
021 * from WarrantFrame who records throttle commands from "_student" throttle.
022 * Warrant fires PropertyChanges for WarrantFrame to record when blocks are
023 * entered. "_engineer" thread is null.
024 * <p>
025 * MODE_RUN - Warrant may be launched from several places. An array of
026 * BlockOrders, _savedOrders, and corresponding _throttleCommands allow an
027 * "_engineer" thread to execute the throttle commands. The blockOrders
028 * establish the route for the Warrant to acquire and reserve OBlocks. The
029 * Warrant monitors block activity (entrances and exits, signals, rogue
030 * occupancy etc) and modifies speed as needed.
031 * <p>
032 * MODE_MANUAL - Warrant may be launched from several places. The Warrant to
033 * acquires and reserves the route from the array of BlockOrders. Throttle
034 * commands are done by a human operator. "_engineer" and "_throttleCommands"
035 * are not used. Warrant monitors block activity but does not set _stoppingBlock
036 * or _shareTOBlock since it cannot control speed. It does attempt to realign
037 * the route as needed, but can be thwarted.
038 * <p>
039 * Version 1.11 - remove setting of SignalHeads
040 *
041 * @author Pete Cressman Copyright (C) 2009, 2010
042 */
043public class Warrant extends jmri.implementation.AbstractNamedBean implements ThrottleListener, java.beans.PropertyChangeListener {
044
045    public static final String Stop = jmri.InstanceManager.getDefault(SignalSpeedMap.class).getNamedSpeed(0.0f); // aspect name
046    public static final String EStop = Bundle.getMessage("EStop");
047    public static final String Normal ="Normal";    // Cannot determine which SignalSystem(s) and their name(s) for "Clear"
048
049    // permanent members.
050    private List<BlockOrder> _orders;
051    private BlockOrder _viaOrder;
052    private BlockOrder _avoidOrder;
053    private List<ThrottleSetting> _commands = new ArrayList<>();
054    protected String _trainName; // User train name for icon
055    private SpeedUtil _speedUtil;
056    private boolean _runBlind; // Unable to use block detection, must run on et only
057    private boolean _partialAllocate;// only allocate one block at a time for sharing route.
058    private boolean _addTracker;    // start tracker when warrant ends normally.
059    private boolean _noRamp; // do not ramp speed changes. make immediate speed change when entering approach block.
060    private boolean _nxWarrant = false;
061
062    // transient members
063    private LearnThrottleFrame _student; // need to callback learning throttle in learn mode
064    private boolean _tempRunBlind; // run mode flag to allow running on ET only
065    protected boolean _delayStart; // allows start block unoccupied and wait for train
066    protected int _idxCurrentOrder; // Index of block at head of train (if running)
067    protected int _idxLastOrder; // Index of block at tail of train just left
068    private String _curSpeedType; // name of last moving speed, i.e. never "Stop".  Used to restore previous speed
069    protected ArrayList<BlockSpeedInfo> _speedInfo; // map max speeds and occupation times of each block in route
070
071    protected int _runMode;
072    private Engineer _engineer; // thread that runs the train
073    @GuardedBy("this")
074    private CommandDelay _delayCommand; // thread for delayed ramp down
075    private boolean _allocated; // initial Blocks of _orders have been allocated
076    private boolean _totalAllocated; // All Blocks of _orders have been allocated
077    private boolean _routeSet; // all allocated Blocks of _orders have paths set for route
078    protected OBlock _stoppingBlock; // Block occupied by rogue train or halted
079    private NamedBean _protectSignal; // Signal stopping train movement
080    private int _idxProtectSignal;
081    // Crossovers typically have both switches controlled by one TO, although each switch is in a different block
082    // At the origin and destination of warrants, TO's shared between warrants may set conflicting paths
083    private OBlock _myShareBlock;   // block belonging to this warrant
084    private OBlock _otherShareBlock;   // block belonging to another warrant
085
086    private boolean _waitForSignal; // train may not move until false
087    private boolean _waitForBlock; // train may not move until false
088    private boolean _waitForWarrant;
089    protected String _message; // last message returned from an action
090    private ThrottleManager tm;
091
092    // Throttle modes
093    public static final int MODE_NONE = 0;
094    public static final int MODE_LEARN = 1; // Record a command list
095    public static final int MODE_RUN = 2;   // Autorun, playback the command list
096    public static final int MODE_MANUAL = 3; // block detection of manually run train
097    public static final String[] MODES = {"none", "LearnMode", "RunAuto", "RunManual"};
098    public static final int MODE_ABORT = 4; // used to set status string in WarrantTableFrame
099
100    // control states
101    public static final int STOP = 0;
102    public static final int HALT = 1;
103    public static final int RESUME = 2;
104    public static final int ABORT = 3;
105    public static final int RETRY = 4;
106    public static final int ESTOP = 5;
107    protected static final int RAMP_HALT = 6;
108    protected static final int RUNNING = 7;
109    protected static final int SPEED_RESTRICTED = 8;
110    protected static final int WAIT_FOR_CLEAR = 9;
111    protected static final int WAIT_FOR_SENSOR = 10;
112    protected static final int WAIT_FOR_TRAIN = 11;
113    protected static final int WAIT_FOR_DELAYED_START = 12;
114    protected static final int LEARNING = 13;
115    protected static final int STOP_PENDING = 14;
116    protected static final int RAMPING_UP = 15;
117    protected static final int DEBUG = 7;
118    protected static final String[] CNTRL_CMDS = {"Stop", "Halt", "Resume", "Abort", "Retry", "EStop", "Ramp", "Debug"};
119    protected static final String[] RUN_STATE = {"HaltStart", "atHalt", "Resumed", "Aborts", "Retried",
120            "EStop", "Ramp", "Running", "RestrictSpeed", "WaitingForClear", "WaitingForSensor",
121            "RunningLate", "WaitingForStart", "RecordingScript", "StopPending", "RampingUp"};
122
123    /**
124     * Create an object with no route defined. The list of BlockOrders is the
125     * route from an Origin to a Destination
126     *
127     * @param sName system name
128     * @param uName user name
129     */
130    public Warrant(String sName, String uName) {
131        super(sName, uName);
132        _idxCurrentOrder = 0;
133        _idxLastOrder = 0;
134        _orders = new ArrayList<>();
135        _runBlind = false;
136        _speedUtil = new SpeedUtil();
137        tm = InstanceManager.getNullableDefault(ThrottleManager.class);
138    }
139
140    protected void setNXWarrant(boolean set) {
141        _nxWarrant = set;
142    }
143    protected boolean isNXWarrant() {
144        return _nxWarrant;
145    }
146
147    @Override
148    public int getState() {
149        if (_engineer != null) {
150            return _engineer.getRunState();
151        }
152        if (_delayStart) {
153            return WAIT_FOR_DELAYED_START;
154        }
155        if (_runMode == MODE_LEARN) {
156            return LEARNING;
157        }
158        if (_runMode != MODE_NONE) {
159            return RUNNING;
160        }
161        return -1;
162    }
163
164    @Override
165    public void setState(int state) {
166        // warrant state is computed from other values
167    }
168
169    public SpeedUtil getSpeedUtil() {
170        return _speedUtil;
171    }
172
173    public void setSpeedUtil(SpeedUtil su) {
174        _speedUtil = su;
175    }
176
177    /**
178     * Return BlockOrders.
179     *
180     * @return list of block orders
181     */
182    public List<BlockOrder> getBlockOrders() {
183        return _orders;
184    }
185
186    /**
187     * Add permanently saved BlockOrder.
188     *
189     * @param order block order
190     */
191    public void addBlockOrder(BlockOrder order) {
192        _orders.add(order);
193    }
194
195    public void setBlockOrders(List<BlockOrder> orders) {
196        _orders = orders;
197    }
198
199    /**
200     * Return permanently saved Origin.
201     *
202     * @return origin block order
203     */
204    public BlockOrder getfirstOrder() {
205        if (_orders.isEmpty()) {
206            return null;
207        }
208        return new BlockOrder(_orders.get(0));
209    }
210
211    /**
212     * Return permanently saved Destination.
213     *
214     * @return destination block order
215     */
216    public BlockOrder getLastOrder() {
217        int size = _orders.size();
218        if (size < 2) {
219            return null;
220        }
221        return new BlockOrder(_orders.get(size - 1));
222    }
223
224    /**
225     * Return permanently saved BlockOrder that must be included in the route.
226     *
227     * @return via block order
228     */
229    public BlockOrder getViaOrder() {
230        if (_viaOrder == null) {
231            return null;
232        }
233        return new BlockOrder(_viaOrder);
234    }
235
236    public void setViaOrder(BlockOrder order) {
237        _viaOrder = order;
238    }
239
240    public BlockOrder getAvoidOrder() {
241        if (_avoidOrder == null) {
242            return null;
243        }
244        return new BlockOrder(_avoidOrder);
245    }
246
247    public void setAvoidOrder(BlockOrder order) {
248        _avoidOrder = order;
249    }
250
251    protected String getRoutePathInBlock(OBlock block) {
252        for (int i = 0; i < _orders.size(); i++) {
253            if (_orders.get(i).getBlock().equals(block)) {
254                return _orders.get(i).getPathName();
255            }
256        }
257        return null;
258    }
259
260    /**
261     * @return block order currently at the train position
262     */
263    final public BlockOrder getCurrentBlockOrder() {
264        return getBlockOrderAt(_idxCurrentOrder);
265    }
266
267    /**
268     * @return index of block order currently at the train position
269     */
270    final public int getCurrentOrderIndex() {
271        return _idxCurrentOrder;
272    }
273
274    /**
275     * Find block AFTER startIdx.
276     *
277     * @param block used by the warrant
278     * @param startIdx index of block order
279     * @return index of block ahead of block order index, -1 if not found
280     */
281    protected int getIndexOfBlock(OBlock block, int startIdx) {
282        for (int i = startIdx; i < _orders.size(); i++) {
283            if (_orders.get(i).getBlock().equals(block)) {
284                return i;
285            }
286        }
287        return -1;
288    }
289
290    /**
291     * Find block BEFORE endIdx.
292     *
293     * @param endIdx index of block order
294     * @param block used by the warrant
295     * @return index of block ahead of block order index, -1 if not found
296     */
297    protected int getIndexOfBlockBefore(int endIdx, OBlock block) {
298        for (int i = endIdx; i >= 0; i--) {
299            if (_orders.get(i).getBlock().equals(block)) {
300                return i;
301            }
302        }
303        return -1;
304    }
305
306    /*
307     * Find block after startIdx. Call is only valid when in MODE_LEARN and
308     * MODE_RUN (previously start was i=_idxCurrentOrder)
309     */
310    protected int getIndexOfBlock(String name, int startIdx) {
311        for (int i = startIdx; i < _orders.size(); i++) {
312            if (_orders.get(i).getBlock().getDisplayName().equals(name)) {
313                return i;
314            }
315        }
316        return -1;
317    }
318
319    /**
320     * Call is only valid when in MODE_LEARN and MODE_RUN.
321     *
322     * @param index index of block order
323     * @return block order or null if not found
324     */
325    protected BlockOrder getBlockOrderAt(int index) {
326        if (index >= 0 && index < _orders.size()) {
327            return _orders.get(index);
328        }
329        return null;
330    }
331
332    /**
333     * Call is only valid when in MODE_LEARN and MODE_RUN.
334     *
335     * @param idx index of block order
336     * @return block of the block order
337     */
338    protected OBlock getBlockAt(int idx) {
339
340        BlockOrder bo = getBlockOrderAt(idx);
341        if (bo != null) {
342            return bo.getBlock();
343        }
344        return null;
345    }
346
347    /**
348     * Call is only valid when in MODE_LEARN and MODE_RUN.
349     *
350     * @return Name of OBlock currently occupied
351     */
352    public String getCurrentBlockName() {
353        OBlock block = getBlockAt(_idxCurrentOrder);
354        if (block == null) {
355            return "";
356        } else {
357            return block.getDisplayName();
358        }
359    }
360
361    /**
362     * Call is only valid when in MODE_LEARN and MODE_RUN.
363     */
364    private int getBlockStateAt(int idx) {
365
366        OBlock b = getBlockAt(idx);
367        if (b != null) {
368            return b.getState();
369        }
370        return NamedBean.UNKNOWN;
371    }
372
373    /**
374     * @return throttle commands
375     */
376    public List<ThrottleSetting> getThrottleCommands() {
377        return _commands;
378    }
379
380    public void setThrottleCommands(List<ThrottleSetting> list) {
381        _commands = list;
382    }
383
384    public void addThrottleCommand(ThrottleSetting ts) {
385        if (ts == null) {
386            log.error("warrant {} cannot add null ThrottleSetting", getDisplayName());
387        } else {
388            _commands.add(ts);
389        }
390    }
391
392    public void setTrackSpeeds() {
393        float speed = 0.0f;
394        for (ThrottleSetting ts :_commands) {
395            CommandValue cmdVal = ts.getValue();
396            ValueType valType = cmdVal.getType();
397            switch (valType) {
398                case VAL_FLOAT:
399                    speed = _speedUtil.getTrackSpeed(cmdVal.getFloat());
400                    break;
401                case VAL_TRUE:
402                    _speedUtil.setIsForward(true);
403                    break;
404                case VAL_FALSE:
405                    _speedUtil.setIsForward(false);
406                    break;
407                default:
408            }
409            ts.setTrackSpeed(speed);
410        }
411    }
412
413    public void setNoRamp(boolean set) {
414        _noRamp = set;
415    }
416
417    public void setShareRoute(boolean set) {
418        _partialAllocate = set;
419    }
420
421    public void setAddTracker (boolean set) {
422        _addTracker = set;
423    }
424
425    public boolean getNoRamp() {
426        return _noRamp;
427    }
428
429    public boolean getShareRoute() {
430        return _partialAllocate;
431    }
432
433    public boolean getAddTracker() {
434        return _addTracker;
435    }
436
437    public String getTrainName() {
438        return _trainName;
439    }
440
441    public void setTrainName(String name) {
442        _trainName = name;
443    }
444
445    public boolean getRunBlind() {
446        return _runBlind;
447    }
448
449    public void setRunBlind(boolean runBlind) {
450        _runBlind = runBlind;
451    }
452
453    /*
454     * Engineer reports its status
455     */
456    @jmri.InvokeOnLayoutThread
457    protected void fireRunStatus(String property, Object old, Object status) {
458//        jmri.util.ThreadingUtil.runOnLayout(() -> {   // Will hang GUI!
459        ThreadingUtil.runOnLayoutEventually(() -> { // OK but can be quite late in reporting speed changes
460            firePropertyChange(property, old, status);
461        });
462    }
463
464    /**
465     * ****************************** state queries ****************
466     */
467    /**
468     * @return true if listeners are installed enough to run
469     */
470    public boolean isAllocated() {
471        return _allocated;
472    }
473
474    /**
475     * @return true if listeners are installed for entire route
476     */
477    public boolean isTotalAllocated() {
478        return _totalAllocated;
479    }
480
481    /**
482     * Turnouts are set for the route
483     *
484     * @return true if turnouts are set
485     */
486    public boolean hasRouteSet() {
487        return _routeSet;
488    }
489
490    /**
491     * Test if the permanent saved blocks of this warrant are free (unoccupied
492     * and unallocated)
493     *
494     * @return true if route is free
495     */
496    public boolean routeIsFree() {
497        for (int i = 0; i < _orders.size(); i++) {
498            OBlock block = _orders.get(i).getBlock();
499            if (!block.isFree()) {
500                return false;
501            }
502        }
503        return true;
504    }
505
506    /**
507     * Test if the permanent saved blocks of this warrant are occupied
508     *
509     * @return true if any block is occupied
510     */
511    public boolean routeIsOccupied() {
512        for (int i = 1; i < _orders.size(); i++) {
513            OBlock block = _orders.get(i).getBlock();
514            if ((block.getState() & Block.OCCUPIED) != 0) {
515                return true;
516            }
517        }
518        return false;
519    }
520
521    /* ************* Methods for running trains *****************/
522
523    protected boolean isWaitingForSignal() {
524        return _waitForSignal;
525    }
526    protected boolean isWaitingForClear() {
527        return _waitForBlock;
528    }
529    protected boolean isWaitingForWarrant() {
530        return _waitForWarrant;
531    }
532    protected Warrant getBlockingWarrant() {
533        if (_stoppingBlock != null && !this.equals(_stoppingBlock.getWarrant())) {
534            return _stoppingBlock.getWarrant();
535        }
536        if (_otherShareBlock != null) {
537            return _otherShareBlock.getWarrant();
538        }
539        return null;
540    }
541     /**
542      *  @return ID of run mode
543     */
544    public int getRunMode() {
545        return _runMode;
546    }
547
548    protected String getRunModeMessage() {
549        String modeDesc = null;
550        switch (_runMode) {
551            case MODE_NONE:
552                return Bundle.getMessage("NotRunning", getDisplayName());
553            case MODE_LEARN:
554                modeDesc = Bundle.getMessage("Recording");
555                break;
556            case MODE_RUN:
557                modeDesc = Bundle.getMessage("AutoRun");
558                break;
559            case MODE_MANUAL:
560                modeDesc = Bundle.getMessage("ManualRun");
561                break;
562            default:
563        }
564        return Bundle.getMessage("WarrantInUse", modeDesc, getDisplayName());
565
566    }
567
568    synchronized protected String getRunningMessage() {
569        if (_delayStart) {
570            return Bundle.getMessage("waitForDelayStart",
571                    _trainName, getBlockOrderAt(0).getBlock().getDisplayName());
572        }
573        switch (_runMode) {
574            case Warrant.MODE_NONE:
575                if (getBlockOrders().isEmpty()) {
576                    return Bundle.getMessage("BlankWarrant");
577                }
578                if (_speedUtil.getAddress() == null) {
579                    return Bundle.getMessage("NoLoco");
580                }
581                if (!(this instanceof SCWarrant) && _commands.size() <= _orders.size()) {
582                    return Bundle.getMessage("NoCommands", getDisplayName());
583                }
584                if (_message == null) {
585                    return Bundle.getMessage("Idle");
586                }
587                if (_idxCurrentOrder != 0 && _idxLastOrder == _idxCurrentOrder) {
588                    return Bundle.getMessage("locationUnknown", _trainName, getCurrentBlockName()) + _message;
589                }
590                return Bundle.getMessage("Idle1", _message);
591            case Warrant.MODE_LEARN:
592                return Bundle.getMessage("Learning", getCurrentBlockName());
593            case Warrant.MODE_RUN:
594                if (_engineer == null) {
595                    return Bundle.getMessage("engineerGone", getCurrentBlockName());
596                }
597                int cmdIdx = _engineer.getCurrentCommandIndex();
598                if (cmdIdx >= _commands.size()) {
599                    cmdIdx = _commands.size() - 1;
600                }
601                cmdIdx++;   // display is 1-based
602                OBlock block = getCurrentBlockOrder().getBlock();
603                if ((block.getState() & (Block.OCCUPIED | Block.UNDETECTED)) == 0) {
604                    return Bundle.getMessage("LostTrain", _trainName, block.getDisplayName());
605                }
606                String blockName = block.getDisplayName();
607                String speed = getSpeedMessage(_curSpeedType);
608
609                switch (_engineer.getRunState()) {
610                    case Warrant.RESUME:
611                        return Bundle.getMessage("reStarted", blockName, cmdIdx, speed);
612
613                    case Warrant.RETRY:
614                        return Bundle.getMessage("reRetry", blockName, cmdIdx, speed);
615
616                    case Warrant.ABORT:
617                        if (cmdIdx == _commands.size() - 1) {
618                            _engineer = null;
619                            return Bundle.getMessage("endOfScript", _trainName);
620                        }
621                        return Bundle.getMessage("Aborted", blockName, cmdIdx);
622
623                    case Warrant.HALT:
624                    case Warrant.WAIT_FOR_CLEAR:
625                        String s = "";
626                        if (_waitForSignal) {
627                            s = Bundle.getMessage("Signal");
628                        } else if (_waitForWarrant) {
629                            Warrant w = getBlockingWarrant();
630                            if (w != null) {
631                                s = Bundle.getMessage("WarrantWait",  w.getDisplayName());
632                            } else {
633                                s = Bundle.getMessage("WaitForClear", blockName, (_waitForSignal
634                                        ? Bundle.getMessage("Signal") : Bundle.getMessage("Occupancy")));
635                            }
636                        } else if (_waitForBlock) {
637                            s = Bundle.getMessage("Occupancy");
638                        } else {
639                            return Bundle.getMessage("Halted", blockName, cmdIdx);
640                        }
641                        return Bundle.getMessage("RampWaitForClear",
642                                getTrainName(), getCurrentBlockName(), s);
643
644                    case Warrant.WAIT_FOR_TRAIN:
645                        int blkIdx = _idxCurrentOrder + 1;
646                        if (blkIdx >= _orders.size()) {
647                            blkIdx = _orders.size() - 1;
648                        }
649                        return Bundle.getMessage("WaitForTrain", cmdIdx,
650                                getBlockOrderAt(blkIdx).getBlock().getDisplayName(), speed);
651
652                    case Warrant.WAIT_FOR_SENSOR:
653                        return Bundle.getMessage("WaitForSensor",
654                                cmdIdx, _engineer.getWaitSensor().getDisplayName(),
655                                blockName, speed);
656
657                    case Warrant.RUNNING:
658                    case Warrant.SPEED_RESTRICTED:
659                    case Warrant.RAMPING_UP:
660                        return Bundle.getMessage("WhereRunning", blockName, cmdIdx, speed);
661
662                    case Warrant.RAMP_HALT:
663                        return Bundle.getMessage("HaltPending", speed, blockName);
664
665                    case Warrant.STOP_PENDING:
666                        return Bundle.getMessage("StopPending", speed, blockName, (_waitForSignal
667                                ? Bundle.getMessage("Signal") : (_waitForBlock
668                                        ? Bundle.getMessage("Occupancy") : Bundle.getMessage("Warrant"))));
669
670                    default:
671                        return _message;
672                }
673
674            case Warrant.MODE_MANUAL:
675                BlockOrder bo = getCurrentBlockOrder();
676                if (bo != null) {
677                    return Bundle.getMessage("ManualRunning", bo.getBlock().getDisplayName());
678                }
679                return Bundle.getMessage("ManualRun");
680
681            default:
682        }
683        return "ERROR mode= " + MODES[_runMode];
684    }
685
686    /**
687     * Calculates the scale speed of the current throttle setting for display
688     * @param speedType name of current speed
689     * @return text message
690     */
691    protected String getSpeedMessage(String speedType) {
692        float speed = 0;
693        String units;
694        SignalSpeedMap speedMap = jmri.InstanceManager.getDefault(SignalSpeedMap.class);
695        switch (speedMap.getInterpretation()) {
696            case SignalSpeedMap.PERCENT_NORMAL:
697/*                units = Bundle.getMessage("percentNormal");
698                if (_idxCurrentOrder == _orders.size() - 1
699                        || _engineer.getCurrentCommandIndex() >= _commands.size() - 1) {
700                    speed = _speedInfo.get(_idxCurrentOrder).getEntranceSpeed();
701                } else {
702                    for (int idx = _engineer.getCurrentCommandIndex(); idx >=0; idx--) {
703                        ThrottleSetting ts = _commands.get(idx);
704                        if ("SPEED".equals(ts.getCommand().toUpperCase())) {
705                            speed = Float.parseFloat(ts.getValue());
706                            break;
707                        }
708                    }
709                }
710                speed = (_engineer.getSpeedSetting() / speed) * 100;
711                break;*/
712            case SignalSpeedMap.PERCENT_THROTTLE:
713                units = Bundle.getMessage("percentThrottle");
714                speed = _engineer.getSpeedSetting() * 100;
715                break;
716            case SignalSpeedMap.SPEED_MPH:
717                units = Bundle.getMessage("mph");
718                speed = _speedUtil.getTrackSpeed(_engineer.getSpeedSetting()) * speedMap.getLayoutScale();
719                speed = speed * 2.2369363f;
720                break;
721            case SignalSpeedMap.SPEED_KMPH:
722                units = Bundle.getMessage("kph");
723                speed = _speedUtil.getTrackSpeed(_engineer.getSpeedSetting()) * speedMap.getLayoutScale();
724                speed = speed * 3.6f;
725                break;
726            default:
727                log.error("Unknown speed interpretation {}", speedMap.getInterpretation());
728                throw new java.lang.IllegalArgumentException("Unknown speed interpretation " + speedMap.getInterpretation());
729        }
730        return Bundle.getMessage("atSpeed", speedType, Math.round(speed), units);
731    }
732
733    @jmri.InvokeOnLayoutThread
734    private void startTracker() {
735        // error if not on Layout thread
736        if (!ThreadingUtil.isLayoutThread()) {
737            log.error("invoked on wrong thread", new Exception("traceback"));
738        }
739        new Tracker(getCurrentBlockOrder().getBlock(), _trainName,
740                null, InstanceManager.getDefault(TrackerTableAction.class));
741    }
742
743    synchronized public void stopWarrant(boolean abort, boolean turnOffFunctions) {
744        _delayStart = false;
745        _curSpeedType = Normal;
746        if (_protectSignal != null) {
747            _protectSignal.removePropertyChangeListener(this);
748            _protectSignal = null;
749            _idxProtectSignal = -1;
750        }
751        if (_stoppingBlock != null) {
752            _stoppingBlock.removePropertyChangeListener(this);
753            _stoppingBlock = null;
754        }
755        if (_otherShareBlock != null) {
756            _otherShareBlock.removePropertyChangeListener(this);
757            _otherShareBlock = null;
758            _myShareBlock = null;
759        }
760        deAllocate();
761        int oldMode = _runMode;
762        _runMode = MODE_NONE;
763
764        if (_student != null) {
765            _student.dispose(); // releases throttle
766            _student = null;
767        }
768        if (_engineer != null) {
769            _engineer.stopRun(abort, turnOffFunctions); // release throttle
770            _engineer = null;
771        }
772        if (abort) {
773            fireRunStatus("runMode", oldMode, MODE_ABORT);
774        } else {
775            if (_idxCurrentOrder == _orders.size()-1) { // run was complete to end
776                _speedUtil.stopRun(true);   // write speed profile measurements
777                if (_addTracker) {
778                    startTracker();
779                }
780            }
781            fireRunStatus("runMode", oldMode, _runMode);
782        }
783        if (log.isDebugEnabled()) {
784            log.debug("Warrant \"{}\" terminated {}.", getDisplayName(), (abort == true ? "- aborted!" : "normally"));
785        }
786    }
787
788    /**
789     * Sets up recording and playing back throttle commands - also cleans up
790     * afterwards. MODE_LEARN and MODE_RUN sessions must end by calling again
791     * with MODE_NONE. It is important that the route be deAllocated (remove
792     * listeners).
793     * <p>
794     * Rule for (auto) MODE_RUN: 1. At least the Origin block must be owned
795     * (allocated) by this warrant. (block._warrant == this) and path set for
796     * Run Mode Rule for (auto) LEARN_RUN: 2. Entire Route must be allocated and
797     * Route Set for Learn Mode. i.e. this warrant has listeners on all block
798     * sensors in the route. Rule for MODE_MANUAL The Origin block must be
799     * allocated to this warrant and path set for the route
800     *
801     * @param mode run mode
802     * @param address DCC loco address
803     * @param student throttle frame for learn mode parameters
804     * @param commands list of throttle commands
805     * @param runBlind true if occupancy should be ignored
806     * @return error message, if any
807     */
808    public String setRunMode(int mode, DccLocoAddress address,
809            LearnThrottleFrame student,
810            List<ThrottleSetting> commands, boolean runBlind) {
811        if (log.isDebugEnabled()) {
812            log.debug("setRunMode({}) ({}) called with _runMode= {}. warrant= {}",
813                    mode, MODES[mode], MODES[_runMode], getDisplayName());
814        }
815        _message = null;
816        if (_runMode != MODE_NONE) {
817            _message = getRunModeMessage();
818            log.error(_message);
819            return _message;
820        }
821        _delayStart = false;
822        _curSpeedType = Normal;
823        _waitForSignal = false;
824        _waitForBlock = false;
825        _waitForWarrant = false;
826        if (address != null) {
827            _speedUtil.setDccAddress(address);
828        }
829        if (mode == MODE_LEARN) {
830            // Cannot record if block 0 is not occupied or not dark. If dark, user is responsible for occupation
831            if (student == null) {
832                _message = Bundle.getMessage("noLearnThrottle", getDisplayName());
833                log.error(_message);
834                return _message;
835            }
836            synchronized (this) {
837                _student = student;
838            }
839            // set mode before notifyThrottleFound is called
840            _runMode = mode;
841        } else if (mode == MODE_RUN || mode == MODE_MANUAL) {
842            if (commands != null && commands.size() > _orders.size()) {
843                _commands = commands;
844            }
845            // set mode before setStoppingBlock and callback to notifyThrottleFound are called
846            _idxCurrentOrder = 0;
847            _runMode = mode;
848            // Delayed start is OK if block 0 is not occupied. Note can't delay start if block is dark
849            if (!runBlind) {
850                int state = getBlockStateAt(0);
851                if ((state & (Block.OCCUPIED | Block.UNDETECTED)) == 0) {
852                    // continuing with no occupation of starting block
853                    setStoppingBlock(getBlockAt(0));
854                    _delayStart = true;
855                }
856            }
857        } else {
858            stopWarrant(true, true);
859        }
860        getBlockAt(0)._entryTime = System.currentTimeMillis();
861        if (_runBlind) {
862            _tempRunBlind = _runBlind;
863        } else {
864            _tempRunBlind = runBlind;
865        }
866        if (!_delayStart) {
867            if (mode != MODE_MANUAL) {
868                _message = acquireThrottle();
869            } else {
870                startupWarrant(); // assuming manual operator will go to start block
871            }
872        }
873        if (log.isDebugEnabled()) {
874            log.debug("Exit setRunMode()  _runMode= {}, msg= {}", MODES[_runMode], _message);
875        }
876        fireRunStatus("runMode", MODE_NONE, _runMode);
877        return _message;
878    } // end setRunMode
879
880    /////////////// start warrant run - end of create/edit/setup methods //////////////////
881
882    /**
883     * @return error message if any
884     */
885    protected String acquireThrottle() {
886        String msg = null;
887        DccLocoAddress dccAddress = _speedUtil.getDccAddress();
888        if (log.isDebugEnabled()) {
889            log.debug("acquireThrottle request at {} for warrant {}",
890                    dccAddress, getDisplayName());
891        }
892        if (dccAddress == null) {
893            msg = Bundle.getMessage("NoAddress", getDisplayName());
894        } else {
895            if (tm == null) {
896                msg = Bundle.getMessage("noThrottle", _speedUtil.getDccAddress().getNumber());
897            } else {
898                if (!tm.requestThrottle(dccAddress, this, false)) {
899                    return Bundle.getMessage("trainInUse", dccAddress.getNumber());
900                }
901            }
902        }
903        if (msg != null) {
904            abortWarrant(msg);
905            fireRunStatus("throttleFail", null, msg);
906            return msg;
907        }
908        return null;
909    }
910
911    @Override
912    public void notifyThrottleFound(DccThrottle throttle) {
913        if (throttle == null) {
914            String msg = Bundle.getMessage("noThrottle", getDisplayName());
915            abortWarrant(msg);
916            fireRunStatus("throttleFail", null, msg);
917            return;
918        }
919        if (log.isDebugEnabled()) {
920            log.debug("notifyThrottleFound for address= {}, class= {}, warrant {}",
921                    throttle.getLocoAddress(), throttle.getClass().getName(), getDisplayName());
922        }
923        _speedUtil.setThrottle(throttle);
924        startupWarrant();
925        runWarrant(throttle);
926    } //end notifyThrottleFound
927
928    @Override
929    public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
930        abortWarrant(Bundle.getMessage("noThrottle",
931                (reason + " " + (address != null ? address.getNumber() : getDisplayName()))));
932        fireRunStatus("throttleFail", null, reason);
933    }
934
935    /**
936     * No steal or share decisions made locally
937     * <p>
938     * {@inheritDoc}
939     */
940    @Override
941    public void notifyDecisionRequired(jmri.LocoAddress address, DecisionType question) {
942    }
943
944    protected void releaseThrottle(DccThrottle throttle) {
945        if (throttle != null) {
946            if (tm != null) {
947                tm.releaseThrottle(throttle, this);
948            } else {
949                log.error("{} on thread {}",Bundle.getMessage("noThrottle", throttle.getLocoAddress()),
950                        Thread.currentThread().getName());
951            }
952        }
953    }
954
955    protected void abortWarrant(String msg) {
956        log.error("Abort warrant \"{}\" - {} ", getDisplayName(), msg);
957        stopWarrant(true, true);
958    }
959
960    /**
961     * Pause and resume auto-running train or abort any allocation state User
962     * issued overriding commands during run time of warrant _engineer.abort()
963     * calls setRunMode(MODE_NONE,...) which calls deallocate all.
964     *
965     * @param idx index of control command
966     * @return false if command cannot be given
967     */
968    public boolean controlRunTrain(int idx) {
969        if (idx < 0) {
970            return false;
971        }
972        boolean ret = false;
973        if (_engineer == null) {
974            if (log.isDebugEnabled()) {
975                log.debug("controlRunTrain({})= \"{}\" for train {} runMode= {}. warrant {}",
976                        idx, CNTRL_CMDS[idx], getTrainName(), MODES[_runMode], getDisplayName());
977            }
978            switch (idx) {
979                case HALT:
980                case RESUME:
981                case RETRY:
982                case RAMP_HALT:
983                    fireRunStatus("SpeedChange", null, idx);
984                    break;
985                case STOP:
986                case ABORT:
987                    if (_runMode == Warrant.MODE_LEARN) {
988                        // let WarrantFrame do the abort. (WarrantFrame listens for "abortLearn")
989                        fireRunStatus("abortLearn", -MODE_LEARN, _idxCurrentOrder);
990                    } else {
991                        fireRunStatus("controlChange", MODE_RUN, ABORT);
992                        stopWarrant(true, true);
993                    }
994                    break;
995                case DEBUG:
996                    ret = debugInfo();
997                    fireRunStatus("SpeedChange", null, idx);
998                    break;
999                default:
1000            }
1001            return ret;
1002        }
1003        int runState = _engineer.getRunState();
1004        if (log.isDebugEnabled()) {
1005            log.debug("controlRunTrain({})= \"{}\" for train {} runstate= {}, in block {}. warrant {}",
1006                    idx, CNTRL_CMDS[idx], getTrainName(), RUN_STATE[runState],
1007                    getBlockAt(_idxCurrentOrder).getDisplayName(), getDisplayName());
1008        }
1009        synchronized (_engineer) {
1010            switch (idx) {
1011                case RAMP_HALT:
1012                    cancelDelayRamp();
1013                    _engineer.rampSpeedTo(Warrant.Stop, 0, false);  // immediate ramp down
1014                    _engineer.setHalt(true);
1015                    ret = true;
1016                    break;
1017                case RESUME:
1018                    BlockOrder bo = getBlockOrderAt(_idxCurrentOrder);
1019                    OBlock block = bo.getBlock();
1020                    if ((block.getState() & (Block.OCCUPIED | Block.UNDETECTED)) != 0) {
1021                        // we assume this occupation is this train. user should know
1022                        if (runState == WAIT_FOR_CLEAR || runState == HALT) {
1023                            // However user knows if condition may have cleared due to overrun.
1024                            _message = allocateFromIndex(true, _idxCurrentOrder + 1);
1025                            // This is user's decision to reset and override wait flags
1026                            if (!_engineer.isRamping()) {   // do not change flags when ramping
1027                                // recheck flags
1028                                if (_idxCurrentOrder < _orders.size() - 1
1029                                        && Stop.equals(getSpeedTypeForBlock(_idxCurrentOrder + 1))) {
1030                                    break; // cannot overide flags
1031                                }
1032                                if (_idxCurrentOrder == 0) {
1033                                    _engineer.setHalt(false);
1034                                }
1035                                _waitForSignal = false;
1036                                _waitForBlock = false;
1037                                _waitForWarrant = false;
1038                                // engineer will clear its flags when ramp completes
1039                                _engineer.rampSpeedTo(_curSpeedType, 0, false);
1040                            }
1041                        } else if (runState == WAIT_FOR_TRAIN || runState == SPEED_RESTRICTED) {
1042                            // user wants to increase throttle of stalled train slowly
1043                            float speedSetting = _engineer.getSpeedSetting();
1044                            _engineer.setSpeed(speedSetting + _speedUtil.getRampThrottleIncrement());
1045                        } else {    // last resort from user to get on script
1046                            _engineer.setSpeed(_speedUtil.modifySpeed(_engineer.getScriptSpeed(), _curSpeedType));
1047                        }
1048                        ret = true;
1049                    } else {    // train must be lost.
1050                        // user wants to increase throttle of stalled train slowly
1051                        float speedSetting = _engineer.getSpeedSetting();
1052                        _engineer.setSpeed(speedSetting + _speedUtil.getRampThrottleIncrement());
1053                    }
1054                    break;
1055                case RETRY: // Force move into next block, which was seen as rogue occupied
1056                    bo = getBlockOrderAt(_idxCurrentOrder + 1);
1057                    // if block belongs to this warrant, then move unconditionally into block
1058                    if (bo != null) {
1059                        block = bo.getBlock();
1060                        if (block.allocate(this) == null && (block.getState() & OBlock.OCCUPIED) != 0) {
1061                            _idxCurrentOrder++;
1062                            if (block.equals(_stoppingBlock)) {
1063                                clearStoppingBlock();
1064                            }
1065                            String msg = bo.setPath(this);
1066                            if (msg != null) {
1067                                log.warn("Cannot clear path for warrant \"{}\" at block \"{}\" - msg = {}",
1068                                        getDisplayName(), block.getDisplayName(), msg);
1069                            }
1070                            goingActive(block);
1071                            ret = true;
1072                        }
1073                    }
1074                    break;
1075                case ABORT:
1076                    stopWarrant(true, true);
1077                    ret = true;
1078                    break;
1079                case HALT:
1080                case STOP:
1081                    cancelDelayRamp();
1082                    _engineer.setStop(false, true); // sets _halt
1083                    ret = true;
1084                    break;
1085                case ESTOP:
1086                    cancelDelayRamp();
1087                    _engineer.setStop(true, true); // E-stop & halt
1088                    ret = true;
1089                    break;
1090                case DEBUG:
1091                    ret = debugInfo();
1092                    fireRunStatus("SpeedChange", null, idx);
1093                    return ret;
1094                default:
1095                    return false;
1096            }
1097        }
1098        int state = runState;
1099        if (state == Warrant.HALT) {
1100            if (_waitForSignal || _waitForBlock || _waitForWarrant) {
1101                state = WAIT_FOR_CLEAR;
1102            }
1103        }
1104        if (ret) {
1105            fireRunStatus("controlChange", state, idx);
1106        } else {
1107            fireRunStatus("controlFailed", state, idx);
1108        }
1109        return ret;
1110    }
1111
1112    protected boolean debugInfo() {
1113        StringBuffer info = new StringBuffer("\nWarrant ");
1114        info.append(getDisplayName()); info.append(", ");
1115        info.append("Current BlockOrder idx= "); info.append(_idxCurrentOrder);
1116        info.append(",  Block \""); info.append(getBlockAt(_idxCurrentOrder).getDisplayName()); info.append("\"");
1117        info.append("\n\tWarrant flags: _waitForSignal= ");
1118        info.append(_waitForSignal); info.append(", _waitForBlock= ");
1119        info.append(_waitForBlock); info.append(", _waitForWarrant= ");
1120        info.append(_waitForWarrant); info.append("\n\t");
1121        if (_engineer != null) {
1122            info.append("Engineer runstate= ");
1123            info.append(RUN_STATE[_engineer.getRunState()]); info.append(", ");
1124            info.append("speed setting= ");
1125            info.append(_engineer.getSpeedSetting()); info.append(", ");
1126            info.append("scriptSpeed= ");
1127            info.append(_engineer.getScriptSpeed()); info.append("\n\t");
1128            int cmdIdx = _engineer.getCurrentCommandIndex();
1129            info.append("current Cmd Index #");
1130            // Note: command indexes biased from 0 to 1 to match Warrant's 1-based display of commands.
1131            info.append(cmdIdx + 1); info.append(", Command: ");
1132            info.append(getThrottleCommands().get(cmdIdx).toString()); info.append("\n\t");
1133            info.append(_engineer.getFlags());
1134            for (StackTraceElement elem :_engineer.getStackTrace()) {
1135                info.append("\n\t\t");
1136                info.append(elem.getClassName()); info.append(".");
1137                info.append(elem.getMethodName()); info.append(", line ");
1138                info.append(elem.getLineNumber());
1139            }
1140            info.append("\n\tEngineer Thread.State= ");
1141            info.append(_engineer.getState()); info.append(", isAlive= ");
1142            info.append(_engineer.isAlive()); info.append(", isInterrupted= ");
1143            info.append(_engineer.isInterrupted()); info.append("\n\t");
1144            Engineer.ThrottleRamp ramp = _engineer.getRamp();
1145            if (ramp != null) {
1146                info.append("Ramp Thread.State= "); info.append(ramp.getState());
1147                info.append(", ready= "); info.append(ramp.ready);
1148                info.append(", isAlive= "); info.append(ramp.isAlive());
1149                info.append(", isInterrupted= "); info.append(ramp.isInterrupted());
1150                for (StackTraceElement elem : ramp.getStackTrace()) {
1151                    info.append("\n\t\t");
1152                    info.append(elem.getClassName()); info.append(".");
1153                    info.append(elem.getMethodName()); info.append(", line ");
1154                    info.append(elem.getLineNumber());
1155                }
1156            } else {
1157                info.append("No ramp");
1158            }
1159        } else {
1160            info.append("No engineer");
1161        }
1162        log.info(info.toString());
1163        return true;
1164    }
1165
1166    protected void startupWarrant() {
1167        _idxCurrentOrder = 0;
1168        _idxLastOrder = 0;
1169        _curSpeedType = Normal;
1170        // set block state to show our train occupies the block
1171        BlockOrder bo = getBlockOrderAt(0);
1172        OBlock b = bo.getBlock();
1173        b.setValue(_trainName);
1174        b.setState(b.getState() | OBlock.RUNNING);
1175    }
1176
1177    private void runWarrant(DccThrottle throttle) {
1178        if (_runMode == MODE_LEARN) {
1179            synchronized (this) {
1180                _student.notifyThrottleFound(throttle);
1181            }
1182        } else {
1183             _engineer = new Engineer(this, throttle);
1184            if (_tempRunBlind) {
1185                _engineer.setRunOnET(true);
1186            }
1187            if (_delayStart) {
1188                _engineer.setHalt(true);    // throttle already at 0
1189            }
1190            // if there may be speed changes due to signals or rogue occupation.
1191            if (_noRamp) { // make immediate speed changes
1192                _speedInfo = null;
1193            } else { // make smooth speed changes by ramping
1194                getBlockSpeedTimes();
1195            }
1196            _engineer.start();
1197            _speedUtil.clearStats();
1198            _speedUtil._prevSpeed = 0;
1199
1200            if (_delayStart) {
1201                // user must explicitly start train (resume) in a dark block
1202                fireRunStatus("ReadyToRun", -1, 0);   // ready to start msg
1203            }
1204            if (_engineer.getRunState() == Warrant.RUNNING) {
1205                setMovement();
1206            }
1207            _delayStart = false; // script should start when user resumes - no more delay
1208        }
1209    }
1210
1211    /**
1212     * Allocate as many blocks as possible from the start of the warrant.
1213     * The first block must be allocated and all blocks of the route must
1214     * be in service. Otherwise partial success is OK.
1215     * Installs listeners for the entire route.
1216     * If occupation by another train is detected, a message will be
1217     * posted to the Warrant List Window. Note that warrants sharing their
1218     * clearance only allocate and set paths one block in advance.
1219     *
1220     * @param orders list of block orders
1221     * @param show (for use ONLY to display a temporary route) continues to
1222     *  allocate skipping over blocks occupied or owned by another warrant.
1223     * @return error message, if unable to allocate first block or if any block
1224     *         is OUT_OF_SERVICE
1225     */
1226    public String allocateRoute(boolean show, List<BlockOrder> orders) {
1227        if (_totalAllocated) {
1228            return null;
1229        }
1230        if (orders != null) {
1231            _orders = orders;
1232        }
1233        _allocated = false;
1234        OBlock block = getBlockAt(0);
1235        _message = block.allocate(this);
1236        if (_message != null) {
1237            return _message;
1238        }
1239
1240        _allocated = true; // start block allocated
1241        String msg = allocateFromIndex(false, 1);
1242        if (msg != null) {
1243            _message = msg;
1244        } else if (_partialAllocate) {
1245            _message = Bundle.getMessage("sharedRoute");
1246        }
1247        if (show) {
1248            return _message;
1249        }
1250        return null;
1251    }
1252
1253    /*
1254     * Allocate and set path
1255     * Only return a message if allocation of first index block fails.
1256     * @param set only allocates and sets path in one block, the 'index' block
1257     * show the entire route but do not set any turnouts in occupied blocks
1258     */
1259    private String allocateFromIndex(boolean set, int index) {
1260        int limit;
1261        if (_partialAllocate || set) {
1262            limit = Math.min(index + 1, _orders.size());
1263        } else {
1264            limit = _orders.size();
1265        }
1266        OBlock currentBlock = getBlockAt(_idxCurrentOrder);
1267        if (log.isDebugEnabled()) {
1268            log.debug("allocateFromIndex({}) block= {} _partialAllocate= {} for warrant \"{}\".",
1269                    index, currentBlock.getDisplayName(), _partialAllocate, getDisplayName());
1270        }
1271        _message = null;
1272        boolean passageDenied = false;  // cannot allocate beyond this point
1273        for (int i = index; i < limit; i++) {
1274            BlockOrder bo = _orders.get(i);
1275            OBlock block = bo.getBlock();
1276            String msg = block.allocate(this);
1277            if (msg != null && _message == null) {
1278                _message = msg;
1279                if (!this.equals(block.getWarrant())) {
1280                    _waitForWarrant = true;
1281                } else {
1282                    _waitForBlock = true;
1283                }
1284                passageDenied = true;
1285            }
1286            if (!passageDenied) {
1287                // loop back routes may enter a block a second time
1288                // Do not make current block a stopping block
1289                if (!currentBlock.equals(block)) {
1290                    if ((block.getState() & OBlock.OCCUPIED) != 0) {  // (!block.isAllocatedTo(this) || ) removed 7/1/18
1291                        if (_message == null) {
1292                            _message = Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
1293                        }
1294                        passageDenied = true;
1295                    }
1296                    if (!passageDenied && Warrant.Stop.equals(getPermissibleSpeedAt(bo))) {
1297                        if (_message == null) {
1298                            _message = Bundle.getMessage("BlockStopAspect", block.getDisplayName());
1299                        }
1300                        passageDenied = true;
1301                    }
1302                }
1303                if (!passageDenied && set) {
1304                    msg = bo.setPath(this);
1305                    if (msg != null) {
1306                        if (_message == null) {
1307                            _message = msg;
1308                        }
1309                        passageDenied = true;
1310                    }
1311                }
1312            }
1313        }
1314        if (!passageDenied && limit == _orders.size()) {
1315            _totalAllocated = true;
1316        }
1317        return _message;
1318    }
1319
1320    /**
1321     * Deallocates blocks from the current BlockOrder list
1322     */
1323    public void deAllocate() {
1324        _allocated = false;
1325        _totalAllocated = false;
1326        _routeSet = false;
1327        for (int i = 0; i < _orders.size(); i++) {
1328            OBlock block = _orders.get(i).getBlock();
1329            if (block.isAllocatedTo(this)) {
1330                block.deAllocate(this);
1331            }
1332        }
1333        _message = null;
1334        if (log.isDebugEnabled()) {
1335            log.debug("deallocated Route for warrant \"{}\".", getDisplayName());
1336        }
1337    }
1338
1339    /**
1340     * Convenience routine to use from Python to start a warrant.
1341     *
1342     * @param mode run mode
1343     */
1344    public void runWarrant(int mode) {
1345        if (_partialAllocate) {
1346            deAllocate();   // allow route to be shared with another warrant
1347        }
1348        setRoute(false, null);
1349        setRunMode(mode, null, null, null, false);
1350    }
1351
1352    /**
1353     * Set the route paths and turnouts for the warrant. Only the first block
1354     * must be allocated and have its path set. Partial success is OK.
1355     * A message of the first subsequent block that fails allocation
1356     * or path setting is written to a field that is
1357     * displayed in the Warrant List window. When running with block
1358     * detection, occupation by another train or block 'not in use' or
1359     * Signals denying movement are reasons
1360     * for such a message, otherwise only allocation to another warrant
1361     * prevents total success. Note that warrants sharing their clearance
1362     * only allocate and set paths one block in advance.
1363     *
1364     * @param show  value==1 will ignore _partialAllocate (to show route only)
1365     *            parm name delay of turnout steting deprecated
1366     * @param orders  BlockOrder list of route. If null, use permanent warrant
1367     *            copy.
1368     * @return message if the first block fails allocation, otherwise null
1369     */
1370    public String setRoute(boolean show, List<BlockOrder> orders) {
1371        // we assume our train is occupying the first block
1372        _routeSet = false;
1373        String msg = allocateRoute(show, orders);
1374        if (msg != null) {
1375            _message = msg;
1376            log.debug("setRoute: {}", msg);
1377            return _message;
1378        }
1379//        _allocated = true;
1380        BlockOrder bo = _orders.get(0);
1381        msg = bo.setPath(this);
1382        if (msg != null) {
1383            _message = msg;
1384            log.debug("setRoute: {}", msg);
1385            return _message;
1386        }
1387        _routeSet = true;   // partially set OK
1388        if (!_partialAllocate) {
1389            for (int i = 1; i < _orders.size(); i++) {
1390                bo = _orders.get(i);
1391                OBlock block = bo.getBlock();
1392                if ((block.getState() & OBlock.OCCUPIED) != 0) {
1393                    if (_message != null) {
1394                        _message = Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
1395                    }
1396                    break; // OK. warning status is posted with _message
1397                }
1398                if (Warrant.Stop.equals(getPermissibleSpeedAt(bo))) {
1399                    if (_message != null) {
1400                        _message = Bundle.getMessage("BlockStopAspect", block.getDisplayName());
1401                    }
1402                    break; // OK. warning status is posted with _message
1403                }
1404                msg = bo.setPath(this);
1405                if (msg != null && _message == null) {
1406                    _message = msg;
1407                    log.debug("setRoute: {}", msg);
1408                    break; // OK. warning status is posted with _message
1409                }
1410            }
1411        }
1412        _routeSet = true;
1413        return null;
1414    } // setRoute
1415
1416    /**
1417     * Check start block for occupied for start of run
1418     *
1419     * @return error message, if any
1420     */
1421    public String checkStartBlock() {
1422        if (log.isDebugEnabled()) {
1423            log.debug("checkStartBlock for warrant \"{}\".", getDisplayName());
1424        }
1425        BlockOrder bo = _orders.get(0);
1426        OBlock block = bo.getBlock();
1427        String msg = block.allocate(this);
1428        if (msg != null) {
1429            return msg;
1430        }
1431        msg = bo.setPath(this);
1432        if (msg != null) {
1433            return msg;
1434        }
1435        int state = block.getState();
1436        if ((state & OBlock.UNDETECTED) != 0 || _tempRunBlind) {
1437            msg = "BlockDark";
1438        } else if ((state & OBlock.OCCUPIED) == 0) {
1439            msg = "warnStart";
1440        }
1441        return msg;
1442    }
1443
1444    protected String checkforTrackers() {
1445        BlockOrder bo = _orders.get(0);
1446        OBlock block = bo.getBlock();
1447        log.debug("checkforTrackers at block {}", block.getDisplayName());
1448        Tracker t = InstanceManager.getDefault(TrackerTableAction.class).findTrackerIn(block);
1449        if (t != null) {
1450            return Bundle.getMessage("blockInUse", t.getTrainName(), block.getDisplayName());
1451        }
1452        return null;
1453    }
1454
1455    /**
1456     * Report any occupied blocks in the route
1457     *
1458     * @return String
1459     */
1460    public String checkRoute() {
1461        if (log.isDebugEnabled()) {
1462            log.debug("checkRoute for warrant \"{}\".", getDisplayName());
1463        }
1464        if (_orders==null || _orders.size() == 0) {
1465            return Bundle.getMessage("noBlockOrders");
1466        }
1467        OBlock startBlock = _orders.get(0).getBlock();
1468        for (int i = 1; i < _orders.size(); i++) {
1469            OBlock block = _orders.get(i).getBlock();
1470            if ((block.getState() & OBlock.OCCUPIED) != 0 && !startBlock.equals(block)) {
1471                return Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
1472            }
1473            Warrant w = block.getWarrant();
1474            if (w !=null && !this.equals(w)) {
1475                return Bundle.getMessage("AllocatedToWarrant",
1476                        w.getDisplayName(), block.getDisplayName(), w.getTrainName());
1477            }
1478        }
1479        return null;
1480    }
1481
1482    @Override
1483    public void propertyChange(java.beans.PropertyChangeEvent evt) {
1484        if (!(evt.getSource() instanceof NamedBean)) {
1485            return;
1486        }
1487        String property = evt.getPropertyName();
1488        if (log.isDebugEnabled()) {
1489            log.debug("propertyChange \"{}\" new= {} source= {} - warrant= {}",
1490                    property, evt.getNewValue(), ((NamedBean) evt.getSource()).getDisplayName(), getDisplayName());
1491        }
1492
1493        if (_protectSignal != null && _protectSignal == evt.getSource()) {
1494            if (property.equals("Aspect") || property.equals("Appearance")) {
1495                // signal controlling warrant has changed.
1496                readStoppingSignal();
1497            }
1498        } else if (property.equals("state")) {
1499            if (_stoppingBlock != null && _stoppingBlock.equals(evt.getSource())) {
1500                // starting block is allocated but not occupied
1501                if (_delayStart) { // wait for arrival of train to begin the run
1502                    if ((((Number) evt.getNewValue()).intValue() & OBlock.OCCUPIED) != 0) {
1503                        // train arrived at starting block
1504                        Warrant w = _stoppingBlock.getWarrant();
1505                        if (this.equals(w) || w == null) {
1506                            if (clearStoppingBlock()) {
1507                                OBlock block = getBlockAt(_idxCurrentOrder);
1508                                if (_runMode == MODE_RUN) {
1509                                    acquireThrottle();
1510                                } else if (_runMode == MODE_MANUAL) {
1511                                    fireRunStatus("ReadyToRun", -1, 0);   // ready to start msg
1512                                    _delayStart = false;
1513                                } else {
1514                                    _delayStart = false;
1515                                    log.error("StoppingBlock \"{}\" set with mode {}", block.getDisplayName(),
1516                                            MODES[_runMode]);
1517                                }
1518                                block._entryTime = System.currentTimeMillis();
1519                                block.setValue(_trainName);
1520                                block.setState(block.getState() | OBlock.RUNNING);
1521                            }
1522                        }
1523                    }
1524                } else if ((((Number) evt.getNewValue()).intValue() & OBlock.UNOCCUPIED) != 0) {
1525                    // normal wait for a train underway but blocked ahead by occupation
1526                    //  blocking occupation has left the stopping block
1527                    int idx = getIndexOfBlock(_stoppingBlock, _idxLastOrder);
1528                    if (idx >= 0) {
1529                        // Wait to allow departing rogue train to clear turnouts before re-allocation
1530                        // of this warrant resets the path. Rogue may leave on a conflicting path
1531                        // whose turnout control is shared with this path
1532                        ThreadingUtil.runOnGUIDelayed(() -> {
1533                            clearStoppingBlock();
1534                        }, 7000);   // 7 seconds
1535
1536                    }
1537                }
1538            } else if (_otherShareBlock != null && _otherShareBlock == evt.getSource()) {
1539                if ((((Number) evt.getNewValue()).intValue() & OBlock.UNOCCUPIED) != 0) {
1540                    clearShareTOBlock();
1541                }
1542            }
1543        } else if (_delayStart && property.equals("runMode") && ((Number) evt.getNewValue()).intValue() == MODE_NONE) {
1544            // Starting block was owned by another warrant for this engine
1545            // Engine has arrived and Blocking Warrant has finished
1546            ((Warrant) evt.getSource()).removePropertyChangeListener(this);
1547            if (clearStoppingBlock()) {
1548                acquireThrottle();
1549            }
1550        }
1551    } //end propertyChange
1552
1553    private boolean readStoppingSignal() {
1554        String speedType;
1555        if (_protectSignal instanceof SignalHead) {
1556            SignalHead head = (SignalHead) _protectSignal;
1557            int appearance = head.getAppearance();
1558            speedType = jmri.InstanceManager.getDefault(SignalSpeedMap.class)
1559                    .getAppearanceSpeed(head.getAppearanceName(appearance));
1560            if (log.isDebugEnabled()) {
1561                log.debug("SignalHead {} sets appearance speed to {} - warrant= {}",
1562                        _protectSignal.getDisplayName(), speedType, getDisplayName());
1563            }
1564        } else {
1565            SignalMast mast = (SignalMast) _protectSignal;
1566            String aspect = mast.getAspect();
1567            speedType = jmri.InstanceManager.getDefault(SignalSpeedMap.class).getAspectSpeed(aspect,
1568                    mast.getSignalSystem());
1569            if (log.isDebugEnabled()) {
1570                log.debug("SignalMast {} sets aspect speed to {} - warrant= {}",
1571                        _protectSignal.getDisplayName(), speedType, getDisplayName());
1572            }
1573        }
1574        if (speedType == null) {
1575            return false; // dark or no specified speed
1576        } else if (speedType.equals(Warrant.Stop)) {
1577            _waitForSignal = true;
1578            return true;
1579        } else {
1580            _curSpeedType = speedType;
1581            _waitForSignal = false;
1582            if (!_waitForBlock && !_waitForWarrant && _engineer != null) {
1583                allocateFromIndex(true, _idxCurrentOrder + 1);
1584                // engineer will clear its flags when ramp completes
1585                _engineer.rampSpeedTo(speedType, 0, false);
1586                return true;
1587            }
1588            fireRunStatus("SpeedChange", _idxCurrentOrder - 1, _idxCurrentOrder);
1589            return false;
1590        }
1591    }
1592
1593    private boolean doStoppingBlockClear() {
1594        if (_stoppingBlock == null) {
1595            return true;
1596        }
1597        String blockName = _stoppingBlock.getDisplayName();
1598        _stoppingBlock.removePropertyChangeListener(this);
1599        _stoppingBlock = null;
1600        if (log.isDebugEnabled())
1601            log.debug("Warrant \"{}\" Cleared _stoppingBlock= \"{}\".", getDisplayName(), blockName);
1602        return restoreRunning();
1603    }
1604
1605    /**
1606     * Called when a rogue train has left a block. Allows the warrant to continue to run.
1607     * Also called from propertyChange() to allow warrant to acquire a throttle
1608     * and launch an engineer. Also called by retry control command to help user
1609     * work out of an error condition.
1610     */
1611    private boolean clearStoppingBlock() {
1612        if (_stoppingBlock == null) {
1613            return false;
1614        }
1615        String blockName = _stoppingBlock.getDisplayName();
1616        if (log.isDebugEnabled())
1617            log.debug("Warrant \"{}\" clearing _stoppingBlock= \"{}\".",
1618                getDisplayName(), blockName);
1619
1620        String msg = allocateFromIndex(true, _idxCurrentOrder + 1);
1621        if (msg == null && doStoppingBlockClear()) {
1622            return true;
1623        }
1624
1625        if (log.isDebugEnabled())
1626            log.debug("Warrant \"{}\" allocation failed. {}. runState= {}",
1627                getDisplayName(), msg, (_engineer!=null?RUN_STATE[_engineer.getRunState()]:"NoEngineer"));
1628        // If this warrant is waiting for the block that another
1629        // warrant has occupied, and now the latter warrant leaves
1630        // the block - there are notifications to each warrant "simultaneously".
1631        // The latter warrant's deallocation may not have happened yet and
1632        // this has prevented allocation to this warrant.  For this case,
1633        // wait until leaving warrant's deallocation is seen and completed.
1634        @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification="false postive, guarded by while statement")
1635        final Runnable allocateBlocks = new Runnable() {
1636            @Override
1637            public void run() {
1638                long time = 0;
1639                String msg = null;
1640                try {
1641                    while (time < 200) {
1642                        msg = allocateFromIndex(true, _idxCurrentOrder + 1);
1643                        if (msg == null && doStoppingBlockClear()) {
1644                            break;
1645                        }
1646                        synchronized (this) {
1647                            wait(20);
1648                            time += 20;
1649                        }
1650                    }
1651                    if (msg != null) {
1652                        log.warn("Warrant \"{}\" unable to clear StoppingBlock message= \"{}\" time= {}", getDisplayName(), msg, time);
1653                    }
1654                    _message = msg;
1655                }
1656                catch (InterruptedException ie) {
1657                    log.warn("Warrant \"{}\" InterruptedException message= \"{}\" time= {}", getDisplayName(), ie.toString(), time);
1658                    Thread.currentThread().interrupt();
1659                }
1660                if (log.isDebugEnabled())
1661                    log.debug("Warrant \"{}\" waited {}ms for clearStoppingBlock to allocateFrom {}",
1662                           getDisplayName(), time, getBlockAt(_idxCurrentOrder + 1).getDisplayName());
1663            }
1664        };
1665
1666        synchronized (allocateBlocks) {
1667            Thread doit = jmri.util.ThreadingUtil.newThread(
1668                    () -> {
1669                        try {
1670                            javax.swing.SwingUtilities.invokeAndWait(allocateBlocks);
1671                        }
1672                        catch (Exception e) {
1673                            log.error("Exception in allocateBlocks", e);
1674                        }
1675                    },
1676                    "Warrant doit");
1677            doit.start();
1678        }
1679        return true;
1680    }
1681
1682    private boolean restoreRunning() {
1683        int runState = -1;
1684        if (_engineer != null) {
1685            runState = _engineer.getRunState();
1686            if (log.isDebugEnabled()) {
1687                log.debug("restoreRunning(): rampSpeedTo to \"{}\". runState= {}. warrant= {}",
1688                        _curSpeedType, RUN_STATE[runState], getDisplayName());
1689            }
1690            if (runState == HALT || runState == RAMP_HALT) {
1691                _waitForBlock = true;
1692            } else {
1693                _waitForBlock = false;
1694                _waitForWarrant = false;
1695            }
1696            if (!_waitForSignal && !_waitForBlock && !_waitForWarrant) {
1697                getBlockOrderAt(_idxCurrentOrder).setPath(this);
1698                _engineer.rampSpeedTo(_curSpeedType, 0, false);
1699            }
1700            return true;
1701        }
1702        return false;
1703    }
1704
1705    /**
1706     * block (nextBlock) sharing a turnout with _shareTOBlock is already
1707     * allocated.
1708     */
1709    private void clearShareTOBlock() {
1710        if (_otherShareBlock == null) {
1711            return;
1712        }
1713        _otherShareBlock.removePropertyChangeListener(this);
1714        String msg = _orders.get(getIndexOfBlock(_myShareBlock, _idxCurrentOrder)).setPath(this);
1715        if (log.isDebugEnabled()) {
1716            log.debug("_otherShareBlock= \"{}\" Cleared. {}",
1717                    _otherShareBlock.getDisplayName(), (msg==null?"":"msg"));
1718        }
1719        _otherShareBlock = null;
1720        _myShareBlock = null;
1721        if (_waitForWarrant) {
1722            _waitForWarrant = false;
1723            restoreRunning();
1724        }
1725    }
1726
1727    /**
1728     * Callback from trying to setPath() for this warrant. This warrant's Oblock
1729     * notices that another warrant has its path set and uses a turnout also
1730     * used by the current path of this. Rights to the turnout must be
1731     * negotiated, otherwise warrants may deadlock or derail a train.
1732     *
1733     * @param block block of another warrant that has a path set
1734     * @param myBlock block in this warrant sharing a TO with 'block'
1735     */
1736    protected void setShareTOBlock(OBlock block, OBlock myBlock) {
1737        OBlock prevBlk = _otherShareBlock;
1738        if (_myShareBlock != null) {
1739            if (_myShareBlock.equals(myBlock)) {
1740                return;
1741            }
1742            int idxBlock = getIndexOfBlock(myBlock, _idxCurrentOrder);
1743            int idxStop = getIndexOfBlock(_myShareBlock, _idxCurrentOrder);
1744            if (idxStop < idxBlock && idxStop >= 0) {
1745                return;
1746            }
1747            _otherShareBlock.removePropertyChangeListener(this);
1748        }
1749        _myShareBlock = myBlock;
1750        _otherShareBlock = block;
1751        _otherShareBlock.addPropertyChangeListener(this);
1752        if (log.isDebugEnabled()) {
1753            String msg = "Warrant \"{}\" sets _shareTOBlock= \"{}\" owned by warrant \"{}\"";
1754            if (prevBlk != null) {
1755                msg = msg + ", removes \"{}\"";
1756            }
1757            log.debug(msg, getDisplayName(), block.getDisplayName(), block.getWarrant(),
1758                    (prevBlk == null ? "" : prevBlk.getDisplayName()));
1759        }
1760    }
1761
1762    /**
1763     * Stopping block only used in MODE_RUN _stoppingBlock is an occupied OBlock
1764     * preventing the train from continuing the route
1765     * <p>
1766     */
1767    private void setStoppingBlock(OBlock block) {
1768        if (block == null) {
1769            return;
1770        }
1771        int idxBlock = getIndexOfBlock(block, _idxCurrentOrder);
1772        // _idxCurrentOrder == 0 may be a delayed start waiting for loco.
1773        // Otherwise don't set _stoppingBlock for a block occupied by train
1774        if (idxBlock < 0 || (_idxCurrentOrder == idxBlock && _idxCurrentOrder > 0)) {
1775            return;
1776        }
1777        OBlock prevBlk = _stoppingBlock;
1778        if (_stoppingBlock != null) {
1779            if (_stoppingBlock.equals(block)) {
1780                return;
1781            }
1782
1783            int idxStop = getIndexOfBlock(_stoppingBlock, _idxCurrentOrder);
1784            if ((idxBlock < idxStop) || idxStop < 0) {
1785                prevBlk.removePropertyChangeListener(this);
1786            } else {
1787                return;
1788            }
1789        }
1790        _stoppingBlock = block;
1791        _stoppingBlock.addPropertyChangeListener(this);
1792        if (log.isDebugEnabled()) {
1793            String msg = "Warrant \"{}\" sets _stoppingBlock= \"{}\"";
1794            if (prevBlk != null) {
1795                msg = msg + ", removes \"{}\"";
1796            }
1797            log.debug(msg, getDisplayName(), _stoppingBlock.getDisplayName(),
1798                    (prevBlk == null ? "" : prevBlk.getDisplayName()));
1799        }
1800    }
1801
1802    private void setStoppingSignal(int idx) {
1803        BlockOrder blkOrder = getBlockOrderAt(idx);
1804        NamedBean signal = blkOrder.getSignal();
1805
1806        NamedBean prevSignal = _protectSignal;
1807        if (_protectSignal != null) {
1808            if (_protectSignal.equals(signal)) {
1809                // Must be the route coming back to the same block
1810                if (_idxProtectSignal < idx && idx >= 0) {
1811                    _idxProtectSignal = idx;
1812                }
1813                return;
1814            } else {
1815                if ((_idxProtectSignal <= _idxCurrentOrder && !_waitForSignal) ||
1816                        (signal != null && idx < _idxProtectSignal)) {
1817                    _protectSignal.removePropertyChangeListener(this);
1818                    _protectSignal = null;
1819                    _idxProtectSignal = -1;
1820                } else {    // keep current _protectSignal
1821                    if (log.isDebugEnabled()) {
1822                        log.debug("Block \"{}\" of warrant \"{}\" keeps signal \"{}\" for block \"{}\"",
1823                                blkOrder.getBlock().getDisplayName(), getDisplayName(), _protectSignal.getDisplayName(),
1824                                getBlockAt(_idxProtectSignal).getDisplayName());
1825                    }
1826                    return;
1827                }
1828            }
1829        }
1830
1831        if (signal != null) {
1832            _protectSignal = signal;
1833            _idxProtectSignal = idx;
1834            _protectSignal.addPropertyChangeListener(this);
1835        }
1836        if (log.isDebugEnabled()) {
1837            if (signal == null && prevSignal == null) {
1838                return;
1839            }
1840            String msg = "Block \"{}\" Warrant \"{}\"";
1841            if (signal != null) {
1842                msg = msg + " sets _protectSignal= \"" + _protectSignal.getDisplayName() + "\"";
1843            }
1844            if (prevSignal != null) {
1845                msg = msg + ", removes signal= \"" + prevSignal.getDisplayName() + "\"";
1846            }
1847            log.debug(msg, blkOrder.getBlock().getDisplayName(), getDisplayName());
1848        }
1849    }
1850
1851    /**
1852     * Check if this is the next block of the train moving under the warrant
1853     * Learn mode assumes route is set and clear. Run mode update conditions.
1854     * <p>
1855     * Must be called on Layout thread.
1856     *
1857     * @param block Block in the route is going active.
1858     */
1859    @jmri.InvokeOnLayoutThread
1860    protected void goingActive(OBlock block) {
1861        // error if not on Layout thread
1862        if (!ThreadingUtil.isLayoutThread()) {
1863            log.error("invoked on wrong thread", new Exception("traceback"));
1864        }
1865
1866        if (_runMode == MODE_NONE) {
1867            return;
1868        }
1869        int activeIdx = getIndexOfBlock(block, _idxCurrentOrder);
1870        if (log.isDebugEnabled()) {
1871            log.debug("**Block \"{}\" goingActive. activeIdx= {}, _idxCurrentOrder= {}. warrant= {}",
1872                    block.getDisplayName(), activeIdx, _idxCurrentOrder, getDisplayName());
1873        }
1874        if (activeIdx <= 0) {
1875            // Not found or starting block, in which case 0 is handled as the _stoppingBlock
1876            if (activeIdx == 0 && _idxCurrentOrder == 0) {
1877                getBlockOrderAt(activeIdx).setPath(this);
1878            }
1879            return;
1880        }
1881        int runState = -1;
1882        if (_engineer != null) {
1883            runState = _engineer.getRunState();
1884        }
1885        if (activeIdx == _idxCurrentOrder) {
1886            // unusual occurrence.  dirty track? sensor glitch?
1887            log.info("Head of Train {} regained detection at Block= {}", getTrainName(), block.getDisplayName());
1888        } else if (activeIdx == _idxCurrentOrder + 1) {
1889            if (_delayStart || (runState == HALT && _engineer.getSpeedSetting() > 0.0f)) {
1890                log.warn("Rogue entered Block \"{}\" ahead of {}.", block.getDisplayName(), getTrainName());
1891                _message = Bundle.getMessage("BlockRougeOccupied", block.getDisplayName());
1892                return;
1893            }
1894            // be sure path is set for train in this block
1895            String msg = getBlockOrderAt(activeIdx).setPath(this);
1896            if (msg != null) {
1897                log.error("goingActive setPath fails: {}", msg);
1898            }
1899            if (_engineer != null && _engineer.getSpeedSetting() <= 0.0f) {
1900                // Train can still be moving after throttle set to 0. Block
1901                // boundaries can be crossed.  This is due to momentum 'gliding'
1902                // for any nonE-Stop or by choosing ramping to a stop.
1903                // spotbugs knows runState != HALT here
1904                if (runState != WAIT_FOR_CLEAR && runState != STOP_PENDING && runState != RAMP_HALT) {
1905                    // Apparently NOT already stopped or just about to be.
1906                    // Therefore, assume a Rogue has just entered.
1907                    setStoppingBlock(block);
1908                    _engineer.setWaitforClear(true);
1909                    _engineer.setSpeedToType(Warrant.Stop);     // for safety
1910                    return;
1911                }
1912            }
1913            // Since we are moving we assume it is our train entering the block
1914            // continue on.
1915            _idxLastOrder = _idxCurrentOrder;
1916            _idxCurrentOrder = activeIdx;
1917        } else if (activeIdx > _idxCurrentOrder + 1) {
1918            if (_runMode == MODE_LEARN) {
1919                log.error("Block \"{}\" became occupied before block \"{}\". ABORT recording.",
1920                        block.getDisplayName(), getBlockAt(_idxCurrentOrder + 1).getDisplayName());
1921                fireRunStatus("abortLearn", activeIdx, _idxCurrentOrder);
1922                return;
1923            }
1924            // if previous blocks are dark, this could be for our train
1925            // check from current block to this just activated block
1926            for (int idx = _idxCurrentOrder + 1; idx < activeIdx; idx++) {
1927                OBlock preBlock = getBlockAt(idx);
1928                if ((preBlock.getState() & OBlock.UNDETECTED) == 0) {
1929                    // not dark, therefore not our train
1930                    setStoppingBlock(block);
1931                    if (log.isDebugEnabled()) {
1932                        OBlock curBlock = getBlockAt(_idxCurrentOrder);
1933                        log.debug("Rogue train entered block \"{}\" ahead of train {} currently in block \"{}\"!",
1934                                block.getDisplayName(), _trainName, curBlock.getDisplayName());
1935                    }
1936                    return;
1937                }
1938                // we assume this is our train entering block
1939            }
1940            // previous blocks were checked as UNDETECTED above
1941            // Indicate the previous dark block was entered
1942            OBlock prevBlock = getBlockAt(activeIdx - 1);
1943            prevBlock._entryTime = System.currentTimeMillis() - 1000; // arbitrarily say
1944            prevBlock.setValue(_trainName);
1945            prevBlock.setState(prevBlock.getState() | OBlock.RUNNING);
1946            if (log.isDebugEnabled()) {
1947                log.debug("Train leaving UNDETECTED block \"{}\" now entering block\"{}\". warrant {}",
1948                        prevBlock.getDisplayName(), block.getDisplayName(), getDisplayName());
1949            }
1950            // Since we are moving we assume it is our train entering the block
1951            // continue on.
1952        } else if (_idxCurrentOrder > activeIdx) {
1953            // unusual occurrence.  dirty track, sensor glitch, too fast for goingInactive() for complete?
1954            log.info("Tail of Train {} regained detection behind Block= {} at block= {}", 
1955                    getTrainName(), block.getDisplayName(), getBlockAt(activeIdx).getDisplayName());
1956            return;
1957        }
1958        setHeadOfTrain(block);
1959        fireRunStatus("blockChange", getBlockAt(activeIdx - 1), block);
1960        if (_engineer != null) {
1961            _engineer.clearWaitForSync(); // Sync commands if train is faster than ET
1962        }
1963        // _idxCurrentOrder has been incremented. Warranted train has entered this block.
1964        // Do signals, speed etc.
1965        if (_idxCurrentOrder < _orders.size() - 1) {
1966            allocateFromIndex(true, _idxCurrentOrder + 1);
1967            if (_engineer != null) {
1968                BlockOrder bo = _orders.get(_idxCurrentOrder + 1);
1969                if ((bo.getBlock().getState() & OBlock.UNDETECTED) != 0) {
1970                    // can't detect next block, use ET
1971                    _engineer.setRunOnET(true);
1972                } else if (!_tempRunBlind) {
1973                    _engineer.setRunOnET(false);
1974                }
1975            }
1976        } else { // train is in last block. past all signals
1977            if (_protectSignal != null) {
1978                _protectSignal.removePropertyChangeListener(this);
1979                _protectSignal = null;
1980                _idxProtectSignal = -1;
1981            }
1982            if (_runMode == MODE_MANUAL) { // no script, so terminate warrant run
1983                stopWarrant(false, true);
1984            }
1985        }
1986        if (log.isTraceEnabled()) {
1987            log.debug("end of goingActive. leaving \"{}\" entered \"{}\". warrant {}",
1988                    getBlockAt(activeIdx - 1).getDisplayName(), block.getDisplayName(), getDisplayName());
1989        }
1990        setMovement();
1991    } //end goingActive
1992
1993    private void setHeadOfTrain(OBlock block ) {
1994        block.setValue(_trainName);
1995        block.setState(block.getState() | OBlock.RUNNING);
1996        block._entryTime = System.currentTimeMillis();
1997        if (_runMode == MODE_RUN) {
1998//            BlockOrder blkOrd = getBlockOrderAt(_idxLastOrder);
1999            float length = 0;
2000            if (_idxCurrentOrder > 1 && _idxCurrentOrder < _orders.size()) {
2001                for (int i = _idxLastOrder; i < _idxCurrentOrder; i++) {
2002                    // add length of possible dark block
2003                    BlockOrder bo = getBlockOrderAt(i);
2004                    length += bo.getPath().getLengthMm();
2005                }
2006            }
2007            _speedUtil.enteredBlock(block, length);
2008        }
2009    }
2010    /**
2011     * @param block Block in the route is going Inactive
2012     */
2013    @jmri.InvokeOnLayoutThread
2014    protected void goingInactive(OBlock block) {
2015        if (_runMode == MODE_NONE) {
2016            return;
2017        }
2018
2019        // error if not on Layout thread
2020        if (!ThreadingUtil.isLayoutThread()) {
2021            log.error("invoked on wrong thread", new Exception("traceback"));
2022        }
2023
2024        int idx = getIndexOfBlockBefore(_idxCurrentOrder, block); // if idx >= 0, it is in this warrant
2025        if (log.isDebugEnabled()) {
2026            log.debug("*Block \"{}\" goingInactive. idx= {}, _idxCurrentOrder= {}. warrant= {}",
2027                    block.getDisplayName(), idx, _idxCurrentOrder, getDisplayName());
2028        }
2029        if (idx < _idxCurrentOrder) {
2030            releaseBlock(block, idx);
2031        } else if (idx == _idxCurrentOrder) {
2032            // Train not visible if current block goes inactive
2033            if (_idxCurrentOrder + 1 < _orders.size()) {
2034                OBlock nextBlock = getBlockAt(_idxCurrentOrder + 1);
2035                if ((nextBlock.getState() & OBlock.UNDETECTED) != 0) {
2036                    if (_engineer != null) {
2037                        goingActive(nextBlock); // fake occupancy for dark block
2038                        releaseBlock(block, idx);
2039                    } else {
2040                        if (_runMode == MODE_LEARN) {
2041                            _idxCurrentOrder++; // assume train has moved into the dark block
2042                            fireRunStatus("blockChange", block, nextBlock);
2043                        } else if (_runMode == MODE_RUN) {
2044                            controlRunTrain(ABORT);
2045                        }
2046                    }
2047                } else {
2048                    if ((nextBlock.getState() & OBlock.OCCUPIED) != 0 && (_waitForBlock || _waitForWarrant)) {
2049                        // assume train rolled into occupied ahead block.
2050                        // Should _idxCurrentOrder & _idxLastOrder be incremented? Better to let user take control?
2051                        releaseBlock(block, idx);
2052                        setHeadOfTrain(nextBlock);
2053                        fireRunStatus("blockChange", block, nextBlock);
2054                        log.warn("block \"{}\" goingInactive. train has entered rogue occupied block {}! warrant {}",
2055                                block.getDisplayName(), nextBlock.getDisplayName(), getDisplayName());
2056                   } else {
2057                       boolean lost = true;
2058                       if (_idxCurrentOrder > 0) {
2059                           OBlock prevBlock = getBlockAt(_idxCurrentOrder - 1);
2060                           if ((prevBlock.getState() & OBlock.OCCUPIED) != 0 && this.equals(prevBlock.getWarrant())) {
2061                               // assume nosed into block, then lost contact
2062                               _idxCurrentOrder -= 1;  // set head to previous BlockOrder
2063                               lost = false;
2064                           }
2065                       }
2066                       if (lost) {
2067                           log.warn("block \"{}\" goingInactive. train is lost! warrant {}",
2068                                       block.getDisplayName(), getDisplayName());
2069                           fireRunStatus("blockChange", block, null);
2070                           if (_engineer != null) {
2071                               _engineer.setStop(false, true);   // halt and set 0 throttle
2072                               if (_idxCurrentOrder == 0) {
2073                                   setStoppingBlock(block);
2074                                   _delayStart = true;
2075                               }
2076                           }
2077                       }
2078                   }
2079                }
2080            } else {    // at last block
2081                 OBlock prevBlock = getBlockAt(_idxCurrentOrder - 1);
2082                if ((prevBlock.getState() & OBlock.OCCUPIED) != 0 && this.equals(prevBlock.getWarrant())) {
2083                    // assume nosed into block, then lost contact
2084                    _idxCurrentOrder -= 1;  // set head to previous BlockOrder
2085                } else {
2086                    log.warn("block \"{}\" Last Block goingInactive. train is lost! warrant {}",
2087                            block.getDisplayName(), getDisplayName());
2088                    if (_engineer != null) {
2089                        _engineer.setStop(false, false);   // set 0 throttle
2090                    }
2091                }
2092            }
2093        }
2094    } // end goingInactive
2095
2096    /**
2097     * Deallocates all blocks prior to and including block
2098     */
2099    private void releaseBlock(OBlock block, int idx) {
2100        /*
2101         * Only deallocate block if train will not use the block again. Blocks
2102         * ahead could loop back over blocks previously traversed. That is,
2103         * don't disturb re-allocation of blocks ahead. Previous Dark blocks do
2104         * need deallocation
2105         */
2106        for (int i = idx; i > -1; i--) {
2107            boolean dealloc = true;
2108            OBlock prevBlock = getBlockAt(i);
2109            for (int j = i + 1; j < _orders.size(); j++) {
2110                if (prevBlock.equals(getBlockAt(j))) {
2111                    dealloc = false;
2112                }
2113            }
2114            if (dealloc && prevBlock.isAllocatedTo(this)) {
2115                prevBlock.setValue(null);
2116                prevBlock.deAllocate(this);
2117                _totalAllocated = false;
2118                fireRunStatus("blockRelease", null, block);
2119            }
2120        }
2121    }
2122
2123    /**
2124     * build map of BlockSpeedInfo's for the route.
2125     * <p>
2126     * Put max speed and time in block's first occurrence after current command
2127     * index
2128     */
2129    private void getBlockSpeedTimes() {
2130        _speedInfo = new ArrayList<>();
2131        String blkName = null;
2132        float firstSpeed = 0.0f; // used for entrance
2133        float maxSpeed = 0.0f;
2134        float lastSpeed = 0.0f;
2135        float prevSpeed = 0.0f;
2136        long blkTime = 0;
2137        float blkDist = 0;
2138        int firstIdx = 0; // for all blocks except first, this is index of NOOP command
2139        boolean hasSpeed = false;
2140        ThrottleSetting ts = _commands.get(0);
2141        blkName = ts.getBeanDisplayName();
2142        for (int i = 0; i < _commands.size(); i++) {
2143            ts = _commands.get(i);
2144            if (ts.getCommand().equals(Command.NOOP)) {
2145                // make map entry
2146                blkTime += ts.getTime();
2147                _speedInfo.add(new BlockSpeedInfo(firstSpeed, maxSpeed, lastSpeed, blkTime, blkDist, firstIdx, i));
2148                if (log.isDebugEnabled()) {
2149                    log.debug("block: {} speeds: entrance= {} max= {} exit= {} time: {}ms distance= {}. index {} to {}",
2150                            blkName, firstSpeed, maxSpeed, lastSpeed, blkTime, blkDist, firstIdx, i);
2151                }
2152                blkName = ts.getBeanDisplayName();
2153                blkTime = 0;
2154                blkDist = 0;
2155                firstSpeed = lastSpeed;
2156                maxSpeed = lastSpeed;
2157                prevSpeed = lastSpeed;
2158                firstIdx = i + 1; // first in next block is next index
2159            } else { // collect block info
2160                ThrottleSetting.CommandValue cmdVal = ts.getValue();
2161                if (hasSpeed) {
2162                    blkTime += ts.getTime();
2163                    blkDist += _speedUtil.getDistanceOfSpeedChange(prevSpeed, lastSpeed, blkTime);
2164                }
2165                if (cmdVal.getType() == ThrottleSetting.ValueType.VAL_FLOAT) {
2166                    prevSpeed = lastSpeed;
2167                    lastSpeed = cmdVal.getFloat();
2168                    if (lastSpeed > maxSpeed) {
2169                        maxSpeed = lastSpeed;
2170                    }
2171                    hasSpeed = (lastSpeed > 0);
2172                    blkTime = 0;
2173                }
2174            }
2175        }
2176        _speedInfo.add(new BlockSpeedInfo(firstSpeed, maxSpeed, lastSpeed, blkTime, blkDist, firstIdx, _commands.size() - 1));
2177        if (log.isDebugEnabled()) {
2178            log.debug("block: {} speeds: entrance= {} max= {} exit= {} time: {}ms. index {} to {}",
2179                    blkName, Math.round(firstSpeed*100)/100, Math.round(maxSpeed*100)/100, Math.round(lastSpeed*100)/100,
2180                    blkTime, firstIdx, (_commands.size() - 1));
2181        }
2182    }
2183
2184    static class BlockSpeedInfo {
2185
2186        float entranceSpeed;
2187        float maxSpeed;
2188        float exitSpeed;
2189        long time;
2190        float blkDist;
2191        int firstIdx;
2192        int lastIdx;
2193
2194        BlockSpeedInfo(float ens, float ms, float exs, long t, float d, int fi, int li) {
2195            entranceSpeed = ens;
2196            maxSpeed = ms;
2197            exitSpeed = exs;
2198            time = t;
2199            blkDist = d;
2200            firstIdx = fi;
2201            lastIdx = li;
2202        }
2203
2204        // Throttle setting at entrance of block
2205        float getEntranceSpeed() {
2206            return entranceSpeed;
2207        }
2208
2209        // Maximum throttle setting in block
2210        float getMaxSpeed() {
2211            return maxSpeed;
2212        }
2213
2214        // Throttle setting at exit of block
2215        float getExitSpeed() {
2216            return exitSpeed;
2217        }
2218
2219        long getTime() {
2220            return time;
2221        }
2222
2223        float getDistance() {
2224            return blkDist;
2225        }
2226        int getFirstIndex() {
2227            return firstIdx;
2228        }
2229
2230        int getLastIndex() {
2231            return lastIdx;
2232        }
2233    }
2234
2235    // utility for Engineer to look ahead and find ramp up speed it should match
2236    protected float getEntranceSpeed(OBlock block) {
2237        return _speedInfo.get(getIndexOfBlock(block, _idxCurrentOrder)).getEntranceSpeed();
2238    }
2239
2240    /**
2241     * Finds speed change type at entrance of a block. Called by:
2242     * getSpeedTypeForBlock
2243     * setMovement
2244     * allocateFromIndex
2245     * setRoute
2246     *
2247     * @return a speed type or null for continue at current type
2248     */
2249    private String getPermissibleSpeedAt(BlockOrder bo) {
2250        OBlock block = bo.getBlock();
2251        String speedType = bo.getPermissibleEntranceSpeed();
2252        if (speedType != null) {
2253            if (log.isDebugEnabled()) {
2254                log.debug("getPermissibleSpeedAt(): \"{}\" Signal speed= {} warrant= {}",
2255                        block.getDisplayName(), speedType, getDisplayName());
2256            }
2257        } else { //  if signal is configured, ignore block
2258            speedType = block.getBlockSpeed();
2259            if (speedType.equals("")) {
2260                speedType = null;
2261            }
2262            if (speedType != null) {
2263                if (log.isDebugEnabled()) {
2264                    log.debug("getPermissibleSpeedAt(): \"{}\" Block speed= {} warrant= {}",
2265                            block.getDisplayName(), speedType, getDisplayName());
2266                }
2267            }
2268        }
2269        if (speedType == null) {
2270            speedType = _curSpeedType;
2271        }
2272        return speedType;
2273    }
2274
2275    synchronized private void cancelDelayRamp() {
2276        if (_delayCommand != null) {
2277            _delayCommand.interrupt();
2278            log.debug("cancelDelayRamp called on warrant {}", getDisplayName());
2279            _delayCommand = null;
2280        }
2281    }
2282
2283    synchronized private void rampDelayDone() {
2284        _delayCommand = null;
2285    }
2286
2287    @Override
2288    public void dispose() {
2289        stopWarrant(false, true);
2290        super.dispose();
2291    }
2292
2293    @Override
2294    public String getBeanType() {
2295        return Bundle.getMessage("BeanNameWarrant");
2296    }
2297
2298    private class CommandDelay extends Thread {
2299
2300        String nextSpeedType;
2301        long _startTime = 0;
2302        long _waitTime = 0;
2303        boolean quit = false;
2304        int _endBlockIdx;
2305        boolean _useIndex;
2306
2307        CommandDelay(String speedType, long startWait, int endBlockIdx, boolean useIndex) {
2308            nextSpeedType = speedType;
2309            if (startWait > 0) {
2310                _waitTime = startWait;
2311            }
2312            _endBlockIdx = endBlockIdx;
2313            _useIndex = useIndex;
2314            setName("CommandDelay(" + getTrainName() +")");
2315            if (log.isDebugEnabled()) {
2316                log.debug("CommandDelay: will wait {}ms, then Ramp to {} in block {}. warrant {}",
2317                        startWait, speedType, getBlockAt(endBlockIdx).getDisplayName(), getDisplayName());
2318            }
2319        }
2320
2321        // check if request for a duplicate CommandDelay can be cancelled
2322        boolean doNotCancel(String speedType, long startWait, int endBlockIdx) {
2323            if (endBlockIdx == _endBlockIdx && speedType.equals(nextSpeedType) &&
2324                    (_waitTime - (System.currentTimeMillis() - _startTime)) < startWait) {
2325                return true;
2326            }
2327            return false;   // not a duplicate or shortens time wait.
2328        }
2329
2330        @Override
2331        @SuppressFBWarnings(value = "WA_NOT_IN_LOOP", justification = "notify never called on this thread")
2332        public void run() {
2333            _startTime = System.currentTimeMillis();
2334            synchronized (this) {
2335                if (_waitTime > 0.0) {
2336                    try {
2337                        wait(_waitTime);
2338                    } catch (InterruptedException ie) {
2339                        if (log.isDebugEnabled()) {
2340                            log.debug("CommandDelay interrupt.  Ramp to {} not done. warrant {}",
2341                                    nextSpeedType, getDisplayName());
2342                        }
2343                        quit = true;
2344                    }
2345                }
2346                if (!quit && _engineer != null) {
2347                    if (log.isDebugEnabled()) {
2348                        log.debug("CommandDelay: after wait of {}ms, start Ramp to {}. warrant {}",
2349                                _waitTime, nextSpeedType, getDisplayName());
2350                    }
2351                    _engineer.rampSpeedTo(nextSpeedType, _endBlockIdx, _useIndex);
2352                    // start ramp first
2353                    if (nextSpeedType.equals(Stop) || nextSpeedType.equals(EStop)) {
2354                        _engineer.setWaitforClear(true);
2355                    } else {
2356                        _curSpeedType = nextSpeedType;
2357                    }
2358                }
2359            }
2360            rampDelayDone();
2361        }
2362    }
2363
2364    /**
2365     * Orders are parsed to get any speed restrictions. Called from
2366     * setMovement
2367     * controlRunTrain (RESUME)
2368     * 
2369     * @param idxBlockOrder index of Orders
2370     * @return Speed type name
2371     */
2372    private String getSpeedTypeForBlock(int idxBlockOrder) {
2373        BlockOrder blkOrder = getBlockOrderAt(idxBlockOrder);
2374        OBlock block = blkOrder.getBlock();
2375
2376        String speedType = getPermissibleSpeedAt(blkOrder);
2377        setStoppingSignal(idxBlockOrder);
2378        if (speedType.equals(Warrant.Stop)) {
2379            // block speed cannot be Stop, so OK to assume signal
2380            _waitForSignal = true;
2381        }
2382
2383        String msg = block.allocate(this);
2384        if (msg != null) {
2385            if (!this.equals(block.getWarrant())) {
2386                _waitForWarrant = true;
2387            } else {
2388                _waitForBlock = true;
2389            }
2390            setStoppingBlock(block);
2391            speedType = Warrant.Stop;
2392        } else if ((block.getState() & OBlock.OCCUPIED) != 0) {
2393            if (idxBlockOrder > _idxCurrentOrder) {
2394                setStoppingBlock(block);
2395                _waitForBlock = true;
2396                speedType = Warrant.Stop;
2397            }
2398        } else if (!speedType.equals(Warrant.Stop)) {
2399            msg = blkOrder.setPath(this);
2400            if (msg != null) {
2401                // when setPath fails, it calls setShareTOBlock
2402                _waitForWarrant = true;
2403                speedType = Warrant.Stop;
2404                _message = msg;
2405                log.warn(msg);
2406            }
2407        }
2408
2409        if (log.isDebugEnabled()) {
2410            if (_waitForSignal || _waitForBlock || _waitForWarrant) {
2411                log.debug ("Found \"{}\" speed change of type \"{}\" needed to enter block \"{}\".",
2412                        (_waitForSignal?"Signal":(_waitForWarrant?"Warrant":"Block")), speedType, block.getDisplayName());
2413            }
2414        }
2415        return speedType;
2416    }
2417
2418    /*
2419     * Return pathLength of the block.
2420     */
2421    private float getAvailableDistance(int idxBlockOrder) {
2422        BlockOrder blkOrder = getBlockOrderAt(idxBlockOrder);
2423        float blkDist = _speedInfo.get(idxBlockOrder).getDistance();
2424        float pathLength = blkOrder.getPath().getLengthMm();
2425        if (log.isDebugEnabled()) {
2426            log.debug("getAvailableDistance: in block \"{}\" blkDist= {}, pathLength= {}. warrant= {}",
2427                    blkOrder.getBlock().getDisplayName(), blkDist, pathLength, getDisplayName());
2428        }
2429        if (idxBlockOrder == 0 || pathLength <= 1.0f) {
2430            return blkDist;
2431        } else {
2432            return pathLength;
2433        }
2434    }
2435
2436    /**
2437     * Get ramp length needed to change speed using the WarrantPreference deltas for
2438     * throttle increment and time increment.  This should only be used for ramping down.
2439     * @param idxBlockOrder index of BlockOrder
2440     * @param toSpeedType Speed type change
2441     * @return distance in millimeters
2442     */
2443    private float rampLengthOfEntrance(int idxBlockOrder, String toSpeedType) {
2444        BlockSpeedInfo blkSpeedInfo = _speedInfo.get(idxBlockOrder);
2445        float throttleSetting = blkSpeedInfo.getEntranceSpeed();
2446        float rampLen = _speedUtil.rampLengthForRampDown(throttleSetting, _curSpeedType, toSpeedType);
2447        if (_waitForBlock || _waitForWarrant) {    // occupied or foreign track ahead.
2448            rampLen *= RAMP_ADJUST;    // provide more space to avoid collisions
2449        } else if (_waitForSignal) {        // signal restricting speed
2450            rampLen += getBlockOrderAt(idxBlockOrder).getEntranceSpace(); // signal's adjustment
2451        }
2452        return rampLen;
2453    }
2454
2455    private static float RAMP_ADJUST = 1.05f;
2456
2457    /**
2458     * Called to set the correct speed for the train when the scripted speed
2459     * must be modified due to a track condition (signaled speed or rogue
2460     * occupation). Also called to return to the scripted speed after the
2461     * condition is cleared. Assumes the train occupies the block of the current
2462     * block order.
2463     * <p>
2464     * Looks for speed requirements of this block and takes immediate action if
2465     * found. Otherwise looks ahead for future speed change needs. If speed
2466     * restriction changes are required to begin in this block, but the change
2467     * is not immediate, then determine the proper time delay to start the speed
2468     * change.
2469     *
2470     * @return false on errors
2471     */
2472    private boolean setMovement() {
2473        if (_runMode != Warrant.MODE_RUN || _idxCurrentOrder > _orders.size() - 1) {
2474            return false;
2475        }
2476        if (_engineer == null) {
2477            controlRunTrain(ABORT);
2478            return false;
2479        }
2480        int runState = _engineer.getRunState();
2481        BlockOrder blkOrder = getBlockOrderAt(_idxCurrentOrder);
2482        OBlock curBlock = blkOrder.getBlock();
2483        if (log.isDebugEnabled()) {
2484            log.debug("!-setMovement: Block\"{}\" runState= {} current speedType= {} {} warrant {}.",
2485                    curBlock.getDisplayName(), RUN_STATE[runState], _curSpeedType,
2486                    (_partialAllocate ? "ShareRoute" : ""), getDisplayName());
2487        }
2488
2489        String msg = blkOrder.setPath(this);
2490        if (msg != null) {
2491            log.error("Train {} in block \"{}\" but path cannot be set! msg= {}, warrant= {}",
2492                    getTrainName(), curBlock.getDisplayName(), msg, getDisplayName());
2493            _engineer.setStop(false, true);   // speed set to 0.0 (not E-top) User must restart
2494            return false;
2495        }
2496
2497        if ((curBlock.getState() & (OBlock.OCCUPIED | OBlock.UNDETECTED)) == 0) {
2498            log.error("Train {} expected in block \"{}\" but block is unoccupied! warrant= {}",
2499                    getTrainName(), curBlock.getDisplayName(), getDisplayName());
2500            _engineer.setStop(false, true); // user needs to see what happened and restart
2501            return false;
2502        }
2503        // Error checking done.
2504
2505        // checking situation for the current block
2506        // _curSpeedType is the speed type train is currently running
2507        // currentType is the required speed limit for this block
2508        String currentType = getPermissibleSpeedAt(blkOrder);
2509
2510        float speedSetting = _engineer.getSpeedSetting();
2511        if (log.isDebugEnabled()) {
2512            log.debug("Stopping flags: _waitForBlock={}, _waitForSignal={}, _waitForWarrant={} runState= {}, speedSetting= {}, SpeedType= {}. warrant {}",
2513                    _waitForBlock, _waitForSignal, _waitForWarrant, RUN_STATE[runState], speedSetting, currentType, getDisplayName());
2514        }
2515
2516        if (_noRamp || _speedInfo == null) {
2517            if (_idxCurrentOrder < _orders.size() - 1) {
2518                currentType = getSpeedTypeForBlock(_idxCurrentOrder + 1);
2519                if (_speedUtil.secondGreaterThanFirst(currentType, _curSpeedType)) {
2520                    if (log.isDebugEnabled()) {
2521                        log.debug("No ramp speed change of \"{}\" from \"{}\" in block \"{}\" warrant= {}",
2522                                currentType, _curSpeedType, curBlock.getDisplayName(), getDisplayName());
2523                    }
2524                    _engineer.setSpeedToType(currentType);
2525                    if (!currentType.equals(Stop) && !currentType.equals(EStop)) {
2526                        _curSpeedType = currentType;
2527                    }
2528                }
2529            }
2530            if (log.isDebugEnabled()) {
2531                log.debug("Exit setMovement due to no ramping. warrant= {}", getDisplayName());
2532            }
2533            return true;
2534        }
2535
2536        if (!currentType.equals(_curSpeedType)) {
2537            if (_speedUtil.secondGreaterThanFirst(currentType, _curSpeedType)) {
2538                // currentType Speed violation!
2539                // Cancel any delayed speed changes currently in progress.
2540                cancelDelayRamp();
2541                jmri.NamedBean signal = blkOrder.getSignal();
2542                String name;
2543                if (signal != null) {
2544                    name = signal.getDisplayName();
2545                } else {
2546                    name = curBlock.getDisplayName();
2547                }
2548                if (currentType.equals(Stop) || currentType.equals(EStop)) {
2549                    _engineer.setStop(currentType.equals(EStop), false);   // sets speed 0
2550                    log.info("Train missed Stop signal {} in block \"{}\". warrant= {}",
2551                                name, curBlock.getDisplayName(), getDisplayName());
2552                    fireRunStatus("SpeedRestriction", name, currentType); // message of speed violation
2553                    return true; // don't do anything else until stopping condition cleared
2554                } else {
2555                    _curSpeedType = currentType;
2556                    _engineer.setSpeedToType(currentType); // immediate decrease
2557                    if (log.isDebugEnabled()) {
2558                        log.debug(
2559                        "Train {} moved past required speed of \"{}\" at speed \"{}\" in block \"{}\"! Set speed to {}. warrant= {}",
2560                                getTrainName(), currentType, _curSpeedType, curBlock.getDisplayName(), currentType,
2561                                getDisplayName());
2562                    }
2563                    fireRunStatus("SpeedRestriction", name, currentType); // message of speed violation
2564                 }
2565            } else {    // speed increases since type currentType >_curSpeedType
2566                // Sometimes an advance signal does not indicate a distant signal needs help in slowing a train.
2567                // (user does not use signal logic or has not considered block lengths properly)
2568                // e.g. distance signal has a stop but advance signal has clear instead of reduce speed.
2569                if (_engineer.isRamping()) {
2570                    // warrant previously detected the need to ramp down and has already begun it
2571                    log.warn("Inconsistency beteen the speed profile of {} and length of path \"{}\" in block \"{}\"!",
2572                            _speedUtil.getDccAddress(), blkOrder.getPathName(), curBlock.getDisplayName());
2573                    // Cannot increase speed if there is an obstacle or speed restriction ahead
2574                    return false;
2575                }
2576                // Cancel any delayed speed changes currently in progress.
2577                cancelDelayRamp();
2578                if (log.isTraceEnabled()) {
2579                    log.trace("Increasing speed to \"{}\" from \"{}\" in block \"{}\" warrant= {}",
2580                            currentType, _curSpeedType, curBlock.getDisplayName(), getDisplayName());
2581                }
2582                _curSpeedType = currentType; // cannot be Stop or EStop
2583                _engineer.rampSpeedTo(currentType, 0, false);
2584            }
2585            // continue, there may be blocks ahead that need a speed decrease to begin in this block
2586        } else {
2587            if (runState == WAIT_FOR_CLEAR || runState == HALT) {
2588                // trust that STOP_PENDING or RAMP_HALT makes hard stop unnecessary
2589                cancelDelayRamp();
2590                _engineer.setStop(false, false);
2591                if (log.isDebugEnabled()) {
2592                    log.debug("Set Stop to hold train at block \"{}\" runState= {}, speedSetting= {}.warrant {}",
2593                            curBlock.getDisplayName(), RUN_STATE[runState], speedSetting, getDisplayName());
2594                }
2595                fireRunStatus("SpeedChange", _idxCurrentOrder - 1, _idxCurrentOrder); // message reason for hold
2596                return true;
2597            } else if (runState == STOP_PENDING || runState == RAMP_HALT) {
2598                if (log.isDebugEnabled()) {
2599                    log.debug("Hold train at block \"{}\" runState= {}, speedSetting= {}.warrant {}",
2600                            curBlock.getDisplayName(), RUN_STATE[runState], speedSetting, getDisplayName());
2601                }
2602                fireRunStatus("SpeedChange", _idxCurrentOrder - 1, _idxCurrentOrder); // message reason for hold
2603                return true;
2604            }
2605            // Continue, look ahead for possible speed modification decrease.
2606        }
2607
2608        //look ahead for a speed change slower than the current speed
2609        // Note: blkOrder still is blkOrder = getBlockOrderAt(_idxCurrentOrder);
2610        // Do while speedType >= currentType
2611        int idxBlockOrder = _idxCurrentOrder;
2612        String speedType = _curSpeedType;   // the first reduced speed found
2613        float availDist = getAvailableDistance(idxBlockOrder);
2614        // getSpeedTypeForBlock() sets flags if speed change is detected
2615        while (!_speedUtil.secondGreaterThanFirst(speedType, _curSpeedType) && idxBlockOrder < _orders.size() - 1) {
2616            //  speedType >=_curSpeedType
2617            speedType = getSpeedTypeForBlock(++idxBlockOrder);  // speed for entry into next block
2618            availDist += getAvailableDistance(idxBlockOrder);
2619        }
2620
2621        if (idxBlockOrder == _orders.size() - 1) {
2622            // went through remaining BlockOrders,
2623            if (!_speedUtil.secondGreaterThanFirst(speedType, _curSpeedType)) {
2624                // found no speed decreases, except for possibly the last
2625                float curSpeedRampLen = _speedUtil.rampLengthForRampDown(speedSetting, _curSpeedType, Stop);
2626                if (log.isDebugEnabled()) {
2627                    if (_curSpeedType.equals(speedType)) {
2628                        log.debug("No speed modifications for runState= {} from {} found after block \"{}\". RampLen = {} warrant {}",
2629                                RUN_STATE[runState], _curSpeedType, curBlock.getDisplayName(), curSpeedRampLen, getDisplayName());
2630                    }
2631                }
2632                if (curSpeedRampLen > availDist) {
2633                    log.info("Throttle at {} needs {}mm to ramp to Stop but only {}mm available. May Overrun. Warrant {}",
2634                            speedSetting, curSpeedRampLen, availDist, getDisplayName());
2635                    if (runState == STOP_PENDING || runState == RAMP_HALT) {
2636                        // ramp to stop in progress - no need for further stop calls
2637                         return true;
2638                    }
2639                } // else {Space exceeded when ramping down over several blocks is not catastrophic - let it be}
2640                _waitForBlock = false;
2641                _waitForSignal = false;
2642                _waitForWarrant = false;
2643                return true;
2644            }
2645        }
2646
2647        // blkOrder.getBlock() is the block that must be entered at a speed modified by speedType.
2648        blkOrder = getBlockOrderAt(idxBlockOrder);
2649        if (log.isDebugEnabled()) {
2650            log.debug("Speed decrease to {} from {} needed before entering block \"{}\" warrant {}",
2651                    speedType, _curSpeedType, blkOrder.getBlock().getDisplayName(), getDisplayName());
2652        }
2653        // There is a speed decrease needed before entering the above block
2654        // From this block, check if there is enough room to make the change
2655        // using the exit speed of the block. Walk back using previous
2656        // blocks, if necessary.
2657        float rampLen = rampLengthOfEntrance(idxBlockOrder, speedType);
2658        availDist = 0.0f;
2659        int endBlockIdx = idxBlockOrder - 1;    // index of block where ramp ends
2660
2661        while (idxBlockOrder > _idxCurrentOrder && rampLen > availDist) {
2662            // start at block before the block with slower speed type and walk
2663            // back to current block. Get availDist to cover rampLen
2664            idxBlockOrder--;
2665            availDist += getAvailableDistance(idxBlockOrder);
2666            rampLen = rampLengthOfEntrance(idxBlockOrder, speedType);
2667        }
2668        if (log.isDebugEnabled()) {
2669            log.debug("availDist= {}, at Block \"{}\" for rampLen= {}. warrant {}",
2670                    availDist, getBlockOrderAt(idxBlockOrder).getBlock().getDisplayName(), rampLen, getDisplayName());
2671        }
2672
2673        if (idxBlockOrder > _idxCurrentOrder && rampLen <= availDist) {
2674            // sufficient ramp room was found before walking all the way back to current block
2675            // therefore no speed change needed yet.
2676            if (log.isDebugEnabled()) {
2677                log.debug("Will decrease speed for runState= {} to {} from {} later at block \"{}\", warrant {}",
2678                        RUN_STATE[runState], speedType, _curSpeedType, blkOrder.getBlock().getDisplayName(),
2679                        getDisplayName());
2680            }
2681            _waitForBlock = false;
2682            _waitForWarrant = false;
2683            _waitForSignal = false;
2684            return true; // change speed later
2685        }
2686
2687        // Should have rampLen < availDist, but just in case....
2688        if (rampLen >= availDist) {
2689            // not enough room (shouldn't happen) so do ramp/stop immediately
2690            log.warn("No room for train {} to ramp to speed \"{}\" in block \"{}\".  warrant= {}",
2691                    getTrainName(), speedType, curBlock.getDisplayName(), getDisplayName());
2692            cancelDelayRamp();
2693            _engineer.setSpeedToType(speedType);
2694            if (!speedType.equals(Stop) && !speedType.equals(EStop)) {
2695                _curSpeedType = speedType;
2696            }
2697            fireRunStatus("SpeedChange", _idxCurrentOrder - 1, _idxCurrentOrder);
2698            return true;
2699        }        // otherwise, figure out time to ramp down from script
2700
2701        // If speed change is due to a signal, this signal should be the next signal ahead
2702        // However (see lines 2552-4) it is possible for it not to be the current _protectSignal.
2703        if (!_waitForBlock && !_waitForWarrant) {   // must be a signal change
2704            for (int idx = _idxCurrentOrder + 1; idx < _orders.size(); idx++) {
2705                blkOrder = getBlockOrderAt(idx);
2706                String sType = getPermissibleSpeedAt(blkOrder);
2707                if (speedType.equals(sType)) {
2708                    NamedBean signal = blkOrder.getSignal();
2709                    if (!_protectSignal.equals(signal)) {
2710                        _protectSignal.removePropertyChangeListener(this);
2711                        signal.addPropertyChangeListener(this);
2712                        if (log.isDebugEnabled()) {
2713                            log.debug("Warrant \"{}\", removes signal= \"{}\" sets _protectSignal= \"{}\" at Block \"{}\"", 
2714                                    blkOrder.getBlock().getDisplayName(), getDisplayName(), _protectSignal, signal);
2715                        }
2716                        _protectSignal = signal;
2717                    }   // else OK, _protectSignal is the correct one
2718                    break;
2719                }
2720            }
2721        }
2722        
2723        // Need to start a ramp down in the block of _idxCurrentOrder
2724        blkOrder = getBlockOrderAt(_idxCurrentOrder);
2725        if (log.isDebugEnabled()) {
2726            log.debug("Schedule speed change to {} in block \"{}\" availDist={}, warrant= {}",
2727                    speedType, blkOrder.getBlock().getDisplayName(), availDist, getDisplayName());
2728        }
2729
2730        // find the time when ramp should start in this block, then use thread CommandDelay
2731        // ramp length to change to modified speed from current speed.
2732        // waitSpeed is throttleSpeed when ramp is started. Start with it being at the entrance to the block.
2733        float waitSpeed = 0;
2734        long waitTime = 0; // time to wait after entering the block before starting ramp
2735        // availDist - rampLen = waitDist == waitSpeed * waitTime
2736        // currently rampLen is rampLengthOfEntrance and availDist is distance to block that has speedType requirement
2737
2738        // set throttleSpeed to what it is at the start of the block
2739        boolean hasSpeed = false;
2740        BlockSpeedInfo blkSpeedInfo = _speedInfo.get(_idxCurrentOrder);
2741        float throttleSetting;
2742        if (idxBlockOrder == 0) {
2743            throttleSetting = 0.0f;
2744        } else {
2745            throttleSetting = blkSpeedInfo.getEntranceSpeed();
2746            waitSpeed = _speedUtil.getTrackSpeed(_speedUtil.modifySpeed(throttleSetting, _curSpeedType));
2747            waitTime = Math.round((availDist - rampLen) / waitSpeed);
2748            hasSpeed = true;
2749        }
2750        float timeRatio; // time adjustment for current speed type.
2751        if (Math.abs(throttleSetting - waitSpeed) > .0001f) {
2752            timeRatio = throttleSetting / waitSpeed;
2753        } else {
2754            timeRatio = 1.0f;
2755        }
2756
2757        long speedTime = 0; // time running at a given speed until next speed change
2758        int startIdx = blkSpeedInfo.getFirstIndex();
2759        int endIdx = blkSpeedInfo.getLastIndex();
2760        for (int i = startIdx; i <= endIdx; i++) {
2761            ThrottleSetting ts = _commands.get(i);
2762            if (hasSpeed) {
2763                speedTime += ts.getTime() * timeRatio;
2764                if (speedTime >= waitTime) {
2765                    break;
2766                }
2767            }
2768            ThrottleSetting.CommandValue cmdVal = ts.getValue();
2769            if (cmdVal.getType() == ThrottleSetting.ValueType.VAL_FLOAT) {
2770                throttleSetting = cmdVal.getFloat();
2771                if (throttleSetting > .0001f) {
2772                    hasSpeed = true;
2773                    waitSpeed = _speedUtil.getTrackSpeed(_speedUtil.modifySpeed(throttleSetting, _curSpeedType));
2774                    rampLen = _speedUtil.rampLengthForRampDown(throttleSetting, _curSpeedType, speedType);
2775                    long nextWait = Math.round((availDist - rampLen) / waitSpeed);
2776                    if (speedTime >= nextWait) {
2777                        waitTime = nextWait;
2778                        break;
2779                    }
2780                } else {
2781                    hasSpeed = false;
2782                }
2783            }
2784        }
2785
2786        if (log.isDebugEnabled()) {
2787            log.debug(" waitTime= {}, availDist= {} waitSpeed= {}, rampLen= {}, ramp start speed= {}",
2788                    waitTime, availDist, waitSpeed, rampLen,speedSetting);
2789        }
2790        rampSpeedDelay(waitTime, speedType, endBlockIdx);
2791        return true;
2792    }   // end setMovement
2793
2794    private void rampSpeedDelay (long waitTime, String speedType, int endBlockIdx) {
2795        synchronized(this) {
2796           if (_delayCommand != null) {
2797               if (_delayCommand.doNotCancel(speedType, waitTime, endBlockIdx)) {
2798                   return;
2799               }
2800               cancelDelayRamp();
2801           }
2802        }
2803        if (waitTime <= 0) {
2804            _engineer.rampSpeedTo(speedType, endBlockIdx, true);
2805            if (speedType.equals(Stop) || speedType.equals(EStop)) {
2806                _engineer.setWaitforClear(true);
2807            } else {
2808                _curSpeedType = speedType;
2809            }
2810        } else {    // cancelDelayRamp has been called
2811            synchronized(this) {
2812                _delayCommand = new CommandDelay(speedType, waitTime, endBlockIdx, true);
2813                _delayCommand.start();
2814            }
2815        }
2816    }
2817
2818    /**
2819     * {@inheritDoc}
2820     * <p>
2821     * This implementation tests that
2822     * {@link jmri.NamedBean#getSystemName()}
2823     * is equal for this and obj.
2824     * To allow a warrant to run with sections, DccLocoAddress is included to test equality
2825     *
2826     * @param obj the reference object with which to compare.
2827     * @return {@code true} if this object is the same as the obj argument;
2828     *         {@code false} otherwise.
2829     */
2830    @Override
2831    public boolean equals(Object obj) {
2832        if (obj == null) return false; // by contract
2833
2834        if (obj instanceof Warrant) {  // NamedBeans are not equal to things of other types
2835            Warrant b = (Warrant) obj;
2836            DccLocoAddress addr = this._speedUtil.getDccAddress();
2837            if (addr == null) {
2838                if (b._speedUtil.getDccAddress() != null) {
2839                    return false;
2840                }
2841                return (this.getSystemName().equals(b.getSystemName()));
2842            }
2843            return (this.getSystemName().equals(b.getSystemName()) && addr.equals(b._speedUtil.getDccAddress()));
2844        }
2845        return false;
2846    }
2847
2848    /**
2849     * {@inheritDoc}
2850     *
2851     * @return hash code value is based on the system name and DccLocoAddress.
2852     */
2853    @Override
2854    public int hashCode() {
2855        return (getSystemName().concat(_speedUtil.getDccAddress().toString())).hashCode();
2856    }
2857
2858    @Override
2859    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
2860        List<NamedBeanUsageReport> report = new ArrayList<>();
2861        if (bean != null) {
2862            if (bean.equals(getBlockingWarrant())) {
2863                report.add(new NamedBeanUsageReport("WarrantBlocking"));
2864            }
2865            getBlockOrders().forEach((blockOrder) -> {
2866                if (bean.equals(blockOrder.getBlock())) {
2867                    report.add(new NamedBeanUsageReport("WarrantBlock"));
2868                }
2869                if (bean.equals(blockOrder.getSignal())) {
2870                    report.add(new NamedBeanUsageReport("WarrantSignal"));
2871                }
2872            });
2873        }
2874        return report;
2875    }
2876
2877    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Warrant.class);
2878}