001package jmri.jmrit.dispatcher;
002
003import java.awt.BorderLayout;
004import java.awt.Container;
005import java.awt.FlowLayout;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.util.ArrayList;
009import java.util.Calendar;
010import java.util.HashSet;
011import java.util.List;
012
013import javax.annotation.Nonnull;
014import javax.swing.BoxLayout;
015import javax.swing.JButton;
016import javax.swing.JCheckBox;
017import javax.swing.JCheckBoxMenuItem;
018import javax.swing.JComboBox;
019import javax.swing.JLabel;
020import javax.swing.JMenuBar;
021import javax.swing.JPanel;
022import javax.swing.JPopupMenu;
023import javax.swing.JScrollPane;
024import javax.swing.JSeparator;
025import javax.swing.JTable;
026import javax.swing.JTextField;
027import javax.swing.table.TableColumn;
028
029import jmri.Block;
030import jmri.EntryPoint;
031import jmri.InstanceManager;
032import jmri.InstanceManagerAutoDefault;
033import jmri.JmriException;
034import jmri.Scale;
035import jmri.ScaleManager;
036import jmri.Section;
037import jmri.SectionManager;
038import jmri.Sensor;
039import jmri.SignalMast;
040import jmri.Timebase;
041import jmri.Transit;
042import jmri.TransitManager;
043import jmri.TransitSection;
044import jmri.Turnout;
045import jmri.NamedBean.DisplayOptions;
046import jmri.Transit.TransitType;
047import jmri.jmrit.dispatcher.TaskAllocateRelease.TaskAction;
048import jmri.jmrit.dispatcher.ActiveTrain.TrainDetection;
049import jmri.jmrit.display.EditorManager;
050import jmri.jmrit.display.layoutEditor.LayoutBlock;
051import jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools;
052import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
053import jmri.jmrit.display.layoutEditor.LayoutDoubleXOver;
054import jmri.jmrit.display.layoutEditor.LayoutEditor;
055import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState;
056import jmri.jmrit.display.layoutEditor.LayoutTurnout;
057import jmri.jmrit.display.layoutEditor.LevelXing;
058import jmri.jmrit.roster.Roster;
059import jmri.jmrit.roster.RosterEntry;
060import jmri.swing.JTablePersistenceManager;
061import jmri.util.JmriJFrame;
062import jmri.util.ThreadingUtil;
063import jmri.util.swing.JmriJOptionPane;
064import jmri.util.swing.JmriMouseAdapter;
065import jmri.util.swing.JmriMouseEvent;
066import jmri.util.swing.JmriMouseListener;
067import jmri.util.swing.XTableColumnModel;
068import jmri.util.table.ButtonEditor;
069import jmri.util.table.ButtonRenderer;
070
071/**
072 * Dispatcher functionality, working with Sections, Transits and ActiveTrain.
073 * <p>
074 * Dispatcher serves as the manager for ActiveTrains. All allocation of Sections
075 * to ActiveTrains is performed here.
076 * <p>
077 * Programming Note: Use the managed instance returned by
078 * {@link jmri.InstanceManager#getDefault(java.lang.Class)} to access the
079 * running Dispatcher.
080 * <p>
081 * Dispatcher listens to fast clock minutes to handle all ActiveTrain items tied
082 * to fast clock time.
083 * <p>
084 * Delayed start of manual and automatic trains is enforced by not allocating
085 * Sections for trains until the fast clock reaches the departure time.
086 * <p>
087 * This file is part of JMRI.
088 * <p>
089 * JMRI is open source software; you can redistribute it and/or modify it under
090 * the terms of version 2 of the GNU General Public License as published by the
091 * Free Software Foundation. See the "COPYING" file for a copy of this license.
092 * <p>
093 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
094 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
095 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
096 *
097 * @author Dave Duchamp Copyright (C) 2008-2011
098 */
099public class DispatcherFrame extends jmri.util.JmriJFrame implements InstanceManagerAutoDefault {
100
101    public static boolean dispatcherSystemSchedulingInOperation = false;    // required for Dispatcher System
102                                // to inhibit error message if train being scheduled is not in required station
103
104    public DispatcherFrame() {
105        super(true, true); // remember size a position.
106
107
108        editorManager = InstanceManager.getDefault(EditorManager.class);
109        initializeOptions();
110        openDispatcherWindow();
111        autoTurnouts = new AutoTurnouts(this);
112        InstanceManager.getDefault(jmri.SectionManager.class).initializeBlockingSensors();
113        getActiveTrainFrame();
114
115        if (fastClock == null) {
116            log.error("Failed to instantiate a fast clock when constructing Dispatcher");
117        } else {
118            minuteChangeListener = new java.beans.PropertyChangeListener() {
119                @Override
120                public void propertyChange(java.beans.PropertyChangeEvent e) {
121                    //process change to new minute
122                    newFastClockMinute();
123                }
124            };
125            fastClock.addMinuteChangeListener(minuteChangeListener);
126        }
127        jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).register(new DispatcherShutDownTask("Dispatch Shutdown"));
128    }
129
130    /***
131     *  reads thru all the traininfo files found in the dispatcher directory
132     *  and loads the ones flagged as "loadAtStartup"
133     */
134    public void loadAtStartup() {
135        log.debug("Loading saved trains flagged as LoadAtStartup");
136        TrainInfoFile tif = new TrainInfoFile();
137        String[] names = tif.getTrainInfoFileNames();
138        log.debug("initializing block paths early"); //TODO: figure out how to prevent the "regular" init
139        InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class)
140                .initializeLayoutBlockPaths();
141        if (names.length > 0) {
142            for (int i = 0; i < names.length; i++) {
143                TrainInfo info;
144                try {
145                    info = tif.readTrainInfo(names[i]);
146                } catch (java.io.IOException ioe) {
147                    log.error("IO Exception when reading train info file {}", names[i], ioe);
148                    continue;
149                } catch (org.jdom2.JDOMException jde) {
150                    log.error("JDOM Exception when reading train info file {}", names[i], jde);
151                    continue;
152                }
153                if (info != null && info.getLoadAtStartup()) {
154                    if (loadTrainFromTrainInfo(info) != 0) {
155                        /*
156                         * Error loading occurred The error will have already
157                         * been sent to the log and to screen
158                         */
159                    } else {
160                        /* give time to set up throttles etc */
161                        try {
162                            Thread.sleep(500);
163                        } catch (InterruptedException e) {
164                            log.warn("Sleep Interrupted in loading trains, likely being stopped", e);
165                            Thread.currentThread().interrupt();
166                        }
167                    }
168                }
169            }
170        }
171    }
172
173    @Override
174    public void dispose( ) {
175        super.dispose();
176        if (autoAllocate != null) {
177            autoAllocate.setAbort();
178        }
179    }
180
181    /**
182     * Constants for the override type
183     */
184    public static final String OVERRIDETYPE_NONE = "NONE";
185    public static final String OVERRIDETYPE_USER = "USER";
186    public static final String OVERRIDETYPE_DCCADDRESS = "DCCADDRESS";
187    public static final String OVERRIDETYPE_OPERATIONS = "OPERATIONS";
188    public static final String OVERRIDETYPE_ROSTER = "ROSTER";
189
190    /**
191     * Loads a train into the Dispatcher from a traininfo file
192     *
193     * @param traininfoFileName  the file name of a traininfo file.
194     * @return 0 good, -1 create failure, -2 -3 file errors, -9 bother.
195     */
196    public int loadTrainFromTrainInfo(String traininfoFileName) {
197        return loadTrainFromTrainInfo(traininfoFileName, "NONE", "");
198    }
199
200    /**
201     * Loads a train into the Dispatcher from a traininfo file, overriding
202     * dccaddress
203     *
204     * @param traininfoFileName  the file name of a traininfo file.
205     * @param overRideType  "NONE", "USER", "ROSTER" or "OPERATIONS"
206     * @param overRideValue  "" , dccAddress, RosterEntryName or Operations
207     *            trainname.
208     * @return 0 good, -1 create failure, -2 -3 file errors, -9 bother.
209     */
210    public int loadTrainFromTrainInfo(String traininfoFileName, String overRideType, String overRideValue) {
211        //read xml data from selected filename and move it into trainfo
212        try {
213            // maybe called from jthon protect our selves
214            TrainInfoFile tif = new TrainInfoFile();
215            TrainInfo info;
216            try {
217                info = tif.readTrainInfo(traininfoFileName);
218            } catch (java.io.FileNotFoundException fnfe) {
219                log.error("Train info file not found {}", traininfoFileName);
220                return -2;
221            } catch (java.io.IOException ioe) {
222                log.error("IO Exception when reading train info file {}", traininfoFileName, ioe);
223                return -2;
224            } catch (org.jdom2.JDOMException jde) {
225                log.error("JDOM Exception when reading train info file {}", traininfoFileName, jde);
226                return -3;
227            }
228            return loadTrainFromTrainInfo(info, overRideType, overRideValue);
229        } catch (RuntimeException ex) {
230            log.error("Unexpected, uncaught exception loading traininfofile [{}]", traininfoFileName, ex);
231            return -9;
232        }
233    }
234
235    /**
236     * Loads a train into the Dispatcher
237     *
238     * @param info  a completed TrainInfo class.
239     * @return 0 good, -1 failure
240     */
241    public int loadTrainFromTrainInfo(TrainInfo info) {
242        return loadTrainFromTrainInfo(info, "NONE", "");
243    }
244
245    /**
246     * Loads a train into the Dispatcher
247     * returns an integer. Messages written to log.
248     * @param info  a completed TrainInfo class.
249     * @param overRideType  "NONE", "USER", "ROSTER" or "OPERATIONS"
250     * @param overRideValue  "" , dccAddress, RosterEntryName or Operations
251     *            trainName.
252     * @return 0 good, -1 failure
253     */
254    public int loadTrainFromTrainInfo(TrainInfo info, String overRideType, String overRideValue) {
255        try {
256            loadTrainFromTrainInfoThrowsException( info, overRideType, overRideValue);
257            return 0;
258        } catch (IllegalArgumentException ex) {
259            return -1;
260        }
261    }
262
263    /**
264     * Loads a train into the Dispatcher
265     * throws IllegalArgumentException on errors
266     * @param info  a completed TrainInfo class.
267     * @param overRideType  "NONE", "USER", "ROSTER" or "OPERATIONS"
268     * @param overRideValue  "" , dccAddress, RosterEntryName or Operations
269     *            trainName.
270     * @throws IllegalArgumentException validation errors.
271     */
272    public void loadTrainFromTrainInfoThrowsException(TrainInfo info, String overRideType, String overRideValue)
273                throws IllegalArgumentException {
274
275        log.debug("loading train:{}, startblockname:{}, destinationBlockName:{}", info.getTrainName(),
276                info.getStartBlockName(), info.getDestinationBlockName());
277        // create a new Active Train
278
279        //set up defaults from traininfo
280        int tSource = 0;
281        if (info.getTrainFromRoster()) {
282            tSource = ActiveTrain.ROSTER;
283        } else if (info.getTrainFromTrains()) {
284            tSource = ActiveTrain.OPERATIONS;
285        } else if (info.getTrainFromUser()) {
286            tSource = ActiveTrain.USER;
287        }
288        String dccAddressToUse = info.getDccAddress();
289        String trainNameToUse = info.getTrainUserName();
290        String rosterIDToUse = info.getRosterId();
291        //process override
292        switch (overRideType) {
293            case "":
294            case OVERRIDETYPE_NONE:
295                break;
296            case OVERRIDETYPE_USER:
297            case OVERRIDETYPE_DCCADDRESS:
298                tSource = ActiveTrain.USER;
299                dccAddressToUse = overRideValue;
300                if (trainNameToUse.isEmpty()) {
301                    trainNameToUse = overRideValue;
302                }
303                break;
304            case OVERRIDETYPE_OPERATIONS:
305                tSource = ActiveTrain.OPERATIONS;
306                trainNameToUse = overRideValue;
307                break;
308            case OVERRIDETYPE_ROSTER:
309                tSource = ActiveTrain.ROSTER;
310                rosterIDToUse = overRideValue;
311                RosterEntry re = Roster.getDefault().getEntryForId(rosterIDToUse);
312                if (re != null) {
313                    dccAddressToUse = re.getDccAddress();
314                }
315                if (trainNameToUse.isEmpty()) {
316                    trainNameToUse = overRideValue;
317                }
318                break;
319            default:
320                /* just leave as in traininfo */
321        }
322        if (info.getDynamicTransit()) {
323            // attempt to build transit
324            Transit tmpTransit = createTemporaryTransit(InstanceManager.getDefault(jmri.BlockManager.class).getBlock(info.getStartBlockName()),
325                    InstanceManager.getDefault(jmri.BlockManager.class).getBlock(info.getDestinationBlockName()),
326                    InstanceManager.getDefault(jmri.BlockManager.class).getBlock(info.getViaBlockName()));
327            if (tmpTransit == null ) {
328                throw new IllegalArgumentException(Bundle.getMessage("Error51"));
329            }
330            info.setTransitName(tmpTransit.getDisplayName());
331            info.setTransitId(tmpTransit.getDisplayName());
332            info.setDestinationBlockSeq(tmpTransit.getMaxSequence());
333        }
334        if (tSource == 0) {
335            log.warn("Invalid Trains From [{}]",
336                    tSource);
337            throw new IllegalArgumentException(Bundle.getMessage("Error21"));
338        }
339        if (!isTrainFree(trainNameToUse)) {
340            log.warn("TrainName [{}] already in use",
341                    trainNameToUse);
342            throw new IllegalArgumentException(Bundle.getMessage("Error24",trainNameToUse));
343        }
344        ActiveTrain at = createActiveTrain(info.getTransitId(), trainNameToUse, tSource,
345                info.getStartBlockId(), info.getStartBlockSeq(), info.getDestinationBlockId(),
346                info.getDestinationBlockSeq(),
347                info.getAutoRun(), dccAddressToUse, info.getPriority(),
348                info.getResetWhenDone(), info.getReverseAtEnd(), true, null, info.getAllocationMethod());
349        if (at != null) {
350            if (tSource == ActiveTrain.ROSTER) {
351            RosterEntry re = Roster.getDefault().getEntryForId(rosterIDToUse);
352                if (re != null) {
353                    at.setRosterEntry(re);
354                    at.setDccAddress(re.getDccAddress());
355                } else {
356                    log.warn("Roster Entry '{}' not found, could not create ActiveTrain '{}'",
357                            trainNameToUse, info.getTrainName());
358                    throw new IllegalArgumentException(Bundle.getMessage("Error40",rosterIDToUse));
359                }
360            }
361            at.setTrainDetection(info.getTrainDetection());
362            at.setAllocateMethod(info.getAllocationMethod());
363            at.setDelayedStart(info.getDelayedStart()); //this is a code: NODELAY, TIMEDDELAY, SENSORDELAY
364            at.setDepartureTimeHr(info.getDepartureTimeHr()); // hour of day (fast-clock) to start this train
365            at.setDepartureTimeMin(info.getDepartureTimeMin()); //minute of hour to start this train
366            at.setDelayedRestart(info.getDelayedRestart()); //this is a code: NODELAY, TIMEDDELAY, SENSORDELAY
367            at.setRestartDelay(info.getRestartDelayMin()); //this is number of minutes to delay between runs
368            at.setDelaySensor(info.getDelaySensor());
369            at.setResetStartSensor(info.getResetStartSensor());
370            if ((isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin()) &&
371                    info.getDelayedStart() != ActiveTrain.SENSORDELAY) ||
372                    info.getDelayedStart() == ActiveTrain.NODELAY) {
373                at.setStarted();
374            }
375            at.setRestartSensor(info.getRestartSensor());
376            at.setResetRestartSensor(info.getResetRestartSensor());
377            at.setReverseDelayRestart(info.getReverseDelayedRestart());
378            at.setReverseRestartDelay(info.getReverseRestartDelayMin());
379            at.setReverseDelaySensor(info.getReverseRestartSensor());
380            at.setReverseResetRestartSensor(info.getReverseResetRestartSensor());
381            at.setTrainType(info.getTrainType());
382            at.setTerminateWhenDone(info.getTerminateWhenDone());
383            at.setNextTrain(info.getNextTrain());
384            if (info.getAutoRun()) {
385                AutoActiveTrain aat = new AutoActiveTrain(at);
386                aat.setSpeedFactor(info.getSpeedFactor());
387                aat.setMaxSpeed(info.getMaxSpeed());
388                aat.setMinReliableOperatingSpeed(info.getMinReliableOperatingSpeed());
389                aat.setRampRate(AutoActiveTrain.getRampRateFromName(info.getRampRate()));
390                aat.setRunInReverse(info.getRunInReverse());
391                aat.setSoundDecoder(info.getSoundDecoder());
392                aat.setMaxTrainLength(info.getMaxTrainLengthScaleMeters(),getScale().getScaleFactor());
393                aat.setStopBySpeedProfile(info.getStopBySpeedProfile());
394                aat.setStopBySpeedProfileAdjust(info.getStopBySpeedProfileAdjust());
395                aat.setUseSpeedProfile(info.getUseSpeedProfile());
396                aat.setFunctionLight(info.getFNumberLight());
397                getAutoTrainsFrame().addAutoActiveTrain(aat);
398                if (!aat.initialize()) {
399                    log.error("ERROR initializing autorunning for train {}", at.getTrainName());
400                    throw new IllegalArgumentException(Bundle.getMessage("Error27",at.getTrainName()));
401                }
402            }
403            // we can go no further without attaching this.
404            at.setDispatcher(this);
405            allocateNewActiveTrain(at);
406            newTrainDone(at);
407
408        } else {
409            log.warn("failed to create Active Train '{}'", info.getTrainName());
410            throw new IllegalArgumentException(Bundle.getMessage("Error48",info.getTrainName()));
411        }
412    }
413
414    /**
415     * Get a list of {@link jmri.jmrit.display.layoutEditor.LayoutBlock} that represent a route
416     * @param start First Block
417     * @param dest Last Block
418     * @param via Next Block
419     * @return null if a route cannot be found, else the list.
420     */
421    protected List<LayoutBlock> getAdHocRoute(Block start, Block dest, Block via) {
422        LayoutBlockManager lBM = jmri.InstanceManager.getDefault(LayoutBlockManager.class);
423        LayoutBlock lbStart = lBM.getByUserName(start.getDisplayName(DisplayOptions.USERNAME));
424        LayoutBlock lbEnd = lBM.getByUserName(dest.getDisplayName(DisplayOptions.USERNAME));
425        LayoutBlock lbVia =  lBM.getByUserName(via.getDisplayName(DisplayOptions.USERNAME));
426        List<LayoutBlock> blocks = new ArrayList<LayoutBlock>();
427        try {
428            boolean result = lBM.getLayoutBlockConnectivityTools().checkValidDest(
429                    lbStart, lbVia, lbEnd, blocks, LayoutBlockConnectivityTools.Routing.NONE);
430            if (!result) {
431                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error51"),
432                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
433            }
434            blocks = lBM.getLayoutBlockConnectivityTools().getLayoutBlocks(
435                    lbStart, lbEnd, lbVia, false, LayoutBlockConnectivityTools.Routing.NONE);
436        } catch (JmriException JEx) {
437            log.error("Finding route {}",JEx.getMessage());
438            return null;
439        }
440        return blocks;
441    }
442
443    /**
444     * Converts a list of {@link jmri.jmrit.display.layoutEditor.LayoutBlock} that represent a route to a transit.
445     * @param start First Block
446     * @param dest Last Block
447     * @param via Next Block
448     * @return null if the transit is valid. Else an AdHoc transit
449     */
450    protected Transit createTemporaryTransit(Block start, Block dest, Block via) {
451        List<LayoutBlock> blocks =  getAdHocRoute( start,  dest,  via);
452        if (blocks == null) {
453            return null;
454        }
455        SectionManager sm = jmri.InstanceManager.getDefault(SectionManager.class);
456        Transit tempTransit = null;
457        int wNo = 0;
458        String baseTransitName = "-" + start.getDisplayName() + "-" + dest.getDisplayName();
459        while (tempTransit == null && wNo < 99) {
460            wNo++;
461            try {
462                tempTransit = transitManager.createNewTransit("#" + Integer.toString(wNo) + baseTransitName);
463            } catch (Exception ex) {
464                log.trace("Transit [{}} already used, try next.", "#" + Integer.toString(wNo) + baseTransitName);
465            }
466        }
467        if (tempTransit == null) {
468            log.error("Limit of Dynamic Transits for [{}] has been exceeded!", baseTransitName);
469            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("DynamicTransitsExceeded",baseTransitName),
470                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
471            return null;
472        }
473        tempTransit.setTransitType(TransitType.DYNAMICADHOC);
474        int seq = 1;
475        TransitSection prevTs = null;
476        TransitSection curTs = null;
477        for (LayoutBlock lB : blocks) {
478            Block b = lB.getBlock();
479            Section currentSection = sm.createNewSection(tempTransit.getUserName() + Integer.toString(seq) + "-" + b.getDisplayName());
480            currentSection.setSectionType(Section.DYNAMICADHOC);
481            currentSection.addBlock(b);
482            if (curTs == null) {
483                //first block shove it in.
484                curTs = new TransitSection(currentSection, seq, Section.FORWARD);
485            } else {
486                prevTs = curTs;
487                EntryPoint fEp = new EntryPoint(prevTs.getSection().getBlockBySequenceNumber(0),b,"up");
488                fEp.setTypeReverse();
489                prevTs.getSection().addToReverseList(fEp);
490                EntryPoint rEp = new EntryPoint(b,prevTs.getSection().getBlockBySequenceNumber(0),"down");
491                rEp.setTypeForward();
492                currentSection.addToForwardList(rEp);
493                curTs = new TransitSection(currentSection, seq, Section.FORWARD);
494            }
495            curTs.setTemporary(true);
496            tempTransit.addTransitSection(curTs);
497            seq++;
498        }
499        return tempTransit;
500    }
501
502    protected enum TrainsFrom {
503        TRAINSFROMROSTER,
504        TRAINSFROMOPS,
505        TRAINSFROMUSER,
506        TRAINSFROMSETLATER
507    }
508
509    // Dispatcher options (saved to disk if user requests, and restored if present)
510    private LayoutEditor _LE = null;
511    public static final int SIGNALHEAD = 0x00;
512    public static final int SIGNALMAST = 0x01;
513    public static final int SECTIONSALLOCATED = 2;
514    private int _SignalType = SIGNALHEAD;
515    private String _StoppingSpeedName = "RestrictedSlow";
516    private boolean _UseConnectivity = false;
517    private boolean _HasOccupancyDetection = false; // "true" if blocks have occupancy detection
518    private boolean _SetSSLDirectionalSensors = true;
519    private TrainsFrom _TrainsFrom = TrainsFrom.TRAINSFROMROSTER;
520    private boolean _AutoAllocate = false;
521    private boolean _AutoRelease = false;
522    private boolean _AutoTurnouts = false;
523    private boolean _TrustKnownTurnouts = false;
524    private boolean _UseOccupiedTrackSpeed = false;
525    private boolean _useTurnoutConnectionDelay = false;
526    private boolean _ShortActiveTrainNames = false;
527    private boolean _ShortNameInBlock = true;
528    private boolean _RosterEntryInBlock = false;
529    private boolean _ExtraColorForAllocated = true;
530    private boolean _NameInAllocatedBlock = false;
531    private boolean _UseScaleMeters = false;  // "true" if scale meters, "false" for scale feet
532    private Scale _LayoutScale = ScaleManager.getScale("HO");
533    private boolean _SupportVSDecoder = false;
534    private int _MinThrottleInterval = 100; //default time (in ms) between consecutive throttle commands
535    private int _FullRampTime = 10000; //default time (in ms) for RAMP_FAST to go from 0% to 100%
536    private float maximumLineSpeed = 0.0f;
537
538    // operational instance variables
539    private Thread autoAllocateThread ;
540    private static final jmri.NamedBean.DisplayOptions USERSYS = jmri.NamedBean.DisplayOptions.USERNAME_SYSTEMNAME;
541    private final List<ActiveTrain> activeTrainsList = new ArrayList<>();  // list of ActiveTrain objects
542    private final List<java.beans.PropertyChangeListener> _atListeners
543            = new ArrayList<>();
544    private final List<ActiveTrain> delayedTrains = new ArrayList<>();  // list of delayed Active Trains
545    private final List<ActiveTrain> restartingTrainsList = new ArrayList<>();  // list of Active Trains with restart requests
546    private final TransitManager transitManager = InstanceManager.getDefault(jmri.TransitManager.class);
547    private final List<AllocationRequest> allocationRequests = new ArrayList<>();  // List of AllocatedRequest objects
548    protected final List<AllocatedSection> allocatedSections = new ArrayList<>();  // List of AllocatedSection objects
549    private boolean optionsRead = false;
550    private AutoTurnouts autoTurnouts = null;
551    private AutoAllocate autoAllocate = null;
552    private OptionsMenu optionsMenu = null;
553    private ActivateTrainFrame atFrame = null;
554    private EditorManager editorManager = null;
555
556    public ActivateTrainFrame getActiveTrainFrame() {
557        if (atFrame == null) {
558            atFrame = new ActivateTrainFrame(this);
559        }
560        return atFrame;
561    }
562    private boolean newTrainActive = false;
563
564    public boolean getNewTrainActive() {
565        return newTrainActive;
566    }
567
568    public void setNewTrainActive(boolean boo) {
569        newTrainActive = boo;
570    }
571    private AutoTrainsFrame _autoTrainsFrame = null;
572    private final Timebase fastClock = InstanceManager.getNullableDefault(jmri.Timebase.class);
573    private final Sensor fastClockSensor = InstanceManager.sensorManagerInstance().provideSensor("ISCLOCKRUNNING");
574    private transient java.beans.PropertyChangeListener minuteChangeListener = null;
575
576    // dispatcher window variables
577    protected JmriJFrame dispatcherFrame = null;
578    private Container contentPane = null;
579    private ActiveTrainsTableModel activeTrainsTableModel = null;
580    private JButton addTrainButton = null;
581    private JButton terminateTrainButton = null;
582    private JButton cancelRestartButton = null;
583    private JButton allocateExtraButton = null;
584    private JCheckBox autoReleaseBox = null;
585    private JCheckBox autoAllocateBox = null;
586    private AllocationRequestTableModel allocationRequestTableModel = null;
587    private AllocatedSectionTableModel allocatedSectionTableModel = null;
588
589    void initializeOptions() {
590        if (optionsRead) {
591            return;
592        }
593        optionsRead = true;
594        try {
595            InstanceManager.getDefault(OptionsFile.class).readDispatcherOptions(this);
596        } catch (org.jdom2.JDOMException jde) {
597            log.error("JDOM Exception when retrieving dispatcher options", jde);
598        } catch (java.io.IOException ioe) {
599            log.error("I/O Exception when retrieving dispatcher options", ioe);
600        }
601    }
602
603    void openDispatcherWindow() {
604        if (dispatcherFrame == null) {
605            if (editorManager.getAll(LayoutEditor.class).size() > 0 && autoAllocate == null) {
606                autoAllocate = new AutoAllocate(this, allocationRequests);
607                autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator ");
608                autoAllocateThread.start();
609            }
610            dispatcherFrame = this;
611            dispatcherFrame.setTitle(Bundle.getMessage("TitleDispatcher"));
612            JMenuBar menuBar = new JMenuBar();
613            optionsMenu = new OptionsMenu(this);
614            menuBar.add(optionsMenu);
615            setJMenuBar(menuBar);
616            dispatcherFrame.addHelpMenu("package.jmri.jmrit.dispatcher.Dispatcher", true);
617            contentPane = dispatcherFrame.getContentPane();
618            contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
619
620            // set up active trains table
621            JPanel p11 = new JPanel();
622            p11.setLayout(new FlowLayout());
623            p11.add(new JLabel(Bundle.getMessage("ActiveTrainTableTitle")));
624            contentPane.add(p11);
625            JPanel p12 = new JPanel();
626            p12.setLayout(new BorderLayout());
627             activeTrainsTableModel = new ActiveTrainsTableModel();
628            JTable activeTrainsTable = new JTable(activeTrainsTableModel);
629            activeTrainsTable.setName(this.getClass().getName().concat(":activeTrainsTableModel"));
630            activeTrainsTable.setRowSelectionAllowed(false);
631            activeTrainsTable.setPreferredScrollableViewportSize(new java.awt.Dimension(950, 160));
632            activeTrainsTable.setColumnModel(new XTableColumnModel());
633            activeTrainsTable.createDefaultColumnsFromModel();
634            XTableColumnModel activeTrainsColumnModel = (XTableColumnModel)activeTrainsTable.getColumnModel();
635            // Button Columns
636            TableColumn allocateButtonColumn = activeTrainsColumnModel.getColumn(ActiveTrainsTableModel.ALLOCATEBUTTON_COLUMN);
637            allocateButtonColumn.setCellEditor(new ButtonEditor(new JButton()));
638            allocateButtonColumn.setResizable(true);
639            ButtonRenderer buttonRenderer = new ButtonRenderer();
640            activeTrainsTable.setDefaultRenderer(JButton.class, buttonRenderer);
641            JButton sampleButton = new JButton("WWW..."); //by default 3 letters and elipse
642            activeTrainsTable.setRowHeight(sampleButton.getPreferredSize().height);
643            allocateButtonColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
644            TableColumn terminateTrainButtonColumn = activeTrainsColumnModel.getColumn(ActiveTrainsTableModel.TERMINATEBUTTON_COLUMN);
645            terminateTrainButtonColumn.setCellEditor(new ButtonEditor(new JButton()));
646            terminateTrainButtonColumn.setResizable(true);
647            buttonRenderer = new ButtonRenderer();
648            activeTrainsTable.setDefaultRenderer(JButton.class, buttonRenderer);
649            sampleButton = new JButton("WWW...");
650            activeTrainsTable.setRowHeight(sampleButton.getPreferredSize().height);
651            terminateTrainButtonColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
652
653            addMouseListenerToHeader(activeTrainsTable);
654
655            activeTrainsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
656            JScrollPane activeTrainsTableScrollPane = new JScrollPane(activeTrainsTable);
657            p12.add(activeTrainsTableScrollPane, BorderLayout.CENTER);
658            contentPane.add(p12);
659
660            JPanel p13 = new JPanel();
661            p13.setLayout(new FlowLayout());
662            p13.add(addTrainButton = new JButton(Bundle.getMessage("InitiateTrain") + "..."));
663            addTrainButton.addActionListener(new ActionListener() {
664                @Override
665                public void actionPerformed(ActionEvent e) {
666                    if (!newTrainActive) {
667                        getActiveTrainFrame().initiateTrain(e);
668                        newTrainActive = true;
669                    } else {
670                        getActiveTrainFrame().showActivateFrame();
671                    }
672                }
673            });
674            addTrainButton.setToolTipText(Bundle.getMessage("InitiateTrainButtonHint"));
675            p13.add(new JLabel("   "));
676            p13.add(new JLabel("   "));
677            p13.add(allocateExtraButton = new JButton(Bundle.getMessage("AllocateExtra") + "..."));
678            allocateExtraButton.addActionListener(new ActionListener() {
679                @Override
680                public void actionPerformed(ActionEvent e) {
681                    allocateExtraSection(e);
682                }
683            });
684            allocateExtraButton.setToolTipText(Bundle.getMessage("AllocateExtraButtonHint"));
685            p13.add(new JLabel("   "));
686            p13.add(cancelRestartButton = new JButton(Bundle.getMessage("CancelRestart") + "..."));
687            cancelRestartButton.addActionListener(new ActionListener() {
688                @Override
689                public void actionPerformed(ActionEvent e) {
690                    if (!newTrainActive) {
691                        cancelRestart(e);
692                    } else if (restartingTrainsList.size() > 0) {
693                        getActiveTrainFrame().showActivateFrame();
694                        JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Message2"),
695                                Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
696                    } else {
697                        getActiveTrainFrame().showActivateFrame();
698                    }
699                }
700            });
701            cancelRestartButton.setToolTipText(Bundle.getMessage("CancelRestartButtonHint"));
702            p13.add(new JLabel("   "));
703            p13.add(terminateTrainButton = new JButton(Bundle.getMessage("TerminateTrain"))); // immediate if there is only one train
704            terminateTrainButton.addActionListener(new ActionListener() {
705                @Override
706                public void actionPerformed(ActionEvent e) {
707                    if (!newTrainActive) {
708                        terminateTrain(e);
709                    } else if (activeTrainsList.size() > 0) {
710                        getActiveTrainFrame().showActivateFrame();
711                        JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Message1"),
712                                Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
713                    } else {
714                        getActiveTrainFrame().showActivateFrame();
715                    }
716                }
717            });
718            terminateTrainButton.setToolTipText(Bundle.getMessage("TerminateTrainButtonHint"));
719            contentPane.add(p13);
720
721            // Reset and then persist the table's ui state
722            JTablePersistenceManager tpm = InstanceManager.getNullableDefault(JTablePersistenceManager.class);
723            if (tpm != null) {
724                tpm.resetState(activeTrainsTable);
725                tpm.persist(activeTrainsTable);
726            }
727
728            // set up pending allocations table
729            contentPane.add(new JSeparator());
730            JPanel p21 = new JPanel();
731            p21.setLayout(new FlowLayout());
732            p21.add(new JLabel(Bundle.getMessage("RequestedAllocationsTableTitle")));
733            contentPane.add(p21);
734            JPanel p22 = new JPanel();
735            p22.setLayout(new BorderLayout());
736            allocationRequestTableModel = new AllocationRequestTableModel();
737            JTable allocationRequestTable = new JTable(allocationRequestTableModel);
738            allocationRequestTable.setName(this.getClass().getName().concat(":allocationRequestTable"));
739            allocationRequestTable.setRowSelectionAllowed(false);
740            allocationRequestTable.setPreferredScrollableViewportSize(new java.awt.Dimension(950, 100));
741            allocationRequestTable.setColumnModel(new XTableColumnModel());
742            allocationRequestTable.createDefaultColumnsFromModel();
743            XTableColumnModel allocationRequestColumnModel = (XTableColumnModel)allocationRequestTable.getColumnModel();
744            // Button Columns
745            TableColumn allocateColumn = allocationRequestColumnModel.getColumn(AllocationRequestTableModel.ALLOCATEBUTTON_COLUMN);
746            allocateColumn.setCellEditor(new ButtonEditor(new JButton()));
747            allocateColumn.setResizable(true);
748            buttonRenderer = new ButtonRenderer();
749            allocationRequestTable.setDefaultRenderer(JButton.class, buttonRenderer);
750            sampleButton = new JButton(Bundle.getMessage("AllocateButton"));
751            allocationRequestTable.setRowHeight(sampleButton.getPreferredSize().height);
752            allocateColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
753            TableColumn cancelButtonColumn = allocationRequestColumnModel.getColumn(AllocationRequestTableModel.CANCELBUTTON_COLUMN);
754            cancelButtonColumn.setCellEditor(new ButtonEditor(new JButton()));
755            cancelButtonColumn.setResizable(true);
756            cancelButtonColumn.setPreferredWidth((sampleButton.getPreferredSize().width) + 2);
757            // add listener
758            addMouseListenerToHeader(allocationRequestTable);
759            allocationRequestTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
760            JScrollPane allocationRequestTableScrollPane = new JScrollPane(allocationRequestTable);
761            p22.add(allocationRequestTableScrollPane, BorderLayout.CENTER);
762            contentPane.add(p22);
763            if (tpm != null) {
764                tpm.resetState(allocationRequestTable);
765                tpm.persist(allocationRequestTable);
766            }
767
768            // set up allocated sections table
769            contentPane.add(new JSeparator());
770            JPanel p30 = new JPanel();
771            p30.setLayout(new FlowLayout());
772            p30.add(new JLabel(Bundle.getMessage("AllocatedSectionsTitle") + "    "));
773            autoAllocateBox = new JCheckBox(Bundle.getMessage("AutoDispatchItem"));
774            p30.add(autoAllocateBox);
775            autoAllocateBox.setToolTipText(Bundle.getMessage("AutoAllocateBoxHint"));
776            autoAllocateBox.addActionListener(new ActionListener() {
777                @Override
778                public void actionPerformed(ActionEvent e) {
779                    handleAutoAllocateChanged(e);
780                }
781            });
782            autoAllocateBox.setSelected(_AutoAllocate);
783            autoReleaseBox = new JCheckBox(Bundle.getMessage("AutoReleaseBoxLabel"));
784            p30.add(autoReleaseBox);
785            autoReleaseBox.setToolTipText(Bundle.getMessage("AutoReleaseBoxHint"));
786            autoReleaseBox.addActionListener(new ActionListener() {
787                @Override
788                public void actionPerformed(ActionEvent e) {
789                    handleAutoReleaseChanged(e);
790                }
791            });
792            autoReleaseBox.setSelected(_AutoAllocate); // initialize autoRelease to match autoAllocate
793            _AutoRelease = _AutoAllocate;
794            contentPane.add(p30);
795            JPanel p31 = new JPanel();
796            p31.setLayout(new BorderLayout());
797            allocatedSectionTableModel = new AllocatedSectionTableModel();
798            JTable allocatedSectionTable = new JTable(allocatedSectionTableModel);
799            allocatedSectionTable.setName(this.getClass().getName().concat(":allocatedSectionTable"));
800            allocatedSectionTable.setRowSelectionAllowed(false);
801            allocatedSectionTable.setPreferredScrollableViewportSize(new java.awt.Dimension(730, 200));
802            allocatedSectionTable.setColumnModel(new XTableColumnModel());
803            allocatedSectionTable.createDefaultColumnsFromModel();
804            XTableColumnModel allocatedSectionColumnModel = (XTableColumnModel)allocatedSectionTable.getColumnModel();
805            // Button columns
806            TableColumn releaseColumn = allocatedSectionColumnModel.getColumn(AllocatedSectionTableModel.RELEASEBUTTON_COLUMN);
807            releaseColumn.setCellEditor(new ButtonEditor(new JButton()));
808            releaseColumn.setResizable(true);
809            allocatedSectionTable.setDefaultRenderer(JButton.class, buttonRenderer);
810            JButton sampleAButton = new JButton(Bundle.getMessage("ReleaseButton"));
811            allocatedSectionTable.setRowHeight(sampleAButton.getPreferredSize().height);
812            releaseColumn.setPreferredWidth((sampleAButton.getPreferredSize().width) + 2);
813            JScrollPane allocatedSectionTableScrollPane = new JScrollPane(allocatedSectionTable);
814            p31.add(allocatedSectionTableScrollPane, BorderLayout.CENTER);
815            // add listener
816            addMouseListenerToHeader(allocatedSectionTable);
817            allocatedSectionTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
818            contentPane.add(p31);
819            if (tpm != null) {
820                tpm.resetState(allocatedSectionTable);
821                tpm.persist(allocatedSectionTable);
822            }
823        }
824        ThreadingUtil.runOnGUI( () -> {
825            dispatcherFrame.pack();
826            dispatcherFrame.setVisible(true);
827        });
828    }
829
830    void releaseAllocatedSectionFromTable(int index) {
831        AllocatedSection as = allocatedSections.get(index);
832        releaseAllocatedSection(as, false);
833    }
834
835    // allocate extra window variables
836    private JmriJFrame extraFrame = null;
837    private Container extraPane = null;
838    private final JComboBox<String> atSelectBox = new JComboBox<>();
839    private final JComboBox<String> extraBox = new JComboBox<>();
840    private final List<Section> extraBoxList = new ArrayList<>();
841    private int atSelectedIndex = -1;
842
843    public void allocateExtraSection(ActionEvent e, ActiveTrain at) {
844        allocateExtraSection(e);
845        if (_ShortActiveTrainNames) {
846            atSelectBox.setSelectedItem(at.getTrainName());
847        } else {
848            atSelectBox.setSelectedItem(at.getActiveTrainName());
849        }
850    }
851
852    // allocate an extra Section to an Active Train
853    private void allocateExtraSection(ActionEvent e) {
854        if (extraFrame == null) {
855            extraFrame = new JmriJFrame(Bundle.getMessage("ExtraTitle"));
856            extraFrame.addHelpMenu("package.jmri.jmrit.dispatcher.AllocateExtra", true);
857            extraPane = extraFrame.getContentPane();
858            extraPane.setLayout(new BoxLayout(extraFrame.getContentPane(), BoxLayout.Y_AXIS));
859            JPanel p1 = new JPanel();
860            p1.setLayout(new FlowLayout());
861            p1.add(new JLabel(Bundle.getMessage("ActiveColumnTitle") + ":"));
862            p1.add(atSelectBox);
863            atSelectBox.addActionListener(new ActionListener() {
864                @Override
865                public void actionPerformed(ActionEvent e) {
866                    handleATSelectionChanged(e);
867                }
868            });
869            atSelectBox.setToolTipText(Bundle.getMessage("ATBoxHint"));
870            extraPane.add(p1);
871            JPanel p2 = new JPanel();
872            p2.setLayout(new FlowLayout());
873            p2.add(new JLabel(Bundle.getMessage("ExtraBoxLabel") + ":"));
874            p2.add(extraBox);
875            extraBox.setToolTipText(Bundle.getMessage("ExtraBoxHint"));
876            extraPane.add(p2);
877            JPanel p7 = new JPanel();
878            p7.setLayout(new FlowLayout());
879            JButton cancelButton = null;
880            p7.add(cancelButton = new JButton(Bundle.getMessage("ButtonCancel")));
881            cancelButton.addActionListener(new ActionListener() {
882                @Override
883                public void actionPerformed(ActionEvent e) {
884                    cancelExtraRequested(e);
885                }
886            });
887            cancelButton.setToolTipText(Bundle.getMessage("CancelExtraHint"));
888            p7.add(new JLabel("    "));
889            JButton aExtraButton = null;
890            p7.add(aExtraButton = new JButton(Bundle.getMessage("AllocateButton")));
891            aExtraButton.addActionListener(new ActionListener() {
892                @Override
893                public void actionPerformed(ActionEvent e) {
894                    addExtraRequested(e);
895                }
896            });
897            aExtraButton.setToolTipText(Bundle.getMessage("AllocateButtonHint"));
898            extraPane.add(p7);
899        }
900        initializeATComboBox();
901        initializeExtraComboBox();
902        extraFrame.pack();
903        extraFrame.setVisible(true);
904    }
905
906    private void handleAutoAllocateChanged(ActionEvent e) {
907        setAutoAllocate(autoAllocateBox.isSelected());
908        stopStartAutoAllocateRelease();
909        if (autoAllocateBox != null) {
910            autoAllocateBox.setSelected(_AutoAllocate);
911        }
912
913        if (optionsMenu != null) {
914            optionsMenu.initializeMenu();
915        }
916        if (_AutoAllocate ) {
917            queueScanOfAllocationRequests();
918        }
919    }
920
921    /*
922     * Queue a scan
923     */
924    protected void queueScanOfAllocationRequests() {
925        if (_AutoAllocate) {
926            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.SCAN_REQUESTS));
927        }
928    }
929
930    /*
931     * Queue a release all reserved sections for a train.
932     */
933    protected void queueReleaseOfReservedSections(String trainName) {
934        if (_AutoRelease || _AutoAllocate) {
935            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_RESERVED, trainName));
936        }
937    }
938
939    /*
940     * Queue a release all reserved sections for a train.
941     */
942    protected void queueAllocate(AllocationRequest aRequest) {
943        if (_AutoRelease || _AutoAllocate) {
944            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.ALLOCATE_IMMEDIATE, aRequest));
945        }
946    }
947
948    /*
949     * Wait for the queue to empty
950     */
951    protected void queueWaitForEmpty() {
952        if (_AutoAllocate) {
953            while (!autoAllocate.allRequestsDone()) {
954                try {
955                    Thread.sleep(10);
956                } catch (InterruptedException iex) {
957                    // we closing do done
958                    return;
959                }
960            }
961        }
962        return;
963    }
964
965    /*
966     * Queue a general release of completed sections
967     */
968    protected void queueReleaseOfCompletedAllocations() {
969        if (_AutoRelease) {
970            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.AUTO_RELEASE));
971        }
972    }
973
974    /*
975     * autorelease option has been changed
976     */
977    private void handleAutoReleaseChanged(ActionEvent e) {
978        _AutoRelease = autoReleaseBox.isSelected();
979        stopStartAutoAllocateRelease();
980        if (autoReleaseBox != null) {
981            autoReleaseBox.setSelected(_AutoRelease);
982        }
983        if (_AutoRelease) {
984            queueReleaseOfCompletedAllocations();
985        }
986    }
987
988    /* Check trainName not in use */
989    protected boolean isTrainFree(String rName) {
990        for (int j = 0; j < getActiveTrainsList().size(); j++) {
991            ActiveTrain at = getActiveTrainsList().get(j);
992            if (rName.equals(at.getTrainName())) {
993                return false;
994            }
995        }
996        return true;
997    }
998
999    /**
1000     * Check DCC not already in use
1001     * @param addr DCC address.
1002     * @return true / false
1003     */
1004    public boolean isAddressFree(int addr) {
1005        for (int j = 0; j < activeTrainsList.size(); j++) {
1006            ActiveTrain at = activeTrainsList.get(j);
1007            if (addr == Integer.parseInt(at.getDccAddress())) {
1008                return false;
1009            }
1010        }
1011        return true;
1012    }
1013
1014    private void handleATSelectionChanged(ActionEvent e) {
1015        atSelectedIndex = atSelectBox.getSelectedIndex();
1016        initializeExtraComboBox();
1017        extraFrame.pack();
1018        extraFrame.setVisible(true);
1019    }
1020
1021    private void initializeATComboBox() {
1022        atSelectedIndex = -1;
1023        atSelectBox.removeAllItems();
1024        for (int i = 0; i < activeTrainsList.size(); i++) {
1025            ActiveTrain at = activeTrainsList.get(i);
1026            if (_ShortActiveTrainNames) {
1027                atSelectBox.addItem(at.getTrainName());
1028            } else {
1029                atSelectBox.addItem(at.getActiveTrainName());
1030            }
1031        }
1032        if (activeTrainsList.size() > 0) {
1033            atSelectBox.setSelectedIndex(0);
1034            atSelectedIndex = 0;
1035        }
1036    }
1037
1038    private void initializeExtraComboBox() {
1039        extraBox.removeAllItems();
1040        extraBoxList.clear();
1041        if (atSelectedIndex < 0) {
1042            return;
1043        }
1044        ActiveTrain at = activeTrainsList.get(atSelectedIndex);
1045        //Transit t = at.getTransit();
1046        List<AllocatedSection> allocatedSectionList = at.getAllocatedSectionList();
1047        for (Section s : InstanceManager.getDefault(jmri.SectionManager.class).getNamedBeanSet()) {
1048            if (s.getState() == Section.FREE) {
1049                // not already allocated, check connectivity to this train's allocated sections
1050                boolean connected = false;
1051                for (int k = 0; k < allocatedSectionList.size(); k++) {
1052                    if (connected(s, allocatedSectionList.get(k).getSection())) {
1053                        connected = true;
1054                    }
1055                }
1056                if (connected) {
1057                    // add to the combo box, not allocated and connected to allocated
1058                    extraBoxList.add(s);
1059                    extraBox.addItem(getSectionName(s));
1060                }
1061            }
1062        }
1063        if (extraBoxList.size() > 0) {
1064            extraBox.setSelectedIndex(0);
1065        }
1066    }
1067
1068    private boolean connected(Section s1, Section s2) {
1069        if ((s1 != null) && (s2 != null)) {
1070            List<EntryPoint> s1Entries = s1.getEntryPointList();
1071            List<EntryPoint> s2Entries = s2.getEntryPointList();
1072            for (int i = 0; i < s1Entries.size(); i++) {
1073                Block b = s1Entries.get(i).getFromBlock();
1074                for (int j = 0; j < s2Entries.size(); j++) {
1075                    if (b == s2Entries.get(j).getBlock()) {
1076                        return true;
1077                    }
1078                }
1079            }
1080        }
1081        return false;
1082    }
1083
1084    public String getSectionName(Section sec) {
1085        String s = sec.getDisplayName();
1086        return s;
1087    }
1088
1089    private void cancelExtraRequested(ActionEvent e) {
1090        extraFrame.setVisible(false);
1091        extraFrame.dispose();   // prevent listing in the Window menu.
1092        extraFrame = null;
1093    }
1094
1095    private void addExtraRequested(ActionEvent e) {
1096        int index = extraBox.getSelectedIndex();
1097        if ((atSelectedIndex < 0) || (index < 0)) {
1098            cancelExtraRequested(e);
1099            return;
1100        }
1101        ActiveTrain at = activeTrainsList.get(atSelectedIndex);
1102        Transit t = at.getTransit();
1103        Section s = extraBoxList.get(index);
1104        //Section ns = null;
1105        AllocationRequest ar = null;
1106        boolean requested = false;
1107        if (t.containsSection(s)) {
1108            if (s == at.getNextSectionToAllocate()) {
1109                // this is a request that the next section in the transit be allocated
1110                allocateNextRequested(atSelectedIndex);
1111                return;
1112            } else {
1113                // requesting allocation of a section in the Transit, but not the next Section
1114                int seq = -99;
1115                List<Integer> seqList = t.getSeqListBySection(s);
1116                if (seqList.size() > 0) {
1117                    seq = seqList.get(0);
1118                }
1119                if (seqList.size() > 1) {
1120                    // this section is in the Transit multiple times
1121                    int test = at.getNextSectionSeqNumber() - 1;
1122                    int diff = java.lang.Math.abs(seq - test);
1123                    for (int i = 1; i < seqList.size(); i++) {
1124                        if (diff > java.lang.Math.abs(test - seqList.get(i))) {
1125                            seq = seqList.get(i);
1126                            diff = java.lang.Math.abs(seq - test);
1127                        }
1128                    }
1129                }
1130                requested = requestAllocation(at, s, at.getAllocationDirectionFromSectionAndSeq(s, seq),
1131                        seq, true, extraFrame);
1132                ar = findAllocationRequestInQueue(s, seq,
1133                        at.getAllocationDirectionFromSectionAndSeq(s, seq), at);
1134            }
1135        } else {
1136            // requesting allocation of a section outside of the Transit, direction set arbitrary
1137            requested = requestAllocation(at, s, Section.FORWARD, -99, true, extraFrame);
1138            ar = findAllocationRequestInQueue(s, -99, Section.FORWARD, at);
1139        }
1140        // if allocation request is OK, allocate the Section, if not already allocated
1141        if (requested && (ar != null)) {
1142            allocateSection(ar, null);
1143        }
1144        if (extraFrame != null) {
1145            extraFrame.setVisible(false);
1146            extraFrame.dispose();   // prevent listing in the Window menu.
1147            extraFrame = null;
1148        }
1149    }
1150
1151    /**
1152     * Extend the allocation of a section to a active train. Allows a dispatcher
1153     * to manually route a train to its final destination.
1154     *
1155     * @param s      the section to allocate
1156     * @param at     the associated train
1157     * @param jFrame the window to update
1158     * @return true if section was allocated; false otherwise
1159     */
1160    public boolean extendActiveTrainsPath(Section s, ActiveTrain at, JmriJFrame jFrame) {
1161        if (s.getEntryPointFromSection(at.getEndBlockSection(), Section.FORWARD) != null
1162                && at.getNextSectionToAllocate() == null) {
1163
1164            int seq = at.getEndBlockSectionSequenceNumber() + 1;
1165            if (!at.addEndSection(s, seq)) {
1166                return false;
1167            }
1168            jmri.TransitSection ts = new jmri.TransitSection(s, seq, Section.FORWARD);
1169            ts.setTemporary(true);
1170            at.getTransit().addTransitSection(ts);
1171
1172            // requesting allocation of a section outside of the Transit, direction set arbitrary
1173            boolean requested = requestAllocation(at, s, Section.FORWARD, seq, true, jFrame);
1174
1175            AllocationRequest ar = findAllocationRequestInQueue(s, seq, Section.FORWARD, at);
1176            // if allocation request is OK, force an allocation the Section so that the dispatcher can then allocate futher paths through
1177            if (requested && (ar != null)) {
1178                allocateSection(ar, null);
1179                return true;
1180            }
1181        }
1182        return false;
1183    }
1184
1185    public boolean removeFromActiveTrainPath(Section s, ActiveTrain at, JmriJFrame jFrame) {
1186        if (s == null || at == null) {
1187            return false;
1188        }
1189        if (at.getEndBlockSection() != s) {
1190            log.error("Active trains end section {} is not the same as the requested section to remove {}", at.getEndBlockSection().getDisplayName(USERSYS), s.getDisplayName(USERSYS));
1191            return false;
1192        }
1193        if (!at.getTransit().removeLastTemporarySection(s)) {
1194            return false;
1195        }
1196
1197        //Need to find allocation and remove from list.
1198        for (int k = allocatedSections.size(); k > 0; k--) {
1199            if (at == allocatedSections.get(k - 1).getActiveTrain()
1200                    && allocatedSections.get(k - 1).getSection() == s) {
1201                releaseAllocatedSection(allocatedSections.get(k - 1), true);
1202            }
1203        }
1204        at.removeLastAllocatedSection();
1205        return true;
1206    }
1207
1208    // cancel the automatic restart request of an Active Train from the button in the Dispatcher window
1209    void cancelRestart(ActionEvent e) {
1210        ActiveTrain at = null;
1211        if (restartingTrainsList.size() == 1) {
1212            at = restartingTrainsList.get(0);
1213        } else if (restartingTrainsList.size() > 1) {
1214            Object choices[] = new Object[restartingTrainsList.size()];
1215            for (int i = 0; i < restartingTrainsList.size(); i++) {
1216                if (_ShortActiveTrainNames) {
1217                    choices[i] = restartingTrainsList.get(i).getTrainName();
1218                } else {
1219                    choices[i] = restartingTrainsList.get(i).getActiveTrainName();
1220                }
1221            }
1222            Object selName = JmriJOptionPane.showInputDialog(dispatcherFrame,
1223                    Bundle.getMessage("CancelRestartChoice"),
1224                    Bundle.getMessage("CancelRestartTitle"), JmriJOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
1225            if (selName == null) {
1226                return;
1227            }
1228            for (int j = 0; j < restartingTrainsList.size(); j++) {
1229                if (selName.equals(choices[j])) {
1230                    at = restartingTrainsList.get(j);
1231                }
1232            }
1233        }
1234        if (at != null) {
1235            at.setResetWhenDone(false);
1236            for (int j = restartingTrainsList.size(); j > 0; j--) {
1237                if (restartingTrainsList.get(j - 1) == at) {
1238                    restartingTrainsList.remove(j - 1);
1239                    return;
1240                }
1241            }
1242        }
1243    }
1244
1245    // terminate an Active Train from the button in the Dispatcher window
1246    void terminateTrain(ActionEvent e) {
1247        ActiveTrain at = null;
1248        if (activeTrainsList.size() == 1) {
1249            at = activeTrainsList.get(0);
1250        } else if (activeTrainsList.size() > 1) {
1251            Object choices[] = new Object[activeTrainsList.size()];
1252            for (int i = 0; i < activeTrainsList.size(); i++) {
1253                if (_ShortActiveTrainNames) {
1254                    choices[i] = activeTrainsList.get(i).getTrainName();
1255                } else {
1256                    choices[i] = activeTrainsList.get(i).getActiveTrainName();
1257                }
1258            }
1259            Object selName = JmriJOptionPane.showInputDialog(dispatcherFrame,
1260                    Bundle.getMessage("TerminateTrainChoice"),
1261                    Bundle.getMessage("TerminateTrainTitle"), JmriJOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
1262            if (selName == null) {
1263                return;
1264            }
1265            for (int j = 0; j < activeTrainsList.size(); j++) {
1266                if (selName.equals(choices[j])) {
1267                    at = activeTrainsList.get(j);
1268                }
1269            }
1270        }
1271        if (at != null) {
1272            terminateActiveTrain(at,true,false);
1273        }
1274    }
1275
1276    /**
1277     * Checks that exit Signal Heads are in place for all Sections in this
1278     * Transit and for Block boundaries at turnouts or level crossings within
1279     * Sections of the Transit for the direction defined in this Transit. Signal
1280     * Heads are not required at anchor point block boundaries where both blocks
1281     * are within the same Section, and for turnouts with two or more
1282     * connections in the same Section.
1283     *
1284     * <p>
1285     * Moved from Transit in JMRI 4.19.7
1286     *
1287     * @param t The transit being checked.
1288     * @return 0 if all Sections have all required signals or the number of
1289     *         Sections missing required signals; -1 if the panel is null
1290     */
1291    private int checkSignals(Transit t) {
1292        int numErrors = 0;
1293        for (TransitSection ts : t.getTransitSectionList() ) {
1294            numErrors = numErrors + ts.getSection().placeDirectionSensors();
1295        }
1296        return numErrors;
1297    }
1298
1299    /**
1300     * Validates connectivity through a Transit. Returns the number of errors
1301     * found. Sends log messages detailing the errors if break in connectivity
1302     * is detected. Checks all Sections before quitting.
1303     *
1304     * <p>
1305     * Moved from Transit in JMRI 4.19.7
1306     *
1307     * To support multiple panel dispatching, this version uses a null panel reference to bypass
1308     * the Section layout block connectivity checks. The assumption is that the existing block / path
1309     * relationships are valid.  When a section does not span panels, the layout block process can
1310     * result in valid block paths being removed.
1311     *
1312     * @return number of invalid sections
1313     */
1314    private int validateConnectivity(Transit t) {
1315        int numErrors = 0;
1316        for (int i = 0; i < t.getTransitSectionList().size(); i++) {
1317            String s = t.getTransitSectionList().get(i).getSection().validate();
1318            if (!s.isEmpty()) {
1319                log.error(s);
1320                numErrors++;
1321            }
1322        }
1323        return numErrors;
1324    }
1325
1326    // allocate the next section for an ActiveTrain at dispatcher's request
1327    void allocateNextRequested(int index) {
1328        // set up an Allocation Request
1329        ActiveTrain at = activeTrainsList.get(index);
1330        allocateNextRequestedForTrain(at);
1331    }
1332
1333    // allocate the next section for an ActiveTrain
1334    protected void allocateNextRequestedForTrain(ActiveTrain at) {
1335        // set up an Allocation Request
1336        Section next = at.getNextSectionToAllocate();
1337        if (next == null) {
1338            return;
1339        }
1340        int seqNext = at.getNextSectionSeqNumber();
1341        int dirNext = at.getAllocationDirectionFromSectionAndSeq(next, seqNext);
1342        if (requestAllocation(at, next, dirNext, seqNext, true, dispatcherFrame)) {
1343            AllocationRequest ar = findAllocationRequestInQueue(next, seqNext, dirNext, at);
1344            if (ar == null) {
1345                return;
1346            }
1347            // attempt to allocate
1348            allocateSection(ar, null);
1349        }
1350    }
1351
1352    /**
1353     * Creates a new ActiveTrain, and registers it with Dispatcher.
1354     *
1355     * @param transitID                       system or user name of a Transit
1356     *                                        in the Transit Table
1357     * @param trainID                         any text that identifies the train
1358     * @param tSource                         either ROSTER, OPERATIONS, or USER
1359     *                                        (see ActiveTrain.java)
1360     * @param startBlockName                  system or user name of Block where
1361     *                                        train currently resides
1362     * @param startBlockSectionSequenceNumber sequence number in the Transit of
1363     *                                        the Section containing the
1364     *                                        startBlock (if the startBlock is
1365     *                                        within the Transit), or of the
1366     *                                        Section the train will enter from
1367     *                                        the startBlock (if the startBlock
1368     *                                        is outside the Transit)
1369     * @param endBlockName                    system or user name of Block where
1370     *                                        train will end up after its
1371     *                                        transit
1372     * @param endBlockSectionSequenceNumber   sequence number in the Transit of
1373     *                                        the Section containing the
1374     *                                        endBlock.
1375     * @param autoRun                         set to "true" if computer is to
1376     *                                        run the train automatically,
1377     *                                        otherwise "false"
1378     * @param dccAddress                      required if "autoRun" is "true",
1379     *                                        set to null otherwise
1380     * @param priority                        any integer, higher number is
1381     *                                        higher priority. Used to arbitrate
1382     *                                        allocation request conflicts
1383     * @param resetWhenDone                   set to "true" if the Active Train
1384     *                                        is capable of continuous running
1385     *                                        and the user has requested that it
1386     *                                        be automatically reset for another
1387     *                                        run thru its Transit each time it
1388     *                                        completes running through its
1389     *                                        Transit.
1390     * @param reverseAtEnd                    true if train should automatically
1391     *                                        reverse at end of transit; false
1392     *                                        otherwise
1393     * @param showErrorMessages               "true" if error message dialogs
1394     *                                        are to be displayed for detected
1395     *                                        errors Set to "false" to suppress
1396     *                                        error message dialogs from this
1397     *                                        method.
1398     * @param frame                           window request is from, or "null"
1399     *                                        if not from a window
1400     * @param allocateMethod                  How allocations will be performed.
1401     *                                        999 - Allocate as many section from start to finish as it can
1402     *                                        0 - Allocate to the next "Safe" section. If it cannot allocate all the way to
1403     *                                        the next "safe" section it does not allocate any sections. It will
1404     *                                        not allocate beyond the next safe section until it arrives there. This
1405     *                                        is useful for bidirectional single track running.
1406     *                                        Any other positive number (in reality thats 1-150 as the create transit
1407     *                                        allows a max of 150 sections) allocate the specified number of sections a head.
1408     * @return a new ActiveTrain or null on failure
1409     */
1410    public ActiveTrain createActiveTrain(String transitID, String trainID, int tSource, String startBlockName,
1411            int startBlockSectionSequenceNumber, String endBlockName, int endBlockSectionSequenceNumber,
1412            boolean autoRun, String dccAddress, int priority, boolean resetWhenDone, boolean reverseAtEnd,
1413            boolean showErrorMessages, JmriJFrame frame, int allocateMethod) {
1414        log.debug("trainID:{}, tSource:{}, startBlockName:{}, startBlockSectionSequenceNumber:{}, endBlockName:{}, endBlockSectionSequenceNumber:{}",
1415                trainID,tSource,startBlockName,startBlockSectionSequenceNumber,endBlockName,endBlockSectionSequenceNumber);
1416        // validate input
1417        Transit t = transitManager.getTransit(transitID);
1418        if (t == null) {
1419            if (showErrorMessages) {
1420                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1421                        "Error1"), new Object[]{transitID}), Bundle.getMessage("ErrorTitle"),
1422                        JmriJOptionPane.ERROR_MESSAGE);
1423            }
1424            log.error("Bad Transit name '{}' when attempting to create an Active Train", transitID);
1425            return null;
1426        }
1427        if (t.getState() != Transit.IDLE) {
1428            if (showErrorMessages) {
1429                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1430                        "Error2"), new Object[]{transitID}), Bundle.getMessage("ErrorTitle"),
1431                        JmriJOptionPane.ERROR_MESSAGE);
1432            }
1433            log.error("Transit '{}' not IDLE, cannot create an Active Train", transitID);
1434            return null;
1435        }
1436        if ((trainID == null) || trainID.equals("")) {
1437            if (showErrorMessages) {
1438                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error3"),
1439                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1440            }
1441            log.error("TrainID string not provided, cannot create an Active Train");
1442            return null;
1443        }
1444        if ((tSource != ActiveTrain.ROSTER) && (tSource != ActiveTrain.OPERATIONS)
1445                && (tSource != ActiveTrain.USER)) {
1446            if (showErrorMessages) {
1447                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error21"),
1448                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1449            }
1450            log.error("Train source is invalid - {} - cannot create an Active Train", tSource);
1451            return null;
1452        }
1453        Block startBlock = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(startBlockName);
1454        if (startBlock == null) {
1455            if (showErrorMessages) {
1456                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1457                        "Error4"), new Object[]{startBlockName}), Bundle.getMessage("ErrorTitle"),
1458                        JmriJOptionPane.ERROR_MESSAGE);
1459            }
1460            log.error("Bad startBlockName '{}' when attempting to create an Active Train", startBlockName);
1461            return null;
1462        }
1463        if (isInAllocatedSection(startBlock)) {
1464            if (showErrorMessages && !DispatcherFrame.dispatcherSystemSchedulingInOperation) {
1465                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1466                        "Error5"), new Object[]{startBlock.getDisplayName()}), Bundle.getMessage("ErrorTitle"),
1467                        JmriJOptionPane.ERROR_MESSAGE);
1468            }
1469            log.error("Start block '{}' in allocated Section, cannot create an Active Train", startBlock.getDisplayName(USERSYS));
1470            return null;
1471        }
1472        if (_HasOccupancyDetection && (!(startBlock.getState() == Block.OCCUPIED))) {
1473            if (showErrorMessages && !DispatcherFrame.dispatcherSystemSchedulingInOperation) {
1474                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1475                        "Error6"), new Object[]{startBlock.getDisplayName()}), Bundle.getMessage("ErrorTitle"),
1476                        JmriJOptionPane.ERROR_MESSAGE);
1477            }
1478            log.error("No train in start block '{}', cannot create an Active Train", startBlock.getDisplayName(USERSYS));
1479            return null;
1480        }
1481        if (startBlockSectionSequenceNumber <= 0) {
1482            if (showErrorMessages) {
1483                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error12"),
1484                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1485            }
1486        } else if (startBlockSectionSequenceNumber > t.getMaxSequence()) {
1487            if (showErrorMessages) {
1488                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1489                        "Error13"), new Object[]{"" + startBlockSectionSequenceNumber}),
1490                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1491            }
1492            log.error("Invalid sequence number '{}' when attempting to create an Active Train", startBlockSectionSequenceNumber);
1493            return null;
1494        }
1495        Block endBlock = InstanceManager.getDefault(jmri.BlockManager.class).getBlock(endBlockName);
1496        if ((endBlock == null) || (!t.containsBlock(endBlock))) {
1497            if (showErrorMessages) {
1498                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1499                        "Error7"), new Object[]{endBlockName}), Bundle.getMessage("ErrorTitle"),
1500                        JmriJOptionPane.ERROR_MESSAGE);
1501            }
1502            log.error("Bad endBlockName '{}' when attempting to create an Active Train", endBlockName);
1503            return null;
1504        }
1505        if ((endBlockSectionSequenceNumber <= 0) && (t.getBlockCount(endBlock) > 1)) {
1506            JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error8"),
1507                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1508        } else if (endBlockSectionSequenceNumber > t.getMaxSequence()) {
1509            if (showErrorMessages) {
1510                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1511                        "Error9"), new Object[]{"" + endBlockSectionSequenceNumber}),
1512                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1513            }
1514            log.error("Invalid sequence number '{}' when attempting to create an Active Train", endBlockSectionSequenceNumber);
1515            return null;
1516        }
1517        if ((!reverseAtEnd) && resetWhenDone && (!t.canBeResetWhenDone())) {
1518            if (showErrorMessages) {
1519                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1520                        "Error26"), new Object[]{(t.getDisplayName())}),
1521                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1522            }
1523            log.error("Incompatible Transit set up and request to Reset When Done when attempting to create an Active Train");
1524            return null;
1525        }
1526        if (autoRun && ((dccAddress == null) || dccAddress.equals(""))) {
1527            if (showErrorMessages) {
1528                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error10"),
1529                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1530            }
1531            log.error("AutoRun requested without a dccAddress when attempting to create an Active Train");
1532            return null;
1533        }
1534        if (autoRun) {
1535            if (_autoTrainsFrame == null) {
1536                // This is the first automatic active train--check if all required options are present
1537                //   for automatic running.  First check for layout editor panel
1538                if (!_UseConnectivity || (editorManager.getAll(LayoutEditor.class).size() == 0)) {
1539                    if (showErrorMessages) {
1540                        JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error33"),
1541                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1542                        log.error("AutoRun requested without a LayoutEditor panel for connectivity.");
1543                        return null;
1544                    }
1545                }
1546                if (!_HasOccupancyDetection) {
1547                    if (showErrorMessages) {
1548                        JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error35"),
1549                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1550                        log.error("AutoRun requested without occupancy detection.");
1551                        return null;
1552                    }
1553                }
1554                // get Maximum line speed once. We need to use this when the current signal mast is null.
1555                for (var panel : editorManager.getAll(LayoutEditor.class)) {
1556                    for (int iSM = 0; iSM < panel.getSignalMastList().size();  iSM++ )  {
1557                        float msl = panel.getSignalMastList().get(iSM).getSignalMast().getSignalSystem().getMaximumLineSpeed();
1558                        if ( msl > maximumLineSpeed ) {
1559                            maximumLineSpeed = msl;
1560                        }
1561                    }
1562                }
1563            }
1564            // check/set Transit specific items for automatic running
1565            // validate connectivity for all Sections in this transit
1566            int numErrors = validateConnectivity(t);
1567
1568            if (numErrors != 0) {
1569                if (showErrorMessages) {
1570                    JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1571                            "Error34"), new Object[]{("" + numErrors)}),
1572                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1573                }
1574                return null;
1575            }
1576            // check/set direction sensors in signal logic for all Sections in this Transit.
1577            if (getSignalType() == SIGNALHEAD && getSetSSLDirectionalSensors()) {
1578                numErrors = checkSignals(t);
1579                if (numErrors == 0) {
1580                    t.initializeBlockingSensors();
1581                }
1582                if (numErrors != 0) {
1583                    if (showErrorMessages) {
1584                        JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1585                                "Error36"), new Object[]{("" + numErrors)}),
1586                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1587                    }
1588                    return null;
1589                }
1590            }
1591            // TODO: Need to check signalMasts as well
1592            // this train is OK, activate the AutoTrains window, if needed
1593            if (_autoTrainsFrame == null) {
1594                _autoTrainsFrame = new AutoTrainsFrame(this);
1595            } else {
1596                ThreadingUtil.runOnGUI( () -> _autoTrainsFrame.setVisible(true));
1597            }
1598        } else if (_UseConnectivity && (editorManager.getAll(LayoutEditor.class).size() > 0)) {
1599            // not auto run, set up direction sensors in signals since use connectivity was requested
1600            if (getSignalType() == SIGNALHEAD) {
1601                int numErrors = checkSignals(t);
1602                if (numErrors == 0) {
1603                    t.initializeBlockingSensors();
1604                }
1605                if (numErrors != 0) {
1606                    if (showErrorMessages) {
1607                        JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1608                                "Error36"), new Object[]{("" + numErrors)}),
1609                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1610                    }
1611                    return null;
1612                }
1613            }
1614        }
1615        // all information checks out - create
1616        ActiveTrain at = new ActiveTrain(t, trainID, tSource);
1617        //if (at==null) {
1618        // if (showErrorMessages) {
1619        //JmriJOptionPaneane.showMessageDialog(frame,java.text.MessageFormat.format(Bundle.getMessage(
1620        //    "Error11"),new Object[] { transitID, trainID }), Bundle.getMessage("ErrorTitle"),
1621        //     JmriJOptionPane.ERROR_MESSAGE);
1622        // }
1623        // log.error("Creating Active Train failed, Transit - "+transitID+", train - "+trainID);
1624        // return null;
1625        //}
1626        activeTrainsList.add(at);
1627        java.beans.PropertyChangeListener listener = null;
1628        at.addPropertyChangeListener(listener = new java.beans.PropertyChangeListener() {
1629            @Override
1630            public void propertyChange(java.beans.PropertyChangeEvent e) {
1631                handleActiveTrainChange(e);
1632            }
1633        });
1634        _atListeners.add(listener);
1635        t.setState(Transit.ASSIGNED);
1636        at.setStartBlock(startBlock);
1637        at.setStartBlockSectionSequenceNumber(startBlockSectionSequenceNumber);
1638        at.setEndBlock(endBlock);
1639        at.setEndBlockSection(t.getSectionFromBlockAndSeq(endBlock, endBlockSectionSequenceNumber));
1640        at.setEndBlockSectionSequenceNumber(endBlockSectionSequenceNumber);
1641        at.setResetWhenDone(resetWhenDone);
1642        if (resetWhenDone) {
1643            restartingTrainsList.add(at);
1644        }
1645        at.setReverseAtEnd(reverseAtEnd);
1646        at.setAllocateMethod(allocateMethod);
1647        at.setPriority(priority);
1648        at.setDccAddress(dccAddress);
1649        at.setAutoRun(autoRun);
1650        return at;
1651    }
1652
1653    public void allocateNewActiveTrain(ActiveTrain at) {
1654        if (at.getDelayedStart() == ActiveTrain.SENSORDELAY && at.getDelaySensor() != null) {
1655            if (at.getDelaySensor().getState() != jmri.Sensor.ACTIVE) {
1656                at.initializeDelaySensor();
1657            }
1658        }
1659        AllocationRequest ar = at.initializeFirstAllocation();
1660        if (ar == null) {
1661            log.debug("First allocation returned null, normal for auotallocate");
1662        }
1663        // removed. initializeFirstAllocation already does this.
1664        /* if (ar != null) {
1665            if ((ar.getSection()).containsBlock(at.getStartBlock())) {
1666                // Active Train is in the first Section, go ahead and allocate it
1667                AllocatedSection als = allocateSection(ar, null);
1668                if (als == null) {
1669                    log.error("Problem allocating the first Section of the Active Train - {}", at.getActiveTrainName());
1670                }
1671            }
1672        } */
1673        activeTrainsTableModel.fireTableDataChanged();
1674        if (allocatedSectionTableModel != null) {
1675            allocatedSectionTableModel.fireTableDataChanged();
1676        }
1677    }
1678
1679    private void handleActiveTrainChange(java.beans.PropertyChangeEvent e) {
1680        activeTrainsTableModel.fireTableDataChanged();
1681    }
1682
1683    private boolean isInAllocatedSection(jmri.Block b) {
1684        for (int i = 0; i < allocatedSections.size(); i++) {
1685            Section s = allocatedSections.get(i).getSection();
1686            if (s.containsBlock(b)) {
1687                return true;
1688            }
1689        }
1690        return false;
1691    }
1692
1693    /**
1694     * Terminate an Active Train and remove it from the Dispatcher. The
1695     * ActiveTrain object should not be used again after this method is called.
1696     *
1697     * @param at the train to terminate
1698     * @param terminateNow TRue if doing a full terminate, not just an end of transit.
1699     * @param runNextTrain if false the next traininfo is not run.
1700     */
1701    public void terminateActiveTrain(final ActiveTrain at, boolean terminateNow, boolean runNextTrain) {
1702        // ensure there is a train to terminate
1703        if (at == null) {
1704            log.error("Null ActiveTrain pointer when attempting to terminate an ActiveTrain");
1705            return;
1706        }
1707        // terminate the train - remove any allocation requests
1708        for (int k = allocationRequests.size(); k > 0; k--) {
1709            if (at == allocationRequests.get(k - 1).getActiveTrain()) {
1710                allocationRequests.get(k - 1).dispose();
1711                allocationRequests.remove(k - 1);
1712            }
1713        }
1714        // remove any allocated sections
1715        // except occupied if not a full termination
1716        for (int k = allocatedSections.size(); k > 0; k--) {
1717            try {
1718                if (at == allocatedSections.get(k - 1).getActiveTrain()) {
1719                    if ( !terminateNow ) {
1720                        if (allocatedSections.get(k - 1).getSection().getOccupancy()!=Section.OCCUPIED) {
1721                            releaseAllocatedSection(allocatedSections.get(k - 1), terminateNow);
1722                        } else {
1723                            // allocatedSections.get(k - 1).getSection().setState(Section.FREE);
1724                            log.debug("Section[{}] State [{}]",allocatedSections.get(k - 1).getSection().getUserName(),
1725                                    allocatedSections.get(k - 1).getSection().getState());
1726                        }
1727                    } else {
1728                        releaseAllocatedSection(allocatedSections.get(k - 1), terminateNow);
1729                    }
1730                }
1731            } catch (RuntimeException e) {
1732                log.warn("releaseAllocatedSection failed - maybe the AllocatedSection was removed due to a terminating train?? {}", e.getMessage());
1733            }
1734        }
1735        // remove from restarting trains list, if present
1736        for (int j = restartingTrainsList.size(); j > 0; j--) {
1737            if (at == restartingTrainsList.get(j - 1)) {
1738                restartingTrainsList.remove(j - 1);
1739            }
1740        }
1741        if (autoAllocate != null) {
1742            queueReleaseOfReservedSections(at.getTrainName());
1743        }
1744        // terminate the train
1745        if (terminateNow) {
1746            for (int m = activeTrainsList.size(); m > 0; m--) {
1747                if (at == activeTrainsList.get(m - 1)) {
1748                    activeTrainsList.remove(m - 1);
1749                    at.removePropertyChangeListener(_atListeners.get(m - 1));
1750                    _atListeners.remove(m - 1);
1751                }
1752            }
1753            if (at.getAutoRun()) {
1754                AutoActiveTrain aat = at.getAutoActiveTrain();
1755                aat.terminate();
1756                aat.dispose();
1757            }
1758            removeHeldMast(null, at);
1759
1760            at.terminate();
1761            if (runNextTrain && !at.getNextTrain().isEmpty() && !at.getNextTrain().equals("None")) {
1762                log.debug("Loading Next Train[{}]", at.getNextTrain());
1763                // must wait at least 2 secs to allow dispose to fully complete.
1764                if (at.getRosterEntry() != null) {
1765                    jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
1766                        loadTrainFromTrainInfo(at.getNextTrain(),"ROSTER",at.getRosterEntry().getId());},2000);
1767                } else {
1768                    jmri.util.ThreadingUtil.runOnLayoutDelayed(()-> {
1769                        loadTrainFromTrainInfo(at.getNextTrain(),"USER",at.getDccAddress());},2000);
1770                }
1771            }
1772            at.dispose();
1773        }
1774        activeTrainsTableModel.fireTableDataChanged();
1775        if (allocatedSectionTableModel != null) {
1776            allocatedSectionTableModel.fireTableDataChanged();
1777        }
1778        allocationRequestTableModel.fireTableDataChanged();
1779    }
1780
1781    /**
1782     * Creates an Allocation Request, and registers it with Dispatcher
1783     * <p>
1784     * Required input entries:
1785     *
1786     * @param activeTrain       ActiveTrain requesting the allocation
1787     * @param section           Section to be allocated
1788     * @param direction         direction of travel in the allocated Section
1789     * @param seqNumber         sequence number of the Section in the Transit of
1790     *                          the ActiveTrain. If the requested Section is not
1791     *                          in the Transit, a sequence number of -99 should
1792     *                          be entered.
1793     * @param showErrorMessages "true" if error message dialogs are to be
1794     *                          displayed for detected errors Set to "false" to
1795     *                          suppress error message dialogs from this method.
1796     * @param frame             window request is from, or "null" if not from a
1797     *                          window
1798     * @param firstAllocation           True if first allocation
1799     * @return true if successful; false otherwise
1800     */
1801    protected boolean requestAllocation(ActiveTrain activeTrain, Section section, int direction,
1802            int seqNumber, boolean showErrorMessages, JmriJFrame frame,boolean firstAllocation) {
1803        // check input entries
1804        if (activeTrain == null) {
1805            if (showErrorMessages) {
1806                JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("Error16"),
1807                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
1808            }
1809            log.error("Missing ActiveTrain specification");
1810            return false;
1811        }
1812        if (section == null) {
1813            if (showErrorMessages) {
1814                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1815                        "Error17"), new Object[]{activeTrain.getActiveTrainName()}), Bundle.getMessage("ErrorTitle"),
1816                        JmriJOptionPane.ERROR_MESSAGE);
1817            }
1818            log.error("Missing Section specification in allocation request from {}", activeTrain.getActiveTrainName());
1819            return false;
1820        }
1821        if (((seqNumber <= 0) || (seqNumber > (activeTrain.getTransit().getMaxSequence()))) && (seqNumber != -99)) {
1822            if (showErrorMessages) {
1823                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1824                        "Error19"), new Object[]{"" + seqNumber, activeTrain.getActiveTrainName()}), Bundle.getMessage("ErrorTitle"),
1825                        JmriJOptionPane.ERROR_MESSAGE);
1826            }
1827            log.error("Out-of-range sequence number *{}* in allocation request", seqNumber);
1828            return false;
1829        }
1830        if ((direction != Section.FORWARD) && (direction != Section.REVERSE)) {
1831            if (showErrorMessages) {
1832                JmriJOptionPane.showMessageDialog(frame, java.text.MessageFormat.format(Bundle.getMessage(
1833                        "Error18"), new Object[]{"" + direction, activeTrain.getActiveTrainName()}), Bundle.getMessage("ErrorTitle"),
1834                        JmriJOptionPane.ERROR_MESSAGE);
1835            }
1836            log.error("Invalid direction '{}' specification in allocation request", direction);
1837            return false;
1838        }
1839        // check if this allocation has already been requested
1840        AllocationRequest ar = findAllocationRequestInQueue(section, seqNumber, direction, activeTrain);
1841        if (ar == null) {
1842            ar = new AllocationRequest(section, seqNumber, direction, activeTrain);
1843            if (!firstAllocation && _AutoAllocate) {
1844                allocationRequests.add(ar);
1845                if (_AutoAllocate) {
1846                    queueScanOfAllocationRequests();
1847                }
1848            } else if (_AutoAllocate) {  // It is auto allocate and First section
1849                queueAllocate(ar);
1850            } else {
1851                // manual
1852                allocationRequests.add(ar);
1853            }
1854        }
1855        activeTrainsTableModel.fireTableDataChanged();
1856        allocationRequestTableModel.fireTableDataChanged();
1857        return true;
1858    }
1859
1860    protected boolean requestAllocation(ActiveTrain activeTrain, Section section, int direction,
1861            int seqNumber, boolean showErrorMessages, JmriJFrame frame) {
1862        return requestAllocation( activeTrain,  section,  direction,
1863                 seqNumber,  showErrorMessages,  frame, false);
1864    }
1865
1866    // ensures there will not be any duplicate allocation requests
1867    protected AllocationRequest findAllocationRequestInQueue(Section s, int seq, int dir, ActiveTrain at) {
1868        for (int i = 0; i < allocationRequests.size(); i++) {
1869            AllocationRequest ar = allocationRequests.get(i);
1870            if ((ar.getActiveTrain() == at) && (ar.getSection() == s) && (ar.getSectionSeqNumber() == seq)
1871                    && (ar.getSectionDirection() == dir)) {
1872                return ar;
1873            }
1874        }
1875        return null;
1876    }
1877
1878    private void cancelAllocationRequest(int index) {
1879        AllocationRequest ar = allocationRequests.get(index);
1880        allocationRequests.remove(index);
1881        ar.dispose();
1882        allocationRequestTableModel.fireTableDataChanged();
1883    }
1884
1885    private void allocateRequested(int index) {
1886        AllocationRequest ar = allocationRequests.get(index);
1887        allocateSection(ar, null);
1888    }
1889
1890    protected void addDelayedTrain(ActiveTrain at, int restartType, Sensor delaySensor, boolean resetSensor) {
1891        if (restartType == ActiveTrain.TIMEDDELAY) {
1892            if (!delayedTrains.contains(at)) {
1893                delayedTrains.add(at);
1894            }
1895        } else if (restartType == ActiveTrain.SENSORDELAY) {
1896            if (delaySensor != null) {
1897                at.initializeRestartSensor(delaySensor, resetSensor);
1898            }
1899        }
1900        activeTrainsTableModel.fireTableDataChanged();
1901    }
1902
1903    /**
1904     * Allocates a Section to an Active Train according to the information in an
1905     * AllocationRequest.
1906     * <p>
1907     * If successful, returns an AllocatedSection and removes the
1908     * AllocationRequest from the queue. If not successful, returns null and
1909     * leaves the AllocationRequest in the queue.
1910     * <p>
1911     * To be allocatable, a Section must be FREE and UNOCCUPIED. If a Section is
1912     * OCCUPIED, the allocation is rejected unless the dispatcher chooses to
1913     * override this restriction. To be allocatable, the Active Train must not
1914     * be waiting for its start time. If the start time has not been reached,
1915     * the allocation is rejected, unless the dispatcher chooses to override the
1916     * start time.
1917     *
1918     * @param ar the request containing the section to allocate
1919     * @param ns the next section; use null to allow the next section to be
1920     *           automatically determined, if the next section is the last
1921     *           section, of if an extra section is being allocated
1922     * @return the allocated section or null if not successful
1923     */
1924    public AllocatedSection allocateSection(@Nonnull AllocationRequest ar, Section ns) {
1925        log.trace("{}: Checking Section [{}]", ar.getActiveTrain().getTrainName(), (ns != null ? ns.getDisplayName(USERSYS) : "auto"));
1926        AllocatedSection as = null;
1927        Section nextSection = null;
1928        int nextSectionSeqNo = 0;
1929        ActiveTrain at = ar.getActiveTrain();
1930        Section s = ar.getSection();
1931        if (at.reachedRestartPoint()) {
1932            log.debug("{}: waiting for restart, [{}] not allocated", at.getTrainName(), s.getDisplayName(USERSYS));
1933            return null;
1934        }
1935        if (at.holdAllocation()) {
1936            log.debug("{}: allocation is held, [{}] not allocated", at.getTrainName(), s.getDisplayName(USERSYS));
1937            return null;
1938        }
1939        if (s.getState() != Section.FREE) {
1940            log.debug("{}: section [{}] is not free", at.getTrainName(), s.getDisplayName(USERSYS));
1941            return null;
1942        }
1943        // skip occupancy check if this is the first allocation and the train is occupying the Section
1944        boolean checkOccupancy = true;
1945        if ((at.getLastAllocatedSection() == null) && (s.containsBlock(at.getStartBlock()))) {
1946            checkOccupancy = false;
1947        }
1948        // check if section is occupied
1949        if (checkOccupancy && (s.getOccupancy() == Section.OCCUPIED)) {
1950            if (_AutoAllocate) {
1951                return null;  // autoAllocate never overrides occupancy
1952            }
1953            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
1954                    Bundle.getMessage("Question1"), Bundle.getMessage("WarningTitle"),
1955                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
1956                    new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")},
1957                    Bundle.getMessage("ButtonNo"));
1958            if (selectedValue != 0 ) { // array position 0, override not pressed
1959                return null;   // return without allocating if "No" or "Cancel" response
1960            }
1961        }
1962        // check if train has reached its start time if delayed start
1963        if (checkOccupancy && (!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) {
1964            if (_AutoAllocate) {
1965                return null;  // autoAllocate never overrides start time
1966            }
1967            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
1968                    Bundle.getMessage("Question4"), Bundle.getMessage("WarningTitle"),
1969                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
1970                    new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")},
1971                    Bundle.getMessage("ButtonNo"));
1972            if (selectedValue != 0 ) { // array position 0, override not pressed
1973                return null;
1974            } else {
1975                at.setStarted();
1976                for (int i = delayedTrains.size() - 1; i >= 0; i--) {
1977                    if (delayedTrains.get(i) == at) {
1978                        delayedTrains.remove(i);
1979                    }
1980                }
1981            }
1982        }
1983        //check here to see if block is already assigned to an allocated section;
1984        if (checkBlocksNotInAllocatedSection(s, ar) != null) {
1985            return null;
1986        }
1987        // Programming
1988        // Note: if ns is not null, the program will not check for end Block, but will use ns.
1989        // Calling code must do all validity checks on a non-null ns.
1990        if (ns != null) {
1991            nextSection = ns;
1992        } else if ((ar.getSectionSeqNumber() != -99) && (at.getNextSectionSeqNumber() == ar.getSectionSeqNumber())
1993                && (!((s == at.getEndBlockSection()) && (ar.getSectionSeqNumber() == at.getEndBlockSectionSequenceNumber())))
1994                && (!(at.isAllocationReversed() && (ar.getSectionSeqNumber() == 1)))) {
1995            // not at either end - determine the next section
1996            int seqNum = ar.getSectionSeqNumber();
1997            if (at.isAllocationReversed()) {
1998                seqNum -= 1;
1999            } else {
2000                seqNum += 1;
2001            }
2002            List<Section> secList = at.getTransit().getSectionListBySeq(seqNum);
2003            if (secList.size() == 1) {
2004                nextSection = secList.get(0);
2005
2006            } else if (secList.size() > 1) {
2007                if (_AutoAllocate) {
2008                    nextSection = autoChoice(secList, ar, seqNum);
2009                } else {
2010                    nextSection = dispatcherChoice(secList, ar);
2011                }
2012            }
2013            nextSectionSeqNo = seqNum;
2014        } else if (at.getReverseAtEnd() && (!at.isAllocationReversed()) && (s == at.getEndBlockSection())
2015                && (ar.getSectionSeqNumber() == at.getEndBlockSectionSequenceNumber())) {
2016            // need to reverse Transit direction when train is in the last Section, set next section.
2017            at.holdAllocation(true);
2018            nextSectionSeqNo = at.getEndBlockSectionSequenceNumber() - 1;
2019            at.setAllocationReversed(true);
2020            List<Section> secList = at.getTransit().getSectionListBySeq(nextSectionSeqNo);
2021            if (secList.size() == 1) {
2022                nextSection = secList.get(0);
2023            } else if (secList.size() > 1) {
2024                if (_AutoAllocate) {
2025                    nextSection = autoChoice(secList, ar, nextSectionSeqNo);
2026                } else {
2027                    nextSection = dispatcherChoice(secList, ar);
2028                }
2029            }
2030        } else if (((!at.isAllocationReversed()) && (s == at.getEndBlockSection())
2031                && (ar.getSectionSeqNumber() == at.getEndBlockSectionSequenceNumber()))
2032                || (at.isAllocationReversed() && (ar.getSectionSeqNumber() == 1))) {
2033            // request to allocate the last block in the Transit, or the Transit is reversed and
2034            //      has reached the beginning of the Transit--check for automatic restart
2035            if (at.getResetWhenDone()) {
2036                if (at.getDelayedRestart() != ActiveTrain.NODELAY) {
2037                    log.debug("{}: setting allocation to held", at.getTrainName());
2038                    at.holdAllocation(true);
2039                }
2040                nextSection = at.getSecondAllocatedSection();
2041                nextSectionSeqNo = 2;
2042                at.setAllocationReversed(false);
2043            }
2044        }
2045
2046        //This might be the location to check to see if we have an intermediate section that we then need to perform extra checks on.
2047        //Working on the basis that if the nextsection is not null, then we are not at the end of the transit.
2048        List<Section> intermediateSections = new ArrayList<>();
2049        Section mastHeldAtSection = null;
2050        Object imSecProperty = ar.getSection().getProperty("intermediateSection");
2051        if (nextSection != null
2052            && imSecProperty != null
2053                && ((Boolean) imSecProperty)) {
2054
2055            String property = "forwardMast";
2056            if (at.isAllocationReversed()) {
2057                property = "reverseMast";
2058            }
2059
2060            Object sectionDirProp = ar.getSection().getProperty(property);
2061            if ( sectionDirProp != null) {
2062                SignalMast endMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(sectionDirProp.toString());
2063                if (endMast != null) {
2064                    if (endMast.getHeld()) {
2065                        mastHeldAtSection = ar.getSection();
2066                    }
2067                }
2068            }
2069            List<TransitSection> tsList = ar.getActiveTrain().getTransit().getTransitSectionList();
2070            boolean found = false;
2071            if (at.isAllocationReversed()) {
2072                for (int i = tsList.size() - 1; i > 0; i--) {
2073                    TransitSection ts = tsList.get(i);
2074                    if (ts.getSection() == ar.getSection() && ts.getSequenceNumber() == ar.getSectionSeqNumber()) {
2075                        found = true;
2076                    } else if (found) {
2077                        Object imSecProp = ts.getSection().getProperty("intermediateSection");
2078                        if ( imSecProp != null) {
2079                            if ((Boolean) imSecProp) {
2080                                intermediateSections.add(ts.getSection());
2081                            } else {
2082                                //we add the section after the last intermediate in, so that the last allocation request can be built correctly
2083                                intermediateSections.add(ts.getSection());
2084                                break;
2085                            }
2086                        }
2087                    }
2088                }
2089            } else {
2090                for (int i = 0; i <= tsList.size() - 1; i++) {
2091                    TransitSection ts = tsList.get(i);
2092                    if (ts.getSection() == ar.getSection() && ts.getSequenceNumber() == ar.getSectionSeqNumber()) {
2093                        found = true;
2094                    } else if (found) {
2095                        Object imSecProp = ts.getSection().getProperty("intermediateSection");
2096                        if ( imSecProp != null ){
2097                            if ((Boolean) imSecProp) {
2098                                intermediateSections.add(ts.getSection());
2099                            } else {
2100                                //we add the section after the last intermediate in, so that the last allocation request can be built correctly
2101                                intermediateSections.add(ts.getSection());
2102                                break;
2103                            }
2104                        }
2105                    }
2106                }
2107            }
2108            boolean intermediatesOccupied = false;
2109
2110            for (int i = 0; i < intermediateSections.size() - 1; i++) {  // ie do not check last section which is not an intermediate section
2111                Section se = intermediateSections.get(i);
2112                if (se.getState() == Section.FREE  && se.getOccupancy() == Section.UNOCCUPIED) {
2113                    //If the section state is free, we need to look to see if any of the blocks are used else where
2114                    Section conflict = checkBlocksNotInAllocatedSection(se, null);
2115                    if (conflict != null) {
2116                        //We have a conflicting path
2117                        //We might need to find out if the section which the block is allocated to is one in our transit, and if so is it running in the same direction.
2118                        return null;
2119                    } else {
2120                        if (mastHeldAtSection == null) {
2121                            Object heldProp = se.getProperty(property);
2122                            if (heldProp != null) {
2123                                SignalMast endMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(heldProp.toString());
2124                                if (endMast != null && endMast.getHeld()) {
2125                                    mastHeldAtSection = se;
2126                                }
2127                            }
2128                        }
2129                    }
2130                } else if (se.getState() != Section.FREE
2131                                && at.getLastAllocatedSection() != null
2132                                && se.getState() != at.getLastAllocatedSection().getState())  {
2133                    // train coming other way...
2134                    return null;
2135                } else {
2136                    intermediatesOccupied = true;
2137                    break;
2138                }
2139            }
2140            //If the intermediate sections are already occupied or allocated then we clear the intermediate list and only allocate the original request.
2141            if (intermediatesOccupied) {
2142                intermediateSections = new ArrayList<>();
2143            }
2144        }
2145
2146        // check/set turnouts if requested or if autorun
2147        // Note: If "Use Connectivity..." is specified in the Options window, turnouts are checked. If
2148        //   turnouts are not set correctly, allocation will not proceed without dispatcher override.
2149        //   If in addition Auto setting of turnouts is requested, the turnouts are set automatically
2150        //   if not in the correct position.
2151        // Note: Turnout checking and/or setting is not performed when allocating an extra section.
2152        List<LayoutTrackExpectedState<LayoutTurnout>> expectedTurnOutStates = null;
2153        if ((_UseConnectivity) && (ar.getSectionSeqNumber() != -99)) {
2154            expectedTurnOutStates = checkTurnoutStates(s, ar.getSectionSeqNumber(), nextSection, at, at.getLastAllocatedSection());
2155            if (expectedTurnOutStates == null) {
2156                return null;
2157            }
2158            Section preSec = s;
2159            Section tmpcur = nextSection;
2160            int tmpSeqNo = nextSectionSeqNo;
2161            //The first section in the list will be the same as the nextSection, so we skip that.
2162            for (int i = 1; i < intermediateSections.size(); i++) {
2163                Section se = intermediateSections.get(i);
2164                if (preSec == mastHeldAtSection) {
2165                    log.debug("Section is beyond held mast do not set turnouts {}", (tmpcur != null ? tmpcur.getDisplayName(USERSYS) : "null"));
2166                    break;
2167                }
2168                if (checkTurnoutStates(tmpcur, tmpSeqNo, se, at, preSec) == null) {
2169                    return null;
2170                }
2171                preSec = tmpcur;
2172                tmpcur = se;
2173                if (at.isAllocationReversed()) {
2174                    tmpSeqNo -= 1;
2175                } else {
2176                    tmpSeqNo += 1;
2177                }
2178            }
2179        }
2180
2181        as = allocateSection(at, s, ar.getSectionSeqNumber(), nextSection, nextSectionSeqNo, ar.getSectionDirection());
2182        if (as != null) {
2183            as.setAutoTurnoutsResponse(expectedTurnOutStates);
2184        }
2185
2186        if (intermediateSections.size() > 1 && mastHeldAtSection != s) {
2187            Section tmpcur = nextSection;
2188            int tmpSeqNo = nextSectionSeqNo;
2189            int tmpNxtSeqNo = tmpSeqNo;
2190            if (at.isAllocationReversed()) {
2191                tmpNxtSeqNo -= 1;
2192            } else {
2193                tmpNxtSeqNo += 1;
2194            }
2195            //The first section in the list will be the same as the nextSection, so we skip that.
2196            for (int i = 1; i < intermediateSections.size(); i++) {
2197                if (tmpcur == mastHeldAtSection) {
2198                    log.debug("Section is beyond held mast do not allocate any more sections {}", (tmpcur != null ? tmpcur.getDisplayName(USERSYS) : "null"));
2199                    break;
2200                }
2201                Section se = intermediateSections.get(i);
2202                as = allocateSection(at, tmpcur, tmpSeqNo, se, tmpNxtSeqNo, ar.getSectionDirection());
2203                tmpcur = se;
2204                if (at.isAllocationReversed()) {
2205                    tmpSeqNo -= 1;
2206                    tmpNxtSeqNo -= 1;
2207                } else {
2208                    tmpSeqNo += 1;
2209                    tmpNxtSeqNo += 1;
2210                }
2211            }
2212        }
2213        int ix = -1;
2214        for (int i = 0; i < allocationRequests.size(); i++) {
2215            if (ar == allocationRequests.get(i)) {
2216                ix = i;
2217            }
2218        }
2219        if (ix != -1) {
2220            allocationRequests.remove(ix);
2221        }
2222        ar.dispose();
2223        allocationRequestTableModel.fireTableDataChanged();
2224        activeTrainsTableModel.fireTableDataChanged();
2225        if (allocatedSectionTableModel != null) {
2226            allocatedSectionTableModel.fireTableDataChanged();
2227        }
2228        if (extraFrame != null) {
2229            cancelExtraRequested(null);
2230        }
2231        if (_AutoAllocate) {
2232            requestNextAllocation(at);
2233            queueScanOfAllocationRequests();
2234        }
2235        return as;
2236    }
2237
2238    private AllocatedSection allocateSection(ActiveTrain at, Section s, int seqNum, Section nextSection, int nextSectionSeqNo, int direction) {
2239        AllocatedSection as = null;
2240        // allocate the section
2241        as = new AllocatedSection(s, at, seqNum, nextSection, nextSectionSeqNo);
2242        if (_SupportVSDecoder) {
2243            as.addPropertyChangeListener(InstanceManager.getDefault(jmri.jmrit.vsdecoder.VSDecoderManager.class));
2244        }
2245
2246        s.setState(direction/*ar.getSectionDirection()*/);
2247        if (getSignalType() == SIGNALMAST) {
2248            String property = "forwardMast";
2249            if (s.getState() == Section.REVERSE) {
2250                property = "reverseMast";
2251            }
2252            Object smProperty = s.getProperty(property);
2253            if (smProperty != null) {
2254                SignalMast toHold = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(smProperty.toString());
2255                if (toHold != null) {
2256                    if (!toHold.getHeld()) {
2257                        heldMasts.add(new HeldMastDetails(toHold, at));
2258                        toHold.setHeld(true);
2259                    }
2260                }
2261
2262            }
2263
2264            Section lastOccSec = at.getLastAllocatedSection();
2265            if (lastOccSec != null) {
2266                smProperty = lastOccSec.getProperty(property);
2267                if ( smProperty != null) {
2268                    SignalMast toRelease = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(smProperty.toString());
2269                    if (toRelease != null && isMastHeldByDispatcher(toRelease, at)) {
2270                        removeHeldMast(toRelease, at);
2271                        //heldMasts.remove(toRelease);
2272                        toRelease.setHeld(false);
2273                    }
2274                }
2275            }
2276        }
2277        at.addAllocatedSection(as);
2278        allocatedSections.add(as);
2279        log.debug("{}: Allocated section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS));
2280        return as;
2281    }
2282
2283    /**
2284     * Check an active train has an occupied section
2285     * @param at  ActiveTRain object
2286     * @return true / false
2287     */
2288    protected boolean hasTrainAnOccupiedSection(ActiveTrain at) {
2289        for (AllocatedSection asItem : at.getAllocatedSectionList()) {
2290            if (asItem.getSection().getOccupancy() == Section.OCCUPIED) {
2291                return true;
2292            }
2293        }
2294        return false;
2295    }
2296
2297    /**
2298     *
2299     * @param s Section to check
2300     * @param sSeqNum Sequence number of section
2301     * @param nextSection section after
2302     * @param at the active train
2303     * @param prevSection the section before
2304     * @return null if error else a list of the turnouts and their expected states.
2305     */
2306    List<LayoutTrackExpectedState<LayoutTurnout>> checkTurnoutStates(Section s, int sSeqNum, Section nextSection, ActiveTrain at, Section prevSection) {
2307        List<LayoutTrackExpectedState<LayoutTurnout>> turnoutsOK;
2308        if (_AutoTurnouts || at.getAutoRun()) {
2309            // automatically set the turnouts for this section before allocation
2310            turnoutsOK = autoTurnouts.setTurnoutsInSection(s, sSeqNum, nextSection,
2311                    at, _TrustKnownTurnouts, prevSection, _useTurnoutConnectionDelay);
2312        } else {
2313            // check that turnouts are correctly set before allowing allocation to proceed
2314            turnoutsOK = autoTurnouts.checkTurnoutsInSection(s, sSeqNum, nextSection,
2315                    at, prevSection, _useTurnoutConnectionDelay);
2316        }
2317        if (turnoutsOK == null) {
2318            if (_AutoAllocate) {
2319                return turnoutsOK;
2320            } else {
2321                // give the manual dispatcher a chance to override turnouts not OK
2322                int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
2323                        Bundle.getMessage("Question2"), Bundle.getMessage("WarningTitle"),
2324                        JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2325                        new Object[]{Bundle.getMessage("ButtonOverride"), Bundle.getMessage("ButtonNo")},
2326                        Bundle.getMessage("ButtonNo"));
2327                if (selectedValue != 0 ) { // array position 0, override not pressed
2328                    return null;
2329                }
2330                // return empty list
2331                turnoutsOK = new ArrayList<>();
2332            }
2333        }
2334        return turnoutsOK;
2335    }
2336
2337    List<HeldMastDetails> heldMasts = new ArrayList<>();
2338
2339    static class HeldMastDetails {
2340
2341        SignalMast mast = null;
2342        ActiveTrain at = null;
2343
2344        HeldMastDetails(SignalMast sm, ActiveTrain a) {
2345            mast = sm;
2346            at = a;
2347        }
2348
2349        ActiveTrain getActiveTrain() {
2350            return at;
2351        }
2352
2353        SignalMast getMast() {
2354            return mast;
2355        }
2356    }
2357
2358    public boolean isMastHeldByDispatcher(SignalMast sm, ActiveTrain at) {
2359        for (HeldMastDetails hmd : heldMasts) {
2360            if (hmd.getMast() == sm && hmd.getActiveTrain() == at) {
2361                return true;
2362            }
2363        }
2364        return false;
2365    }
2366
2367    private void removeHeldMast(SignalMast sm, ActiveTrain at) {
2368        List<HeldMastDetails> toRemove = new ArrayList<>();
2369        for (HeldMastDetails hmd : heldMasts) {
2370            if (hmd.getActiveTrain() == at) {
2371                if (sm == null) {
2372                    toRemove.add(hmd);
2373                } else if (sm == hmd.getMast()) {
2374                    toRemove.add(hmd);
2375                }
2376            }
2377        }
2378        for (HeldMastDetails hmd : toRemove) {
2379            hmd.getMast().setHeld(false);
2380            heldMasts.remove(hmd);
2381        }
2382    }
2383
2384    /*
2385     * returns a list of level crossings (0 to n) in a section.
2386     */
2387    private List<LevelXing> containedLevelXing(Section s) {
2388        List<LevelXing> _levelXingList = new ArrayList<>();
2389        if (s == null) {
2390            log.error("null argument to 'containsLevelCrossing'");
2391            return _levelXingList;
2392        }
2393
2394        for (var panel : editorManager.getAll(LayoutEditor.class)) {
2395            for (Block blk: s.getBlockList()) {
2396                for (LevelXing temLevelXing: panel.getConnectivityUtil().getLevelCrossingsThisBlock(blk)) {
2397                    // it is returned if the block is in the crossing or connected to the crossing
2398                    // we only need it if it is in the crossing
2399                    if (temLevelXing.getLayoutBlockAC().getBlock() == blk || temLevelXing.getLayoutBlockBD().getBlock() == blk ) {
2400                        _levelXingList.add(temLevelXing);
2401                    }
2402                }
2403            }
2404        }
2405        return _levelXingList;
2406    }
2407
2408    /*
2409     * returns a list of XOvers  (0 to n) in a list of blocks
2410     */
2411    private List<LayoutTurnout> containedXOver( Section s ) {
2412        List<LayoutTurnout> _XOverList = new ArrayList<>();
2413        LayoutBlockManager lbm = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class);
2414        for (var panel : editorManager.getAll(LayoutEditor.class)) {
2415            for (Block blk: s.getBlockList()) {
2416                LayoutBlock lb = lbm.getLayoutBlock(blk);
2417                List<LayoutTurnout> turnoutsInBlock = panel.getConnectivityUtil().getAllTurnoutsThisBlock(lb);
2418                for (LayoutTurnout lt: turnoutsInBlock) {
2419                    if (lt.isTurnoutTypeXover() && !_XOverList.contains(lt)) {
2420                        _XOverList.add(lt);
2421                    }
2422                }
2423            }
2424        }
2425        return _XOverList;
2426    }
2427
2428    /**
2429     * Checks for a block in allocated section, except one
2430     * @param b - The Block
2431     * @param ignoreSection - ignore this section, can be null
2432     * @return true is The Block is being used in a section.
2433     */
2434    protected boolean checkForBlockInAllocatedSection ( Block b, Section ignoreSection ) {
2435        for ( AllocatedSection as : allocatedSections) {
2436            if (ignoreSection == null || as.getSection() != ignoreSection) {
2437                if (as.getSection().getBlockList().contains(b)) {
2438                    return true;
2439                }
2440            }
2441        }
2442        return false;
2443    }
2444
2445    /*
2446     * This is used to determine if the blocks in a section we want to allocate are already allocated to a section, or if they are now free.
2447     */
2448    protected Section checkBlocksNotInAllocatedSection(Section s, AllocationRequest ar) {
2449        ActiveTrain at = null;
2450        if (ar != null) {
2451            at = ar.getActiveTrain();
2452        }
2453        for (AllocatedSection as : allocatedSections) {
2454            if (as.getSection() != s) {
2455                List<Block> blas = as.getSection().getBlockList();
2456                //
2457                // When allocating the initial section for an Active Train,
2458                // we need not be concerned with any blocks in the initial section
2459                // which are unoccupied and to the rear of any occupied blocks in
2460                // the section as the train is not expected to enter those blocks.
2461                // When sections include the OS section these blocks prevented
2462                // allocation.
2463                //
2464                // The procedure is to remove those blocks (for the moment) from
2465                // the blocklist for the section during the initial allocation.
2466                //
2467
2468                List<Block> bls = new ArrayList<>();
2469                if (ar != null && ar.getActiveTrain().getAllocatedSectionList().size() == 0) {
2470                    int j;
2471                    if (ar.getSectionDirection() == Section.FORWARD) {
2472                        j = 0;
2473                        for (int i = 0; i < s.getBlockList().size(); i++) {
2474                            if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) {
2475                                j = 1;
2476                            }
2477                            if (j == 1) {
2478                                bls.add(s.getBlockList().get(i));
2479                            }
2480                        }
2481                    } else {
2482                        j = 0;
2483                        for (int i = s.getBlockList().size() - 1; i >= 0; i--) {
2484                            if (j == 0 && s.getBlockList().get(i).getState() == Block.OCCUPIED) {
2485                                j = 1;
2486                            }
2487                            if (j == 1) {
2488                                bls.add(s.getBlockList().get(i));
2489                            }
2490                        }
2491                    }
2492                } else {
2493                    bls = s.getBlockList();
2494                    // Add Blocks in any XCrossing, dont add ones already in the list
2495                    for ( LevelXing lx: containedLevelXing(s)) {
2496                        Block bAC = lx.getLayoutBlockAC().getBlock();
2497                        Block bBD = lx.getLayoutBlockBD().getBlock();
2498                        if (!bls.contains(bAC)) {
2499                            bls.add(bAC);
2500                        }
2501                        if (!bls.contains(bBD)) {
2502                            bls.add(bBD);
2503                        }
2504                    }
2505                    for (LayoutTurnout lx : containedXOver(s)) {
2506                        if (lx instanceof LayoutDoubleXOver) {
2507                            HashSet<Block> bhs = new HashSet<Block>(4);
2508                            /* quickest way to count number of unique blocks */
2509                            bhs.add(lx.getLayoutBlock().getBlock());
2510                            bhs.add(lx.getLayoutBlockB().getBlock());
2511                            bhs.add(lx.getLayoutBlockC().getBlock());
2512                            bhs.add(lx.getLayoutBlockD().getBlock());
2513                            if (bhs.size() == 4) {
2514                                for (Block b : bhs) {
2515                                    if ( checkBlockInAnyAllocatedSection(b, at)
2516                                            || b.getState() == Block.OCCUPIED) {
2517                                        // the die is cast and switch can not be changed.
2518                                        // Check diagonal. If we are going continuing or divergeing
2519                                        // we need to check the diagonal.
2520                                        if (lx.getTurnout().getKnownState() != Turnout.CLOSED) {
2521                                            if (bls.contains(lx.getLayoutBlock().getBlock()) ||
2522                                                    bls.contains(lx.getLayoutBlockC().getBlock())) {
2523                                                bls.add(lx.getLayoutBlockB().getBlock());
2524                                                bls.add(lx.getLayoutBlockD().getBlock());
2525                                            } else {
2526                                                bls.add(lx.getLayoutBlock().getBlock());
2527                                                bls.add(lx.getLayoutBlockC().getBlock());
2528                                            }
2529                                        }
2530                                    }
2531                                }
2532                            }
2533 /*                     If further processing needed for other crossover types it goes here.
2534                        } else if (lx instanceof LayoutRHXOver) {
2535                        } else if (lx instanceof LayoutLHXOver) {
2536                        } else {
2537*/
2538                        }
2539                    }
2540                }
2541
2542                for (Block b : bls) {
2543                    if (blas.contains(b)) {
2544                        if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) {
2545                            // no clue where the tail is some must assume this block still in use.
2546                            return as.getSection();
2547                        }
2548                        if (as.getActiveTrain().getTrainDetection() == TrainDetection.TRAINDETECTION_HEADANDTAIL) {
2549                            // if this is in the oldest section then we treat as whole train..
2550                            // if there is a section that exited but occupied the tail is there
2551                            for (AllocatedSection tas : allocatedSections) {
2552                                if (tas.getActiveTrain() == as.getActiveTrain() && tas.getExited() && tas.getSection().getOccupancy() == Section.OCCUPIED ) {
2553                                    return as.getSection();
2554                                }
2555                            }
2556                        } else if (at != as.getActiveTrain() && as.getActiveTrain().getTrainDetection() != TrainDetection.TRAINDETECTION_WHOLETRAIN) {
2557                            return as.getSection();
2558                        }
2559                        if (as.getSection().getOccupancy() == Block.OCCUPIED) {
2560                            //The next check looks to see if the block has already been passed or not and therefore ready for allocation.
2561                            if (as.getSection().getState() == Section.FORWARD) {
2562                                for (int i = 0; i < blas.size(); i++) {
2563                                    //The block we get to is occupied therefore the subsequent blocks have not been entered
2564                                    if (blas.get(i).getState() == Block.OCCUPIED) {
2565                                        if (ar != null) {
2566                                            ar.setWaitingOnBlock(b);
2567                                        }
2568                                        return as.getSection();
2569                                    } else if (blas.get(i) == b) {
2570                                        break;
2571                                    }
2572                                }
2573                            } else {
2574                                for (int i = blas.size() - 1; i >= 0; i--) {
2575                                    //The block we get to is occupied therefore the subsequent blocks have not been entered
2576                                    if (blas.get(i).getState() == Block.OCCUPIED) {
2577                                        if (ar != null) {
2578                                            ar.setWaitingOnBlock(b);
2579                                        }
2580                                        return as.getSection();
2581                                    } else if (blas.get(i) == b) {
2582                                        break;
2583                                    }
2584                                }
2585                            }
2586                        } else if (as.getSection().getOccupancy() != Section.FREE) {
2587                            if (ar != null) {
2588                                ar.setWaitingOnBlock(b);
2589                            }
2590                            return as.getSection();
2591                        }
2592                    }
2593                }
2594            }
2595        }
2596        return null;
2597    }
2598
2599    // check if block is being used by anyone else but us
2600    private boolean checkBlockInAnyAllocatedSection(Block b, ActiveTrain at) {
2601        for (AllocatedSection as : allocatedSections) {
2602            if (as.getActiveTrain() != at && as.getSection().getBlockList().contains(b)) {
2603                return true;
2604            }
2605        }
2606        return false;
2607    }
2608
2609    // automatically make a choice of next section
2610    private Section autoChoice(List<Section> sList, AllocationRequest ar, int sectionSeqNo) {
2611        Section tSection = autoAllocate.autoNextSectionChoice(sList, ar, sectionSeqNo);
2612        if (tSection != null) {
2613            return tSection;
2614        }
2615        // if automatic choice failed, ask the dispatcher
2616        return dispatcherChoice(sList, ar);
2617    }
2618
2619    // manually make a choice of next section
2620    private Section dispatcherChoice(List<Section> sList, AllocationRequest ar) {
2621        Object choices[] = new Object[sList.size()];
2622        for (int i = 0; i < sList.size(); i++) {
2623            Section s = sList.get(i);
2624            String txt = s.getDisplayName();
2625            choices[i] = txt;
2626        }
2627        Object secName = JmriJOptionPane.showInputDialog(dispatcherFrame,
2628                Bundle.getMessage("ExplainChoice", ar.getSectionName()),
2629                Bundle.getMessage("ChoiceFrameTitle"), JmriJOptionPane
2630                        .QUESTION_MESSAGE, null, choices, choices[0]);
2631        if (secName == null) {
2632            JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("WarnCancel"));
2633            return sList.get(0);
2634        }
2635        for (int j = 0; j < sList.size(); j++) {
2636            if (secName.equals(choices[j])) {
2637                return sList.get(j);
2638            }
2639        }
2640        return sList.get(0);
2641    }
2642
2643    // submit an AllocationRequest for the next Section of an ActiveTrain
2644    private void requestNextAllocation(ActiveTrain at) {
2645        // set up an Allocation Request
2646        Section next = at.getNextSectionToAllocate();
2647        if (next == null) {
2648            return;
2649        }
2650        int seqNext = at.getNextSectionSeqNumber();
2651        int dirNext = at.getAllocationDirectionFromSectionAndSeq(next, seqNext);
2652        requestAllocation(at, next, dirNext, seqNext, true, dispatcherFrame);
2653    }
2654
2655    /**
2656     * Check if any allocation requests need to be allocated, or if any
2657     * allocated sections need to be released
2658     */
2659    protected void checkAutoRelease() {
2660        if (_AutoRelease) {
2661            // Auto release of exited sections has been requested - because of possible noise in block detection
2662            //    hardware, allocated sections are automatically released in the order they were allocated only
2663            // Only unoccupied sections that have been exited are tested.
2664            // The next allocated section must be assigned to the same train, and it must have been entered for
2665            //    the exited Section to be released.
2666            // Extra allocated sections are not automatically released (allocation number = -1).
2667            boolean foundOne = true;
2668            while ((allocatedSections.size() > 0) && foundOne) {
2669                try {
2670                    foundOne = false;
2671                    AllocatedSection as = null;
2672                    for (int i = 0; (i < allocatedSections.size()) && !foundOne; i++) {
2673                        as = allocatedSections.get(i);
2674                        if (as.getExited() && (as.getSection().getOccupancy() != Section.OCCUPIED)
2675                                && (as.getAllocationNumber() != -1)) {
2676                            // possible candidate for deallocation - check order
2677                            foundOne = true;
2678                            for (int j = 0; (j < allocatedSections.size()) && foundOne; j++) {
2679                                if (j != i) {
2680                                    AllocatedSection asx = allocatedSections.get(j);
2681                                    if ((asx.getActiveTrain() == as.getActiveTrain())
2682                                            && (asx.getAllocationNumber() != -1)
2683                                            && (asx.getAllocationNumber() < as.getAllocationNumber())) {
2684                                        foundOne = false;
2685                                    }
2686                                }
2687                            }
2688
2689                            // The train must have one occupied section.
2690                            // The train may be sitting in one of its allocated section undetected.
2691                            if ( foundOne && !hasTrainAnOccupiedSection(as.getActiveTrain())) {
2692                                log.warn("[{}]:CheckAutoRelease release section [{}] failed, train has no occupied section",
2693                                        as.getActiveTrain().getActiveTrainName(),as.getSectionName());
2694                                foundOne = false;
2695                            }
2696
2697                            if (foundOne) {
2698                                // check its not the last allocated section
2699                                int allocatedCount = 0;
2700                                for (int j = 0; (j < allocatedSections.size()); j++) {
2701                                    AllocatedSection asx = allocatedSections.get(j);
2702                                    if (asx.getActiveTrain() == as.getActiveTrain()) {
2703                                            allocatedCount++ ;
2704                                    }
2705                                }
2706                                if (allocatedCount == 1) {
2707                                    foundOne = false;
2708                                }
2709                            }
2710                            if (foundOne) {
2711                                // check if the next section is allocated to the same train and has been entered
2712                                ActiveTrain at = as.getActiveTrain();
2713                                Section ns = as.getNextSection();
2714                                AllocatedSection nas = null;
2715                                for (int k = 0; (k < allocatedSections.size()) && (nas == null); k++) {
2716                                    if (allocatedSections.get(k).getSection() == ns) {
2717                                        nas = allocatedSections.get(k);
2718                                    }
2719                                }
2720                                if ((nas == null) || (at.getStatus() == ActiveTrain.WORKING)
2721                                        || (at.getStatus() == ActiveTrain.STOPPED)
2722                                        || (at.getStatus() == ActiveTrain.READY)
2723                                        || (at.getMode() == ActiveTrain.MANUAL)) {
2724                                    // do not autorelease allocated sections from an Active Train that is
2725                                    //    STOPPED, READY, or WORKING, or is in MANUAL mode.
2726                                    foundOne = false;
2727                                    //But do so if the active train has reached its restart point
2728                                    if (nas != null && at.reachedRestartPoint()) {
2729                                        foundOne = true;
2730                                    }
2731                                } else {
2732                                    if ((nas.getActiveTrain() != as.getActiveTrain()) || (!nas.getEntered())) {
2733                                        foundOne = false;
2734                                    }
2735                                }
2736                                foundOne = sectionNotRequiredByHeadOnly(foundOne,at,as);
2737                                if (foundOne) {
2738                                    log.debug("{}: releasing section [{}]", at.getTrainName(), as.getSection().getDisplayName(USERSYS));
2739                                    doReleaseAllocatedSection(as, false);
2740                                }
2741                            }
2742                        }
2743                    }
2744                } catch (RuntimeException e) {
2745                    log.warn("checkAutoRelease failed  - maybe the AllocatedSection was removed due to a terminating train? {}", e.toString());
2746                    continue;
2747                }
2748            }
2749        }
2750        if (_AutoAllocate) {
2751            queueScanOfAllocationRequests();
2752        }
2753    }
2754
2755    /*
2756     * Check whether the section is in use by a "Head Only" train and can be released.
2757     * calculate the length of exited sections, subtract the length of section
2758     * being released. If the train is moving do not include the length of the occupied section,
2759     * if the train is stationary and was stopped by sensor or speed profile include the length
2760     * of the occupied section. This is done as we dont know where the train is in the section block.
2761     */
2762    private boolean sectionNotRequiredByHeadOnly(boolean foundOne, ActiveTrain at, AllocatedSection as) {
2763        if (at.getAutoActiveTrain() != null && at.getTrainDetection() == TrainDetection.TRAINDETECTION_HEADONLY) {
2764            long allocatedLengthMM = 0;
2765            for (AllocatedSection tas : at.getAllocatedSectionList()) {
2766                if (tas.getSection().getOccupancy() == Section.OCCUPIED) {
2767                    if (at.getAutoActiveTrain().getAutoEngineer().isStopped() &&
2768                            (at.getAutoActiveTrain().getStopBySpeedProfile() ||
2769                                    tas.getSection().getForwardStoppingSensor() != null ||
2770                                    tas.getSection().getReverseStoppingSensor() != null)) {
2771                        allocatedLengthMM += tas.getSection().getActualLength();
2772                        log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] including in length.",
2773                                at.getTrainName(),tas.getSection().getDisplayName());
2774                        break;
2775                    } else {
2776                        log.debug("{}: sectionNotRequiredByHeadOnly Stopping at Secion [{}] excluding from length.",
2777                                at.getTrainName(),tas.getSection().getDisplayName());
2778                        break;
2779                    }
2780                }
2781                if (tas.getExited()) {
2782                    allocatedLengthMM += tas.getSection().getActualLength();
2783                }
2784            }
2785            long trainLengthMM = at.getAutoActiveTrain().getMaxTrainLengthMM();
2786            long releaseLengthMM = as.getSection().getActualLength();
2787            log.debug("[{}]:Release Section [{}] by Length allocated [{}] release [{}] train [{}]",
2788                    at.getTrainName(), as.getSectionName(), allocatedLengthMM, releaseLengthMM, trainLengthMM);
2789            if ((allocatedLengthMM - releaseLengthMM) < trainLengthMM) {
2790                return (false);
2791            }
2792        }
2793        return (true);
2794    }
2795
2796    /**
2797     * Releases an allocated Section, and removes it from the Dispatcher Input.
2798     *
2799     * @param as               the section to release
2800     * @param terminatingTrain true if the associated train is being terminated;
2801     *                         false otherwise
2802     */
2803    public void releaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2804        // Unless the train is termination it must have one occupied section.
2805        // The train may be sitting in an allocated section undetected.
2806        if ( !terminatingTrain && !hasTrainAnOccupiedSection(as.getActiveTrain())) {
2807                log.warn("[{}]: releaseAllocatedSection release section [{}] failed train has no occupied section",as.getActiveTrain().getActiveTrainName(),as.getSectionName());
2808            return;
2809        }
2810        if (_AutoAllocate ) {
2811            autoAllocate.scanAllocationRequests(new TaskAllocateRelease(TaskAction.RELEASE_ONE,as,terminatingTrain));
2812        } else {
2813            doReleaseAllocatedSection( as,  terminatingTrain);
2814        }
2815    }
2816    protected void doReleaseAllocatedSection(AllocatedSection as, boolean terminatingTrain) {
2817        // check that section is not occupied if not terminating train
2818        if (!terminatingTrain && (as.getSection().getOccupancy() == Section.OCCUPIED)) {
2819            // warn the manual dispatcher that Allocated Section is occupied
2820            int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame, java.text.MessageFormat.format(
2821                    Bundle.getMessage("Question5"), new Object[]{as.getSectionName()}), Bundle.getMessage("WarningTitle"),
2822                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
2823                    new Object[]{Bundle.getMessage("ButtonRelease"), Bundle.getMessage("ButtonNo")},
2824                    Bundle.getMessage("ButtonNo"));
2825            if (selectedValue != 0 ) { // array position 0, release not pressed
2826                return;   // return without releasing if "No" or "Cancel" response
2827            }
2828        }
2829        // release the Allocated Section
2830        for (int i = allocatedSections.size(); i > 0; i--) {
2831            if (as == allocatedSections.get(i - 1)) {
2832                allocatedSections.remove(i - 1);
2833            }
2834        }
2835        as.getSection().setState(Section.FREE);
2836        as.getActiveTrain().removeAllocatedSection(as);
2837        as.dispose();
2838        if (allocatedSectionTableModel != null) {
2839            allocatedSectionTableModel.fireTableDataChanged();
2840        }
2841        allocationRequestTableModel.fireTableDataChanged();
2842        activeTrainsTableModel.fireTableDataChanged();
2843        if (_AutoAllocate) {
2844            queueScanOfAllocationRequests();
2845        }
2846    }
2847
2848    /**
2849     * Updates display when occupancy of an allocated section changes Also
2850     * drives auto release if it is selected
2851     */
2852    public void sectionOccupancyChanged() {
2853        queueReleaseOfCompletedAllocations();
2854        if (allocatedSectionTableModel != null) {
2855            allocatedSectionTableModel.fireTableDataChanged();
2856        }
2857        allocationRequestTableModel.fireTableDataChanged();
2858    }
2859
2860    /**
2861     * Handle activity that is triggered by the fast clock
2862     */
2863    protected void newFastClockMinute() {
2864        for (int i = delayedTrains.size() - 1; i >= 0; i--) {
2865            ActiveTrain at = delayedTrains.get(i);
2866            // check if this Active Train is waiting to start
2867            if ((!at.getStarted()) && at.getDelayedStart() != ActiveTrain.NODELAY) {
2868                // is it time to start?
2869                if (at.getDelayedStart() == ActiveTrain.TIMEDDELAY) {
2870                    if (isFastClockTimeGE(at.getDepartureTimeHr(), at.getDepartureTimeMin())) {
2871                        // allow this train to start
2872                        at.setStarted();
2873                        delayedTrains.remove(i);
2874                    }
2875                }
2876            } else if (at.getStarted() && at.getStatus() == ActiveTrain.READY && at.reachedRestartPoint()) {
2877                if (isFastClockTimeGE(at.getRestartDepartHr(), at.getRestartDepartMin())) {
2878                    at.restart();
2879                    delayedTrains.remove(i);
2880                }
2881            }
2882        }
2883        if (_AutoAllocate) {
2884            queueScanOfAllocationRequests();
2885        }
2886    }
2887
2888    /**
2889     * This method tests time
2890     *
2891     * @param hr  the hour to test against (0-23)
2892     * @param min the minute to test against (0-59)
2893     * @return true if fast clock time and tested time are the same
2894     */
2895    public boolean isFastClockTimeGE(int hr, int min) {
2896        Calendar now = Calendar.getInstance();
2897        now.setTime(fastClock.getTime());
2898        int nowHours = now.get(Calendar.HOUR_OF_DAY);
2899        int nowMinutes = now.get(Calendar.MINUTE);
2900        return ((nowHours * 60) + nowMinutes) == ((hr * 60) + min);
2901    }
2902
2903    // option access methods
2904    protected LayoutEditor getLayoutEditor() {
2905        return _LE;
2906    }
2907
2908    protected void setLayoutEditor(LayoutEditor editor) {
2909        _LE = editor;
2910    }
2911
2912    protected boolean getUseConnectivity() {
2913        return _UseConnectivity;
2914    }
2915
2916    protected void setUseConnectivity(boolean set) {
2917        _UseConnectivity = set;
2918    }
2919
2920    protected void setSignalType(int type) {
2921        _SignalType = type;
2922    }
2923
2924    protected int getSignalType() {
2925        return _SignalType;
2926    }
2927
2928    protected String getSignalTypeString() {
2929        switch (_SignalType) {
2930            case SIGNALHEAD:
2931                return Bundle.getMessage("SignalType1");
2932            case SIGNALMAST:
2933                return Bundle.getMessage("SignalType2");
2934            case SECTIONSALLOCATED:
2935                return Bundle.getMessage("SignalType3");
2936            default:
2937                return "Unknown";
2938        }
2939    }
2940
2941    protected void setStoppingSpeedName(String speedName) {
2942        _StoppingSpeedName = speedName;
2943    }
2944
2945    protected String getStoppingSpeedName() {
2946        return _StoppingSpeedName;
2947    }
2948
2949    protected float getMaximumLineSpeed() {
2950        return maximumLineSpeed;
2951    }
2952
2953    protected void setTrainsFrom(TrainsFrom value ) {
2954        _TrainsFrom = value;
2955    }
2956
2957    protected TrainsFrom getTrainsFrom() {
2958        return _TrainsFrom;
2959    }
2960
2961    protected boolean getAutoAllocate() {
2962        return _AutoAllocate;
2963    }
2964
2965    protected boolean getAutoRelease() {
2966        return _AutoRelease;
2967    }
2968
2969    protected void stopStartAutoAllocateRelease() {
2970        if (_AutoAllocate || _AutoRelease) {
2971            if (editorManager.getAll(LayoutEditor.class).size() > 0) {
2972                if (autoAllocate == null) {
2973                    autoAllocate = new AutoAllocate(this,allocationRequests);
2974                    autoAllocateThread = jmri.util.ThreadingUtil.newThread(autoAllocate, "Auto Allocator ");
2975                    autoAllocateThread.start();
2976                }
2977            } else {
2978                JmriJOptionPane.showMessageDialog(dispatcherFrame, Bundle.getMessage("Error39"),
2979                        Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
2980                _AutoAllocate = false;
2981                if (autoAllocateBox != null) {
2982                    autoAllocateBox.setSelected(_AutoAllocate);
2983                }
2984                return;
2985            }
2986        } else {
2987            //no need for autoallocateRelease
2988            if (autoAllocate != null) {
2989                autoAllocate.setAbort();
2990                autoAllocate = null;
2991            }
2992        }
2993
2994    }
2995    protected void setAutoAllocate(boolean set) {
2996        _AutoAllocate = set;
2997        stopStartAutoAllocateRelease();
2998        if (autoAllocateBox != null) {
2999            autoAllocateBox.setSelected(_AutoAllocate);
3000        }
3001    }
3002
3003    protected void setAutoRelease(boolean set) {
3004        _AutoRelease = set;
3005        stopStartAutoAllocateRelease();
3006        if (autoReleaseBox != null) {
3007            autoReleaseBox.setSelected(_AutoAllocate);
3008        }
3009    }
3010
3011    protected AutoTurnouts getAutoTurnoutsHelper () {
3012        return autoTurnouts;
3013    }
3014
3015    protected boolean getAutoTurnouts() {
3016        return _AutoTurnouts;
3017    }
3018
3019    protected void setAutoTurnouts(boolean set) {
3020        _AutoTurnouts = set;
3021    }
3022
3023    protected boolean getTrustKnownTurnouts() {
3024        return _TrustKnownTurnouts;
3025    }
3026
3027    protected void setTrustKnownTurnouts(boolean set) {
3028        _TrustKnownTurnouts = set;
3029    }
3030
3031    protected boolean getUseOccupiedTrackSpeed() {
3032        return _UseOccupiedTrackSpeed;
3033    }
3034
3035    protected void setUseOccupiedTrackSpeed(boolean set) {
3036        _UseOccupiedTrackSpeed = set;
3037    }
3038
3039    protected boolean getUseTurnoutConnectionDelay() {
3040        return _useTurnoutConnectionDelay;
3041    }
3042
3043    protected void setUseTurnoutConnectionDelay(boolean set) {
3044        _useTurnoutConnectionDelay = set;
3045    }
3046
3047    protected int getMinThrottleInterval() {
3048        return _MinThrottleInterval;
3049    }
3050
3051    protected void setMinThrottleInterval(int set) {
3052        _MinThrottleInterval = set;
3053    }
3054
3055    protected int getFullRampTime() {
3056        return _FullRampTime;
3057    }
3058
3059    protected void setFullRampTime(int set) {
3060        _FullRampTime = set;
3061    }
3062
3063    protected boolean getHasOccupancyDetection() {
3064        return _HasOccupancyDetection;
3065    }
3066
3067    protected void setHasOccupancyDetection(boolean set) {
3068        _HasOccupancyDetection = set;
3069    }
3070
3071    protected boolean getSetSSLDirectionalSensors() {
3072        return _SetSSLDirectionalSensors;
3073    }
3074
3075    protected void setSetSSLDirectionalSensors(boolean set) {
3076        _SetSSLDirectionalSensors = set;
3077    }
3078
3079    protected boolean getUseScaleMeters() {
3080        return _UseScaleMeters;
3081    }
3082
3083    protected void setUseScaleMeters(boolean set) {
3084        _UseScaleMeters = set;
3085    }
3086
3087    protected boolean getShortActiveTrainNames() {
3088        return _ShortActiveTrainNames;
3089    }
3090
3091    protected void setShortActiveTrainNames(boolean set) {
3092        _ShortActiveTrainNames = set;
3093        if (allocatedSectionTableModel != null) {
3094            allocatedSectionTableModel.fireTableDataChanged();
3095        }
3096        if (allocationRequestTableModel != null) {
3097            allocationRequestTableModel.fireTableDataChanged();
3098        }
3099    }
3100
3101    protected boolean getShortNameInBlock() {
3102        return _ShortNameInBlock;
3103    }
3104
3105    protected void setShortNameInBlock(boolean set) {
3106        _ShortNameInBlock = set;
3107    }
3108
3109    protected boolean getRosterEntryInBlock() {
3110        return _RosterEntryInBlock;
3111    }
3112
3113    protected void setRosterEntryInBlock(boolean set) {
3114        _RosterEntryInBlock = set;
3115    }
3116
3117    protected boolean getExtraColorForAllocated() {
3118        return _ExtraColorForAllocated;
3119    }
3120
3121    protected void setExtraColorForAllocated(boolean set) {
3122        _ExtraColorForAllocated = set;
3123    }
3124
3125    protected boolean getNameInAllocatedBlock() {
3126        return _NameInAllocatedBlock;
3127    }
3128
3129    protected void setNameInAllocatedBlock(boolean set) {
3130        _NameInAllocatedBlock = set;
3131    }
3132
3133    public Scale getScale() {
3134        return _LayoutScale;
3135    }
3136
3137    protected void setScale(Scale sc) {
3138        _LayoutScale = sc;
3139    }
3140
3141    public List<ActiveTrain> getActiveTrainsList() {
3142        return activeTrainsList;
3143    }
3144
3145    protected List<AllocatedSection> getAllocatedSectionsList() {
3146        return allocatedSections;
3147    }
3148
3149    public ActiveTrain getActiveTrainForRoster(RosterEntry re) {
3150        if ( _TrainsFrom != TrainsFrom.TRAINSFROMROSTER) {
3151            return null;
3152        }
3153        for (ActiveTrain at : activeTrainsList) {
3154            if (at.getRosterEntry().equals(re)) {
3155                return at;
3156            }
3157        }
3158        return null;
3159
3160    }
3161
3162    protected boolean getSupportVSDecoder() {
3163        return _SupportVSDecoder;
3164    }
3165
3166    protected void setSupportVSDecoder(boolean set) {
3167        _SupportVSDecoder = set;
3168    }
3169
3170    // called by ActivateTrainFrame after a new train is all set up
3171    //      Dispatcher side of activating a new train should be completed here
3172    // Jay Janzen protection changed to public for access via scripting
3173    public void newTrainDone(ActiveTrain at) {
3174        if (at != null) {
3175            // a new active train was created, check for delayed start
3176            if (at.getDelayedStart() != ActiveTrain.NODELAY && (!at.getStarted())) {
3177                delayedTrains.add(at);
3178                fastClockWarn(true);
3179            } // djd needs work here
3180            // check for delayed restart
3181            else if (at.getDelayedRestart() == ActiveTrain.TIMEDDELAY) {
3182                fastClockWarn(false);
3183            }
3184        }
3185        if (atFrame != null) {
3186            ThreadingUtil.runOnGUI( () -> atFrame.setVisible(false));
3187            atFrame.dispose();
3188            atFrame = null;
3189        }
3190        newTrainActive = false;
3191    }
3192
3193    protected void removeDelayedTrain(ActiveTrain at) {
3194        delayedTrains.remove(at);
3195    }
3196
3197    private void fastClockWarn(boolean wMess) {
3198        if (fastClockSensor.getState() == Sensor.ACTIVE) {
3199            return;
3200        }
3201        // warn that the fast clock is not running
3202        String mess = "";
3203        if (wMess) {
3204            mess = Bundle.getMessage("FastClockWarn");
3205        } else {
3206            mess = Bundle.getMessage("FastClockWarn2");
3207        }
3208        int selectedValue = JmriJOptionPane.showOptionDialog(dispatcherFrame,
3209                mess, Bundle.getMessage("WarningTitle"),
3210                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
3211                new Object[]{Bundle.getMessage("ButtonYesStart"), Bundle.getMessage("ButtonNo")},
3212                Bundle.getMessage("ButtonNo"));
3213        if (selectedValue == 0) {
3214            try {
3215                fastClockSensor.setState(Sensor.ACTIVE);
3216            } catch (jmri.JmriException reason) {
3217                log.error("Exception when setting fast clock sensor");
3218            }
3219        }
3220    }
3221
3222    // Jay Janzen
3223    // Protection changed to public to allow access via scripting
3224    public AutoTrainsFrame getAutoTrainsFrame() {
3225        return _autoTrainsFrame;
3226    }
3227
3228    /**
3229     * Table model for Active Trains Table in Dispatcher window
3230     */
3231    public class ActiveTrainsTableModel extends javax.swing.table.AbstractTableModel implements
3232            java.beans.PropertyChangeListener {
3233
3234        public static final int TRANSIT_COLUMN = 0;
3235        public static final int TRANSIT_COLUMN_U = 1;
3236        public static final int TRAIN_COLUMN = 2;
3237        public static final int TYPE_COLUMN = 3;
3238        public static final int STATUS_COLUMN = 4;
3239        public static final int MODE_COLUMN = 5;
3240        public static final int ALLOCATED_COLUMN = 6;
3241        public static final int ALLOCATED_COLUMN_U = 7;
3242        public static final int NEXTSECTION_COLUMN = 8;
3243        public static final int NEXTSECTION_COLUMN_U = 9;
3244        public static final int ALLOCATEBUTTON_COLUMN = 10;
3245        public static final int TERMINATEBUTTON_COLUMN = 11;
3246        public static final int RESTARTCHECKBOX_COLUMN = 12;
3247        public static final int ISAUTO_COLUMN = 13;
3248        public static final int CURRENTSIGNAL_COLUMN = 14;
3249        public static final int CURRENTSIGNAL_COLUMN_U = 15;
3250        public static final int DCC_ADDRESS = 16;
3251        public static final int MAX_COLUMN = 16;
3252        public ActiveTrainsTableModel() {
3253            super();
3254        }
3255
3256        @Override
3257        public void propertyChange(java.beans.PropertyChangeEvent e) {
3258            if (e.getPropertyName().equals("length")) {
3259                fireTableDataChanged();
3260            }
3261        }
3262
3263        @Override
3264        public Class<?> getColumnClass(int col) {
3265            switch (col) {
3266                case ALLOCATEBUTTON_COLUMN:
3267                case TERMINATEBUTTON_COLUMN:
3268                    return JButton.class;
3269                case RESTARTCHECKBOX_COLUMN:
3270                case ISAUTO_COLUMN:
3271                    return Boolean.class;
3272                default:
3273                    return String.class;
3274            }
3275        }
3276
3277        @Override
3278        public int getColumnCount() {
3279            return MAX_COLUMN + 1;
3280        }
3281
3282        @Override
3283        public int getRowCount() {
3284            return (activeTrainsList.size());
3285        }
3286
3287        @Override
3288        public boolean isCellEditable(int row, int col) {
3289            switch (col) {
3290                case ALLOCATEBUTTON_COLUMN:
3291                case TERMINATEBUTTON_COLUMN:
3292                case RESTARTCHECKBOX_COLUMN:
3293                    return (true);
3294                default:
3295                    return (false);
3296            }
3297        }
3298
3299        @Override
3300        public String getColumnName(int col) {
3301            switch (col) {
3302                case TRANSIT_COLUMN:
3303                    return Bundle.getMessage("TransitColumnSysTitle");
3304                case TRANSIT_COLUMN_U:
3305                    return Bundle.getMessage("TransitColumnTitle");
3306                case TRAIN_COLUMN:
3307                    return Bundle.getMessage("TrainColumnTitle");
3308                case TYPE_COLUMN:
3309                    return Bundle.getMessage("TrainTypeColumnTitle");
3310                case STATUS_COLUMN:
3311                    return Bundle.getMessage("TrainStatusColumnTitle");
3312                case MODE_COLUMN:
3313                    return Bundle.getMessage("TrainModeColumnTitle");
3314                case ALLOCATED_COLUMN:
3315                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
3316                case ALLOCATED_COLUMN_U:
3317                    return Bundle.getMessage("AllocatedSectionColumnTitle");
3318                case NEXTSECTION_COLUMN:
3319                    return Bundle.getMessage("NextSectionColumnSysTitle");
3320                case NEXTSECTION_COLUMN_U:
3321                    return Bundle.getMessage("NextSectionColumnTitle");
3322                case RESTARTCHECKBOX_COLUMN:
3323                    return(Bundle.getMessage("AutoRestartColumnTitle"));
3324                case ALLOCATEBUTTON_COLUMN:
3325                    return(Bundle.getMessage("AllocateButton"));
3326                case TERMINATEBUTTON_COLUMN:
3327                    return(Bundle.getMessage("TerminateTrain"));
3328                case ISAUTO_COLUMN:
3329                    return(Bundle.getMessage("AutoColumnTitle"));
3330                case CURRENTSIGNAL_COLUMN:
3331                    return(Bundle.getMessage("CurrentSignalSysColumnTitle"));
3332                case CURRENTSIGNAL_COLUMN_U:
3333                    return(Bundle.getMessage("CurrentSignalColumnTitle"));
3334                case DCC_ADDRESS:
3335                    return(Bundle.getMessage("DccColumnTitleColumnTitle"));
3336                default:
3337                    return "";
3338            }
3339        }
3340
3341        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
3342                                justification="better to keep cases in column order rather than to combine")
3343        public int getPreferredWidth(int col) {
3344            switch (col) {
3345                case TRANSIT_COLUMN:
3346                case TRANSIT_COLUMN_U:
3347                case TRAIN_COLUMN:
3348                    return new JTextField(17).getPreferredSize().width;
3349                case TYPE_COLUMN:
3350                    return new JTextField(16).getPreferredSize().width;
3351                case STATUS_COLUMN:
3352                    return new JTextField(8).getPreferredSize().width;
3353                case MODE_COLUMN:
3354                    return new JTextField(11).getPreferredSize().width;
3355                case ALLOCATED_COLUMN:
3356                case ALLOCATED_COLUMN_U:
3357                    return new JTextField(17).getPreferredSize().width;
3358                case NEXTSECTION_COLUMN:
3359                case NEXTSECTION_COLUMN_U:
3360                    return new JTextField(17).getPreferredSize().width;
3361                case ALLOCATEBUTTON_COLUMN:
3362                case TERMINATEBUTTON_COLUMN:
3363                case RESTARTCHECKBOX_COLUMN:
3364                case ISAUTO_COLUMN:
3365                case CURRENTSIGNAL_COLUMN:
3366                case CURRENTSIGNAL_COLUMN_U:
3367                case DCC_ADDRESS:
3368                    return new JTextField(5).getPreferredSize().width;
3369                default:
3370                    // fall through
3371                    break;
3372            }
3373            return new JTextField(5).getPreferredSize().width;
3374        }
3375
3376        @Override
3377        public Object getValueAt(int r, int c) {
3378            int rx = r;
3379            if (rx >= activeTrainsList.size()) {
3380                return null;
3381            }
3382            ActiveTrain at = activeTrainsList.get(rx);
3383            switch (c) {
3384                case TRANSIT_COLUMN:
3385                    return (at.getTransit().getSystemName());
3386                case TRANSIT_COLUMN_U:
3387                    if (at.getTransit() != null && at.getTransit().getUserName() != null) {
3388                        return (at.getTransit().getUserName());
3389                    } else {
3390                        return "";
3391                    }
3392                case TRAIN_COLUMN:
3393                    return (at.getTrainName());
3394                case TYPE_COLUMN:
3395                    return (at.getTrainTypeText());
3396                case STATUS_COLUMN:
3397                    return (at.getStatusText());
3398                case MODE_COLUMN:
3399                    return (at.getModeText());
3400                case ALLOCATED_COLUMN:
3401                    if (at.getLastAllocatedSection() != null) {
3402                        return (at.getLastAllocatedSection().getSystemName());
3403                    } else {
3404                        return "<none>";
3405                    }
3406                case ALLOCATED_COLUMN_U:
3407                    if (at.getLastAllocatedSection() != null && at.getLastAllocatedSection().getUserName() != null) {
3408                        return (at.getLastAllocatedSection().getUserName());
3409                    } else {
3410                        return "<none>";
3411                    }
3412                case NEXTSECTION_COLUMN:
3413                    if (at.getNextSectionToAllocate() != null) {
3414                        return (at.getNextSectionToAllocate().getSystemName());
3415                    } else {
3416                        return "<none>";
3417                    }
3418                case NEXTSECTION_COLUMN_U:
3419                    if (at.getNextSectionToAllocate() != null && at.getNextSectionToAllocate().getUserName() != null) {
3420                        return (at.getNextSectionToAllocate().getUserName());
3421                    } else {
3422                        return "<none>";
3423                    }
3424                case ALLOCATEBUTTON_COLUMN:
3425                    return Bundle.getMessage("AllocateButtonName");
3426                case TERMINATEBUTTON_COLUMN:
3427                    return Bundle.getMessage("TerminateTrain");
3428                case RESTARTCHECKBOX_COLUMN:
3429                    return at.getResetWhenDone();
3430                case ISAUTO_COLUMN:
3431                    return at.getAutoRun();
3432                case CURRENTSIGNAL_COLUMN:
3433                    if (at.getAutoRun()) {
3434                        return(at.getAutoActiveTrain().getCurrentSignal());
3435                    } else {
3436                        return("NA");
3437                    }
3438                case CURRENTSIGNAL_COLUMN_U:
3439                    if (at.getAutoRun()) {
3440                        return(at.getAutoActiveTrain().getCurrentSignalUserName());
3441                    } else {
3442                        return("NA");
3443                    }
3444                case DCC_ADDRESS:
3445                    if (at.getDccAddress() != null) {
3446                        return(at.getDccAddress());
3447                    } else {
3448                        return("NA");
3449                    }
3450                default:
3451                    return (" ");
3452            }
3453        }
3454
3455        @Override
3456        public void setValueAt(Object value, int row, int col) {
3457            if (col == ALLOCATEBUTTON_COLUMN) {
3458                // open an allocate window
3459                allocateNextRequested(row);
3460            }
3461            if (col == TERMINATEBUTTON_COLUMN) {
3462                if (activeTrainsList.get(row) != null) {
3463                    terminateActiveTrain(activeTrainsList.get(row),true,false);
3464                }
3465            }
3466            if (col == RESTARTCHECKBOX_COLUMN) {
3467                ActiveTrain at = null;
3468                at = activeTrainsList.get(row);
3469                if (activeTrainsList.get(row) != null) {
3470                    if (!at.getResetWhenDone()) {
3471                        at.setResetWhenDone(true);
3472                        return;
3473                    }
3474                    at.setResetWhenDone(false);
3475                    for (int j = restartingTrainsList.size(); j > 0; j--) {
3476                        if (restartingTrainsList.get(j - 1) == at) {
3477                            restartingTrainsList.remove(j - 1);
3478                            return;
3479                        }
3480                    }
3481                }
3482            }
3483        }
3484    }
3485
3486    /**
3487     * Table model for Allocation Request Table in Dispatcher window
3488     */
3489    public class AllocationRequestTableModel extends javax.swing.table.AbstractTableModel implements
3490            java.beans.PropertyChangeListener {
3491
3492        public static final int TRANSIT_COLUMN = 0;
3493        public static final int TRANSIT_COLUMN_U = 1;
3494        public static final int TRAIN_COLUMN = 2;
3495        public static final int PRIORITY_COLUMN = 3;
3496        public static final int TRAINTYPE_COLUMN = 4;
3497        public static final int SECTION_COLUMN = 5;
3498        public static final int SECTION_COLUMN_U = 6;
3499        public static final int STATUS_COLUMN = 7;
3500        public static final int OCCUPANCY_COLUMN = 8;
3501        public static final int SECTIONLENGTH_COLUMN = 9;
3502        public static final int ALLOCATEBUTTON_COLUMN = 10;
3503        public static final int CANCELBUTTON_COLUMN = 11;
3504        public static final int MAX_COLUMN = 11;
3505
3506        public AllocationRequestTableModel() {
3507            super();
3508        }
3509
3510        @Override
3511        public void propertyChange(java.beans.PropertyChangeEvent e) {
3512            if (e.getPropertyName().equals("length")) {
3513                fireTableDataChanged();
3514            }
3515        }
3516
3517        @Override
3518        public Class<?> getColumnClass(int c) {
3519            if (c == CANCELBUTTON_COLUMN) {
3520                return JButton.class;
3521            }
3522            if (c == ALLOCATEBUTTON_COLUMN) {
3523                return JButton.class;
3524            }
3525            //if (c == CANCELRESTART_COLUMN) {
3526            //    return JButton.class;
3527            //}
3528            return String.class;
3529        }
3530
3531        @Override
3532        public int getColumnCount() {
3533            return MAX_COLUMN + 1;
3534        }
3535
3536        @Override
3537        public int getRowCount() {
3538            return (allocationRequests.size());
3539        }
3540
3541        @Override
3542        public boolean isCellEditable(int r, int c) {
3543            if (c == CANCELBUTTON_COLUMN) {
3544                return (true);
3545            }
3546            if (c == ALLOCATEBUTTON_COLUMN) {
3547                return (true);
3548            }
3549            return (false);
3550        }
3551
3552        @Override
3553        public String getColumnName(int col) {
3554            switch (col) {
3555                case TRANSIT_COLUMN:
3556                    return Bundle.getMessage("TransitColumnSysTitle");
3557                case TRANSIT_COLUMN_U:
3558                    return Bundle.getMessage("TransitColumnTitle");
3559                case TRAIN_COLUMN:
3560                    return Bundle.getMessage("TrainColumnTitle");
3561                case PRIORITY_COLUMN:
3562                    return Bundle.getMessage("PriorityLabel");
3563                case TRAINTYPE_COLUMN:
3564                    return Bundle.getMessage("TrainTypeColumnTitle");
3565                case SECTION_COLUMN:
3566                    return Bundle.getMessage("SectionColumnSysTitle");
3567                case SECTION_COLUMN_U:
3568                    return Bundle.getMessage("SectionColumnTitle");
3569                case STATUS_COLUMN:
3570                    return Bundle.getMessage("StatusColumnTitle");
3571                case OCCUPANCY_COLUMN:
3572                    return Bundle.getMessage("OccupancyColumnTitle");
3573                case SECTIONLENGTH_COLUMN:
3574                    return Bundle.getMessage("SectionLengthColumnTitle");
3575                case ALLOCATEBUTTON_COLUMN:
3576                    return Bundle.getMessage("AllocateButton");
3577                case CANCELBUTTON_COLUMN:
3578                    return Bundle.getMessage("ButtonCancel");
3579                default:
3580                    return "";
3581            }
3582        }
3583
3584        public int getPreferredWidth(int col) {
3585            switch (col) {
3586                case TRANSIT_COLUMN:
3587                case TRANSIT_COLUMN_U:
3588                case TRAIN_COLUMN:
3589                    return new JTextField(17).getPreferredSize().width;
3590                case PRIORITY_COLUMN:
3591                    return new JTextField(8).getPreferredSize().width;
3592                case TRAINTYPE_COLUMN:
3593                    return new JTextField(15).getPreferredSize().width;
3594                case SECTION_COLUMN:
3595                    return new JTextField(25).getPreferredSize().width;
3596                case STATUS_COLUMN:
3597                    return new JTextField(15).getPreferredSize().width;
3598                case OCCUPANCY_COLUMN:
3599                    return new JTextField(10).getPreferredSize().width;
3600                case SECTIONLENGTH_COLUMN:
3601                    return new JTextField(8).getPreferredSize().width;
3602                case ALLOCATEBUTTON_COLUMN:
3603                    return new JTextField(12).getPreferredSize().width;
3604                case CANCELBUTTON_COLUMN:
3605                    return new JTextField(10).getPreferredSize().width;
3606                default:
3607                    // fall through
3608                    break;
3609            }
3610            return new JTextField(5).getPreferredSize().width;
3611        }
3612
3613        @Override
3614        public Object getValueAt(int r, int c) {
3615            int rx = r;
3616            if (rx >= allocationRequests.size()) {
3617                return null;
3618            }
3619            AllocationRequest ar = allocationRequests.get(rx);
3620            switch (c) {
3621                case TRANSIT_COLUMN:
3622                    return (ar.getActiveTrain().getTransit().getSystemName());
3623                case TRANSIT_COLUMN_U:
3624                    if (ar.getActiveTrain().getTransit() != null && ar.getActiveTrain().getTransit().getUserName() != null) {
3625                        return (ar.getActiveTrain().getTransit().getUserName());
3626                    } else {
3627                        return "";
3628                    }
3629                case TRAIN_COLUMN:
3630                    return (ar.getActiveTrain().getTrainName());
3631                case PRIORITY_COLUMN:
3632                    return ("   " + ar.getActiveTrain().getPriority());
3633                case TRAINTYPE_COLUMN:
3634                    return (ar.getActiveTrain().getTrainTypeText());
3635                case SECTION_COLUMN:
3636                    if (ar.getSection() != null) {
3637                        return (ar.getSection().getSystemName());
3638                    } else {
3639                        return "<none>";
3640                    }
3641                case SECTION_COLUMN_U:
3642                    if (ar.getSection() != null && ar.getSection().getUserName() != null) {
3643                        return (ar.getSection().getUserName());
3644                    } else {
3645                        return "<none>";
3646                    }
3647                case STATUS_COLUMN:
3648                    if (ar.getSection().getState() == Section.FREE) {
3649                        return Bundle.getMessage("FREE");
3650                    }
3651                    return Bundle.getMessage("ALLOCATED");
3652                case OCCUPANCY_COLUMN:
3653                    if (!_HasOccupancyDetection) {
3654                        return Bundle.getMessage("UNKNOWN");
3655                    }
3656                    if (ar.getSection().getOccupancy() == Section.OCCUPIED) {
3657                        return Bundle.getMessage("OCCUPIED");
3658                    }
3659                    return Bundle.getMessage("UNOCCUPIED");
3660                case SECTIONLENGTH_COLUMN:
3661                    return ("  " + ar.getSection().getLengthI(_UseScaleMeters, _LayoutScale));
3662                case ALLOCATEBUTTON_COLUMN:
3663                    return Bundle.getMessage("AllocateButton");
3664                case CANCELBUTTON_COLUMN:
3665                    return Bundle.getMessage("ButtonCancel");
3666                default:
3667                    return (" ");
3668            }
3669        }
3670
3671        @Override
3672        public void setValueAt(Object value, int row, int col) {
3673            if (col == ALLOCATEBUTTON_COLUMN) {
3674                // open an allocate window
3675                allocateRequested(row);
3676            }
3677            if (col == CANCELBUTTON_COLUMN) {
3678                // open an allocate window
3679                cancelAllocationRequest(row);
3680            }
3681        }
3682    }
3683
3684    /**
3685     * Table model for Allocated Section Table
3686     */
3687    public class AllocatedSectionTableModel extends javax.swing.table.AbstractTableModel implements
3688            java.beans.PropertyChangeListener {
3689
3690        public static final int TRANSIT_COLUMN = 0;
3691        public static final int TRANSIT_COLUMN_U = 1;
3692        public static final int TRAIN_COLUMN = 2;
3693        public static final int SECTION_COLUMN = 3;
3694        public static final int SECTION_COLUMN_U = 4;
3695        public static final int OCCUPANCY_COLUMN = 5;
3696        public static final int USESTATUS_COLUMN = 6;
3697        public static final int RELEASEBUTTON_COLUMN = 7;
3698        public static final int MAX_COLUMN = 7;
3699
3700        public AllocatedSectionTableModel() {
3701            super();
3702        }
3703
3704        @Override
3705        public void propertyChange(java.beans.PropertyChangeEvent e) {
3706            if (e.getPropertyName().equals("length")) {
3707                fireTableDataChanged();
3708            }
3709        }
3710
3711        @Override
3712        public Class<?> getColumnClass(int c) {
3713            if (c == RELEASEBUTTON_COLUMN) {
3714                return JButton.class;
3715            }
3716            return String.class;
3717        }
3718
3719        @Override
3720        public int getColumnCount() {
3721            return MAX_COLUMN + 1;
3722        }
3723
3724        @Override
3725        public int getRowCount() {
3726            return (allocatedSections.size());
3727        }
3728
3729        @Override
3730        public boolean isCellEditable(int r, int c) {
3731            if (c == RELEASEBUTTON_COLUMN) {
3732                return (true);
3733            }
3734            return (false);
3735        }
3736
3737        @Override
3738        public String getColumnName(int col) {
3739            switch (col) {
3740                case TRANSIT_COLUMN:
3741                    return Bundle.getMessage("TransitColumnSysTitle");
3742                case TRANSIT_COLUMN_U:
3743                    return Bundle.getMessage("TransitColumnTitle");
3744                case TRAIN_COLUMN:
3745                    return Bundle.getMessage("TrainColumnTitle");
3746                case SECTION_COLUMN:
3747                    return Bundle.getMessage("AllocatedSectionColumnSysTitle");
3748                case SECTION_COLUMN_U:
3749                    return Bundle.getMessage("AllocatedSectionColumnTitle");
3750                case OCCUPANCY_COLUMN:
3751                    return Bundle.getMessage("OccupancyColumnTitle");
3752                case USESTATUS_COLUMN:
3753                    return Bundle.getMessage("UseStatusColumnTitle");
3754                case RELEASEBUTTON_COLUMN:
3755                    return Bundle.getMessage("ReleaseButton");
3756                default:
3757                    return "";
3758            }
3759        }
3760
3761        public int getPreferredWidth(int col) {
3762            switch (col) {
3763                case TRANSIT_COLUMN:
3764                case TRANSIT_COLUMN_U:
3765                case TRAIN_COLUMN:
3766                    return new JTextField(17).getPreferredSize().width;
3767                case SECTION_COLUMN:
3768                case SECTION_COLUMN_U:
3769                    return new JTextField(25).getPreferredSize().width;
3770                case OCCUPANCY_COLUMN:
3771                    return new JTextField(10).getPreferredSize().width;
3772                case USESTATUS_COLUMN:
3773                    return new JTextField(15).getPreferredSize().width;
3774                case RELEASEBUTTON_COLUMN:
3775                    return new JTextField(12).getPreferredSize().width;
3776                default:
3777                    // fall through
3778                    break;
3779            }
3780            return new JTextField(5).getPreferredSize().width;
3781        }
3782
3783        @Override
3784        public Object getValueAt(int r, int c) {
3785            int rx = r;
3786            if (rx >= allocatedSections.size()) {
3787                return null;
3788            }
3789            AllocatedSection as = allocatedSections.get(rx);
3790            switch (c) {
3791                case TRANSIT_COLUMN:
3792                    return (as.getActiveTrain().getTransit().getSystemName());
3793                case TRANSIT_COLUMN_U:
3794                    if (as.getActiveTrain().getTransit() != null && as.getActiveTrain().getTransit().getUserName() != null) {
3795                        return (as.getActiveTrain().getTransit().getUserName());
3796                    } else {
3797                        return "";
3798                    }
3799                case TRAIN_COLUMN:
3800                    return (as.getActiveTrain().getTrainName());
3801                case SECTION_COLUMN:
3802                    if (as.getSection() != null) {
3803                        return (as.getSection().getSystemName());
3804                    } else {
3805                        return "<none>";
3806                    }
3807                case SECTION_COLUMN_U:
3808                    if (as.getSection() != null && as.getSection().getUserName() != null) {
3809                        return (as.getSection().getUserName());
3810                    } else {
3811                        return "<none>";
3812                    }
3813                case OCCUPANCY_COLUMN:
3814                    if (!_HasOccupancyDetection) {
3815                        return Bundle.getMessage("UNKNOWN");
3816                    }
3817                    if (as.getSection().getOccupancy() == Section.OCCUPIED) {
3818                        return Bundle.getMessage("OCCUPIED");
3819                    }
3820                    return Bundle.getMessage("UNOCCUPIED");
3821                case USESTATUS_COLUMN:
3822                    if (!as.getEntered()) {
3823                        return Bundle.getMessage("NotEntered");
3824                    }
3825                    if (as.getExited()) {
3826                        return Bundle.getMessage("Exited");
3827                    }
3828                    return Bundle.getMessage("Entered");
3829                case RELEASEBUTTON_COLUMN:
3830                    return Bundle.getMessage("ReleaseButton");
3831                default:
3832                    return (" ");
3833            }
3834        }
3835
3836        @Override
3837        public void setValueAt(Object value, int row, int col) {
3838            if (col == RELEASEBUTTON_COLUMN) {
3839                releaseAllocatedSectionFromTable(row);
3840            }
3841        }
3842    }
3843
3844    /*
3845     * Mouse popup stuff
3846     */
3847
3848    /**
3849     * Process the column header click
3850     * @param e     the evnt data
3851     * @param table the JTable
3852     */
3853    protected void showTableHeaderPopup(JmriMouseEvent e, JTable table) {
3854        JPopupMenu popupMenu = new JPopupMenu();
3855        XTableColumnModel tcm = (XTableColumnModel) table.getColumnModel();
3856        for (int i = 0; i < tcm.getColumnCount(false); i++) {
3857            TableColumn tc = tcm.getColumnByModelIndex(i);
3858            String columnName = table.getModel().getColumnName(i);
3859            if (columnName != null && !columnName.equals("")) {
3860                JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(table.getModel().getColumnName(i), tcm.isColumnVisible(tc));
3861                menuItem.addActionListener(new HeaderActionListener(tc, tcm));
3862                popupMenu.add(menuItem);
3863            }
3864
3865        }
3866        popupMenu.show(e.getComponent(), e.getX(), e.getY());
3867    }
3868
3869    /**
3870     * Adds the column header pop listener to a JTable using XTableColumnModel
3871     * @param table The JTable effected.
3872     */
3873    protected void addMouseListenerToHeader(JTable table) {
3874        JmriMouseListener mouseHeaderListener = new TableHeaderListener(table);
3875        table.getTableHeader().addMouseListener(JmriMouseListener.adapt(mouseHeaderListener));
3876    }
3877
3878    static protected class HeaderActionListener implements ActionListener {
3879
3880        TableColumn tc;
3881        XTableColumnModel tcm;
3882
3883        HeaderActionListener(TableColumn tc, XTableColumnModel tcm) {
3884            this.tc = tc;
3885            this.tcm = tcm;
3886        }
3887
3888        @Override
3889        public void actionPerformed(ActionEvent e) {
3890            JCheckBoxMenuItem check = (JCheckBoxMenuItem) e.getSource();
3891            //Do not allow the last column to be hidden
3892            if (!check.isSelected() && tcm.getColumnCount(true) == 1) {
3893                return;
3894            }
3895            tcm.setColumnVisible(tc, check.isSelected());
3896        }
3897    }
3898
3899    /**
3900     * Class to support Columnheader popup menu on XTableColum model.
3901     */
3902    class TableHeaderListener extends JmriMouseAdapter {
3903
3904        JTable table;
3905
3906        TableHeaderListener(JTable tbl) {
3907            super();
3908            table = tbl;
3909        }
3910
3911        /**
3912         * {@inheritDoc}
3913         */
3914        @Override
3915        public void mousePressed(JmriMouseEvent e) {
3916            if (e.isPopupTrigger()) {
3917                showTableHeaderPopup(e, table);
3918            }
3919        }
3920
3921        /**
3922         * {@inheritDoc}
3923         */
3924        @Override
3925        public void mouseReleased(JmriMouseEvent e) {
3926            if (e.isPopupTrigger()) {
3927                showTableHeaderPopup(e, table);
3928            }
3929        }
3930
3931        /**
3932         * {@inheritDoc}
3933         */
3934        @Override
3935        public void mouseClicked(JmriMouseEvent e) {
3936            if (e.isPopupTrigger()) {
3937                showTableHeaderPopup(e, table);
3938            }
3939        }
3940    }
3941
3942    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DispatcherFrame.class);
3943
3944}