001package jmri.jmrit.dispatcher;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.Iterator;
006import java.util.List;
007import java.util.Map;
008import java.util.Map.Entry;
009import java.util.concurrent.LinkedBlockingQueue;
010
011import jmri.Block;
012import jmri.EntryPoint;
013import jmri.InstanceManager;
014import jmri.Section;
015import jmri.Sensor;
016import jmri.Transit;
017import jmri.TransitSection;
018import jmri.jmrit.dispatcher.TaskAllocateRelease.TaskAction;
019
020import org.slf4j.Logger;
021import org.slf4j.LoggerFactory;
022
023/**
024 * Handles automatic allocation of Sections for Dispatcher
025 * <p>
026 * AutoAllocate.java is an extension of DispatcherFrame.java.
027 * <p>
028 * When AutoAllocate is triggered, it scans the list of Allocation Requests, in
029 * order of the priorities of ActiveTrains with pending AllocationRequests,
030 * testing if a requested allocation can be made. AutoAllocate returns when
031 * either: A Section has been allocated -or- All AllocationRequests have been
032 * tested, and no allocation is indicated.
033 * <p>
034 * If AutoAllocate needs to save information related to a plan requiring
035 * multiple allocations, an AllocationPlan object is created. When the plan is
036 * complete, the AllocationPlan object is disposed of. Multiple AllocationPlan
037 * objects may be active at any one time.
038 * <p>
039 * AutoAllocate is triggered by each of the following events: An
040 * AllocatedSection has been released, freeing up a Section. A new
041 * AllocationRequest has been entered into the queue of AllocationRequests. A
042 * Section has been allocated, either by AutoAllocate or manually by the
043 * dispatcher.
044 * <p>
045 * AutoAllocate requires that AutoRelease is active.
046 * <p>
047 * AutoAllocate operates conservatively, that is, if there is any doubt that a
048 * Section should be allocated, it will not allocate the Section.
049 * <p>
050 * AutoAllocate develops plans for meets when multiple ActiveTrains are using
051 * the same Sections of track. These plans are automatically created and
052 * removed. They are stored in AllocationPlan objects to avoid having to
053 * continually recreate them, since the logic to create them is rather
054 * complicated.
055 * <p>
056 * The dispatcher is free to switch AutoAllocate on or off at any tine in
057 * DispatcherFrame. When AutoAllocate is switched off, all existing
058 * AllocationPlan objects are discarded.
059 * <p>
060 * All work done within the class is queued using a blocking queue. This is
061 * to ensure the integrity of arrays, both in Dispatcher and ActiveTrain,
062 * and to prevent calls within calls to modify those arrays, including autorelease.
063 * <br>
064 * <hr>
065 * This file is part of JMRI.
066 * <p>
067 * JMRI is free software; you can redistribute it and/or modify it under the
068 * terms of version 2 of the GNU General Public License as published by the Free
069 * Software Foundation. See the "COPYING" file for a copy of this license.
070 * <p>
071 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
072 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
073 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
074 *
075 * @author Dave Duchamp Copyright (C) 2011
076 */
077public class AutoAllocate implements Runnable {
078
079    LinkedBlockingQueue<TaskAllocateRelease> taskList;
080
081    public AutoAllocate(DispatcherFrame d, List<AllocationRequest> inAllocationRequests) {
082        _dispatcher = d;
083        allocationRequests = inAllocationRequests;
084        if (_dispatcher == null) {
085            log.error("null DispatcherFrame when constructing AutoAllocate");
086            return;
087        }
088        taskList = new LinkedBlockingQueue<>();
089    }
090
091    // operational variables
092    private static final jmri.NamedBean.DisplayOptions USERSYS = jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME;
093    private DispatcherFrame _dispatcher = null;
094    private final List<AllocationPlan> _planList = new ArrayList<>();
095    private int nextPlanNum = 1;
096    private final List<AllocationRequest> orderedRequests = new ArrayList<>();
097    private List<AllocationRequest> allocationRequests = null;
098    private final Map<String, String> reservedSections = new HashMap<String, String>();
099
100    private boolean abort = false;
101
102    /**
103     * Stops the autoAllocate nicely
104     */
105    protected void setAbort() {
106        abort = true;
107        scanAllocationRequests(new TaskAllocateRelease(TaskAction.ABORT)); //force queue flush
108    }
109
110    /*
111     * return true when the taskList queue is Empty
112     */
113    protected boolean allRequestsDone() {
114        return taskList.isEmpty();
115    }
116
117    protected void scanAllocationRequests(TaskAllocateRelease task) {
118        log.trace("Add request from [{}] for [{}][{}]",
119                task.getTrainName(),task.getAction().name(),
120                task.getAllocationRequest() == null ? "None" : task.getAllocationRequest().getSectionName());
121        synchronized (queueLock) {
122            TaskAction action = task.getAction();
123            if (lastQueuedTask != null && lastQueuedTask.getAction() == action
124                    && (action == TaskAction.SCAN_REQUESTS || action == TaskAction.AUTO_RELEASE)) {
125                // the same check is already at the end of the queue and has not
126                // started, so it will see whatever caused this request
127                return;
128            }
129            lastQueuedTask = task;
130            taskList.add(task);
131        }
132    }
133
134    private final Object queueLock = new Object();
135    private TaskAllocateRelease lastQueuedTask = null;  // last task queued, while it is still in the queue
136
137    /*
138     * Main loop processing queue
139     */
140    @Override
141    public void run() {
142        while (!abort) {
143            try {
144                TaskAllocateRelease task = taskList.take();
145                synchronized (queueLock) {
146                    if (task == lastQueuedTask) {
147                        lastQueuedTask = null;
148                    }
149                }
150                try {
151                    switch (task.getAction()) {
152                        case AUTO_RELEASE:
153                            _dispatcher.checkAutoRelease();
154                            break;
155                        case RELEASE_ONE:
156                            _dispatcher.doReleaseAllocatedSection(task.getAllocatedSection(),
157                                    task.getTerminatingTrain());
158                            break;
159                        case RELEASE_RESERVED:
160                            removeAllReservesForTrain(task.getTrainName());
161                            break;
162                        case SCAN_REQUESTS:
163                            scanAllocationRequestList(allocationRequests);
164                            break;
165                        case ALLOCATE_IMMEDIATE:
166                            _dispatcher.allocateSection(task.getAllocationRequest(), null);
167                            break;
168                        case ABORT:
169                            abort = true; //belt an braces
170                            break;
171                        case BARRIER:
172                            // every task queued before this one is done
173                            task.getWhenReached().run();
174                            break;
175                        default:
176                            log.error("Unknown action in TaskAllocateRelease - ignoring");
177                    }
178                } catch (Exception ex) {
179                    log.error("Unexpected Exeption, likely bad task request.", ex);
180                }
181            } catch (InterruptedException ex) {
182                log.error("Blocklist killed, taking this as terminate", ex);
183                abort = true;
184            }
185        }
186    }
187
188    /**
189     * This is the entry point to AutoAllocate when it is triggered.
190     *
191     * @param list list to scan
192     */
193    private synchronized void scanAllocationRequestList(List<AllocationRequest> list) {
194        boolean okToAllocate = false;
195        if (list.size() <= 0) {
196            return;
197        }
198        // copy AllocationRequests in order of priority of ActiveTrain.
199        copyAndSortARs(list);
200        removeCompletePlans();
201        for (int i = 0; i < orderedRequests.size(); i++) {
202            try {
203                okToAllocate = false;
204                AllocationRequest ar = orderedRequests.get(i);
205                if (ar == null) {
206                    log.error("error in allocation request list - AllocationRequest is null");
207                    continue;
208                }
209                // Check to see if there is a sensor temporarily block
210                // allocation blocking allocation
211                ActiveTrain activeTrain = ar.getActiveTrain();
212                String trainName = activeTrain.getTrainName();
213                log.trace("{}: try to allocate [{}]", trainName, ar.getSection().getDisplayName(USERSYS));
214                if (activeTrain.getLastAllocatedSection() != null) {
215                    // do stuff associated with the last allocated section
216                    Transit arTransit = activeTrain.getTransit();
217                    TransitSection arCurrentTransitSection =
218                            arTransit.getTransitSectionFromSectionAndSeq(activeTrain.getLastAllocatedSection(),
219                                    activeTrain.getLastAllocatedSectionSeqNumber());
220                    // stop allocating sensor active?
221                    if (stopAllocateSensorSet(activeTrain, arCurrentTransitSection)) {
222                        log.debug("[{}]:StopAllocateSensor active", trainName);
223                        continue;
224                    }
225                    // is the train held
226                    if (activeTrain.holdAllocation()|| (!activeTrain.getStarted()) && activeTrain.getDelayedStart() != ActiveTrain.NODELAY) {
227                        log.debug("[{}]:Allocation is Holding or Delayed hold[{}] started[{}], delayedstart[{}]", trainName, activeTrain.holdAllocation(), activeTrain.getStarted(), activeTrain.getDelayedStart() != ActiveTrain.NODELAY);
228                        continue;
229                    }
230                    // apparently holdAllocation() is not set when holding !!!
231                    if (activeTrain.getSignalType() == DispatcherFrame.SIGNALMAST &&
232                            isSignalHeldAtStartOfSection(ar)) {
233                        continue;
234                    }
235                    // this already reserved for the train, allocate.
236                    String reservedTrainName = reservedSections.get(ar.getSection().getSystemName());
237                    if (reservedTrainName != null) {
238                        if (reservedTrainName.equals(trainName)) {
239                            String sectionName = ar.getSection().getSystemName();
240                            if (allocateMore(ar)) {
241                                reservedSections.remove(sectionName);
242                            }
243                            continue;
244                        }
245                    }
246
247                    if (activeTrain.getAllocateMethod() == ActiveTrain.ALLOCATE_BY_SAFE_SECTIONS) {
248                        log.trace("{}: Allocating [{}] using Safe Sections", trainName,
249                                ar.getSection().getDisplayName());
250                        // if the last allocated section is safe but not
251                        // occupied short cut out of here
252                        if ( (activeTrain.getLastAllocOverrideSafe() == null ||
253                                    ( activeTrain.getLastAllocOverrideSafe() != arCurrentTransitSection.getSection()))
254                                && arCurrentTransitSection.isSafe()
255                                && activeTrain.getLastAllocatedSection().getOccupancy() != Section.OCCUPIED) {
256                            log.debug("Allocating Train [{}] has not arrived at Passing Point",
257                                    trainName);
258                            continue;
259                        }
260                        // Check all forward sections till a passing point.
261                        int itSequ = ar.getSectionSeqNumber();
262                        int iIncrement = 0;
263                        int iLimit = 0;
264                        int ix = 0;
265                        boolean skip = false;
266                        int iStart = 0;
267                        if (activeTrain.isTransitReversed()) {
268                            iIncrement = -1;
269                            iLimit = 0;
270                            iStart = itSequ; // reverse transits start
271                                             // allocating from the next
272                                             // one, they allocate the one
273                                             // there in already
274                        } else {
275                            if (activeTrain.getStartBlockSectionSequenceNumber() == ar.getSectionSeqNumber()) {
276                                skip = true;
277                            }
278                            iIncrement = +1;
279                            iLimit = arTransit.getMaxSequence() + 1;
280                            iStart = itSequ;
281                        }
282                        if (!skip) {
283                            boolean areForwardsFree = false;
284                            log.trace("index [{}] Limit [{}] transitsize [{}]", ix, iLimit,
285                                    arTransit.getTransitSectionList().size());
286                            for (ix = iStart; ix != iLimit; ix += iIncrement) {
287                                log.trace("index [{}] Limit [{}] transitsize [{}]", ix, iLimit,
288                                        arTransit.getTransitSectionList().size());
289                                // ensure all blocks section and blocks free
290                                // till next Passing Point, check alternates
291                                // if they exist.
292                                Section sS;
293                                ArrayList<TransitSection> sectionsInSeq = arTransit.getTransitSectionListBySeq(ix);
294                                areForwardsFree = false; // Posit will be
295                                                         // bad
296                                log.trace("Search ALternates Size[{}]", sectionsInSeq.size());
297                                int seqNumberfound = 0;
298                                for (int iSectionsInSeq = 0; iSectionsInSeq < sectionsInSeq.size() &&
299                                        !areForwardsFree; iSectionsInSeq++) {
300                                    log.trace("iSectionInSeq[{}]", iSectionsInSeq);
301                                    sS = sectionsInSeq.get(iSectionsInSeq).getSection();
302                                    seqNumberfound = iSectionsInSeq; // save
303                                                                     // for
304                                                                     // later
305                                    // debug code
306                                    log.trace("SectionName[{}] getState[{}] occupancy[{}] ",
307                                            sS.getDisplayName(USERSYS),
308                                            sS.getState(), sS.getOccupancy());
309                                    if (!checkUnallocatedCleanly(activeTrain, sS)) {
310                                        areForwardsFree = false;
311                                    } else if (sS.getState() != Section.FREE) {
312                                        log.debug("{}: Forward section [{}] unavailable", trainName,
313                                                sS.getDisplayName(USERSYS));
314                                        areForwardsFree = false;
315                                    } else if (sS.getOccupancy() != Section.UNOCCUPIED) {
316                                        log.debug("{}: Forward section [{}] is not unoccupied", trainName,
317                                                sS.getDisplayName(USERSYS));
318                                        areForwardsFree = false;
319                                    } else if (_dispatcher.checkBlocksNotInAllocatedSection(sS, ar) != null) {
320                                        if (log.isDebugEnabled()) {
321                                            log.debug("{}: Forward section [{}] is in conflict with [{}]",
322                                                    trainName, sS.getUserName(),
323                                                    _dispatcher.checkBlocksNotInAllocatedSection(sS, ar));
324                                        }
325                                        areForwardsFree = false;
326                                    } else if (checkBlocksNotInReservedSection(activeTrain, sS) != null) {
327                                        if (log.isDebugEnabled()) {
328                                            log.debug("{}: Forward section [{}] is in conflict with [{}]",
329                                                    trainName, sS.getDisplayName(),
330                                                    checkBlocksNotInReservedSection(activeTrain, sS).getDisplayName());
331                                        }
332                                        areForwardsFree = false;
333
334                                    } else if (reservedSections.get(sS.getSystemName()) != null &&
335                                            !reservedSections.get(sS.getSystemName()).equals(trainName)) {
336                                        log.debug("{}: Forward section [{}] is reserved for [{}]",
337                                                trainName, sS.getDisplayName(USERSYS),
338                                                reservedSections.get(sS.getSystemName()));
339                                        areForwardsFree = false;
340                                    } else {
341                                        log.debug("Adding [{}],[{}]", sS.getDisplayName(USERSYS), trainName);
342                                        reservedSections.put(sS.getSystemName(), trainName);
343                                        areForwardsFree = true;
344                                    }
345                                }
346                                if (!areForwardsFree) {
347                                    // delete all reserves for this train
348                                    removeAllReservesForTrain(trainName);
349                                    break;
350                                }
351                                if (sectionsInSeq.get(seqNumberfound).isSafe()) {
352                                    log.trace("Safe Section Found");
353                                    break;
354                                }
355                            }
356
357                            log.trace("ForwardsFree[{}]", areForwardsFree);
358                            if (!areForwardsFree) {
359                                // delete all reserves for this train
360                                removeAllReservesForTrain(trainName);
361                                continue;
362                            }
363                        }
364                        String sectionSystemName;
365                        try {
366                            sectionSystemName = ar.getSection().getSystemName();
367                        } catch (Exception ex) {
368                            log.error("Error", ex);
369                            sectionSystemName = "Unknown";
370                        }
371                        if (allocateMore(ar)) {
372                            // First Time thru this will in the list
373                            if (!sectionSystemName.equals("Unknown")) {
374                                log.debug("removing : [{}]", sectionSystemName);
375                                reservedSections.remove(sectionSystemName);
376                            } else {
377                                log.error("{};Cannot allocate allocatable section[{}]", trainName,
378                                        sectionSystemName);
379                            }
380                        }
381                        continue;
382                    } // end of allocating by safe sections
383                }
384                log.trace("Using Regular");
385                if (!checkUnallocatedCleanly(activeTrain, ar.getSection())) {
386                    okToAllocate = false;
387                    continue;
388                }
389                if (getPlanThisTrain(activeTrain) != null) {
390                    // this train is in an active Allocation Plan, anything
391                    // to do now?
392                    if (willAllocatingFollowPlan(ar, getPlanThisTrain(activeTrain))) {
393                        if (allocateMore(ar)) {
394                            continue;
395                        }
396                    }
397                } else if (!waitingForStartTime(ar)) {
398                    // train isn't waiting, continue only if requested
399                    // Section is currently free and not occupied
400                    if ((ar.getSection().getState() == Section.FREE) &&
401                            (ar.getSection().getOccupancy() != Section.OCCUPIED) &&
402                            (ar.getActiveTrain().getSignalType() == DispatcherFrame.SIGNALHEAD ||
403                                    ar.getActiveTrain().getSignalType() == DispatcherFrame.SECTIONSALLOCATED ||
404                                    (ar.getActiveTrain().getSignalType() == DispatcherFrame.SIGNALMAST &&
405                                            _dispatcher.checkBlocksNotInAllocatedSection(ar.getSection(),
406                                                    ar) == null))) {
407                        // requested Section is currently free and not
408                        // occupied
409                        List<ActiveTrain> activeTrainsList = _dispatcher.getActiveTrainsList();
410                        if (activeTrainsList.size() == 1) {
411                            // this is the only ActiveTrain
412                            if (allocateMore(ar)) {
413                                continue;
414                            }
415                        } else {
416                            // check if any other ActiveTrain will need this
417                            // Section or its alternates, if any
418                            okToAllocate = true;
419                            List<ActiveTrain> neededByTrainList = new ArrayList<>();
420                            for (int j = 0; j < activeTrainsList.size(); j++) {
421                                ActiveTrain at = activeTrainsList.get(j);
422                                if (at != activeTrain) {
423                                    if (sectionNeeded(ar, at)) {
424                                        neededByTrainList.add(at);
425                                    }
426                                }
427                            }
428                            // requested Section (or alternate) is
429                            // needed by other active Active Train(s)
430                            for (int k = 0; k < neededByTrainList.size(); k++) {
431                                // section is also needed by this active
432                                // train
433                                ActiveTrain nt = neededByTrainList.get(k);
434                                // are trains moving in same direction
435                                // through the requested Section?
436                                if (sameDirection(ar, nt)) {
437                                    // trains will move in the same
438                                    // direction thru requested section
439                                    if (firstTrainLeadsSecond(activeTrain, nt) &&
440                                            (nt.getPriority() > activeTrain.getPriority())) {
441                                        // a higher priority train is
442                                        // trailing this train, can we
443                                        // let it pass?
444                                        if (checkForPassingPlan(ar, nt, neededByTrainList)) {
445                                            // PASSING_MEET plan created
446                                            if (!willAllocatingFollowPlan(ar,
447                                                    getPlanThisTrain(activeTrain))) {
448                                                okToAllocate = false;
449                                            }
450                                        }
451                                    }
452                                } else {
453                                    // trains will move in opposite
454                                    // directions thru requested section
455                                    // explore possibility of an
456                                    // XING_MEET to avoid gridlock
457                                    if (willTrainsCross(activeTrain, nt)) {
458                                        if (checkForXingPlan(ar, nt, neededByTrainList)) {
459                                            // XING_MEET plan created
460                                            if (!willAllocatingFollowPlan(ar,
461                                                    getPlanThisTrain(activeTrain))) {
462                                                okToAllocate = false;
463                                            }
464                                        }
465                                    }
466                                }
467                            }
468                            if (okToAllocate) {
469                                if (allocateMore(ar)) {
470                                    continue;
471                                }
472                            }
473                        }
474                    }
475                }
476            } catch (RuntimeException e) {
477                log.warn(
478                        "scanAllocationRequestList - maybe the allocationrequest was removed due to a terminating train??",e);
479                continue;
480            }
481        }
482    }
483
484    /**
485     * Remove all reserved sections for a train name
486     *
487     * @param trainName remove reserved spaces for this train
488     */
489    protected void removeAllReservesForTrain(String trainName) {
490        Iterator<Entry<String, String>> iterRS = reservedSections.entrySet().iterator();
491        while (iterRS.hasNext()) {
492            Map.Entry<String, String> pair = iterRS.next();
493            if (pair.getValue().equals(trainName)) {
494                iterRS.remove();
495            }
496        }
497    }
498
499    /**
500     * Remove a specific section reservation for a train.
501     *
502     * @param trainName         Name of the train
503     * @param sectionSystemName Systemname
504     */
505    protected void releaseReservation(String trainName, String sectionSystemName) {
506        String reservedTrainName = reservedSections.get(sectionSystemName);
507        if (reservedTrainName.equals(trainName)) {
508            reservedSections.remove(sectionSystemName);
509        }
510    }
511
512    /*
513     * Check conflicting blocks acros reserved sections.
514     */
515    protected Section checkBlocksNotInReservedSection(ActiveTrain at, Section sectionToCheck) {
516        String trainName = at.getTrainName();
517        List<Block> lb = sectionToCheck.getBlockList();
518        Iterator<Entry<String, String>> iterRS = reservedSections.entrySet().iterator();
519        while (iterRS.hasNext()) {
520            Map.Entry<String, String> pair = iterRS.next();
521            if (!pair.getValue().equals(trainName)) {
522                Section s = InstanceManager.getDefault(jmri.SectionManager.class).getSection(pair.getKey());
523                for (Block rb : s.getBlockList()) {
524                    if (lb.contains(rb)) {
525                        return s;
526                    }
527                }
528            }
529        }
530        return null;
531    }
532
533    /*
534     * Check each ActiveTrains sections for a given section. We need to do this
535     * as Section is flagged as free before it is fully released, then when it
536     * is released it updates , incorrectly, the section status and allocations.
537     */
538    private boolean checkUnallocatedCleanly(ActiveTrain at, Section section) {
539        for (ActiveTrain atx : InstanceManager.getDefault(DispatcherFrame.class).getActiveTrainsList()) {
540            for (AllocatedSection asx : atx.getAllocatedSectionList()) {
541                if (asx.getSection() == section) {
542                    return false;
543                }
544            }
545        }
546        return true;
547    }
548
549    /**
550     * Entered to request a choice of Next Section when a Section is being
551     * allocated and there are alternate Section choices for the next Section.
552     *
553     * @param sList the possible next Sections
554     * @param ar    the section being allocated when a choice is needed
555     * @param sectionSeqNo transit sequence number attempting to be allocated
556     * @return the allocated section
557     */
558    protected Section autoNextSectionChoice(List<Section> sList, AllocationRequest ar, int sectionSeqNo) {
559        // check if AutoAllocate has prepared for this question
560        if ((savedAR != null) && (savedAR == ar)) {
561            for (int j = 0; j < sList.size(); j++) {
562                if (savedSection == sList.get(j)) {
563                    return savedSection;
564                }
565            }
566            log.warn("Failure of prepared choice of next Section in AutoAllocate");
567        }
568
569        // check to see if AutoAllocate by safesections has reserved a section
570        // already
571        ActiveTrain at = ar.getActiveTrain();
572        for (Section sectionOption : sList) {
573            String reservedTrainName = reservedSections.get(sectionOption.getSystemName());
574            if (reservedTrainName != null) {
575                if (reservedTrainName.equals(at.getTrainName())) {
576                    return sectionOption;
577                }
578            }
579        }
580
581        // Jay Janzen
582        // If there is an AP check to see if the AP's target is on the list of
583        // choices
584        // and if so, return that.
585        at = ar.getActiveTrain();
586        AllocationPlan ap = getPlanThisTrain(at);
587        Section as = null;
588        if (ap != null) {
589            if (ap.getActiveTrain(1) == at) {
590                as = ap.getTargetSection(1);
591            } else if (ap.getActiveTrain(2) == at) {
592                as = ap.getTargetSection(2);
593            } else {
594                return null;
595            }
596            for (int i = 0; i < sList.size(); i++) {
597                if (as != null && as == sList.get(i)) {
598                    return as;
599                }
600            }
601        }
602        // If our end block section is on the list of choices
603        // return that occupied or not. In the list of choices the primary
604        // occurs
605        // ahead any alternates, so if our end block is an alternate and its
606        // primary is unoccupied, the search will select the primary and
607        // we wind up skipping right over our end section.
608        for (int i = 0; i < sList.size(); i++) {
609            if (at.getEndBlockSectionSequenceNumber() == sectionSeqNo
610                     && at.getEndBlockSection().getSystemName().equals(sList.get(i).getSystemName())) {
611                return sList.get(i);
612            }
613        }
614        // no prepared choice, or prepared choice failed, is there an unoccupied
615        // Section available
616        for (int i = 0; i < sList.size(); i++) {
617            if ((sList.get(i).getOccupancy() == Section.UNOCCUPIED) &&
618                    (sList.get(i).getState() == Section.FREE) &&
619                    (ar.getActiveTrain().getSignalType() == DispatcherFrame.SIGNALHEAD ||
620                            ar.getActiveTrain().getSignalType() == DispatcherFrame.SECTIONSALLOCATED ||
621                            (ar.getActiveTrain().getSignalType() == DispatcherFrame.SIGNALMAST &&
622                                    _dispatcher.checkBlocksNotInAllocatedSection(sList.get(i), ar) == null))) {
623                return sList.get(i);
624            }
625        }
626        // no unoccupied Section available, check for Section allocated in same
627        // direction as this request
628        int dir = ar.getSectionDirection();
629        List<AllocatedSection> allocatedSections = _dispatcher.getAllocatedSectionsList();
630        for (int m = 0; m < sList.size(); m++) {
631            boolean notFound = true;
632            for (int k = 0; (k < allocatedSections.size()) && notFound; k++) {
633                if (sList.get(m) == allocatedSections.get(k).getSection()) {
634                    notFound = false;
635                    if (allocatedSections.get(k).getSection().getState() == dir) {
636                        return sList.get(m);
637                    }
638                }
639            }
640        }
641        // if all else fails, return null so Dispatcher will ask the dispatcher
642        // to choose
643        return null;
644    }
645
646    private final AllocationRequest savedAR = null;
647    private final Section savedSection = null;
648
649    // private implementation methods
650    private void copyAndSortARs(List<AllocationRequest> list) {
651        orderedRequests.clear();
652        // copy across and then sort...
653        for (int i = 0; i < list.size(); i++) {
654            orderedRequests.add(list.get(i));
655        }
656        orderedRequests.sort((AllocationRequest e1, AllocationRequest e2) -> {
657            if (e1.getActiveTrain().getPriority() < e2.getActiveTrain().getPriority()) {
658                return 1;
659            } else if (e1.getActiveTrain().getPriority() > e2.getActiveTrain().getPriority()) {
660                return -1;
661            } else {
662                return e1.getActiveTrain().getTrainName().compareTo(e2.getActiveTrain().getTrainName());
663            }
664        });
665    }
666
667    /**
668     * Check whether it is nessassary to pause/stop further allocation because a
669     * specified sensor is active.
670     *
671     * @param lastAllocatedTransitSection
672     * @return true stop allocating, false dont
673     */
674    private boolean stopAllocateSensorSet(ActiveTrain at, TransitSection lastAllocatedTransitSection) {
675        if (lastAllocatedTransitSection.getStopAllocatingSensor() != null &&
676                !lastAllocatedTransitSection.getStopAllocatingSensor().equals("")) {
677            String sensorName = lastAllocatedTransitSection.getStopAllocatingSensor();
678            Sensor sensor;
679            try {
680                sensor = InstanceManager.sensorManagerInstance().provideSensor(sensorName);
681                if (sensor.getKnownState() == Sensor.ACTIVE) {
682                    log.trace("Sensor[{}] InActive", sensor.getDisplayName(USERSYS));
683                    at.initializeRestartAllocationSensor(jmri.InstanceManager
684                            .getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sensorName, sensor));
685                    return true;
686                }
687            } catch (NumberFormatException ex) {
688                log.error("Error with pause/stop allocation sensor[{}]", sensorName, ex);
689                return false;
690            }
691        }
692        return false;
693    }
694
695    private AllocationPlan getPlanThisTrain(ActiveTrain at) {
696        for (int i = 0; i < _planList.size(); i++) {
697            AllocationPlan ap = _planList.get(i);
698            for (int j = 1; j < 3; j++) {
699                if (ap.getActiveTrain(j) == at) {
700                    return ap;
701                }
702            }
703        }
704        // train not in an AllocationPlan
705        return null;
706    }
707
708    private boolean willAllocatingFollowPlan(AllocationRequest ar, AllocationPlan ap) {
709        // return 'true' if this AllocationRequest is consistent with specified
710        // plan,
711        // returns 'false' otherwise
712        ActiveTrain at = ar.getActiveTrain();
713        int cTrainNum = 0;
714        if (ap.getActiveTrain(1) == at) {
715            cTrainNum = 1;
716        } else if (ap.getActiveTrain(2) == at) {
717            cTrainNum = 2;
718        } else {
719            log.error("Requesting train not in Allocation Plan");
720            return false;
721        }
722        if (!at.isAllocationReversed()) {
723            if (ap.getTargetSectionSequenceNum(cTrainNum) >= ar.getSectionSeqNumber()) {
724                if ((ar.getSection().getState() == Section.FREE) &&
725                        (ar.getSection().getOccupancy() != Section.OCCUPIED)) {
726                    return true;
727                }
728            }
729        } else {
730            if (ap.getTargetSectionSequenceNum(cTrainNum) <= ar.getSectionSeqNumber()) {
731                if ((ar.getSection().getState() == Section.FREE) &&
732                        (ar.getSection().getOccupancy() != Section.OCCUPIED)) {
733                    return true;
734                }
735            }
736        }
737        return false;
738    }
739
740    private void removeCompletePlans() {
741        boolean foundCompletePlan = true;
742        while (foundCompletePlan) {
743            foundCompletePlan = false;
744            for (int i = 0; (!foundCompletePlan) && (i < _planList.size()); i++) {
745                // remove if all planned allocations have been made, or if
746                // they never can be
747                foundCompletePlan = _planList.get(i).isComplete() || isPlanAbandoned(_planList.get(i));
748                if (foundCompletePlan) {
749                    _planList.get(i).dispose();
750                    _planList.remove(i);
751                }
752            }
753        }
754    }
755
756    /*
757     * A plan can never be completed once one of its trains has been
758     * terminated, has finished, or has been reset and lost its allocations.
759     * Keeping such a plan would hold the other train forever.
760     */
761    private boolean isPlanAbandoned(AllocationPlan ap) {
762        List<ActiveTrain> activeTrains = _dispatcher.getActiveTrainsList();
763        for (int j = 1; j < 3; j++) {
764            ActiveTrain at = ap.getActiveTrain(j);
765            if ((at == null) || !activeTrains.contains(at) || (at.getMode() == ActiveTrain.TERMINATED) ||
766                    (at.getStatus() == ActiveTrain.DONE) || at.getAllocatedSectionList().isEmpty()) {
767                log.debug("Allocation Plan {} abandoned, a train has gone or been reset", ap.getPlanNum());
768                return true;
769            }
770        }
771        return false;
772    }
773
774    protected void clearAllocationPlans() {
775        for (int i = _planList.size() - 1; i >= 0; i--) {
776            AllocationPlan ap = _planList.get(i);
777            _planList.remove(i);
778            ap.dispose();
779        }
780    }
781
782    // test to see how far ahead allocations have already been made
783    // and go no farther than the number requested, or the next safe section.
784    // return true if allocation successful, false otherwise
785    private boolean allocateMore(AllocationRequest ar) {
786        log.trace("in allocateMore, ar.Section={}", ar.getSection().getDisplayName(USERSYS));
787        int allocateSectionsAhead = ar.getActiveTrain().getAllocateMethod();
788        if (allocateSectionsAhead == ActiveTrain.ALLOCATE_AS_FAR_AS_IT_CAN) {
789            if (_dispatcher.allocateSection(ar, null) == null) {
790                return false;
791            }
792            return true;
793        }
794        // test how far ahead of occupied track this requested section is
795        List<AllocatedSection> aSectionList = ar.getActiveTrain().getAllocatedSectionList();
796        boolean allocateBySafeSections = false;
797        // check for allocating By Safe Sections
798        if (allocateSectionsAhead == 0) {
799            // check for type of allocating N ahead or until passing
800            allocateBySafeSections = true;
801        }
802        if ((allocateBySafeSections && aSectionList.size() >= 1) ||
803                (!allocateBySafeSections && aSectionList.size() >= (allocateSectionsAhead + 1))) {
804            int curSeq = ar.getSectionSeqNumber() - 1;
805            if (ar.getActiveTrain().isAllocationReversed()) {
806                curSeq = ar.getSectionSeqNumber() + 1;
807            }
808            if ((curSeq == 1) && ar.getActiveTrain().getResetWhenDone()) {
809                curSeq = ar.getActiveTrain().getTransit().getMaxSequence();
810            }
811            AllocatedSection curAS = null;
812            for (int i = aSectionList.size() - 1; i >= 0; i--) {
813                AllocatedSection as = aSectionList.get(i);
814                if ((as != null) && (as.getSequence() == curSeq)) {
815                    curAS = as;
816                }
817            }
818            if (allocateBySafeSections &&
819                    (curAS != null) &&
820                    ((curAS.getSection().getOccupancy() != jmri.Section.OCCUPIED) &&
821                            (ar.getActiveTrain().getLastAllocOverrideSafe() == null ||
822                                    ( ar.getActiveTrain().getLastAllocOverrideSafe() != curAS.getSection())) &&
823                            ar.getActiveTrain().getTransit()
824                                    .getTransitSectionFromSectionAndSeq(curAS.getSection(), curSeq).isSafe())) {
825                // last allocated section exists and is not occupied but is a
826                // Passing point
827                // block further allocations till occupied.
828                log.trace("{}: not at end of safe allocations, [{}] not allocated", ar.getActiveTrain().getTrainName(),
829                        ar.getSection().getDisplayName(USERSYS));
830                return false;
831            } else if (allocateBySafeSections) {
832                log.trace("auto allocating Section keep going");
833                if (_dispatcher.allocateSection(ar, null) != null) {
834                    return true;
835                } else {
836                    return false;
837                }
838            }
839            log.trace("Auto allocating by count");
840            int numberAllocatedButUnoccupied = 0;
841            for (int i = aSectionList.size() - 1; i >= 0; i--) {
842                AllocatedSection as = aSectionList.get(i);
843                if ((as != null) && (as.getSection().getOccupancy() != jmri.Section.OCCUPIED && !as.getExited())) {
844                    numberAllocatedButUnoccupied++;
845                }
846            }
847            log.trace("FinalCounter[{}]", numberAllocatedButUnoccupied);
848            if (numberAllocatedButUnoccupied < allocateSectionsAhead) {
849                if (_dispatcher.allocateSection(ar, null) == null) {
850                    return false;
851                }
852                return true;
853            }
854            return false;
855
856        }
857        log.debug("{}: auto allocating Section {}", ar.getActiveTrain().getTrainName(),
858                ar.getSection().getDisplayName(USERSYS));
859        if (_dispatcher.allocateSection(ar, null) == null) {
860            return false;
861        }
862        return true;
863    }
864
865    private boolean checkForXingPlan(AllocationRequest ar, ActiveTrain nt,
866            List<ActiveTrain> neededByTrainList) {
867        // returns 'true' if an AllocationPlan has been set up, returns 'false'
868        // otherwise
869        Section nSec = null;
870        Section aSec = null;
871        int nSecSeq = 0;
872        int aSecSeq = 0;
873        ActiveTrain at = ar.getActiveTrain();
874        AllocationPlan apx = getPlanThisTrain(nt);
875        if (apx != null) {
876            if (apx.getPlanType() != AllocationPlan.XING_MEET) {
877                return false;
878            }
879            // already in a XING_MEET Allocation Plan - find target Section and
880            // sequence
881            if (apx.getActiveTrain(1) == nt) {
882                nSecSeq = apx.getTargetSectionSequenceNum(1);
883                nSec = apx.getTargetSection(1);
884            } else {
885                nSecSeq = apx.getTargetSectionSequenceNum(2);
886                nSec = apx.getTargetSection(2);
887            }
888            List<Section> nSections = nt.getTransit().getSectionListBySeq(nSecSeq);
889            if (nSections.size() <= 1) {
890                return false;
891            }
892            // is a passing siding, find a suitable track
893            aSec = getBestOtherSection(nSections, nSec);
894            if (aSec == null) {
895                return false;
896            }
897            aSecSeq = willTraverse(aSec, at, getCurrentSequenceNumber(at));
898            if (aSecSeq == 0) {
899                return false;
900            }
901        } else {
902            // neither train is in an AllocationPlan currently, check for
903            // suitable passing siding
904            int aSeq = ar.getSectionSeqNumber();
905            // is an alternate Section available here or ahead
906            aSecSeq = findPassingSection(at, aSeq);
907            if (aSecSeq == 0) {
908                // none in at's Transit, is there one in nt's Transit
909                int nCurrentSeq = getCurrentSequenceNumber(nt);
910                nSecSeq = findPassingSection(nt, nCurrentSeq);
911                if (nSecSeq > 0) {
912                    // has passing section ahead, will this train traverse a
913                    // Section in it
914                    List<Section> nSections = nt.getTransit().getSectionListBySeq(nSecSeq);
915                    for (int i = 0; (i < nSections.size()) && (aSec == null); i++) {
916                        aSecSeq = willTraverse(nSections.get(i), at, aSeq);
917                        if (aSecSeq > 0) {
918                            aSec = at.getTransit().getSectionListBySeq(aSecSeq).get(0);
919                        }
920                    }
921                    if (aSec != null) {
922                        // found passing Section that should work out
923                        nSec = getBestOtherSection(nSections, aSec);
924                    }
925                }
926            } else {
927                // will other train go through any of these alternate sections
928                List<Section> aSections = at.getTransit().getSectionListBySeq(aSecSeq);
929                int nCurrentSeq = getCurrentSequenceNumber(nt);
930                for (int i = 0; (i < aSections.size()) && (aSec == null); i++) {
931                    nSecSeq = willTraverse(aSections.get(i), nt, nCurrentSeq);
932                    if (nSecSeq > 0) {
933                        nSec = aSections.get(i);
934                        aSec = getBestOtherSection(aSections, nSec);
935                    }
936                }
937            }
938            // if could not find a suitable siding for a crossing meet, return
939            if ((aSec == null) || (nSec == null)) {
940                return false;
941            }
942        }
943        // check for conflicting train or conflicting plan that could cause
944        // gridlock
945        if (neededByTrainList.size() > 2) {
946            // is there another train between these two
947            if (!areTrainsAdjacent(at, nt)) {
948                return false;
949            }
950            if (isThereConflictingPlan(at, aSec, aSecSeq, nt, nSec, nSecSeq,
951                    AllocationPlan.XING_MEET)) {
952                return false;
953            }
954        }
955        // set up allocation plan
956        AllocationPlan ap = new AllocationPlan(this, nextPlanNum);
957        nextPlanNum++;
958        ap.setPlanType(AllocationPlan.XING_MEET);
959        ap.setActiveTrain(at, 1);
960        ap.setTargetSection(aSec, aSecSeq, 1);
961        ap.setActiveTrain(nt, 2);
962        ap.setTargetSection(nSec, nSecSeq, 2);
963        _planList.add(ap);
964        return true;
965    }
966
967    private boolean checkForPassingPlan(AllocationRequest ar, ActiveTrain nt,
968            List<ActiveTrain> neededByTrainList) {
969        // returns 'true' if an AllocationPlan has been set up, returns 'false'
970        // otherwise
971        Section nSec = null;
972        Section aSec = null;
973        int nSecSeq = 0;
974        int aSecSeq = 0;
975        ActiveTrain at = ar.getActiveTrain();
976        AllocationPlan apx = getPlanThisTrain(nt);
977        if (apx != null) {
978            if (apx.getPlanType() != AllocationPlan.PASSING_MEET) {
979                return false;
980            }
981            // already in a PASSING_MEET Allocation Plan - find target Section
982            // and sequence
983            Section oSection = null;
984            // ActiveTrain oTrain = null;
985            if (apx.getActiveTrain(1) == nt) {
986                nSecSeq = apx.getTargetSectionSequenceNum(1);
987                nSec = apx.getTargetSection(1);
988                oSection = apx.getTargetSection(2);
989            } else {
990                nSecSeq = apx.getTargetSectionSequenceNum(2);
991                nSec = apx.getTargetSection(2);
992                oSection = apx.getTargetSection(1);
993            }
994            int aCurrentSeq = getCurrentSequenceNumber(at);
995            aSecSeq = willTraverse(nSec, at, aCurrentSeq);
996            if (aSecSeq == 0) {
997                return false;
998            }
999            List<Section> nSections = nt.getTransit().getSectionListBySeq(nSecSeq);
1000            if (nSections.size() <= 1) {
1001                return false;
1002            }
1003            // is a passing siding, find a suitable track
1004            for (int i = 0; (i < nSections.size()) && (aSec == null); i++) {
1005                if (nSections.get(i) == oSection) {
1006                    aSecSeq = willTraverse(nSections.get(i), at, aCurrentSeq);
1007                    if (aSecSeq > 0) {
1008                        aSec = nSections.get(i);
1009                    }
1010                }
1011            }
1012            if (aSec == null) {
1013                for (int i = 0; (i < nSections.size()) && (aSec == null); i++) {
1014                    if (nSections.get(i) != nSec) {
1015                        aSecSeq = willTraverse(nSections.get(i), at, aCurrentSeq);
1016                        if (aSecSeq > 0) {
1017                            aSec = nSections.get(i);
1018                        }
1019                    }
1020                }
1021            }
1022            if (aSec == null) {
1023                return false;
1024            }
1025        } else {
1026            // both trains are not in Allocation plans
1027            int aSeq = ar.getSectionSeqNumber();
1028            // is an alternate Section available here or ahead
1029            aSecSeq = findPassingSection(at, aSeq);
1030            if (aSecSeq == 0) {
1031                // does higher priority train have a passing section ahead
1032                int nCurrentSeq = getCurrentSequenceNumber(nt);
1033                nSecSeq = findPassingSection(nt, nCurrentSeq);
1034                if (nSecSeq > 0) {
1035                    // has passing section ahead, will this train traverse a
1036                    // Section in it
1037                    List<Section> nSections = nt.getTransit().getSectionListBySeq(nSecSeq);
1038                    for (int i = 0; (i < nSections.size()) && (aSec == null); i++) {
1039                        aSecSeq = willTraverse(nSections.get(i), at, aSeq);
1040                        if (aSecSeq > 0) {
1041                            aSec = at.getTransit().getSectionListBySeq(aSecSeq).get(0);
1042                        }
1043                    }
1044                    if (aSec != null) {
1045                        // found passing Section that should work out
1046                        nSec = getBestOtherSection(nSections, aSec);
1047                    }
1048                }
1049            } else {
1050                // will the higher priority train go through any of these
1051                // alternate sections
1052                List<Section> aSections = at.getTransit().getSectionListBySeq(aSecSeq);
1053                int nCurrentSeq = getCurrentSequenceNumber(nt);
1054                for (int i = 0; (i < aSections.size()) && (aSec == null); i++) {
1055                    nSecSeq = willTraverse(aSections.get(i), nt, nCurrentSeq);
1056                    if (nSecSeq > 0) {
1057                        nSec = aSections.get(i);
1058                        aSec = getBestOtherSection(aSections, nSec);
1059                    }
1060                }
1061            }
1062            // if could not find a suitable passing siding, return
1063            if ((aSec == null) || (nSec == null)) {
1064                return false;
1065            }
1066            // push higher priority train one section further, if possible
1067            if (!nt.isAllocationReversed()) {
1068                if (nSecSeq < nt.getTransit().getMaxSequence()) {
1069                    nSecSeq++;
1070                    nSec = nt.getTransit().getSectionListBySeq(nSecSeq).get(0);
1071                }
1072            } else {
1073                if (nSecSeq > 1) {
1074                    nSecSeq--;
1075                    nSec = nt.getTransit().getSectionListBySeq(nSecSeq).get(0);
1076                }
1077            }
1078        }
1079        // is there another train trying to let this high priority train pass
1080        if (neededByTrainList.size() > 2) {
1081            // Note: e.g. Two lower priority trains ahead of a high priority
1082            // train could cause gridlock
1083            // if both try to set up a PASSING_PLAN meet at the same place, so
1084            // we exclude that case.
1085            // is there another train between these two
1086            if (!areTrainsAdjacent(at, nt)) {
1087                return false;
1088            }
1089            if (isThereConflictingPlan(at, aSec, aSecSeq, nt, nSec, nSecSeq,
1090                    AllocationPlan.PASSING_MEET)) {
1091                return false;
1092            }
1093        }
1094        // set up allocation plan
1095        AllocationPlan ap = new AllocationPlan(this, nextPlanNum);
1096        nextPlanNum++;
1097        ap.setPlanType(AllocationPlan.PASSING_MEET);
1098        ap.setActiveTrain(at, 1);
1099        ap.setTargetSection(aSec, aSecSeq, 1);
1100        ap.setActiveTrain(nt, 2);
1101        ap.setTargetSection(nSec, nSecSeq, 2);
1102        _planList.add(ap);
1103        return true;
1104    }
1105
1106    private boolean isThereConflictingPlan(ActiveTrain at, Section aSec, int aSecSeq,
1107            ActiveTrain nt, Section nSec, int nSecSeq, int type) {
1108        // returns 'true' if there is a conflicting plan that may result in
1109        // gridlock
1110        // if this plan is set up, return 'false' if not.
1111        // Note: may have to add other tests to this method in the future to
1112        // prevent gridlock
1113        // situations not currently tested for.
1114        if (_planList.size() == 0) {
1115            return false;
1116        }
1117        for (int i = 0; i < _planList.size(); i++) {
1118            AllocationPlan ap = _planList.get(i);
1119            // check if this plan involves the second train (it'll never involve
1120            // the first)
1121            int trainNum = 0;
1122            if (ap.getActiveTrain(1) == nt) {
1123                trainNum = 1;
1124            } else if (ap.getActiveTrain(2) == nt) {
1125                trainNum = 2;
1126            }
1127            if (trainNum > 0) {
1128                // check consistency - same type, section, and sequence number
1129                if ((ap.getPlanType() != type) ||
1130                        (ap.getTargetSection(trainNum) != nSec) ||
1131                        (ap.getTargetSectionSequenceNum(trainNum) != nSecSeq)) {
1132                    return true;
1133                }
1134            } else {
1135                // different trains, does this plan use the same Passing
1136                // Section?
1137                List<Section> aSections = at.getTransit().getSectionListBySeq(aSecSeq);
1138                for (int j = 0; j < aSections.size(); j++) {
1139                    if ((aSections.get(j) == ap.getTargetSection(1)) || (aSections.get(j) == ap.getTargetSection(2))) {
1140                        return true;
1141                    }
1142                }
1143            }
1144        }
1145        // passes all tests
1146        return false;
1147    }
1148
1149    private Section getBestOtherSection(List<Section> sList, Section aSec) {
1150        // returns the best Section from the list that is not aSec, or else
1151        // return null
1152        for (int i = 0; i < sList.size(); i++) {
1153            if ((sList.get(i) != aSec) &&
1154                    (sList.get(i).getState() == Section.FREE) &&
1155                    (sList.get(i).getOccupancy() != Section.OCCUPIED)) {
1156                return sList.get(i);
1157            }
1158        }
1159        for (int i = 0; i < sList.size(); i++) {
1160            if ((sList.get(i) != aSec) && (sList.get(i).getOccupancy() != Section.OCCUPIED)) {
1161                return sList.get(i);
1162            }
1163        }
1164        for (int i = 0; i < sList.size(); i++) {
1165            if (sList.get(i) != aSec) {
1166                return sList.get(i);
1167            }
1168        }
1169        return null;
1170    }
1171
1172    private int findPassingSection(ActiveTrain at, int aSeq) {
1173        // returns the sequence number of first area having alternate sections
1174        Transit t = at.getTransit();
1175        if (!at.isTransitReversed()) {
1176            for (int i = aSeq; i <= t.getMaxSequence(); i++) {
1177                if (t.getSectionListBySeq(i).size() > 1) {
1178                    return i;
1179                }
1180            }
1181        } else {
1182            for (int i = aSeq; i >= 0; i--) {
1183                if (t.getSectionListBySeq(i).size() > 1) {
1184                    return i;
1185                }
1186            }
1187        }
1188        return 0;
1189    }
1190
1191    private int willTraverse(Section s, ActiveTrain at, int seq) {
1192        Transit t = at.getTransit();
1193        if (!at.isTransitReversed()) {
1194            for (int i = seq; i <= t.getMaxSequence(); i++) {
1195                for (int j = 0; j < t.getSectionListBySeq(i).size(); j++) {
1196                    if (t.getSectionListBySeq(i).get(j) == s) {
1197                        return i;
1198                    }
1199                }
1200            }
1201        } else {
1202            for (int i = seq; i >= 0; i--) {
1203                for (int j = 0; j < t.getSectionListBySeq(i).size(); j++) {
1204                    if (t.getSectionListBySeq(i).get(j) == s) {
1205                        return i;
1206                    }
1207                }
1208            }
1209        }
1210        return 0;
1211    }
1212
1213    private boolean sectionNeeded(AllocationRequest ar, ActiveTrain at) {
1214        // returns 'true' if request section, or its alternates, will be needed
1215        // by specified train
1216        if ((ar == null) || (at == null)) {
1217            log.error("null argument on entry to 'sectionNeeded'");
1218            return false;
1219        }
1220        List<Section> aSectionList = ar.getActiveTrain().getTransit().getSectionListBySeq(
1221                ar.getSectionSeqNumber());
1222        boolean found = false;
1223        for (int i = 0; i < aSectionList.size(); i++) {
1224            if (at.getTransit().containsSection(aSectionList.get(i))) {
1225                found = true;
1226            }
1227        }
1228        if (!found) {
1229            return false;
1230        } else if ((at.getResetWhenDone()) || (at.getReverseAtEnd() && (!at.isAllocationReversed()))) {
1231            return true;
1232        }
1233        // this train may need this Section, has it already passed this Section?
1234        List<TransitSection> tsList = at.getTransit().getTransitSectionList();
1235        int curSeq = getCurrentSequenceNumber(at);
1236        if (!at.isAllocationReversed()) {
1237            for (int i = 0; i < tsList.size(); i++) {
1238                if (tsList.get(i).getSequenceNumber() > curSeq) {
1239                    for (int j = 0; j < aSectionList.size(); j++) {
1240                        if (tsList.get(i).getSection() == aSectionList.get(j)) {
1241                            return true;
1242                        }
1243                    }
1244                }
1245            }
1246        } else {
1247            for (int i = tsList.size() - 1; i >= 0; i--) {
1248                if (tsList.get(i).getSequenceNumber() < curSeq) {
1249                    for (int j = 0; j < aSectionList.size(); j++) {
1250                        if (tsList.get(i).getSection() == aSectionList.get(j)) {
1251                            return true;
1252                        }
1253                    }
1254                }
1255            }
1256        }
1257        return false;
1258    }
1259
1260    private boolean sameDirection(AllocationRequest ar, ActiveTrain at) {
1261        // returns 'true' if both trains will move thru the requested section in
1262        // the same direction
1263        if ((ar == null) || (at == null)) {
1264            log.error("null argument on entry to 'sameDirection'");
1265            return false;
1266        }
1267        List<TransitSection> tsList = at.getTransit().getTransitSectionList();
1268        List<TransitSection> rtsList = ar.getActiveTrain().getTransit().getTransitSectionListBySeq(
1269                ar.getSectionSeqNumber());
1270        int curSeq = getCurrentSequenceNumber(at);
1271        if (!at.isAllocationReversed()) {
1272            for (int i = 0; i < tsList.size(); i++) {
1273                if (tsList.get(i).getSequenceNumber() > curSeq) {
1274                    for (int k = 0; k < rtsList.size(); k++) {
1275                        if ((tsList.get(i).getSection() == rtsList.get(k).getSection()) &&
1276                                (runDirection(at, tsList.get(i)) == runDirection(ar.getActiveTrain(), rtsList.get(k)))) {
1277                            return true;
1278                        }
1279                    }
1280                }
1281            }
1282        } else {
1283            for (int i = tsList.size() - 1; i >= 0; i--) {
1284                if (tsList.get(i).getSequenceNumber() < curSeq) {
1285                    for (int k = 0; k < rtsList.size(); k++) {
1286                        if ((tsList.get(i).getSection() == rtsList.get(k).getSection()) &&
1287                                (runDirection(at, tsList.get(i)) == runDirection(ar.getActiveTrain(), rtsList.get(k)))) {
1288                            return true;
1289                        }
1290                    }
1291                }
1292            }
1293        }
1294        return false;
1295    }
1296
1297    /*
1298     * The direction a train runs through a Section of its Transit, allowing for
1299     * the train running its Transit in reverse.
1300     */
1301    private int runDirection(ActiveTrain at, TransitSection ts) {
1302        return at.getAllocationDirectionFromSectionAndSeq(ts.getSection(), ts.getSequenceNumber());
1303    }
1304
1305    private boolean firstTrainLeadsSecond(ActiveTrain at, ActiveTrain nt) {
1306        TransitSection aCurrent = getCurrentTransitSection(at);
1307        int aSeq = (aCurrent == null) ? 0 : aCurrent.getSequenceNumber();
1308        Section aSec = (aCurrent == null) ? null : aCurrent.getSection();
1309        TransitSection nCurrent = getCurrentTransitSection(nt);
1310        int nSeq = (nCurrent == null) ? 0 : nCurrent.getSequenceNumber();
1311        Section nSec = (nCurrent == null) ? null : nCurrent.getSection();
1312        List<TransitSection> atsList = at.getTransit().getTransitSectionList();
1313        if (!at.isTransitReversed()) {
1314            for (int i = 0; i < atsList.size(); i++) {
1315                if (atsList.get(i).getSequenceNumber() > aSeq) {
1316                    if (atsList.get(i).getSection() == nSec) {
1317                        // first train has not yet reached second train position
1318                        return false;
1319                    }
1320                }
1321            }
1322        } else {
1323            for (int i = atsList.size() - 1; i >= 0; i--) {
1324                if (atsList.get(i).getSequenceNumber() < aSeq) {
1325                    if (atsList.get(i).getSection() == nSec) {
1326                        // first train has not yet reached second train position
1327                        return false;
1328                    }
1329                }
1330            }
1331        }
1332        List<TransitSection> ntsList = nt.getTransit().getTransitSectionList();
1333        if (!nt.isTransitReversed()) {
1334            for (int i = 0; i < ntsList.size(); i++) {
1335                if (ntsList.get(i).getSequenceNumber() > nSeq) {
1336                    if (ntsList.get(i).getSection() == aSec) {
1337                        // second train has found first train in its on coming
1338                        // Sections
1339                        return true;
1340                    }
1341                }
1342            }
1343        } else {
1344            for (int i = ntsList.size() - 1; i >= 0; i--) {
1345                if (ntsList.get(i).getSequenceNumber() < nSeq) {
1346                    if (ntsList.get(i).getSection() == aSec) {
1347                        // second train has found first train in its on coming
1348                        // Sections
1349                        return true;
1350                    }
1351                }
1352            }
1353        }
1354        return false;
1355    }
1356
1357    private boolean willTrainsCross(ActiveTrain at, ActiveTrain nt) {
1358        // returns true if both trains will eventually reach the others position
1359        TransitSection aCurrent = getCurrentTransitSection(at);
1360        int aSeq = (aCurrent == null) ? 0 : aCurrent.getSequenceNumber();
1361        Section aSec = (aCurrent == null) ? null : aCurrent.getSection();
1362        TransitSection nCurrent = getCurrentTransitSection(nt);
1363        int nSeq = (nCurrent == null) ? 0 : nCurrent.getSequenceNumber();
1364        Section nSec = (nCurrent == null) ? null : nCurrent.getSection();
1365        List<TransitSection> atsList = at.getTransit().getTransitSectionList();
1366        boolean found = false;
1367        if (!at.isTransitReversed()) {
1368            for (int i = 0; (i < atsList.size()) && (!found); i++) {
1369                if (atsList.get(i).getSequenceNumber() > aSeq) {
1370                    if (atsList.get(i).getSection() == nSec) {
1371                        // first train has reached second train position
1372                        found = true;
1373                    }
1374                }
1375            }
1376        } else {
1377            for (int i = atsList.size() - 1; (i >= 0) && (!found); i--) {
1378                if (atsList.get(i).getSequenceNumber() < aSeq) {
1379                    if (atsList.get(i).getSection() == nSec) {
1380                        // first train has reached second train position
1381                        found = true;
1382                    }
1383                }
1384            }
1385        }
1386        if (!found) {
1387            return false;
1388        }
1389        List<TransitSection> ntsList = nt.getTransit().getTransitSectionList();
1390        if (!nt.isTransitReversed()) {
1391            for (int i = 0; i < ntsList.size(); i++) {
1392                if (ntsList.get(i).getSequenceNumber() > nSeq) {
1393                    if (ntsList.get(i).getSection() == aSec) {
1394                        // second train has found first train in its on coming
1395                        // Sections
1396                        return true;
1397                    }
1398                }
1399            }
1400        } else {
1401            for (int i = ntsList.size() - 1; i >= 0; i--) {
1402                if (ntsList.get(i).getSequenceNumber() < nSeq) {
1403                    if (ntsList.get(i).getSection() == aSec) {
1404                        // second train has found first train in its on coming
1405                        // Sections
1406                        return true;
1407                    }
1408                }
1409            }
1410        }
1411        return false;
1412    }
1413
1414    private boolean areTrainsAdjacent(ActiveTrain at, ActiveTrain nt) {
1415        // returns 'false' if a different ActiveTrain has allocated track
1416        // between the
1417        // two trains, returns 'true' otherwise
1418        List<AllocatedSection> allocatedSections = _dispatcher.getAllocatedSectionsList();
1419        List<TransitSection> atsList = at.getTransit().getTransitSectionList();
1420        int aSeq = getCurrentSequenceNumber(at);
1421        Section nSec = getCurrentSection(nt);
1422        if (willTraverse(nSec, at, aSeq) != 0) {
1423            // at is moving toward nt
1424            if (!at.isTransitReversed()) {
1425                for (int i = 0; i < atsList.size(); i++) {
1426                    if (atsList.get(i).getSequenceNumber() > aSeq) {
1427                        Section tSec = atsList.get(i).getSection();
1428                        if (tSec == nSec) {
1429                            // reached second train position, no train in
1430                            // between
1431                            return true;
1432                        } else {
1433                            for (int j = 0; j < allocatedSections.size(); j++) {
1434                                if (allocatedSections.get(j).getSection() == tSec) {
1435                                    if ((allocatedSections.get(j).getActiveTrain() != at) &&
1436                                            (allocatedSections.get(j).getActiveTrain() != nt)) {
1437                                        // allocated to a third train, trains
1438                                        // not adjacent
1439                                        return false;
1440                                    }
1441                                }
1442                            }
1443                        }
1444                    }
1445                }
1446            } else {
1447                for (int i = atsList.size() - 1; i >= 0; i--) {
1448                    if (atsList.get(i).getSequenceNumber() < aSeq) {
1449                        Section tSec = atsList.get(i).getSection();
1450                        if (tSec == nSec) {
1451                            // reached second train position, no train in
1452                            // between
1453                            return true;
1454                        } else {
1455                            for (int j = 0; j < allocatedSections.size(); j++) {
1456                                if (allocatedSections.get(j).getSection() == tSec) {
1457                                    if ((allocatedSections.get(j).getActiveTrain() != at) &&
1458                                            (allocatedSections.get(j).getActiveTrain() != nt)) {
1459                                        // allocated to a third train, trains
1460                                        // not adjacent
1461                                        return false;
1462                                    }
1463                                }
1464                            }
1465                        }
1466                    }
1467                }
1468            }
1469        } else {
1470            // at is moving away from nt, so backtrack
1471            if (at.isTransitReversed()) {
1472                for (int i = 0; i < atsList.size(); i++) {
1473                    if (atsList.get(i).getSequenceNumber() > aSeq) {
1474                        Section tSec = atsList.get(i).getSection();
1475                        if (tSec == nSec) {
1476                            // reached second train position, no train in
1477                            // between
1478                            return true;
1479                        } else {
1480                            for (int j = 0; j < allocatedSections.size(); j++) {
1481                                if (allocatedSections.get(j).getSection() == tSec) {
1482                                    if ((allocatedSections.get(j).getActiveTrain() != at) &&
1483                                            (allocatedSections.get(j).getActiveTrain() != nt)) {
1484                                        // allocated to a third train, trains
1485                                        // not adjacent
1486                                        return false;
1487                                    }
1488                                }
1489                            }
1490                        }
1491                    }
1492                }
1493            } else {
1494                for (int i = atsList.size() - 1; i >= 0; i--) {
1495                    if (atsList.get(i).getSequenceNumber() < aSeq) {
1496                        Section tSec = atsList.get(i).getSection();
1497                        if (tSec == nSec) {
1498                            // reached second train position, no train in
1499                            // between
1500                            return true;
1501                        } else {
1502                            for (int j = 0; j < allocatedSections.size(); j++) {
1503                                if (allocatedSections.get(j).getSection() == tSec) {
1504                                    if ((allocatedSections.get(j).getActiveTrain() != at) &&
1505                                            (allocatedSections.get(j).getActiveTrain() != nt)) {
1506                                        // allocated to a third train, trains
1507                                        // not adjacent
1508                                        return false;
1509                                    }
1510                                }
1511                            }
1512                        }
1513                    }
1514                }
1515            }
1516        }
1517        return false;
1518    }
1519
1520    /*
1521     * Find the current position of the head of an ActiveTrain in its Transit:
1522     * the furthest occupied Section allocated to it.
1523     * This only looks, it does not change anything.
1524     * Returns null if the train has no occupied allocated Section.
1525     */
1526    private TransitSection getCurrentTransitSection(ActiveTrain at) {
1527        if (at == null) {
1528            log.error("null argument on entry to 'getCurrentTransitSection'");
1529            return null;
1530        }
1531        TransitSection current = null;
1532        List<TransitSection> tsList = at.getTransit().getTransitSectionList();
1533        if (!at.isTransitReversed()) {
1534            // find the highest numbered occupied section
1535            for (int i = 0; i < tsList.size(); i++) {
1536                if ((tsList.get(i).getSection().getOccupancy() == Section.OCCUPIED) &&
1537                        isSectionAllocatedToTrain(tsList.get(i).getSection(),
1538                                tsList.get(i).getSequenceNumber(), at)) {
1539                    current = tsList.get(i);
1540                }
1541            }
1542            if ((current != null) && (current.getSequenceNumber() == at.getTransit().getMaxSequence())) {
1543                if (at.getResetWhenDone()) {
1544                    // train may have passed the last Section during continuous
1545                    // running
1546                    boolean further = true;
1547                    for (int j = 0; (j < tsList.size()) && further; j++) {
1548                        if ((tsList.get(j).getSection().getOccupancy() == Section.OCCUPIED) &&
1549                                isSectionAllocatedToTrain(tsList.get(j).getSection(),
1550                                        tsList.get(j).getSequenceNumber(), at)) {
1551                            current = tsList.get(j);
1552                        } else {
1553                            further = false;
1554                        }
1555                    }
1556                }
1557            }
1558        } else {
1559            // transit is running in reverse
1560            for (int i = tsList.size() - 1; i >= 0; i--) {
1561                if ((tsList.get(i).getSection().getOccupancy() == Section.OCCUPIED) &&
1562                        isSectionAllocatedToTrain(tsList.get(i).getSection(),
1563                                tsList.get(i).getSequenceNumber(), at)) {
1564                    current = tsList.get(i);
1565                }
1566            }
1567        }
1568        if (current == null) {
1569            log.debug("{}: ActiveTrain has no occupied Section.", at.getTrainName());
1570        }
1571        return current;
1572    }
1573
1574    /*
1575     * Returns the sequence number of the current position of the head of the
1576     * ActiveTrain in its Transit, or 0 if not known.
1577     */
1578    private int getCurrentSequenceNumber(ActiveTrain at) {
1579        TransitSection current = getCurrentTransitSection(at);
1580        return (current == null) ? 0 : current.getSequenceNumber();
1581    }
1582
1583    /*
1584     * Returns the Section at the current position of the head of the
1585     * ActiveTrain in its Transit, or null if not known.
1586     */
1587    private Section getCurrentSection(ActiveTrain at) {
1588        TransitSection current = getCurrentTransitSection(at);
1589        return (current == null) ? null : current.getSection();
1590    }
1591
1592    private boolean isSectionAllocatedToTrain(Section s, int seq, ActiveTrain at) {
1593        if ((s == null) || (at == null)) {
1594            log.error("null argument to isSectionAllocatedToTrain");
1595            return false;
1596        }
1597        List<AllocatedSection> asList = at.getAllocatedSectionList();
1598        for (int i = 0; i < asList.size(); i++) {
1599            if ((asList.get(i).getSection() == s) && asList.get(i).getSequence() == seq) {
1600                return true;
1601            }
1602        }
1603        return false;
1604    }
1605
1606    private boolean waitingForStartTime(AllocationRequest ar) {
1607        if (ar != null) {
1608            ActiveTrain at = ar.getActiveTrain();
1609            if (at == null) {
1610                return false;
1611            }
1612            if ((!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY || at.reachedRestartPoint()) {
1613                return true;
1614            }
1615        }
1616        return false;
1617    }
1618
1619    private boolean isSignalHeldAtStartOfSection(AllocationRequest ar) {
1620
1621        if (ar == null) {
1622            return false;
1623        }
1624
1625        Section sec = ar.getSection();
1626        ActiveTrain mActiveTrain = ar.getActiveTrain();
1627
1628        if (sec == null || mActiveTrain == null) {
1629            return false;
1630        }
1631
1632        Section lastSec = mActiveTrain.getLastAllocatedSection();
1633
1634        if (lastSec == null) {
1635            return false;
1636        }
1637
1638        if (!sec.equals(mActiveTrain.getNextSectionToAllocate())) {
1639            log.error("[{}]Allocation request section does not match active train next section to allocate",mActiveTrain.getActiveTrainName());
1640            log.error("[{}]Section requested {}",mActiveTrain.getActiveTrainName(), sec.getDisplayName(USERSYS));
1641            if (mActiveTrain.getNextSectionToAllocate() != null) {
1642                log.error("[{}]Section expected {}",
1643                        mActiveTrain.getActiveTrainName(), mActiveTrain.getNextSectionToAllocate().getDisplayName(USERSYS));
1644            }
1645            if (mActiveTrain.getLastAllocatedSection() != null) {
1646                log.error("[{}]Last Section Allocated {}",
1647                        mActiveTrain.getActiveTrainName(), mActiveTrain.getLastAllocatedSection().getDisplayName(USERSYS));
1648            }
1649            return false;
1650        }
1651
1652        Block facingBlock;
1653        Block protectingBlock;
1654        EntryPoint protectingEP = sec.getEntryPointFromSection(lastSec, ar.getSectionDirection());
1655        EntryPoint facingEP =  lastSec.getExitPointToSection(sec, lastSec.getState());
1656        if (protectingEP == null || facingEP == null ) {
1657            return false;
1658        }
1659        protectingBlock = protectingEP.getBlock();
1660        facingBlock = facingEP.getBlock();
1661        if (protectingBlock == null || facingBlock == null) {
1662            return false;
1663        }
1664
1665        jmri.SignalMast sm = jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class)
1666                .getFacingSignalMast(facingBlock, protectingBlock);
1667        if (sm != null && sm.getHeld() && !_dispatcher.isMastHeldByDispatcher(sm, mActiveTrain)) {
1668            ar.setWaitingForSignalMast(sm);
1669            return true;
1670        }
1671        return false;
1672    }
1673
1674    private static final Logger log = LoggerFactory.getLogger(AutoAllocate.class);
1675
1676}