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