001package jmri.jmrix.ieee802154.xbee.swing.nodeconfig;
002
003import java.awt.BorderLayout;
004import java.awt.event.ActionEvent;
005import javax.swing.BorderFactory;
006import javax.swing.BoxLayout;
007import javax.swing.JComboBox;
008import javax.swing.JComponent;
009import javax.swing.JPanel;
010import javax.swing.JScrollPane;
011import jmri.ConfigureManager;
012import jmri.jmrix.AbstractStreamConnectionConfig;
013import jmri.jmrix.StreamConnectionConfig;
014import jmri.jmrix.ConnectionConfigManager;
015import jmri.jmrix.JmrixConfigPane;
016import jmri.InstanceManager;
017import jmri.swing.JTitledSeparator;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020import jmri.jmrix.ieee802154.xbee.XBeeNode;
021
022/**
023 * Provide GUI to configure communications links.
024 * <p>
025 * This is really just a catalog of connections to classes within the systems.
026 * Reflection is used to reduce coupling at load time.
027 * <p>
028 * Objects of this class are based on an underlying ConnectionConfig
029 * implementation, which in turn is obtained from the InstanceManager. Those
030 * must be created at load time by the ConfigXml process, or in some Application
031 * class.
032 * <p>
033 * The classes referenced are the specific subclasses of
034 * {@link jmri.jmrix.StreamConnectionConfig} which provides the methods providing data
035 * to the configuration GUI, and responding to its changes.
036 *
037 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2004, 2010
038 */
039public class StreamConfigPane extends JmrixConfigPane {
040
041    // initialize logging
042    private final static Logger log = LoggerFactory.getLogger(StreamConfigPane.class);
043
044    private XBeeNode confNode = null;
045
046    /*
047     * Create panel is seperated off from the instance and synchronized, so that only
048     * one connection can be configured at once, this prevents multiple threads from
049     * trying to create the same panel at the same time.
050     */
051
052    /**
053     * Create a new connection configuration panel.
054     *
055     * @param node the XBee node associated with the connection.
056     *
057     * @return the panel for the requested connection or for a new connection if
058     *         index did not match an existing connection configuration
059     */
060    public static synchronized StreamConfigPane createPanel(XBeeNode node) {
061        StreamConnectionConfig c = node.getConnectionConfig();
062        return createPanel(c);
063    }
064
065    /**
066     * Create a new configuration panel for the given connection.
067     *
068     * @param c the connection; if null, the panel is ready for a new connection
069     * @return the new panel
070     */
071    public static synchronized StreamConfigPane createPanel(StreamConnectionConfig c) {
072        StreamConfigPane pane = new StreamConfigPane(c);
073        if (c == null) {
074            pane.isDirty = true;
075        }
076        return pane;
077    }
078
079    /**
080     * Disposes of the underlying connection for a configuration pane.
081     *
082     * @param confPane the pane to dispose of
083     */
084    public static void dispose(StreamConfigPane confPane) {
085        if (confPane == null) {
086            log.debug("no instance found therefore can not dispose of it!");
087            return;
088        }
089
090        if (confPane.ccCurrent != null) {
091            try {
092                confPane.ccCurrent.dispose();
093            } catch (RuntimeException ex) {
094                log.error("Error Occurred while disposing connection {}", ex.toString());
095            }
096        }
097        ConfigureManager cmOD = InstanceManager.getNullableDefault(jmri.ConfigureManager.class);
098        if (cmOD != null) {
099            cmOD.deregister(confPane);
100            //cmOD.deregister(confPane.ccCurrent);
101        }
102        InstanceManager.getDefault(ConnectionConfigManager.class).remove(confPane.ccCurrent);
103    }
104
105    public void setXBeeNode(XBeeNode node){
106       confNode = node;
107    }
108
109    private boolean isDirty = false;
110
111    final JComboBox<String> modeBox = new JComboBox<>();
112    final JComboBox<String> manuBox = new JComboBox<>();
113
114    final JPanel details = new JPanel();
115    String[] classConnectionNameList;
116    StreamConnectionConfig[] classConnectionList;
117    final String[] manufactureNameList;
118
119    final jmri.UserPreferencesManager p = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class);
120
121    StreamConnectionConfig ccCurrent = null;
122
123    /**
124     * Use "instance" to get one of these. That allows it to reconnect to
125     * existing information in an existing StreamConnectionConfig object. It's
126     * permitted to call this with a null argument, e.g. for when first
127     * configuring the system.
128     * @param original Connection configuration to use
129     */
130    protected StreamConfigPane(StreamConnectionConfig original) {
131        ConnectionConfigManager manager = InstanceManager.getDefault(ConnectionConfigManager.class);
132        ccCurrent = original;
133
134        setLayout(new BorderLayout());
135        this.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
136
137        manuBox.addItem(NONE_SELECTED);
138
139        manufactureNameList = manager.getConnectionManufacturers();
140        for (String manuName : manufactureNameList) {
141            if (original != null && original.getManufacturer() != null
142                    && original.getManufacturer().equals(manuName)) {
143                manuBox.addItem(manuName);
144                manuBox.setSelectedItem(manuName);
145            } else {
146                manuBox.addItem(manuName);
147            }
148        }
149        manuBox.addActionListener((ActionEvent evt) -> updateComboConnection());
150
151        // get the list of ConnectionConfig items into a selection box
152        classConnectionNameList = manager.getConnectionTypes((String) manuBox.getSelectedItem());
153        classConnectionList = new jmri.jmrix.StreamConnectionConfig[classConnectionNameList.length + 1];
154        modeBox.addItem(NONE_SELECTED);
155        //if (manuBox.getSelectedIndex() != 0) {
156            modeBox.setEnabled(true);
157        //} else {
158            modeBox.setSelectedIndex(0);
159            modeBox.setEnabled(false);
160        //}
161        int n = 1;
162        if (manuBox.getSelectedIndex() != 0) {
163            for (String className : classConnectionNameList) {
164                try {
165                    StreamConnectionConfig config;
166                    if (original != null && original.getClass().getName().equals(className)) {
167                        config = original;
168                        log.debug("matched existing config object");
169                        modeBox.addItem(config.name());
170                        modeBox.setSelectedItem(config.name());
171                        if (classConnectionNameList.length == 1) {
172                            modeBox.setSelectedIndex(1);
173                        }
174                    } else {
175                        Class<?> cl = Class.forName(className);
176                        try {
177                            config = (StreamConnectionConfig) cl.getDeclaredConstructor().newInstance();
178                            modeBox.addItem(config.name());
179                        } catch (ClassCastException cce) {
180                            // the list may include non-StreamConnectinoConfig
181                            // objects, so just ignore those.
182                            continue;
183                        }
184                    }
185                    classConnectionList[n++] = config;
186                } catch (NullPointerException e) {
187                    log.error("Attempt to load {} failed.", className, e);
188                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | java.lang.reflect.InvocationTargetException e) {
189                    log.debug("Attempt to load {} failed", className, e);
190                }
191            }
192            if ((modeBox.getSelectedIndex() == 0) && (p.getComboBoxLastSelection((String) manuBox.getSelectedItem()) != null)) {
193                modeBox.setSelectedItem(p.getComboBoxLastSelection((String) manuBox.getSelectedItem()));
194            }
195        }
196        modeBox.addActionListener((ActionEvent a) -> {
197            if (modeBox.getSelectedItem() != null) {
198                if (!(modeBox.getSelectedItem()).equals(NONE_SELECTED)) {
199                    p.setComboBoxLastSelection((String) manuBox.getSelectedItem(), (String) modeBox.getSelectedItem());
200                }
201            }
202            select();
203        });
204        JPanel manufacturerPanel = new JPanel();
205        manufacturerPanel.add(manuBox);
206        JPanel connectionPanel = new JPanel();
207        connectionPanel.add(modeBox);
208        JPanel initialPanel = new JPanel();
209        initialPanel.setLayout(new BoxLayout(initialPanel, BoxLayout.Y_AXIS));
210        initialPanel.add(new JTitledSeparator(Bundle.getMessage("SystemManufacturer"))); // NOI18N
211        initialPanel.add(manufacturerPanel);
212        initialPanel.add(new JTitledSeparator(Bundle.getMessage("SystemConnection"))); // NOI18N
213        initialPanel.add(connectionPanel);
214        add(initialPanel, BorderLayout.NORTH);
215        initialPanel.add(new JTitledSeparator(Bundle.getMessage("Settings"))); // NOI18N
216        JScrollPane scroll = new JScrollPane(details);
217        scroll.setBorder(BorderFactory.createEmptyBorder());
218        add(scroll, BorderLayout.CENTER);
219
220        select();  // first time through, pretend we've selected a value
221        // to load the rest of the GUI
222    }
223
224    @Override
225    public void updateComboConnection() {
226        modeBox.removeAllItems();
227        modeBox.addItem(NONE_SELECTED);
228        classConnectionNameList = InstanceManager.getDefault(ConnectionConfigManager.class).getConnectionTypes((String) manuBox.getSelectedItem());
229        classConnectionList = new jmri.jmrix.StreamConnectionConfig[classConnectionNameList.length + 1];
230
231        if (manuBox.getSelectedIndex() != 0) {
232            modeBox.setEnabled(true);
233        } else {
234            modeBox.setSelectedIndex(0);
235            modeBox.setEnabled(false);
236        }
237
238        int n = 1;
239        if (manuBox.getSelectedIndex() != 0) {
240            for (String classConnectionNameList1 : classConnectionNameList) {
241                try {
242                    jmri.jmrix.StreamConnectionConfig config;
243                    Class<?> cl = Class.forName(classConnectionNameList1);
244                    config = (jmri.jmrix.StreamConnectionConfig) cl.getDeclaredConstructor().newInstance();
245                    modeBox.addItem(config.name());
246                    classConnectionList[n++] = config;
247                    if (classConnectionNameList.length == 1) {
248                        modeBox.setSelectedIndex(1);
249                    }
250                } catch (ClassCastException cce) {
251                    // the list may include non-StreamConnectinoConfig
252                    // objects, so just ignore those.
253                    log.trace("ignoring {} as invalid connection type", classConnectionNameList1);
254                } catch (NullPointerException | ClassNotFoundException | InstantiationException |
255                            IllegalAccessException | NoSuchMethodException | java.lang.reflect.InvocationTargetException e) {
256                    log.warn("Attempt to load {} failed", classConnectionNameList1, e);
257                }
258            }
259            if (p.getComboBoxLastSelection((String) manuBox.getSelectedItem()) != null) {
260                modeBox.setSelectedItem(p.getComboBoxLastSelection((String) manuBox.getSelectedItem()));
261            }
262        } else {
263            if (ccCurrent != null) {
264                ccCurrent.dispose();
265            }
266        }
267    }
268
269    void select() {
270        StreamConnectionConfig old = this.ccCurrent;
271        int current = modeBox.getSelectedIndex();
272        details.removeAll();
273        // first choice is -no- protocol chosen
274        log.debug("new selection is {} {}", current, modeBox.getSelectedItem());
275        if ((current != 0) && (current != -1)) {
276            if ((ccCurrent != null) && (ccCurrent != classConnectionList[current])) {
277                ccCurrent.dispose();
278            }
279            ccCurrent = classConnectionList[current];
280            classConnectionList[current].loadDetails(details);
281            classConnectionList[current].setManufacturer((String) manuBox.getSelectedItem());
282        } else {
283            if (ccCurrent != null) {
284                ccCurrent.dispose();
285            }
286        }
287        if (old != this.ccCurrent) {
288              // store the connection config with the node.
289              if(ccCurrent instanceof AbstractStreamConnectionConfig) {
290                 confNode.setPortController((AbstractStreamConnectionConfig)ccCurrent);
291              }
292        }
293
294        validate();
295
296        repaint();
297    }
298
299    @Override
300    public String getConnectionName() {
301        int current = modeBox.getSelectedIndex();
302        if (current == 0) {
303            return null;
304        }
305        return classConnectionList[current].getConnectionName();
306    }
307
308    @Override
309    public String getCurrentManufacturerName() {
310        int current = modeBox.getSelectedIndex();
311        if (current == 0) {
312            return NONE;
313        }
314        return classConnectionList[current].getManufacturer();
315    }
316
317    @Override
318    public String getCurrentProtocolName() {
319        int current = modeBox.getSelectedIndex();
320        if (current == 0) {
321            return NONE;
322        }
323        return classConnectionList[current].name();
324    }
325
326    @Override
327    public String getCurrentProtocolInfo() {
328        int current = modeBox.getSelectedIndex();
329        if (current == 0) {
330            return NONE;
331        }
332        return classConnectionList[current].getInfo();
333    }
334
335    @Override
336    public StreamConnectionConfig getCurrentObject() {
337        int current = modeBox.getSelectedIndex();
338        if (current != 0) {
339            return classConnectionList[current];
340        }
341        return null;
342    }
343
344    @Override
345    public boolean getDisabled() {
346        int current = modeBox.getSelectedIndex();
347        if (current == 0) {
348            return false;
349        }
350        return classConnectionList[current].getDisabled();
351    }
352
353    @Override
354    public void setDisabled(boolean disabled) {
355        int current = modeBox.getSelectedIndex();
356        if (current == 0) {
357            return;
358        }
359        classConnectionList[current].setDisabled(disabled);
360    }
361
362    @Override
363    public String getTabbedPreferencesTitle() {
364        String title = this.getConnectionName();
365        if (title == null
366                && this.getCurrentProtocolName() != null
367                && !this.getCurrentProtocolName().equals(JmrixConfigPane.NONE)) {
368            title = this.getCurrentProtocolName();
369        }
370        if (title != null && !this.getDisabled()) {
371            title = "(" + title + ")";
372        }
373        return title;
374    }
375
376    @Override
377    public String getLabelKey() {
378        return null;
379    }
380
381    @Override
382    public JComponent getPreferencesComponent() {
383        return this;
384    }
385
386    @Override
387    public boolean isPersistant() {
388        return true;
389    }
390
391    @Override
392    public String getPreferencesTooltip() {
393        return this.getTabbedPreferencesTitle();
394    }
395
396    @Override
397    public void savePreferences() {
398        // do nothing - the persistant manager will take care of this
399    }
400
401    @Override
402    public boolean isDirty() {
403        // avoid potentially expensive exrta test for isDirty
404        if (log.isDebugEnabled()) {
405            log.debug("Connection \"{}\" is {}.",
406                    this.getConnectionName(),
407                    (this.isDirty || ((this.ccCurrent != null) ? this.ccCurrent.isDirty() : true) ? "dirty" : "clean"));
408        }
409        return this.isDirty || ((this.ccCurrent != null) ? this.ccCurrent.isDirty() : true);
410    }
411
412    @Override
413    public boolean isRestartRequired() {
414        return (this.ccCurrent != null) ? this.ccCurrent.isRestartRequired() : this.isDirty();
415    }
416
417    @Override
418    public boolean isPreferencesValid() {
419        return true; // no validity checking performed
420    }
421
422}