001package jmri.jmrit.logix;
002
003import java.util.List;
004import java.util.concurrent.LinkedBlockingQueue;
005
006import jmri.*;
007import jmri.implementation.SignalSpeedMap;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * An SCWarrant is a warrant that is controlled by the signals on a layout.
013 * It will not run unless you have your layout fully covered with sensors and
014 * signals.
015 *
016 * @author  Karl Johan Lisby Copyright (C) 2016
017 */
018public class SCWarrant extends Warrant {
019
020    private static final String WAIT_UNEXPECTED_EXCEPTION = "{} wait unexpected exception {}";
021    private NamedBean _nextSignal = null; // The signal that we are currently looking at to determine speed.
022    public static final float SPEED_STOP = 0.0f;
023    public static final float SPEED_TO_PLATFORM = 0.2f;
024    public static final float SPEED_UNSIGNALLED = 0.4f;
025    private long timeToPlatform = 500;
026    private float speedFactor = 0.8f;
027    private boolean forward = true;
028    private final boolean _allowShallowAllocation = false;
029    private DccThrottle _throttle = null;
030
031    /**
032     * Create an object with no route defined.
033     * <p>
034     * The list of BlockOrders is the route from an Origin to a Destination.
035     * @param sName system name.
036     * @param uName username.
037     * @param TTP time to platform.
038     */
039    public SCWarrant(String sName, String uName, long TTP) {
040        super(sName, uName);
041        log.debug("new SCWarrant {} TTP={}",uName,TTP);
042        timeToPlatform = TTP;
043        setNoRamp(true);
044    }
045
046    public long getTimeToPlatform() {
047        return timeToPlatform;
048    }
049
050    public void setTimeToPlatform(long TTP) {
051        timeToPlatform = TTP;
052    }
053
054    public void setForward(boolean set) {
055        forward = set;
056    }
057
058    public boolean getForward() {
059        return forward;
060    }
061
062    public void setSpeedFactor(float factor) {
063        if (factor > 1.0) {
064            speedFactor = 1.0f;
065        } else if (factor < 0.1) {
066            speedFactor = 0.1f;
067        } else {
068            speedFactor = factor;
069        }
070    }
071
072    public float getSpeedFactor() {
073        return speedFactor;
074    }
075
076    float _maxBlockLength = 0;
077    float getMaxBlockLength() {
078        return _maxBlockLength;
079    }
080    void setMaxBlockLength() {
081        float blockLength;
082        for (int i=0; i <= getBlockOrders().size()-1; i++) {
083            blockLength = getBlockOrderAt(i).getBlock().getLengthCm();
084            if (blockLength > _maxBlockLength) {
085                _maxBlockLength = blockLength;
086            }
087        }
088    }
089
090    private String allocateStartBlock() {
091        BlockOrder bo = getBlockOrderAt(0);
092        OBlock block = bo.getBlock();
093        String message = block.allocate(this);
094        if (message != null) {
095           log.info("{} START-block allocation failed {} ",_trainName,message);
096           return message;
097        }
098        message = bo.setPath(this);
099        if (message != null) {
100           log.info("{} setting path in START-block failed {}",_trainName,message);
101           return message;
102        }
103        return null;
104    }
105
106    /**
107     * This method has been overridden in order to avoid allocation of occupied blocks.
108     */
109    @Override
110     public String setRoute(boolean delay, List<BlockOrder> orders) {
111        return allocateStartBlock();
112    }
113
114    boolean allTurnoutsSet() {
115        for (int i=0; i<getBlockOrders().size(); i++) {
116            OBlock block_i = getBlockOrderAt(i).getBlock();
117            OPath  path_i  = getBlockOrderAt(i).getPath();
118            if (!path_i.checkPathSet()) {
119                log.debug("{}: turnouts at block {} are not set yet (in allTurnoutsSet).",_trainName,block_i.getDisplayName());
120                return false;
121            }
122        }
123        return true;
124    }
125
126    public boolean isRouteFree() {
127        for (int i=0; i<getBlockOrders().size(); i++) {
128            OBlock block_i = getBlockOrderAt(i).getBlock();
129            if ((block_i.getState() & OBlock.ALLOCATED) == OBlock.ALLOCATED) {
130                log.debug("{}: block {} is allocated to {} (in isRouteFree).",_trainName,block_i.getDisplayName(),block_i.getAllocatingWarrantName());
131                if (!block_i.isAllocatedTo(this)) {
132                    return false;
133                }
134            }
135            if ( ((block_i.getState() & Block.OCCUPIED) == Block.OCCUPIED) && (i>0) ) {
136                log.debug("{}: block {} is not free (in isRouteFree).",_trainName,block_i.getDisplayName());
137                return false;
138            }
139        }
140        return true;
141    }
142
143    boolean isRouteAllocated() {
144        for (int i=0; i<getBlockOrders().size(); i++) {
145            OBlock block_i = getBlockOrderAt(i).getBlock();
146            if (!block_i.isAllocatedTo(this)) {
147                log.debug("{}: block {} is not allocated to this warrant (in isRouteAllocated).",_trainName,block_i.getDisplayName());
148                return false;
149            }
150        }
151        return true;
152    }
153
154    /**
155     * Callback from acquireThrottle() when the throttle has become available.sync
156     */
157    @Override
158    public void notifyThrottleFound(DccThrottle throttle) {
159        _throttle = throttle;
160        if (throttle == null) {
161            abortWarrant("notifyThrottleFound: null throttle(?)!");
162            firePropertyChange("throttleFail", null, Bundle.getMessage("noThrottle"));
163            return;
164        }
165        if (_runMode == MODE_LEARN) {
166            abortWarrant("notifyThrottleFound: No LEARN mode for SCWarrant");
167            InstanceManager.throttleManagerInstance().releaseThrottle(throttle, this);
168            firePropertyChange("throttleFail", null, Bundle.getMessage("noThrottle"));
169            return;
170        }
171        log.debug("{} notifyThrottleFound address= {} _runMode= {}",_trainName,throttle.getLocoAddress(),_runMode);
172
173        startupWarrant();
174
175        firePropertyChange("WarrantStart", Integer.valueOf(MODE_NONE), Integer.valueOf(_runMode));
176        runSignalControlledTrain();
177    }
178
179    /**
180     * Generate status message to show in warrant table
181     **/
182    @Override
183    protected synchronized String getRunningMessage() {
184        if (_throttle == null) {
185            // The warrant is not active
186            return super.getRunningMessage();
187        } else if (_runMode != MODE_RUN) {
188            return ("Idle");
189        } else {
190            String block = getBlockOrderAt(getCurrentOrderIndex()).getBlock().getDisplayName();
191            String signal = "no signal";
192            String aspect = "none";
193            if (_nextSignal != null) {
194                signal = _nextSignal.getDisplayName();
195                if (_nextSignal instanceof SignalHead) {
196                    int appearance = ((SignalHead) _nextSignal).getAppearance();
197                    aspect = "appearance "+appearance;
198                } else {
199                    aspect = ((SignalMast) _nextSignal).getAspect();
200                }
201            }
202            return Bundle.getMessage("SCWStatus", block, getCurrentOrderIndex(), _throttle.getSpeedSetting(),signal,aspect);
203        }
204    }
205
206    /******************************************************************************************************
207     * Use _throttle to control the train.
208     *
209     * Get notified of signals, block occupancy and take care of block allocation status to determine speed.
210     *
211     * We have three speeds: Stop == SPEED_STOP
212     *                       Normal == SPEED_NORMAL
213     *                       Anything else == SPEED_MID (Limited, Medium, Slow, Restricted)
214     *
215     * If you have blocks large enough to ramp speed nicely up and down and to have further control
216     * of speed settings: Use a normal warrant and not a signal controlled one.
217     *
218     * This is "the main loop" for running a Signal Controlled Warrant
219     ******************************************************************************************************/
220    protected void runSignalControlledTrain () {
221        waitForStartblockToGetOccupied();
222        allocateBlocksAndSetTurnouts(0);
223        setTrainDirection();
224        SCTrainRunner thread = new SCTrainRunner(this);
225        Thread t = jmri.util.ThreadingUtil.newThread(thread);
226        t.setName("SCTrainRunner");
227        t.start();
228    }
229
230    /**
231     * Wait until there is a train in the start block.
232     * @return true if block not UNOCCUPIED
233     */
234    protected boolean isStartBlockOccupied() {
235        int blockState = getBlockOrderAt(0).getBlock().getState();
236        return (blockState & Block.UNOCCUPIED) != Block.UNOCCUPIED;
237    }
238
239    protected synchronized void waitForStartblockToGetOccupied() {
240        while (!isStartBlockOccupied()) {
241            log.debug("{} waiting for start block {} to become occupied",_trainName,getBlockOrderAt(0).getBlock().getDisplayName());
242            try {
243                // We will not be woken up by goingActive, since we have not allocated the start block yet.
244                // So do a timed wait.
245                wait(2500);
246            } catch (InterruptedException ie) {
247                log.debug("{} waitForStartblockToGetOccupied InterruptedException {}",_trainName,ie,ie);
248            }
249            catch(Exception e){
250                log.debug("{} waitForStartblockToGetOccupied unexpected exception {}",_trainName,e,e);
251            }
252        }
253    }
254
255    /**
256     * Set this train to run backwards or forwards as specified in the command list.
257     */
258    public void setTrainDirection () {
259        _throttle.setIsForward(forward);
260    }
261
262    /**
263     * Is the next block free or occupied, i.e do we risk to crash into an other train, if we proceed?
264     * And is it allocated to us?
265     * @return true if allocated to us and unoccupied, else false.
266     */
267    public boolean isNextBlockFreeAndAllocated() {
268        BlockOrder bo = getBlockOrderAt(getCurrentOrderIndex()+1);
269        if (bo == null) return false;
270        int blockState = bo.getBlock().getState();
271        if (blockState == (Block.UNOCCUPIED | OBlock.ALLOCATED)) {
272            return getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().isAllocatedTo(this);
273        } else {
274            return false;
275        }
276    }
277
278    /**
279     * Find the next signal along our route and setup subscription for status changes on that signal.
280     */
281    public void getAndGetNotifiedFromNextSignal() {
282        if (_nextSignal != null) {
283            log.debug("{} getAndGetNotifiedFromNextSignal removing property listener for signal {}",_trainName,_nextSignal.getDisplayName());
284            _nextSignal.removePropertyChangeListener(this);
285            _nextSignal = null;
286        }
287        for (int i = getCurrentOrderIndex()+1; i <= getBlockOrders().size()-1; i++) {
288            BlockOrder bo = getBlockOrderAt(i);
289            if (bo == null) {
290                log.debug("{} getAndGetNotifiedFromNextSignal could not find a BlockOrder for index {}",_trainName,i);
291            } else if (bo.getEntryName().equals("")) {
292                log.debug("{} getAndGetNotifiedFromNextSignal could not find an entry to Block for index {}",_trainName,i);
293            } else {
294                log.debug("{} getAndGetNotifiedFromNextSignal examines block {} with entryname = {}",_trainName,bo.getBlock().getDisplayName(),bo.getEntryName());
295                _nextSignal = bo.getSignal();
296                if (_nextSignal != null) {
297                    log.debug("{} getAndGetNotifiedFromNextSignal found a new signal to listen to: {}",_trainName,_nextSignal.getDisplayName());
298                    break;
299                }
300            }
301        }
302        if (_nextSignal != null) {
303            _nextSignal.addPropertyChangeListener(this);
304        }
305    }
306
307    /**
308     * Are we still in the start block?
309     * @return true if still in start block
310     */
311    boolean inStartBlock() {
312        return (getCurrentOrderIndex() == 0);
313    }
314
315    /**
316     * Are we close to the destination block?
317     * @return true if close
318     */
319    boolean approchingDestination() {
320        float distance = 0;
321        float blockLength;
322        if (getCurrentOrderIndex() == getBlockOrders().size()-2) {
323            // We are in the block just before destination
324            return true;
325        }
326        // Calculate the distance to destination
327        for (int i = getCurrentOrderIndex(); i <= getBlockOrders().size()-2; i++) {
328            blockLength = getBlockOrderAt(i).getBlock().getLengthCm();
329            if (blockLength < 1) {
330                // block length not set for at least one block
331                return false;
332            }
333            distance += blockLength;
334        }
335        return (distance < 1.5*getMaxBlockLength());
336    }
337
338    /**
339     * Move the train if _nextSignal permits. If there is no next signal, we will move forward with half speed.
340     */
341    SignalSpeedMap _speedMap = jmri.InstanceManager.getDefault(SignalSpeedMap.class);
342    public void setSpeedFromNextSignal () {
343        String speed = null;
344        if (_nextSignal == null) {
345            _throttle.setSpeedSetting(speedFactor*SPEED_UNSIGNALLED);
346        } else {
347            if (_nextSignal instanceof SignalHead) {
348                int appearance = ((SignalHead) _nextSignal).getAppearance();
349                speed = _speedMap.getAppearanceSpeed(((SignalHead) _nextSignal).getAppearanceName(appearance));
350                log.debug("{} SignalHead {} shows appearance {} which maps to speed {}",_trainName,((SignalHead) _nextSignal).getDisplayName(),appearance,speed);
351            } else {
352                String aspect = ((SignalMast) _nextSignal).getAspect();
353                speed = _speedMap.getAspectSpeed(aspect, ((SignalMast) _nextSignal).getSignalSystem());
354                log.debug("{} SignalMast {} shows aspect {} which maps to speed {}",_trainName,((SignalMast) _nextSignal).getDisplayName(),aspect,speed);
355            }
356            float speed_f = (float) (_speedMap.getSpeed(speed) / 125.);
357            // Ease the speed, if we are approaching the destination block
358            if ((approchingDestination() || inStartBlock()) && (speed_f > SPEED_UNSIGNALLED)) {
359                speed_f = SPEED_UNSIGNALLED;
360            }
361            _throttle.setSpeedSetting(speedFactor*speed_f);
362        }
363    }
364
365     /**
366     * Do what the title says. But make sure not to set the turnouts if already done, since that
367     * would just cause all signals to go to Stop aspects and thus cause a jerky train movement.
368     * @param startIndex Allocate starting with this index
369     */
370    protected void allocateBlocksAndSetTurnouts(int startIndex) {
371        log.debug("{} allocateBlocksAndSetTurnouts startIndex={} _orders.size()={}",_trainName,startIndex,getBlockOrders().size());
372        for (int i = startIndex; i < getBlockOrders().size(); i++) {
373            log.debug("{} allocateBlocksAndSetTurnouts for loop #{}",_trainName,i);
374            BlockOrder bo = getBlockOrderAt(i);
375            OBlock block = bo.getBlock();
376            String pathAlreadySet = block.isPathSet(bo.getPathName());
377            if (pathAlreadySet == null) {
378                String message = null;
379                if ((block.getState() & Block.OCCUPIED) != 0) {
380                    log.info("{} block allocation failed {} not allocated, but Occupied.",_trainName,block.getDisplayName());
381                    message = " block allocation failed ";
382                }
383                if (message == null) {
384                    message = block.allocate(this);
385                    if (message != null) {
386                        log.info("{} block allocation failed {}",_trainName,message);
387                    }
388                }
389                if (message == null) {
390                    message = bo.setPath(this);
391                }
392                if (message != null) {
393                    log.debug("{} path setting failed for {} at block {} {}",_trainName,getDisplayName(),block.getDisplayName(),message);
394                    if (_stoppingBlock != null) {
395                        _stoppingBlock.removePropertyChangeListener(this);
396                    }
397                    _stoppingBlock = block;
398                    _stoppingBlock.addPropertyChangeListener(this);
399                    // This allocation failed. Do not attempt to allocate the rest of the route.allocation
400                    // That would potentially lead to deadlock situations where two warrants are competing
401                    // and each getting every second block along the same route.
402                    return;
403                }
404            } else if (pathAlreadySet.equals(this.getDisplayName())) {
405                log.debug("{} Path {} already set (and thereby block allocated) for {}",_trainName,bo.getPathName(),pathAlreadySet);
406            } else {
407                log.info("{} Block allocation failed: Path {} already set (and thereby block allocated) for {}",_trainName,bo.getPathName(),pathAlreadySet);
408                return;
409            }
410        }
411    }
412
413    /**
414     * Block in the route going active.
415     * Make sure to allocate the rest of the route, update our present location and then tell
416     * the main loop to find a new throttle setting.
417     */
418    @Override
419    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="NotifyAll call triggers recomputation")
420    protected void goingActive(OBlock block) {
421        int activeIdx = getIndexOfBlockAfter(block, getCurrentOrderIndex());
422        log.debug("{} **Block \"{}\" goingActive. activeIdx= {}"
423                    + ", getCurrentOrderIndex()= {}"
424                    + " - warrant= {} _runMode = {} _throttle==null: {}",_trainName,block.getDisplayName(),activeIdx,getCurrentOrderIndex(),getDisplayName(),_runMode,(_throttle==null));
425        if (_runMode != MODE_RUN) {
426            // if we are not running, we must not think that we are going to the next block - it must be another train
427            return;
428        }
429        if (_throttle == null || _throttle.getSpeedSetting() == SPEED_STOP) {
430            // if we are not running, we must not think that we are going to the next block - it must be another train
431            return;
432        }
433        if (activeIdx <= 0) {
434            // The block going active is not part of our route ahead
435            log.debug("{} Block going active is not part of this trains route forward",_trainName);
436        } else if (activeIdx == getCurrentOrderIndex()) {
437            // Unusual case of current block losing detection, then regaining it.  i.e. dirty track, derail etc.
438            log.debug("{} Current block becoming active - ignored",_trainName);
439        } else if (activeIdx == getCurrentOrderIndex() + 1) {
440            // not necessary: It is done in the main loop in SCTrainRunner.run:  allocateBlocksAndSetTurnouts(getCurrentOrderIndex()+1)
441            // update our present location
442            incrementCurrentOrderIndex();
443            // fire property change (entered new block)
444            firePropertyChange("blockChange", getBlockAt(getCurrentOrderIndex() - 1), getBlockAt(getCurrentOrderIndex()));
445            // now let the main loop adjust speed.
446            synchronized(this) {
447                notifyAll();
448            }
449        } else {
450            log.debug("{} Rogue occupation of block.",_trainName);
451            // now let the main loop stop for a train that is coming in our immediate way.
452            synchronized(this) {
453                notifyAll();
454            }
455        }
456    }
457
458    /**
459     * Block in the route is going Inactive.
460     * Release the blocks that we have left.
461     * Check if current block has been left (i.e. we have left our route) and stop the train in that case.
462     */
463    @Override
464    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification="See comment above notify call")
465    protected void goingInactive(OBlock block) {
466        int idx = getIndexOfBlockAfter(block, 0);  // if idx >= 0, it is in this warrant
467        log.debug("{} Block \"{}\" goingInactive. idx= {}"
468                    + ", getCurrentOrderIndex()= {}"
469                    + " - warrant= {}",_trainName,block.getDisplayName(),idx,getCurrentOrderIndex(),getDisplayName());
470        if (_runMode != MODE_RUN) {
471            return;
472        }
473        if (idx < getCurrentOrderIndex()) {
474            if (_allowShallowAllocation) {
475                deallocateUpToBlock(idx);
476            }
477        } else if (idx == getCurrentOrderIndex()) {
478            // train is lost
479            log.debug("{} LOST TRAIN firePropertyChange(\"blockChange\", {}"
480                                + ", null) - warrant= {}",_trainName,block.getDisplayName(),getDisplayName());
481        }
482        // now let the main loop stop our train if this means that the train is now entirely within the last block.
483        // Or let the train continue if an other train that was in its way has now moved.
484        synchronized(this) {
485            notifyAll();
486        }
487    }
488
489    /**
490     * Deallocate all blocks up to and including idx, but only on these conditions in order to ensure that only a consecutive list of blocks are allocated at any time:
491     *     1. Only if our train has left not only this block, but also all previous blocks.
492     *     2. Only if the block shall not be re-used ahead and all block up until the block are allocated.
493     * @param idx Index of final block
494     */
495    protected void deallocateUpToBlock(int idx) {
496        for (int i=0; i<=idx; i++) {
497            OBlock block_i = getBlockOrderAt(i).getBlock();
498            if (block_i.isAllocatedTo(this)) {
499                if ((block_i.getState() & Block.UNOCCUPIED) != Block.UNOCCUPIED) {
500                    //Do not deallocate further blocks, since this one is still allocated to us and not free.
501                    log.debug("{} Block {} occupied. Not de-allocating any further",_trainName,block_i.getDisplayName());
502                    return;
503                }
504                boolean deAllocate = true;
505                // look ahead to see if block_i is reused in the remaining part of the route.
506                for (int j= getCurrentOrderIndex(); j<getBlockOrders().size(); j++) {
507                    OBlock block_j = getBlockOrderAt(j).getBlock();
508                    if (!block_j.isAllocatedTo(this)) {
509                        // There is an unallocated block ahead before we have found block_i is re-used. So deallocate block_i
510                        break;
511                    }
512                    if (block_i == block_j) {
513                        // clock_i is re-used, and we have no "holes" in the string of allocated blocks before it. So do not deallocate.
514                        deAllocate = false;
515                        break;
516                    }
517                }
518                if (deAllocate) {
519                    log.debug("{} De-allocating block {}",_trainName,block_i.getDisplayName());
520                    block_i.deAllocate(this);
521                }
522            }
523        }
524    }
525
526    /**
527     * Something has fired a property change event.
528     * React if:
529     *     - it is a warrant that we need to synchronize with. And then again: Why?
530     *     - it is _nextSignal
531     * Do not worry about sensors and blocks. They are handled by goingActive and goingInactive.
532     */
533    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = {"UW_UNCOND_WAIT", "NN_NAKED_NOTIFY"},
534            justification = "Unconditional wait is give the warrant that now has _stoppingBlock allocated a little time to deallocate it.  This occurs after this method sets _stoppingBlock to null. NotifyAll passing event, not state.")
535    @Override
536    public void propertyChange(java.beans.PropertyChangeEvent evt) {
537        if (!(evt.getSource() instanceof NamedBean)) {
538            log.debug("{} propertyChange \"{}\" old= {} new= {}",_trainName,evt.getPropertyName(),evt.getOldValue(),evt.getNewValue());
539            return;
540        }
541        String property = evt.getPropertyName();
542        log.debug("{} propertyChange \"{}\" new= {} source= {} - warrant= {}",_trainName,property,evt.getNewValue(),((NamedBean) evt.getSource()).getDisplayName(),getDisplayName());
543        if (_nextSignal != null && _nextSignal == evt.getSource()) {
544            if (property.equals("Aspect") || property.equals("Appearance")) {
545                // The signal controlling this warrant has changed. Adjust the speed (in runSignalControlledTrain)
546                synchronized(this) {
547                    notifyAll();
548                }
549                return;
550            }
551        }
552        synchronized(this) {
553            if (_stoppingBlock != null) {
554                log.debug("{} CHECKING STOPPINGBLOCKEVENT ((NamedBean) evt.getSource()).getDisplayName() = '{}' evt.getPropertyName() = '{}' evt.getNewValue() = {} _throttle==null: {}",_trainName,((NamedBean) evt.getSource()).getDisplayName(),evt.getPropertyName(),evt.getNewValue(),(_throttle==null));
555                if (((NamedBean) evt.getSource()).getDisplayName().equals(_stoppingBlock.getDisplayName()) &&
556                        evt.getPropertyName().equals("state") &&
557                        (((Number) evt.getNewValue()).intValue() & Block.UNOCCUPIED) == Block.UNOCCUPIED) {
558                    log.debug("{} being aware that Block {} has become free",_trainName,((NamedBean) evt.getSource()).getDisplayName());
559                    _stoppingBlock.removePropertyChangeListener(this);
560                    _stoppingBlock = null;
561                    // we might be waiting for this block to become free
562                    // Give the warrant that now has _stoppingBlock allocated a little time to deallocate it
563                    try {
564                        wait(100);
565                    } catch (InterruptedException e) {
566                        // ignoring interrupted exceptions
567                    } catch(Exception e){
568                        log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e);
569                    }
570                    // And then let our main loop continue
571                    notifyAll();
572                    return;
573                }
574                if (((NamedBean) evt.getSource()).getDisplayName().equals(getBlockOrderAt(0).getBlock().getDisplayName()) &&
575                        evt.getPropertyName().equals("state") &&
576                        (((Number) evt.getOldValue()).intValue() & Block.UNOCCUPIED) == Block.UNOCCUPIED &&
577                        (((Number) evt.getNewValue()).intValue() & Block.UNOCCUPIED) != Block.UNOCCUPIED &&
578                        _throttle==null && _runMode==MODE_RUN) {
579                    // We are waiting for the train to arrive at the starting block, and that has just happened now.
580                    log.debug("{} has arrived at starting block",_trainName);
581                    String msg = null;
582                    msg = acquireThrottle();
583                    if (msg != null) {
584                        log.warn("propertyChange of \"{}\" has message: {}", property, msg);
585                        _message = msg;
586                        abortWarrant(msg);
587                    }
588                }
589            }
590        }
591    }
592
593
594    /**
595     * Make sure to free up additional resources for a running SCWarrant.
596     */
597    @Override
598    public synchronized void stopWarrant(boolean abort, boolean turnOffFunctions) {
599        if (_nextSignal != null) {
600            _nextSignal.removePropertyChangeListener(this);
601            _nextSignal = null;
602        }
603        super.stopWarrant(abort, false);
604        _message = null;
605    }
606
607    /*******************************************************************************************************************************
608     * The waiting for event must happen in a separate thread.
609     * Therefore the main code of runSignalControlledTrain is put in this class.
610     *******************************************************************************************************************************/
611    static LinkedBlockingQueue<SCWarrant> waitToRunQ = new LinkedBlockingQueue<>();
612    private class SCTrainRunner implements Runnable {
613        private static final String INTERRUPTED_EXCEPTION = "{} InterruptedException {}";
614        SCWarrant _warrant = null;
615        SCTrainRunner(SCWarrant warrant) {
616            _warrant = warrant;
617        }
618
619        /**
620         * When not using shallow allocation, warrants will have to wait until the entire route
621         * is free and allocated to that particular warrant, before strting to run the train.
622         * This method uses the waitToRunQ to ensure that warrants do not just compete about
623         * resources, but waits in line until their route is free and unallocated.
624         */
625        boolean isItOurTurn() {
626            for (SCWarrant e : waitToRunQ) {
627                try { // using another SCWarrant might be dangerous - it might no longer exist
628                    log.debug("{} isItOurTurn is checking {}",_trainName,e.getDisplayName());
629                    if (e.isRouteFree()) {
630                        if (e == _warrant) {
631                            log.debug("{} isItOurTurn: We are first in line",_trainName);
632                            return true;
633                        } else {
634                            log.debug("{} isItOurTurn: An other warrant is before us",_trainName);
635                            return false;
636                        }
637                    } else {
638                        if (e == _warrant) {
639                            log.debug("{} isItOurTurn: our route is not free - keep waiting",_trainName);
640                            return false;
641                        }
642                    }
643                } catch (Exception ex) {
644                    log.debug("{} isItOurTurn exception ignored: {}",_trainName,ex,ex);
645                }
646            }
647            // we should not reach this point, but if we do, we should try to run
648            log.debug("{} isItOurTurn: No warrant with a free route is waiting. Let us try our luck, so that we are not all waiting for each other.",_trainName);
649            return true;
650        }
651
652        @Override
653        public void run() {
654            synchronized(_warrant) {
655
656                // Make sure the entire route is allocated before attemting to start the train
657                if (!_allowShallowAllocation) {
658                    boolean AllocationDone = false;
659                    log.debug("{} ENTERING QUEUE ",_trainName);
660                    try {
661                        waitToRunQ.put(_warrant);
662                    } catch (InterruptedException ie) {
663                        log.debug("{} waitToRunQ.put InterruptedException {}",_trainName,ie,ie);
664                    }
665
666                    while (!AllocationDone) {
667                        log.debug("{} Route is not allocated yet..... ",_trainName);
668                        while (!isItOurTurn()) {
669                            deAllocate();
670                            log.debug("{} Waiting for route to become free ....",_trainName);
671                            try {
672                                _warrant.wait(2500 + Math.round(1000*Math.random()));
673                            } catch (InterruptedException ie) {
674                                log.debug("{} _warrant.wait InterruptedException {}",_trainName,ie,ie);
675                            }
676                            catch(Exception e){
677                                log.debug("{} _warrant.wait unexpected exception {}",_trainName,e,e);
678                            }
679                        }
680                        allocateStartBlock();
681                        allocateBlocksAndSetTurnouts(1);
682                        AllocationDone = isRouteAllocated();
683                        if (!AllocationDone) {
684                            deAllocate();
685                            try {
686                                _warrant.wait(10000 + Math.round(1000*Math.random()));
687                            } catch (InterruptedException ie) {
688                                log.debug("{} _warrant.wait !AllocationDone InterruptedException {}",_trainName,ie,ie);
689                            }
690                            catch(Exception e){
691                                log.debug("{} _warrant.wait !AllocationDone unexpected exception {}",_trainName,e,e);
692                            }
693                        }
694                    }
695
696                    log.debug("{} LEAVING QUEUE ",_trainName);
697                    waitToRunQ.remove(_warrant);
698
699                    while (!allTurnoutsSet()) {
700                        log.debug("{} Waiting for turnouts to settle ....",_trainName);
701                        try {
702                            _warrant.wait(2500);
703                        } catch (InterruptedException ie) {
704                            log.debug("{} _warrant.wait InterruptedException {}",_trainName,ie,ie);
705                        }
706                        catch(Exception e){
707                            log.debug("{} _warrant.wait unexpected exception {}",_trainName,e,e);
708                        }
709                    }
710                    // And then wait another 3 seconds to make the last turnout settle - just in case the command station is not giving correct feedback
711                    try {
712                        _warrant.wait(3000);
713                    } catch (InterruptedException ie) {
714                        log.debug(INTERRUPTED_EXCEPTION,_trainName,ie,ie);
715                    }
716                    catch(Exception e){
717                        log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e);
718                    }
719                }
720
721                // Do not include the stopping block in this while loop. It will be handled after the loop.
722                List<BlockOrder> orders = getBlockOrders();
723                while (_warrant.getCurrentOrderIndex() < orders.size()-1 && _runMode == MODE_RUN) {
724                    log.debug("{} runSignalControlledTrain entering while loop. getCurrentOrderIndex()={} _orders.size()={}",_warrant._trainName,getCurrentOrderIndex(),orders.size());
725                    if (_throttle == null) {
726                        // We lost our throttle, so we might have a runaway train
727                        emergencyStop();
728                    }
729                    if (_allowShallowAllocation) {
730                        allocateBlocksAndSetTurnouts(_warrant.getCurrentOrderIndex());
731                    }
732                    if (isNextBlockFreeAndAllocated()) {
733                        getAndGetNotifiedFromNextSignal();
734                        setSpeedFromNextSignal();
735                    } else {
736                        try {
737                            _throttle.setSpeedSetting(SPEED_STOP);
738                            getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().addPropertyChangeListener(_warrant);
739                            log.debug("{} runSignalControlledTrain stops train due to block not free: {}",_warrant._trainName,getBlockOrderAt(getCurrentOrderIndex()+1).getBlock().getDisplayName());
740                        } catch (Exception e) {
741                            emergencyStop();
742                            log.debug("{} exception trying to stop train due to block not free: {}",_warrant._trainName,e,e);
743                        }
744                    }
745                    log.debug("{} {} before wait {} getCurrentOrderIndex(): {} orders.size(): {}",_warrant._trainName,_warrant.getDisplayName(),_warrant.getRunningMessage(),_warrant.getCurrentOrderIndex(),orders.size());
746                    try {
747                        // We do a timed wait for the sake of robustness, even though we will be woken up by all relevant events.
748                        _warrant.wait(2000);
749                    } catch (InterruptedException ie) {
750                        log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,ie,ie);
751                    }
752                    catch(Exception e){
753                        log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e);
754                    }
755                    log.debug("{} {} after wait {} getCurrentOrderIndex(): {} orders.size(): {}",_warrant._trainName,_warrant.getDisplayName(),_warrant.getRunningMessage(),_warrant.getCurrentOrderIndex(),orders.size());
756                }
757                // We are now in the stop block. Move forward for half a second with half speed until the block before the stop block is free.
758                log.debug("{} runSignalControlledTrain out of while loop, i.e. train entered stop block getCurrentOrderIndex()={}"
759                          + " orders.size()={} waiting for train to clear block {}",
760                          _warrant._trainName,getCurrentOrderIndex(),orders.size(),getBlockAt(orders.size()-2).getDisplayName());
761                if (_throttle==null) {
762                    emergencyStop();
763                    log.debug("Throttle lost at stop block");
764                } else {
765                    _throttle.setSpeedSetting(speedFactor*SPEED_TO_PLATFORM);
766                }
767                while ((getBlockAt(orders.size()-2).getState()&Block.OCCUPIED)==Block.OCCUPIED && getBlockAt(orders.size()-2).isAllocatedTo(_warrant)) {
768                    log.debug(" runSignalControlledTrain {} entering wait. Block {}   free: {}   allocated to this warrant: {}",
769                              _warrant._trainName,
770                              getBlockAt(orders.size()-2).getDisplayName(),
771                              getBlockAt(orders.size()-2).isFree(),
772                              getBlockAt(orders.size()-2).isAllocatedTo(_warrant));
773                    try {
774                        // This does not need to be a timed wait, since we will get interrupted once the block is free
775                        // However, the functionality is more robust with a timed wait.
776                        _warrant.wait(500);
777                    } catch (InterruptedException ie) {
778                        log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,ie,ie);
779                    }
780                    catch(Exception e){
781                        log.debug(WAIT_UNEXPECTED_EXCEPTION,_trainName,e,e);
782                    }
783                    log.debug("{} runSignalControlledTrain woken after last wait.... _orders.size()={}",_warrant._trainName,orders.size());
784                }
785                if (timeToPlatform > 100) {
786                    log.debug("{} runSignalControlledTrain is now fully into the stopping block. Proceeding for {} miliseconds",_warrant._trainName,timeToPlatform);
787                    long timeWhenDone = System.currentTimeMillis() + timeToPlatform;
788                    long remaining;
789                    while ((remaining = timeWhenDone - System.currentTimeMillis()) > 0) {
790                        try {
791                            log.debug("{} running slowly to platform for {} miliseconds",_warrant._trainName,remaining);
792                            _warrant.wait(remaining);
793                        } catch (InterruptedException e) {
794                            log.debug(INTERRUPTED_EXCEPTION,_warrant._trainName,e,e);
795                        }
796                    }
797                }
798                log.debug("{} runSignalControlledTrain STOPPING TRAIN IN STOP BLOCK",_warrant._trainName);
799                if (_throttle==null) {
800                    emergencyStop();
801                    log.debug("Throttle lost after stop block");
802                } else {
803                    _throttle.setSpeedSetting(SPEED_STOP);
804                }
805                stopWarrant(false, false);
806            }
807        }
808
809        /**
810         * If we think we might have a runaway train - take the power of the entire layout.
811         */
812        private void emergencyStop() {
813            PowerManager manager = InstanceManager.getNullableDefault(jmri.PowerManager.class);
814            if (manager == null) {
815                log.debug("{} EMERGENCY STOP IMPOSSIBLE: NO POWER MANAGER",_trainName);
816                return;
817            }
818            try {
819                manager.setPower(PowerManager.OFF);
820            } catch (Exception e) {
821                log.debug("{} EMERGENCY STOP FAILED WITH EXCEPTION: {}",_trainName,e,e);
822            }
823            log.debug("{} EMERGENCY STOP",_trainName);
824        }
825
826    }
827
828    /* What super does currently is fine. But FindBug reports EQ_DOESNT_OVERRIDE_EQUALS
829     * FindBug wants us to duplicate and override anyway
830     */
831    @Override
832    public boolean equals(Object obj) {
833        return super.equals(obj);
834    }
835
836    /* What super does currently is fine. But FindBug reports HE_EQUALS_NO_HASHCODE
837     * FindBug wants us to duplicate and override anyway
838     */
839    @Override
840    public int hashCode() {
841        return super.hashCode();
842    }
843
844    /**
845     *
846     */
847    private static final Logger log = LoggerFactory.getLogger(SCWarrant.class);
848}