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