001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.BorderLayout;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.awt.Point;
009import java.util.ArrayList;
010import java.util.List;
011
012import javax.annotation.Nonnull;
013import javax.swing.AbstractAction;
014import javax.swing.Box;
015import javax.swing.BoxLayout;
016import javax.swing.JButton;
017import javax.swing.JDialog;
018import javax.swing.JMenu;
019import javax.swing.JMenuItem;
020import javax.swing.JPanel;
021import javax.swing.JScrollPane;
022import javax.swing.JTextArea;
023
024import jmri.InstanceManager;
025import jmri.InvokeOnGuiThread;
026import jmri.Path;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * A WarrantAction contains the operating permissions and directives needed for
033 * a train to proceed from an Origin to a Destination. WarrantTableAction
034 * provides the menu for panels to List, Edit and Create Warrants. It launches
035 * the appropriate frame for each action.
036 * <br>
037 * <hr>
038 * This file is part of JMRI.
039 * <p>
040 * JMRI is free software; you can redistribute it and/or modify it under the
041 * terms of version 2 of the GNU General Public License as published by the Free
042 * Software Foundation. See the "COPYING" file for a copy of this license.
043 * <p>
044 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
045 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
046 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
047 *
048 * @author Pete Cressman Copyright (C) 2009, 2010
049 **/
050public class WarrantTableAction extends AbstractAction {
051
052    private JMenu _warrantMenu;
053    
054    private boolean _hasErrors = false;
055    private JDialog _errorDialog;
056    private WarrantFrame _openFrame;
057    private Point _warFrameLoc = new Point(20,20);
058    private NXFrame _nxFrame;
059    private Point _nxFrameLoc = new Point(40,40);
060    private boolean _logging = false;
061    private Runnable _shutDownTask = null;
062
063    private WarrantTableAction(String menuOption) {
064        super(Bundle.getMessage(menuOption));
065    }
066
067    public static WarrantTableAction getDefault() {
068        return InstanceManager.getOptionalDefault(WarrantTableAction.class).orElseGet(() -> {
069            WarrantTableAction wta = new WarrantTableAction("ShowWarrants"); // NOI18N
070            wta.errorCheck();
071            return InstanceManager.setDefault(WarrantTableAction.class, wta);
072        });
073    }
074
075    @Override
076    @InvokeOnGuiThread
077    public void actionPerformed(ActionEvent e) {
078        WarrantTableFrame.getDefault().setVisible(true);
079    }
080
081    /**
082     * @param edit true if portal errors should be shown in window created from
083     *             menu item
084     * @return a menu containing warrant actions
085     */
086    public JMenu makeWarrantMenu(boolean edit) {
087        if ( InstanceManager.getDefault(OBlockManager.class).getNamedBeanSet().size() > 1) {
088            synchronized (this) {
089                _warrantMenu = new JMenu(Bundle.getMessage("MenuWarrant"));
090                updateWarrantMenu();
091                return _warrantMenu;
092            }
093        }
094        return null;
095    }
096
097    @InvokeOnGuiThread
098    protected synchronized void updateWarrantMenu() {
099        _warrantMenu.removeAll();
100        _warrantMenu.add(getDefault());
101        JMenu editWarrantMenu = new JMenu(Bundle.getMessage("EditWarrantMenu"));
102        _warrantMenu.add(editWarrantMenu);
103        ActionListener editWarrantAction = (ActionEvent e) -> openWarrantFrame(e.getActionCommand());
104        WarrantManager manager = InstanceManager.getDefault(WarrantManager.class);
105        if (manager.getObjectCount() == 0) { // when there are no Warrants, enter the word "None" to the submenu
106            JMenuItem noWarrants = new JMenuItem(Bundle.getMessage("None"));
107            editWarrantMenu.add(noWarrants);
108            // disable it
109            noWarrants.setEnabled(false);
110        } else { // when there are warrants, add them to the submenu
111            for (Warrant warrant : manager.getNamedBeanSet()) {
112                JMenuItem mi = new JMenuItem(warrant.getDisplayName());
113                mi.setActionCommand(warrant.getDisplayName());
114                mi.addActionListener(editWarrantAction);
115                editWarrantMenu.add(mi);
116            }
117        }
118         _warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateWarrant")) {
119            @Override
120            public void actionPerformed(ActionEvent e) {
121                makeWarrantFrame(null, null);
122            }
123         });
124        _warrantMenu.add(InstanceManager.getDefault(TrackerTableAction.class));
125        _warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateNXWarrant")) {
126            @Override
127            public void actionPerformed(ActionEvent e) {
128                makeNXFrame();
129            }
130        });
131        _warrantMenu.add(makeLogMenu());
132
133        log.debug("updateMenu to {} warrants.", manager.getObjectCount());
134    }
135
136    protected JMenuItem makeLogMenu() {
137        JMenuItem mi;
138        if (!_logging) {
139            mi = new JMenuItem(Bundle.getMessage("startLog"));
140            mi.addActionListener((ActionEvent e) -> {
141                if (!OpSessionLog.makeLogFile(WarrantTableFrame.getDefault())) {
142                    return;
143                }
144                _logging = true;
145                _shutDownTask = () -> {
146                    OpSessionLog.close();
147                    _logging = false;
148                };
149                InstanceManager.getDefault(jmri.ShutDownManager.class).register(_shutDownTask);
150                updateWarrantMenu();
151            });
152        } else {
153            mi = new JMenuItem(Bundle.getMessage("flushLog"));
154            mi.addActionListener((ActionEvent e) -> OpSessionLog.flush());
155            _warrantMenu.add(mi);
156            mi = new JMenuItem(Bundle.getMessage("stopLog"));
157            mi.addActionListener((ActionEvent e) -> {
158                OpSessionLog.close();
159                InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(_shutDownTask);
160                _shutDownTask = null;
161                _logging = false;
162                updateWarrantMenu();
163            });
164        }
165        return mi;
166    }
167
168    synchronized protected void writetoLog(String text) {
169        if (_logging) {
170            OpSessionLog.writeLn(text);
171        }
172    }
173
174    @InvokeOnGuiThread
175    protected void closeNXFrame() {
176        if (_nxFrame != null) {
177            _nxFrame.clearTempWarrant();
178            _nxFrameLoc = _nxFrame.getLocation();
179            _nxFrame.dispose();
180            _nxFrame = null;
181        }
182    }
183
184    @InvokeOnGuiThread
185    protected void makeNXFrame() {
186        closeWarrantFrame();
187        if (_nxFrame == null) {
188            _nxFrame = new NXFrame();
189        }
190        _nxFrame.setState(java.awt.Frame.NORMAL);
191        _nxFrame.setVisible(true);
192        _nxFrameLoc.setLocation(_nxFrameLoc);
193        _nxFrame.toFront();
194    }
195
196    @InvokeOnGuiThread
197    protected boolean closeWarrantFrame() {
198        if (_openFrame != null) {
199            if (!_openFrame.askClose()) {
200                return false;
201            }
202            _warFrameLoc = _openFrame.getLocation();
203            _openFrame.close();
204            _openFrame = null;
205        }
206        return true;
207    }
208
209    protected void makeWarrantFrame(Warrant startW, Warrant endW) {
210        if (!closeWarrantFrame()) {
211            return;
212        }
213        closeNXFrame();
214        _openFrame = new WarrantFrame(startW, endW);
215        _openFrame.setState(java.awt.Frame.NORMAL);
216        _openFrame.toFront();            
217    }
218
219    protected void editWarrantFrame(Warrant w) {
220        if (!closeWarrantFrame()) {
221            return;
222        }
223        closeNXFrame();
224        _openFrame = new WarrantFrame(w);
225        _openFrame.setState(java.awt.Frame.NORMAL);
226        _openFrame.toFront();            
227        _openFrame.setLocation(_warFrameLoc);
228    }
229
230    private void openWarrantFrame(String key) {
231        Warrant w = InstanceManager.getDefault(WarrantManager.class).getWarrant(key);
232        if (w != null) {
233            editWarrantFrame(w);
234        }
235    }
236
237    synchronized public void mouseClickedOnBlock(OBlock block) {
238        if (block == null) {
239            return;
240        }
241
242        if (_openFrame != null) {
243            _openFrame.mouseClickedOnBlock(block);
244            return;
245        }
246
247        if (_nxFrame != null && _nxFrame.isVisible() && _nxFrame.isRouteSeaching()) {
248            _nxFrame.mouseClickedOnBlock(block);
249            return;
250        }
251
252        InstanceManager.getDefault(TrackerTableAction.class).mouseClickedOnBlock(block);
253    }
254    
255    protected WarrantFrame getOpenFrame() {
256        return _openFrame;
257    }
258
259    /* ****************** Error checking ************************/
260    public boolean errorCheck() {
261        _hasErrors = false;
262        javax.swing.JTextArea textArea = new javax.swing.JTextArea(10, 50);
263        textArea.setEditable(false);
264        textArea.setTabSize(4);
265        textArea.append(Bundle.getMessage("ErrWarnAreaMsg"));
266        textArea.append("\n\n");
267        OBlockManager manager = InstanceManager.getDefault(OBlockManager.class);
268        for (OBlock block : manager.getNamedBeanSet()) {
269            textArea.append(checkPathPortals(block));
270        }
271        return showPathPortalErrors(textArea);
272    }
273
274    /**
275     * Validation of paths within a block. Gathers messages in a text area that
276     * can be displayed after all are written.
277     *
278     * @param b the block to validate
279     * @return error/warning message, if any
280     */
281    @Nonnull
282    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path")
283    public String checkPathPortals(OBlock b) {
284        if (log.isDebugEnabled()) {
285            log.debug("checkPathPortals for {}", b.getDisplayName());
286        }
287        StringBuilder sb = new StringBuilder();
288        List<Path> pathList = b.getPaths();
289        if (pathList.isEmpty()) {
290            if (b.getPortals().isEmpty()) {
291                sb.append(Bundle.getMessage("NoPortals"));
292                sb.append(" ");
293            }
294            sb.append(Bundle.getMessage("NoPaths", b.getDisplayName()));
295            sb.append("\n");
296            _hasErrors = true;
297            return sb.toString();
298        }
299        List<Portal> portalList = b.getPortals();
300        // make list of names of all portals.  Then remove those we check, leaving the orphans
301        ArrayList<String> portalNameList = new ArrayList<>();
302        for (Portal portal : portalList) {
303            if (portal.getFromPaths().isEmpty()) {
304                sb.append(Bundle.getMessage("BlockPortalNoPath", portal.getName(), portal.getFromBlockName()));
305                sb.append("\n");
306                _hasErrors = true;
307                return sb.toString();
308            }
309            if (portal.getToPaths().isEmpty()) {
310                sb.append(Bundle.getMessage("BlockPortalNoPath", portal.getName(), portal.getToBlockName()));
311                sb.append("\n");
312                _hasErrors = true;
313                return sb.toString();
314            }
315            portalNameList.add(portal.getName());
316        }
317        for (Path value : pathList) {
318            OPath path = (OPath) value;
319            OBlock block = (OBlock) path.getBlock();
320            if (block == null || !block.equals(b)) {
321                sb.append(Bundle.getMessage("PathWithBadBlock", path.getName(), b.getDisplayName()));
322                sb.append("\n");
323                _hasErrors = true;
324                return sb.toString();
325            }
326            String msg = null;
327            boolean hasPortal = false;
328            Portal fromPortal = path.getFromPortal();
329            if (fromPortal != null) {
330                if (!fromPortal.isValid()) {
331                    msg = fromPortal.getName();
332                }
333                hasPortal = true;
334                portalNameList.remove(fromPortal.getName());
335            }
336            Portal toPortal = path.getToPortal();
337            if (toPortal != null) {
338                if (!toPortal.isValid()) {
339                    msg = toPortal.getName();
340                }
341                hasPortal = true;
342                portalNameList.remove(toPortal.getName());
343                if (fromPortal != null && fromPortal.equals(toPortal)) {
344                    sb.append(Bundle.getMessage("PathWithDuplicatePortal", path.getName(), b.getDisplayName()));
345                    sb.append("\n");
346                }
347            }
348            if (msg != null) {
349                sb.append(Bundle.getMessage("PortalNeedsBlock", msg));
350                sb.append("\n");
351                _hasErrors = true;
352            } else if (!hasPortal) {
353                sb.append(Bundle.getMessage("PathNeedsPortal", path.getName(), b.getDisplayName()));
354                sb.append("\n");
355                _hasErrors = true;
356            }
357            // check that the path's portals have the path in their lists
358            boolean validPath;
359            if (toPortal != null) {
360                if (fromPortal != null) {
361                    validPath = toPortal.isValidPath(path) && fromPortal.isValidPath(path);
362                } else {
363                    validPath = toPortal.isValidPath(path);
364                }
365            } else {
366                if (fromPortal != null) {
367                    validPath = fromPortal.isValidPath(path);
368                } else {
369                    validPath = false;
370                }
371            }
372            if (!validPath) {
373                sb.append(Bundle.getMessage("PathNotConnectedToPortal", path.getName(), b.getDisplayName()));
374                sb.append("\n");
375                _hasErrors = true;
376            }
377        }
378        for (String s : portalNameList) {
379            sb.append(Bundle.getMessage("BlockPortalNoPath", s, b.getDisplayName()));
380            sb.append("\n");
381            _hasErrors = true;
382        }
383        // check whether any turnouts are shared between two blocks;
384        return sb.toString();
385    }
386
387    public boolean showPathPortalErrors(JTextArea textArea) {
388        if (_errorDialog != null) {
389            _errorDialog.dispose();
390        }
391        if (!_hasErrors) {
392            return false;
393        }
394        JScrollPane scrollPane = new JScrollPane(textArea);
395        _errorDialog = new JDialog();
396        _errorDialog.setTitle(Bundle.getMessage("ErrorDialogTitle"));
397        JButton ok = new JButton(Bundle.getMessage("ButtonOK"));
398        class myListener extends java.awt.event.WindowAdapter implements ActionListener {
399
400            @Override
401            public void actionPerformed(ActionEvent e) {
402                _errorDialog.dispose();
403            }
404
405            @Override
406            public void windowClosing(java.awt.event.WindowEvent e) {
407                _errorDialog.dispose();
408            }
409        }
410        ok.addActionListener(new myListener());
411        ok.setMaximumSize(ok.getPreferredSize());
412
413        java.awt.Container contentPane = _errorDialog.getContentPane();
414        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
415        contentPane.add(scrollPane, BorderLayout.CENTER);
416        contentPane.add(Box.createVerticalStrut(5));
417        contentPane.add(Box.createVerticalGlue());
418        JPanel panel = new JPanel();
419        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
420        panel.add(ok);
421        contentPane.add(panel, BorderLayout.SOUTH);
422        _errorDialog.addWindowListener(new myListener());
423        _errorDialog.pack();
424        _errorDialog.setVisible(true);
425        return true;
426    }
427
428    private final static Logger log = LoggerFactory.getLogger(WarrantTableAction.class);
429
430}