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