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