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