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