001package jmri.jmrix.ecos;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.Component;
006import java.awt.HeadlessException;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.util.ArrayList;
010import java.util.Enumeration;
011import java.util.Hashtable;
012import java.util.List;
013
014import javax.annotation.Nonnull;
015import javax.swing.BorderFactory;
016import javax.swing.BoxLayout;
017import javax.swing.JButton;
018import javax.swing.JCheckBox;
019import javax.swing.JDialog;
020import javax.swing.JLabel;
021import javax.swing.JPanel;
022
023import jmri.*;
024import jmri.implementation.AbstractShutDownTask;
025import jmri.jmrit.beantable.ListedTableFrame;
026import jmri.jmrit.roster.Roster;
027import jmri.jmrit.roster.RosterConfigManager;
028import jmri.jmrit.roster.RosterEntry;
029import jmri.jmrix.ecos.utilities.EcosLocoToRoster;
030import jmri.jmrix.ecos.utilities.GetEcosObjectNumber;
031import jmri.jmrix.ecos.utilities.RemoveObjectFromEcos;
032import jmri.jmrix.ecos.utilities.RosterToEcos;
033import jmri.managers.AbstractManager;
034import jmri.profile.ProfileManager;
035
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * Class to manage the ECoS Loco entries within JMRI.
041 *
042 * @author Kevin Dickerson
043 */
044public class EcosLocoAddressManager extends AbstractManager<NamedBean> implements EcosListener {
045
046    private boolean addLocoToRoster = false;
047    ShutDownTask ecosLocoShutDownTask;
048    private EcosLocoToRoster locoToRoster;
049    private boolean monitorState = false;
050    private boolean processLocoToRosterQueue = true;
051    private EcosPreferences p;
052    private RosterConfigManager rcm;
053    private RosterEntry _re;
054    private String rosterAttribute;
055    private EcosTrafficController tc;
056    private Thread waitPrefLoad;
057    private Hashtable<String, EcosLocoAddress> _tecos = new Hashtable<>();   // stores known Ecos Object ids to DCC
058    private Hashtable<Integer, EcosLocoAddress> _tdcc = new Hashtable<>();  // stores known DCC Address to Ecos Object ids
059
060    public EcosLocoAddressManager(@Nonnull EcosSystemConnectionMemo memo) {
061        super(memo);
062        init();
063    }
064
065    private void init() {
066        locoToRoster = new EcosLocoToRoster(getMemo());
067        tc = getMemo().getTrafficController();
068        p = getMemo().getPreferenceManager();
069        rosterAttribute = p.getRosterAttribute();
070        rcm = InstanceManager.getDefault(RosterConfigManager.class);
071        loadEcosData();
072        try {
073            if (InstanceManager.getNullableDefault(ListedTableFrame.class) == null) {
074                new ListedTableFrame<jmri.Turnout>();
075            }
076            InstanceManager.getDefault(ListedTableFrame.class).addTable("jmri.jmrix.ecos.swing.locodatabase.EcosLocoTableTabAction", "ECoS Loco Database", false);
077        } catch (HeadlessException he) {
078            // silently ignore inability to display dialog
079        }
080    }
081
082    /**
083     * {@inheritDoc}
084     */
085    @Override
086    @Nonnull
087    public EcosSystemConnectionMemo getMemo() {
088        return (EcosSystemConnectionMemo) memo;
089    }
090
091    @Override
092    public char typeLetter() {
093        return 'Z';
094    } // NOI18N
095
096    @Override
097    public Class<NamedBean> getNamedBeanClass() {
098        return NamedBean.class;
099    }
100
101    @Override
102    public int getXMLOrder() {
103        return 65400;
104    }
105
106    /**
107     * EcosLocoAddresses have no system prefix, so return input unchanged.
108     *
109     * @param s the input to make a system name
110     * @return the resultant system name
111     */
112    @Override
113    @Nonnull
114    public String makeSystemName(@Nonnull String s) {
115        return s;
116    }
117
118    public void clearLocoToRoster() {
119        addLocoToRoster = false;
120    }
121
122    public void setLocoToRoster() {
123        addLocoToRoster = true;
124    }
125
126    public boolean getLocoToRoster() {
127        return addLocoToRoster;
128    }
129
130    public EcosLocoAddress provideEcosLoco(String EcosObject, int DCCAddress) {
131        EcosLocoAddress l = getByEcosObject(EcosObject);
132        if (l != null) {
133            return l;
134        }
135        l = new EcosLocoAddress(DCCAddress);
136        l.setEcosObject(EcosObject);
137        register(l);
138        return l;
139    }
140
141    public EcosLocoAddress provideByDccAddress(int dccAddress) {
142        EcosLocoAddress l = getByDccAddress(dccAddress);
143        //Loco doesn't exist, so we shall create it.
144        if (l != null) {
145            return l;
146        }
147
148        l = new EcosLocoAddress(dccAddress);
149        register(l);
150        return _tdcc.get(dccAddress);
151    }
152
153    public EcosLocoAddress provideByEcosObject(String ecosObject) {
154        EcosLocoAddress l = getByEcosObject(ecosObject);
155        //Loco doesn't exist, so we shall create it.
156        if (l != null) {
157            return l;
158        }
159
160        l = new EcosLocoAddress(ecosObject, p.getRosterAttribute());
161        register(l);
162
163        EcosMessage m = new EcosMessage("request(" + ecosObject + ", view)");
164        tc.sendEcosMessage(m, this);
165        m = new EcosMessage("get(" + ecosObject + ", speed)");
166        tc.sendEcosMessage(m, this);
167
168        m = new EcosMessage("get(" + ecosObject + ", dir)");
169        tc.sendEcosMessage(m, this);
170        return _tecos.get(ecosObject);
171    }
172
173    public EcosLocoAddress getByEcosObject(String ecosObject) {
174        return _tecos.get(ecosObject);
175    }
176
177    public EcosLocoAddress getByDccAddress(int dccAddress) {
178        return _tdcc.get(dccAddress);
179    }
180
181    public String[] getEcosObjectArray() {
182        String[] arr = new String[_tecos.size()];
183        Enumeration<String> en = _tecos.keys();
184        int i = 0;
185        while (en.hasMoreElements()) {
186            arr[i] = en.nextElement();
187            i++;
188        }
189        java.util.Arrays.sort(arr);
190        return arr;
191    }
192
193    public List<String> getEcosObjectList() {
194        String[] arr = new String[_tecos.size()];
195        List<String> out = new ArrayList<>();
196        Enumeration<String> en = _tecos.keys();
197        int i = 0;
198        while (en.hasMoreElements()) {
199            arr[i] = en.nextElement();
200            i++;
201        }
202        java.util.Arrays.sort(arr);
203        for (i = 0; i < arr.length; i++) {
204            out.add(arr[i]);
205        }
206        return out;
207    }
208
209    private void loadEcosData() {
210        if (p.getPreferencesLoaded() && rcm.isInitialized(ProfileManager.getDefault().getActiveProfile())) {
211            loadData();
212        } else {
213            /*as the loco address manager is called prior to the remainder of the
214             preferences being loaded, we add a thread which waits for the preferences
215             to be loaded prior to reading the Ecos Loco database.
216             */
217            if (waitPrefLoad != null) {
218                waitPrefLoad.interrupt();
219                waitPrefLoad = null;
220            }
221            waitPrefLoad = new Thread(new WaitPrefLoad());
222            waitPrefLoad.setName("Wait for Preferences to be loaded");
223            waitPrefLoad.start();
224        }
225    }
226
227    private void loadData() {
228        tc.addEcosListener(this);
229
230        try {
231
232            Roster.getDefault().addPropertyChangeListener(this);
233
234            EcosMessage m = new EcosMessage("request(10, view)");
235            tc.sendWaitMessage(m, this);
236
237            /*m = new EcosMessage("queryObjects(10)");
238           tc.sendWaitMessage(m, this);*/
239            m = new EcosMessage("queryObjects(10, addr, name, protocol)");
240            tc.sendEcosMessage(m, this);
241
242            if (ecosLocoShutDownTask == null) {
243                // TODO: I cannot tell what actually syncs the ECoS with the Roster
244                // or what in this ShutDownTask triggers a sync
245                ecosLocoShutDownTask = new AbstractShutDownTask("Ecos Loco Database Shutdown") {
246
247                    @Override
248                    public Boolean call() {
249                        return shutdownDispose();
250                    }
251
252                    @Override
253                    public void run() {
254                        disposefinal();
255                    }
256                };
257            }
258            InstanceManager.getDefault(ShutDownManager.class).register(ecosLocoShutDownTask);
259        } catch (java.lang.NullPointerException npe) {
260            log.debug("Delayed initialization of EcosLocoAddressManager failed, no roster information available");
261        }
262    }
263
264    public void monitorLocos(boolean monitor) {
265        monitorState = monitor;
266        List<String> objects = getEcosObjectList();
267
268        for (String ecosObject : objects) {
269            EcosMessage m = new EcosMessage("get(" + ecosObject + ", speed)");
270            tc.sendEcosMessage(m, this);
271
272            m = new EcosMessage("get(" + ecosObject + ", dir)");
273            tc.sendEcosMessage(m, this);
274        }
275    }
276
277    public void deleteEcosLoco(EcosLocoAddress s) {
278        deregister(s);
279    }
280
281    public void register(EcosLocoAddress s) {
282        //We should always have at least a DCC address to register a loco.
283        //We may not always first time round on initial registration have the Ecos Object.
284        String ecosObject = s.getEcosObject();
285        int oldsize;
286        if (ecosObject != null) {
287            oldsize = _tecos.size();
288            _tecos.put(ecosObject, s);
289            firePropertyChange("length", oldsize, _tecos.size());
290        }
291
292        oldsize = _tdcc.size();
293        int dccAddress = s.getNumber();
294        _tdcc.put(dccAddress, s);
295        firePropertyChange("length", oldsize, _tdcc.size());
296        // listen for name and state changes to forward
297        s.addPropertyChangeListener(this);
298    }
299
300    /**
301     * Forget a NamedBean Object created outside the manager.
302     * <p>
303     * The non-system-specific RouteManager uses this method.
304     * @param s Ecos Loco Address to de-register.
305     */
306    public void deregister(EcosLocoAddress s) {
307        s.removePropertyChangeListener(this);
308        String ecosObject = s.getEcosObject();
309        int oldsize = _tecos.size();
310        _tecos.remove(ecosObject);
311        firePropertyChange("length", Integer.valueOf(oldsize), Integer.valueOf(_tecos.size()));
312
313        int dccAddress = s.getNumber();
314        oldsize = _tdcc.size();
315        _tdcc.remove(dccAddress);
316        firePropertyChange("length", Integer.valueOf(oldsize), Integer.valueOf(_tdcc.size()));
317        EcosMessage m = new EcosMessage("release(" + ecosObject + ", view)");
318        tc.sendEcosMessage(m, this);
319        // listen for name and state changes to forward
320    }
321
322    private boolean disposefinal() {
323        if (InstanceManager.getNullableDefault(ConfigureManager.class) != null) {
324            InstanceManager.getDefault(ConfigureManager.class).deregister(this);
325        }
326        _tecos.clear();
327        _tdcc.clear();
328        return true;
329    }
330
331    /* Dispose is dealt with at shutdown */
332    @Override
333    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
334            justification = "This method intentionally doesn't do anything")
335    public void dispose() {
336    }
337
338    public void terminateThreads() {
339        if (waitPrefLoad != null) {
340            waitPrefLoad.interrupt();
341        }
342    }
343
344    protected boolean threadsRunning() {
345        return ( waitPrefLoad != null ? waitPrefLoad.isAlive() : false );
346    }
347
348    public boolean shutdownDispose() {
349        boolean hasTempEntries = false;
350        Enumeration<String> en = _tecos.keys();
351        _tdcc.clear();
352        // This will remove/deregister non-temporary locos from the list.
353        while (en.hasMoreElements()) {
354            String ecosObject = en.nextElement();
355            if (_tecos.get(ecosObject).getEcosTempEntry()) {
356                hasTempEntries = true;
357            } else {
358                deregister(getByEcosObject(ecosObject));
359                _tecos.remove(ecosObject);
360            }
361        }
362
363        if (p.getAdhocLocoFromEcos() == 0x01) {
364            disposefinal();
365        } else if (!hasTempEntries) {
366            disposefinal();
367        } else if (p.getAdhocLocoFromEcos() == EcosPreferences.ASK) {
368
369            final JDialog dialog = new JDialog();
370            dialog.setTitle(Bundle.getMessage("RemoveLocoTitle"));
371            dialog.setLocation(300, 200);
372            dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
373            JPanel container = new JPanel();
374            container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
375            container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
376
377            JLabel question = new JLabel(Bundle.getMessage("RemoveLocoLine1"));
378            question.setAlignmentX(Component.CENTER_ALIGNMENT);
379            container.add(question);
380            question = new JLabel(Bundle.getMessage("RemoveLocoLine2"));
381            question.setAlignmentX(Component.CENTER_ALIGNMENT);
382            container.add(question);
383            final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
384            remember.setFont(remember.getFont().deriveFont(10f));
385            remember.setAlignmentX(Component.CENTER_ALIGNMENT);
386            // user preferences do not have the save option, but once complete the following line can be removed
387            // TODO get the method to save connection configuration.
388            remember.setVisible(true);
389            JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
390            JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
391            JPanel button = new JPanel();
392            button.setAlignmentX(Component.CENTER_ALIGNMENT);
393            button.add(yesButton);
394            button.add(noButton);
395            container.add(button);
396
397            noButton.addActionListener(new ActionListener() {
398                @Override
399                public void actionPerformed(ActionEvent e) {
400                    if (remember.isSelected()) {
401                        p.setAdhocLocoFromEcos(0x01);
402                    }
403                    dialog.dispose();
404                }
405            });
406
407            yesButton.addActionListener(new ActionListener() {
408                @Override
409                public void actionPerformed(ActionEvent e) {
410                    if (remember.isSelected()) {
411                        p.setAdhocLocoFromEcos(0x02);
412                    }
413                    dialog.dispose();
414                }
415            });
416            container.add(remember);
417            container.setAlignmentX(Component.CENTER_ALIGNMENT);
418            container.setAlignmentY(Component.CENTER_ALIGNMENT);
419            dialog.getContentPane().add(container);
420            dialog.pack();
421            dialog.setModal(true);
422            dialog.setVisible(true);
423        }
424        return true;
425    }
426
427    /**
428     * The PropertyChangeListener interface in this class is intended to keep
429     * track of roster entries and sync them up with the Ecos.
430     */
431    @Override
432    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
433            justification = "Further investigation is needed to handle this correctly")
434    public void propertyChange(java.beans.PropertyChangeEvent e) {
435        //If we are adding the loco to the roster from the ecos, we don't want to be adding it back to the ecos!
436        if (getLocoToRoster()) {
437            return;
438        }
439        if (e.getPropertyName().equals("add")) {
440            _re = (RosterEntry) e.getNewValue();
441
442        } else if (e.getPropertyName().equals("saved")) {
443            if (_re != null) {
444                if (_re.getAttribute(rosterAttribute) != null) {
445                    _re = null;
446                    return;
447                }
448                //if the ecosobject attribute exists this would then indicate that it has already been created on the ecos
449                if (p.getAddLocoToEcos() == EcosPreferences.ASK) {
450                    final JDialog dialog = new JDialog();
451                    dialog.setTitle(Bundle.getMessage("AddLocoTitle"));
452                    //test.setSize(300,130);
453                    dialog.setLocation(300, 200);
454                    dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
455                    JPanel container = new JPanel();
456                    container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
457                    container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
458
459                    JLabel question = new JLabel(Bundle.getMessage("AddLocoXQuestion", _re.getId(), getMemo().getUserName()));
460                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
461                    container.add(question);
462                    final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
463                    remember.setFont(remember.getFont().deriveFont(10f));
464                    remember.setAlignmentX(Component.CENTER_ALIGNMENT);
465                    //user preferences do not have the save option, but once complete the following line can be removed
466                    //Need to get the method to save connection configuration.
467                    remember.setVisible(true);
468                    JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
469                    JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
470                    JPanel button = new JPanel();
471                    button.setAlignmentX(Component.CENTER_ALIGNMENT);
472                    button.add(yesButton);
473                    button.add(noButton);
474                    container.add(button);
475
476                    noButton.addActionListener(new ActionListener() {
477                        @Override
478                        public void actionPerformed(ActionEvent e) {
479                            if (remember.isSelected()) {
480                                p.setAddLocoToEcos(0x01);
481                            }
482                            _re = null;
483                            dialog.dispose();
484                        }
485                    });
486
487                    yesButton.addActionListener(new ActionListener() {
488                        @Override
489                        public void actionPerformed(ActionEvent e) {
490                            if (remember.isSelected()) {
491                                p.setAddLocoToEcos(0x02);
492                            }
493                            RosterToEcos rosterToEcos = new RosterToEcos(getMemo());
494                            rosterToEcos.createEcosLoco(_re);
495                            _re = null;
496                            dialog.dispose();
497                        }
498                    });
499                    container.add(remember);
500                    container.setAlignmentX(Component.CENTER_ALIGNMENT);
501                    container.setAlignmentY(Component.CENTER_ALIGNMENT);
502                    dialog.getContentPane().add(container);
503                    dialog.pack();
504                    dialog.setModal(true);
505                    dialog.setVisible(true);
506                }
507                if (p.getAddLocoToEcos() == 0x02) {
508                    RosterToEcos rosterToEcos = new RosterToEcos(getMemo());
509                    rosterToEcos.createEcosLoco(_re);
510                    _re = null;
511                }
512            }
513        } else if (e.getPropertyName().equals("remove")) {
514            _re = (RosterEntry) e.getNewValue();
515            if (_re.getAttribute(rosterAttribute) != null) {
516                if (p.getRemoveLocoFromEcos() == EcosPreferences.YES){
517                    RemoveObjectFromEcos removeObjectFromEcos = new RemoveObjectFromEcos();
518                    removeObjectFromEcos.removeObjectFromEcos(_re.getAttribute(p.getRosterAttribute()), tc);
519                    deleteEcosLoco(provideByEcosObject(_re.getAttribute(p.getRosterAttribute())));
520                } else if(p.getRemoveLocoFromEcos() == EcosPreferences.ASK ) {
521                    final JDialog dialog = new JDialog();
522                    dialog.setTitle(Bundle.getMessage("RemoveLocoTitle"));
523                    //test.setSize(300,130);
524                    dialog.setLocation(300, 200);
525                    dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
526                    JPanel container = new JPanel();
527                    container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
528                    container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
529
530                    JLabel question = new JLabel(Bundle.getMessage("RemoveLocoXQuestion", getMemo().getUserName()));
531                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
532                    container.add(question);
533                    final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
534                    remember.setFont(remember.getFont().deriveFont(10f));
535                    remember.setAlignmentX(Component.CENTER_ALIGNMENT);
536                    //user preferences do not have the save option, but once complete the following line can be removed
537                    //Need to get the method to save connection configuration.
538                    remember.setVisible(true);
539                    JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
540                    JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
541                    JPanel button = new JPanel();
542                    button.setAlignmentX(Component.CENTER_ALIGNMENT);
543                    button.add(yesButton);
544                    button.add(noButton);
545                    container.add(button);
546
547                    noButton.addActionListener(new ActionListener() {
548                        @Override
549                        public void actionPerformed(ActionEvent e) {
550                            if (remember.isSelected()) {
551                                p.setRemoveLocoFromEcos(0x01);
552                            }
553                            provideByEcosObject(_re.getAttribute(p.getRosterAttribute())).setRosterId(null);
554                            dialog.dispose();
555                        }
556                    });
557
558                    yesButton.addActionListener(new ActionListener() {
559                        @Override
560                        public void actionPerformed(ActionEvent e) {
561                            if (remember.isSelected()) {
562                                p.setRemoveLocoFromEcos(0x02);
563                            }
564                            RemoveObjectFromEcos removeObjectFromEcos = new RemoveObjectFromEcos();
565                            removeObjectFromEcos.removeObjectFromEcos(_re.getAttribute(p.getRosterAttribute()), tc);
566                            deleteEcosLoco(provideByEcosObject(_re.getAttribute(p.getRosterAttribute())));
567                            dialog.dispose();
568                        }
569                    });
570                    container.add(remember);
571                    container.setAlignmentX(Component.CENTER_ALIGNMENT);
572                    container.setAlignmentY(Component.CENTER_ALIGNMENT);
573                    dialog.getContentPane().add(container);
574                    dialog.pack();
575                    dialog.setModal(true);
576                    dialog.setVisible(true);
577                }
578            }
579            _re = null;
580        } else if (e.getPropertyName().equals("throttleAssigned")) {
581            DccLocoAddress la = (DccLocoAddress) e.getNewValue();
582            EcosLocoAddress ela = getByDccAddress(la.getNumber());
583            EcosMessage m = new EcosMessage("get(" + ela.getEcosObject() + ", speed)");
584            tc.sendEcosMessage(m, this);
585            m = new EcosMessage("get(" + ela.getEcosObject() + ", dir)");
586            tc.sendEcosMessage(m, this);
587        }
588    }
589
590    @Override
591    public void reply(EcosReply m) {
592        String strde;
593
594        if (m.getResultCode() == 0) {
595            int ecosObjectId = m.getEcosObjectId();
596            if ((ecosObjectId != 10) && ((ecosObjectId < 1000) || (ecosObjectId > 2000))) {
597                log.debug("message received that is not within the valid loco object range");
598                return;
599            }
600            List<String> headerDetails = m.getReplyHeaderDetails();
601            String[] msgDetails = m.getContents();
602            if (m.isUnsolicited()) {
603                if (ecosObjectId == 10) {
604                    log.debug("We have received notification of a change in the Loco list");
605                    if (msgDetails.length == 0) {
606                        EcosMessage mout = new EcosMessage("queryObjects(10)");
607                        tc.sendEcosMessage(mout, this);
608                        //Version 3.0.1 of the software has an issue in that it stops sending updates on the
609                        //loco objects when a delete has happened, we therefore need to release the old view
610                        //then re-request it.
611                        mout = new EcosMessage("release(10, view)");
612                        tc.sendEcosMessage(mout, this);
613                        mout = new EcosMessage("request(10, view)");
614                        tc.sendEcosMessage(mout, this);
615                    } else if (msgDetails[0].contains("msg[LIST_CHANGED]")) {
616                        EcosMessage mout = new EcosMessage("queryObjects(10)");
617                        tc.sendEcosMessage(mout, this);
618                    }
619                } else {
620                    EcosLocoAddress tmploco;
621                    log.debug("Forwarding on State change for {}", ecosObjectId);
622                    String strLocoObject = Integer.toString(ecosObjectId);
623                    tmploco = _tecos.get(strLocoObject);
624                    if (tmploco != null) {
625                        tmploco.reply(m);
626                    }
627                }
628            } else {
629                String replyType = m.getReplyType();
630
631                if (replyType.equals("queryObjects")) {
632                    if (ecosObjectId == 10) {
633                        if (headerDetails.size() == 0 || (headerDetails.size() == 1 && headerDetails.get(0).equals(""))) {
634                            checkLocoList(msgDetails);
635                        } else {
636                            processLocoToRosterQueue = false;
637                            //Format of the reply details are ObjectId, followed by object ids.
638                            for (String line : msgDetails) {
639                                String[] objectdetail = line.split(" ");
640                                EcosLocoAddress tmploco = null;
641                                //The first part of the messages is always the object id.
642                                strde = objectdetail[0];
643                                strde = strde.trim();
644                                int object = Integer.parseInt(strde);
645                                if ((1000 <= object) && (object < 2000)) {
646                                    tmploco = provideByEcosObject(strde);
647                                }
648                                decodeLocoDetails(tmploco, line, true);
649                            }
650                            locoToRoster.processQueue();
651                            processLocoToRosterQueue = true;
652                        }
653                    }
654                } else if (replyType.equals("get")) {
655                    EcosLocoAddress tmploco = provideByEcosObject(Integer.toString(ecosObjectId));
656                    for (String line : msgDetails) {
657                        decodeLocoDetails(tmploco, line, false);
658                    }
659                }
660            }
661        }
662    }
663
664    void decodeLocoDetails(EcosLocoAddress tmploco, String line, boolean addToRoster) {
665        if (tmploco == null) {
666            return;
667        }
668        if (line.contains("cv")) {
669            String cv = EcosReply.getContentDetails(line, "cv");
670            cv = cv.replaceAll("\\s", "");  //remove all white spaces, as 4.1.0 version removed the space after the ,
671            int cvnum = Integer.parseInt(cv.substring(0, cv.indexOf(",")));
672            int cvval = Integer.parseInt(cv.substring(cv.indexOf(",") + 1, cv.length()));
673            tmploco.setCV(cvnum, cvval);
674            if (cvnum == 8 && processLocoToRosterQueue) {
675                locoToRoster.processQueue();
676            }
677        }
678        if (line.contains("addr")) {
679            tmploco.setLocoAddress(GetEcosObjectNumber.getEcosObjectNumber(line, "addr[", "]"));
680            if (tmploco.getCV(7) == -1) {
681                tmploco.setCV(7, 0);
682                getEcosCVs(tmploco);
683            }
684        }
685        if (line.contains("name")) {
686            String name = EcosReply.getContentDetails(line, "name").trim();
687            name = name.substring(1, name.length() - 1);
688            tmploco.setEcosDescription(name);
689        }
690        if (line.contains("protocol")) {
691            tmploco.setProtocol(EcosReply.getContentDetails(line, "protocol"));
692        }
693        if (line.contains("speed")) {
694            tmploco.setSpeed(Integer.parseInt(EcosReply.getContentDetails(line, "speed")));
695        }
696
697        if (line.contains("dir")) {
698            boolean newDirection = false;
699            if (EcosReply.getContentDetails(line, "dir").equals("0")) {
700                newDirection = true;
701            }
702            tmploco.setDirection(newDirection);
703        }
704        register(tmploco);
705        if (p.getAddLocoToJMRI() != EcosPreferences.NO && addToRoster) {
706            locoToRoster.addToQueue(tmploco);
707        }
708    }
709
710    /* This is used after an event update form the ecos informing us of a change in the
711     * loco list, we have to determine if it is an addition or delete.
712     * We should only ever do either a remove or an add in one go, if we are adding the loco
713     * to the roster otherwise this causes a problem with the roster list.
714     */
715    void checkLocoList(String[] ecoslines) {
716        log.debug("Checking loco list");
717        String loco;
718        for (String ecosline : ecoslines) {
719            loco = ecosline;
720            loco = loco.replaceAll("[\\n\\r]", "");
721            if (getByEcosObject(loco) == null) {
722                log.debug("We are to add loco {} to the Ecos Loco List", loco);
723                EcosMessage mout = new EcosMessage("get(" + loco + ", addr, name, protocol)");
724                tc.sendEcosMessage(mout, this);
725            }
726        }
727
728        String[] jmrilist = getEcosObjectArray();
729        boolean nomatch = true;
730        for (String entry : jmrilist) {
731            nomatch = true;
732            for (String ecosline : ecoslines) {
733                loco = ecosline;
734                loco = loco.replaceAll("[\\n\\r]", "");
735                if (loco.equals(entry)) {
736                    nomatch = false;
737                    break;
738                }
739            }
740            if (nomatch) {
741                // We do not have a match, therefore this should be deleted from the Ecos loco Manager " + jmrilist[i]
742                log.debug("Loco not found so need to remove from register");
743                if (getByEcosObject(entry).getRosterId() != null) {
744                    final String rosterid = getByEcosObject(entry).getRosterId();
745                    final Roster _roster = Roster.getDefault();
746                    final RosterEntry re = _roster.entryFromTitle(rosterid);
747                    re.deleteAttribute(p.getRosterAttribute());
748                    re.writeFile(null, null);
749                    Roster.getDefault().writeRoster();
750                    if (p.getRemoveLocoFromJMRI() == EcosPreferences.YES) {
751                        _roster.removeEntry(re);
752                        Roster.getDefault().writeRoster();
753                    } else if (p.getRemoveLocoFromJMRI() == EcosPreferences.ASK) {
754                        try {
755                            final JDialog dialog = new JDialog();
756                            dialog.setTitle(Bundle.getMessage("RemoveRosterEntryTitle"));
757                            dialog.setLocation(300, 200);
758                            dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
759                            JPanel container = new JPanel();
760                            container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
761                            container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
762
763                            JLabel question = new JLabel(Bundle.getMessage("RemoveRosterEntryX", rosterid));
764                            question.setAlignmentX(Component.CENTER_ALIGNMENT);
765                            container.add(question);
766                            final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
767                            remember.setFont(remember.getFont().deriveFont(10f));
768                            remember.setAlignmentX(Component.CENTER_ALIGNMENT);
769                            //user preferences do not have the save option, but once complete the following line can be removed
770                            //Need to get the method to save connection configuration.
771                            remember.setVisible(true);
772                            JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
773                            JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
774                            JPanel button = new JPanel();
775                            button.setAlignmentX(Component.CENTER_ALIGNMENT);
776                            button.add(yesButton);
777                            button.add(noButton);
778                            container.add(button);
779
780                            noButton.addActionListener(new ActionListener() {
781                                @Override
782                                public void actionPerformed(ActionEvent e) {
783                                    if (remember.isSelected()) {
784                                        p.setRemoveLocoFromJMRI(EcosPreferences.ASK);
785                                    }
786                                    dialog.dispose();
787                                }
788                            });
789
790                            yesButton.addActionListener(new ActionListener() {
791                                @Override
792                                public void actionPerformed(ActionEvent e) {
793                                    if (remember.isSelected()) {
794                                        p.setRemoveLocoFromJMRI(EcosPreferences.YES);
795                                    }
796                                    setLocoToRoster();
797                                    _roster.removeEntry(re);
798                                    Roster.getDefault().writeRoster();
799                                    dialog.dispose();
800                                }
801                            });
802                            container.add(remember);
803                            container.setAlignmentX(Component.CENTER_ALIGNMENT);
804                            container.setAlignmentY(Component.CENTER_ALIGNMENT);
805                            dialog.getContentPane().add(container);
806                            dialog.pack();
807                            dialog.setModal(true);
808                            dialog.setVisible(true);
809
810                        } catch (HeadlessException he) {
811                            // silently ignore inability to display dialog
812                        }
813                    }
814                }
815                // Even if we do not delete the loco from the roster, we need to remove it from the ecos list.
816                deregister(getByEcosObject(entry));
817            }
818        }
819    }
820
821    @Override
822    public void message(EcosMessage m) {
823
824    }
825    /**
826     * The purpose of this is to get some of the basic cv details that are required
827     * for selecting the decoder mfg and family in the roster file.
828     * This might work as sending a single request rather than multiple.
829     */
830    private void getEcosCVs(EcosLocoAddress tmploco) {
831        tc.addEcosListener(this);
832        // ask to be notified
833        // We won't look to add new locos created on the ecos yet this can be added in at a later date.
834
835        EcosMessage m = new EcosMessage("get(" + tmploco.getEcosObject() + ", cv[7])");
836        tc.sendEcosMessage(m, this);
837
838        m = new EcosMessage("get(" + tmploco.getEcosObject() + ", cv[8])");
839        tc.sendEcosMessage(m, this);
840
841    }
842
843    private class WaitPrefLoad implements Runnable {
844
845        @Override
846        public void run() {
847            boolean result = true;
848            log.debug("Waiting for the Ecos preferences to be loaded before loading the loco database on the Ecos");
849            while (!wait) {
850                result = waitForPrefLoad();
851            }
852            if (result) {
853                loadData();
854            } else {
855                log.debug("waitForPrefLoad requested skip loadData()");
856            }
857        }
858
859        boolean wait = false;
860        int count = 0;
861
862        /**
863         * @return true if OK to proceed to load data, false if should abort
864         */
865        private boolean waitForPrefLoad() {
866            try {
867                Thread.sleep(100);
868            } catch (InterruptedException e) {
869                log.trace("waitForPrefLoad received InterruptedException, honoring termination request");
870                wait = true;
871                return false;
872            }
873            wait = p.getPreferencesLoaded() && rcm.isInitialized(ProfileManager.getDefault().getActiveProfile());
874            if (count >= 1000) {
875                wait = true;
876                log.warn("Timeout {} occurred on waiting for the Ecos preferences to be loaded", count);
877                return false;
878            }
879            count++;
880            return true;
881        }
882    }
883
884    public void refreshItems() {
885        // ask to be notified about newly created locos on the layout.
886        EcosMessage m = new EcosMessage("request(10, view)");
887        tc.sendEcosMessage(m, this);
888        if (monitorState) {
889            List<String> objects = getEcosObjectList();
890            for (int x = 0; x < objects.size(); x++) {
891                // Do a release before anything else.
892                m = new EcosMessage("release(" + getByEcosObject(objects.get(x)) + ", view, control)");
893                tc.sendEcosMessage(m, this);
894            }
895            for (int x = 0; x < objects.size(); x++) {
896                //Re-request view on loco
897                m = new EcosMessage("request(" + getByEcosObject(objects.get(x)) + ", view)");
898                tc.sendEcosMessage(m, this);
899
900                m = new EcosMessage("get(" + getByEcosObject(objects.get(x)) + ", speed)");
901                tc.sendEcosMessage(m, this);
902
903                m = new EcosMessage("get(" + getByEcosObject(objects.get(x)) + ", dir)");
904                tc.sendEcosMessage(m, this);
905            }
906        }
907        //monitorLocos(monitorState);
908    }
909
910    @Override
911    @Nonnull
912    public String getBeanTypeHandled(boolean plural) {
913        return Bundle.getMessage("EcosLocoAddresses");
914    }
915
916    private final static Logger log = LoggerFactory.getLogger(EcosLocoAddressManager.class);
917
918}