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