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