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