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