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