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