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<String, EcosLocoAddress>();   // stores known Ecos Object ids to DCC
053    private Hashtable<Integer, EcosLocoAddress> _tdcc = new Hashtable<Integer, EcosLocoAddress>();  // stores known DCC Address to Ecos Object ids
054
055    public EcosLocoAddressManager(@Nonnull EcosSystemConnectionMemo memo) {
056        super(memo);
057        locoToRoster = new EcosLocoToRoster(getMemo());
058        tc = getMemo().getTrafficController();
059        p = getMemo().getPreferenceManager();
060        rosterAttribute = p.getRosterAttribute();
061        rcm = InstanceManager.getDefault(RosterConfigManager.class);
062        loadEcosData();
063        try {
064            if (InstanceManager.getNullableDefault(ListedTableFrame.class) == null) {
065                new ListedTableFrame();
066            }
067            InstanceManager.getDefault(ListedTableFrame.class).addTable("jmri.jmrix.ecos.swing.locodatabase.EcosLocoTableTabAction", "ECoS Loco Database", false);
068        } catch (HeadlessException he) {
069            // silently ignore inability to display dialog
070        }
071    }
072
073    /**
074     * {@inheritDoc}
075     */
076    @Override
077    @Nonnull
078    public EcosSystemConnectionMemo getMemo() {
079        return (EcosSystemConnectionMemo) memo;
080    }
081
082    @Override
083    public char typeLetter() {
084        return 'Z';
085    } // NOI18N
086
087    @Override
088    public Class<NamedBean> getNamedBeanClass() {
089        return NamedBean.class;
090    }
091
092    @Override
093    public int getXMLOrder() {
094        return 65400;
095    }
096
097    /**
098     * EcosLocoAddresses have no system prefix, so return input unchanged.
099     * 
100     * @param s the input to make a system name
101     * @return the resultant system name
102     */
103    @Override
104    @Nonnull
105    public String makeSystemName(@Nonnull String s) {
106        return s;
107    }
108
109
110    @Override
111    @Nonnull
112    @Deprecated  // will be removed when Manager method is removed due to @Override
113    public List<String> getSystemNameList() {
114        jmri.util.LoggingUtil.deprecationWarning(log, "getSystemNameList");
115        return new ArrayList<String>();
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<String>();
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    public void dispose() {
334    }
335
336    public void terminateThreads() {
337        if (waitPrefLoad != null) {
338            waitPrefLoad.interrupt();
339        }
340    }
341
342    public boolean shutdownDispose() {
343        boolean hasTempEntries = false;
344        Enumeration<String> en = _tecos.keys();
345        _tdcc.clear();
346        // This will remove/deregister non-temporary locos from the list.
347        while (en.hasMoreElements()) {
348            String ecosObject = en.nextElement();
349            if (_tecos.get(ecosObject).getEcosTempEntry()) {
350                hasTempEntries = true;
351            } else {
352                deregister(getByEcosObject(ecosObject));
353                _tecos.remove(ecosObject);
354            }
355        }
356
357        if (p.getAdhocLocoFromEcos() == 0x01) {
358            disposefinal();
359        } else if (!hasTempEntries) {
360            disposefinal();
361        } else if (p.getAdhocLocoFromEcos() == EcosPreferences.ASK) {
362
363            final JDialog dialog = new JDialog();
364            dialog.setTitle(Bundle.getMessage("RemoveLocoTitle"));
365            dialog.setLocation(300, 200);
366            dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
367            JPanel container = new JPanel();
368            container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
369            container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
370
371            JLabel question = new JLabel(Bundle.getMessage("RemoveLocoLine1"));
372            question.setAlignmentX(Component.CENTER_ALIGNMENT);
373            container.add(question);
374            question = new JLabel(Bundle.getMessage("RemoveLocoLine2"));
375            question.setAlignmentX(Component.CENTER_ALIGNMENT);
376            container.add(question);
377            final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
378            remember.setFont(remember.getFont().deriveFont(10f));
379            remember.setAlignmentX(Component.CENTER_ALIGNMENT);
380            // user preferences do not have the save option, but once complete the following line can be removed
381            // TODO get the method to save connection configuration.
382            remember.setVisible(true);
383            JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
384            JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
385            JPanel button = new JPanel();
386            button.setAlignmentX(Component.CENTER_ALIGNMENT);
387            button.add(yesButton);
388            button.add(noButton);
389            container.add(button);
390
391            noButton.addActionListener(new ActionListener() {
392                @Override
393                public void actionPerformed(ActionEvent e) {
394                    if (remember.isSelected()) {
395                        p.setAdhocLocoFromEcos(0x01);
396                    }
397                    dialog.dispose();
398                }
399            });
400
401            yesButton.addActionListener(new ActionListener() {
402                @Override
403                public void actionPerformed(ActionEvent e) {
404                    if (remember.isSelected()) {
405                        p.setAdhocLocoFromEcos(0x02);
406                    }
407                    dialog.dispose();
408                }
409            });
410            container.add(remember);
411            container.setAlignmentX(Component.CENTER_ALIGNMENT);
412            container.setAlignmentY(Component.CENTER_ALIGNMENT);
413            dialog.getContentPane().add(container);
414            dialog.pack();
415            dialog.setModal(true);
416            dialog.setVisible(true);
417        }
418        return true;
419    }
420
421    /**
422     * The PropertyChangeListener interface in this class is intended to keep
423     * track of roster entries and sync them up with the Ecos.
424     */
425    @Override
426    public void propertyChange(java.beans.PropertyChangeEvent e) {
427        //If we are adding the loco to the roster from the ecos, we don't want to be adding it back to the ecos!
428        if (getLocoToRoster()) {
429            return;
430        }
431        if (e.getPropertyName().equals("add")) {
432            _re = (RosterEntry) e.getNewValue();
433
434        } else if (e.getPropertyName().equals("saved")) {
435            if (_re != null) {
436                if (_re.getAttribute(rosterAttribute) != null) {
437                    _re = null;
438                    return;
439                }
440                //if the ecosobject attribute exists this would then indicate that it has already been created on the ecos
441                if (p.getAddLocoToEcos() == EcosPreferences.ASK) {
442                    final JDialog dialog = new JDialog();
443                    dialog.setTitle(Bundle.getMessage("AddLocoTitle"));
444                    //test.setSize(300,130);
445                    dialog.setLocation(300, 200);
446                    dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
447                    JPanel container = new JPanel();
448                    container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
449                    container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
450
451                    JLabel question = new JLabel(Bundle.getMessage("AddLocoXQuestion", _re.getId(), getMemo().getUserName()));
452                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
453                    container.add(question);
454                    final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
455                    remember.setFont(remember.getFont().deriveFont(10f));
456                    remember.setAlignmentX(Component.CENTER_ALIGNMENT);
457                    //user preferences do not have the save option, but once complete the following line can be removed
458                    //Need to get the method to save connection configuration.
459                    remember.setVisible(true);
460                    JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
461                    JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
462                    JPanel button = new JPanel();
463                    button.setAlignmentX(Component.CENTER_ALIGNMENT);
464                    button.add(yesButton);
465                    button.add(noButton);
466                    container.add(button);
467
468                    noButton.addActionListener(new ActionListener() {
469                        @Override
470                        public void actionPerformed(ActionEvent e) {
471                            if (remember.isSelected()) {
472                                p.setAddLocoToEcos(0x01);
473                            }
474                            _re = null;
475                            dialog.dispose();
476                        }
477                    });
478
479                    yesButton.addActionListener(new ActionListener() {
480                        @Override
481                        public void actionPerformed(ActionEvent e) {
482                            if (remember.isSelected()) {
483                                p.setAddLocoToEcos(0x02);
484                            }
485                            RosterToEcos rosterToEcos = new RosterToEcos(getMemo());
486                            rosterToEcos.createEcosLoco(_re);
487                            _re = null;
488                            dialog.dispose();
489                        }
490                    });
491                    container.add(remember);
492                    container.setAlignmentX(Component.CENTER_ALIGNMENT);
493                    container.setAlignmentY(Component.CENTER_ALIGNMENT);
494                    dialog.getContentPane().add(container);
495                    dialog.pack();
496                    dialog.setModal(true);
497                    dialog.setVisible(true);
498                }
499                if (p.getAddLocoToEcos() == 0x02) {
500                    RosterToEcos rosterToEcos = new RosterToEcos(getMemo());
501                    rosterToEcos.createEcosLoco(_re);
502                    _re = null;
503                }
504            }
505        } else if (e.getPropertyName().equals("remove")) {
506            _re = (RosterEntry) e.getNewValue();
507            if (_re.getAttribute(rosterAttribute) != null) {
508                if (p.getRemoveLocoFromEcos() == EcosPreferences.YES){
509                    RemoveObjectFromEcos removeObjectFromEcos = new RemoveObjectFromEcos();
510                    removeObjectFromEcos.removeObjectFromEcos(_re.getAttribute(p.getRosterAttribute()), tc);
511                    deleteEcosLoco(provideByEcosObject(_re.getAttribute(p.getRosterAttribute())));
512                } else if(p.getRemoveLocoFromEcos() == EcosPreferences.ASK ) {
513                    final JDialog dialog = new JDialog();
514                    dialog.setTitle(Bundle.getMessage("RemoveLocoTitle"));
515                    //test.setSize(300,130);
516                    dialog.setLocation(300, 200);
517                    dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
518                    JPanel container = new JPanel();
519                    container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
520                    container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
521
522                    JLabel question = new JLabel(Bundle.getMessage("RemoveLocoXQuestion", getMemo().getUserName()));
523                    question.setAlignmentX(Component.CENTER_ALIGNMENT);
524                    container.add(question);
525                    final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
526                    remember.setFont(remember.getFont().deriveFont(10f));
527                    remember.setAlignmentX(Component.CENTER_ALIGNMENT);
528                    //user preferences do not have the save option, but once complete the following line can be removed
529                    //Need to get the method to save connection configuration.
530                    remember.setVisible(true);
531                    JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
532                    JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
533                    JPanel button = new JPanel();
534                    button.setAlignmentX(Component.CENTER_ALIGNMENT);
535                    button.add(yesButton);
536                    button.add(noButton);
537                    container.add(button);
538
539                    noButton.addActionListener(new ActionListener() {
540                        @Override
541                        public void actionPerformed(ActionEvent e) {
542                            if (remember.isSelected()) {
543                                p.setRemoveLocoFromEcos(0x01);
544                            }
545                            provideByEcosObject(_re.getAttribute(p.getRosterAttribute())).setRosterId(null);
546                            dialog.dispose();
547                        }
548                    });
549
550                    yesButton.addActionListener(new ActionListener() {
551                        @Override
552                        public void actionPerformed(ActionEvent e) {
553                            if (remember.isSelected()) {
554                                p.setRemoveLocoFromEcos(0x02);
555                            }
556                            RemoveObjectFromEcos removeObjectFromEcos = new RemoveObjectFromEcos();
557                            removeObjectFromEcos.removeObjectFromEcos(_re.getAttribute(p.getRosterAttribute()), tc);
558                            deleteEcosLoco(provideByEcosObject(_re.getAttribute(p.getRosterAttribute())));
559                            dialog.dispose();
560                        }
561                    });
562                    container.add(remember);
563                    container.setAlignmentX(Component.CENTER_ALIGNMENT);
564                    container.setAlignmentY(Component.CENTER_ALIGNMENT);
565                    dialog.getContentPane().add(container);
566                    dialog.pack();
567                    dialog.setModal(true);
568                    dialog.setVisible(true);
569                }
570            }
571            _re = null;
572        } else if (e.getPropertyName().equals("throttleAssigned")) {
573            DccLocoAddress la = (DccLocoAddress) e.getNewValue();
574            EcosLocoAddress ela = getByDccAddress(la.getNumber());
575            EcosMessage m = new EcosMessage("get(" + ela.getEcosObject() + ", speed)");
576            tc.sendEcosMessage(m, this);
577            m = new EcosMessage("get(" + ela.getEcosObject() + ", dir)");
578            tc.sendEcosMessage(m, this);
579        }
580    }
581
582    @Override
583    public void reply(EcosReply m) {
584        String strde;
585
586        if (m.getResultCode() == 0) {
587            int ecosObjectId = m.getEcosObjectId();
588            if ((ecosObjectId != 10) && ((ecosObjectId < 1000) || (ecosObjectId > 2000))) {
589                log.debug("message received that is not within the valid loco object range");
590                return;
591            }
592            List<String> headerDetails = m.getReplyHeaderDetails();
593            String[] msgDetails = m.getContents();
594            if (m.isUnsolicited()) {
595                if (ecosObjectId == 10) {
596                    log.debug("We have received notification of a change in the Loco list");
597                    if (msgDetails.length == 0) {
598                        EcosMessage mout = new EcosMessage("queryObjects(10)");
599                        tc.sendEcosMessage(mout, this);
600                        //Version 3.0.1 of the software has an issue in that it stops sending updates on the
601                        //loco objects when a delete has happened, we therefore need to release the old view
602                        //then re-request it.
603                        mout = new EcosMessage("release(10, view)");
604                        tc.sendEcosMessage(mout, this);
605                        mout = new EcosMessage("request(10, view)");
606                        tc.sendEcosMessage(mout, this);
607                    } else if (msgDetails[0].contains("msg[LIST_CHANGED]")) {
608                        EcosMessage mout = new EcosMessage("queryObjects(10)");
609                        tc.sendEcosMessage(mout, this);
610                    }
611                } else {
612                    EcosLocoAddress tmploco;
613                    log.debug("Forwarding on State change for {}", ecosObjectId);
614                    String strLocoObject = Integer.toString(ecosObjectId);
615                    tmploco = _tecos.get(strLocoObject);
616                    if (tmploco != null) {
617                        tmploco.reply(m);
618                    }
619                }
620            } else {
621                String replyType = m.getReplyType();
622
623                if (replyType.equals("queryObjects")) {
624                    if (ecosObjectId == 10) {
625                        if (headerDetails.size() == 0 || (headerDetails.size() == 1 && headerDetails.get(0).equals(""))) {
626                            checkLocoList(msgDetails);
627                        } else {
628                            processLocoToRosterQueue = false;
629                            //Format of the reply details are ObjectId, followed by object ids.
630                            for (String line : msgDetails) {
631                                String[] objectdetail = line.split(" ");
632                                EcosLocoAddress tmploco = null;
633                                //The first part of the messages is always the object id.
634                                strde = objectdetail[0];
635                                strde = strde.trim();
636                                int object = Integer.parseInt(strde);
637                                if ((1000 <= object) && (object < 2000)) {
638                                    tmploco = provideByEcosObject(strde);
639                                }
640                                decodeLocoDetails(tmploco, line, true);
641                            }
642                            locoToRoster.processQueue();
643                            processLocoToRosterQueue = true;
644                        }
645                    }
646                } else if (replyType.equals("get")) {
647                    EcosLocoAddress tmploco = provideByEcosObject(Integer.toString(ecosObjectId));
648                    for (String line : msgDetails) {
649                        decodeLocoDetails(tmploco, line, false);
650                    }
651                }
652            }
653        }
654    }
655
656    void decodeLocoDetails(EcosLocoAddress tmploco, String line, boolean addToRoster) {
657        if (tmploco == null) {
658            return;
659        }
660        if (line.contains("cv")) {
661            String cv = EcosReply.getContentDetails(line, "cv");
662            cv = cv.replaceAll("\\s", "");  //remove all white spaces, as 4.1.0 version removed the space after the ,
663            int cvnum = Integer.parseInt(cv.substring(0, cv.indexOf(",")));
664            int cvval = Integer.parseInt(cv.substring(cv.indexOf(",") + 1, cv.length()));
665            tmploco.setCV(cvnum, cvval);
666            if (cvnum == 8 && processLocoToRosterQueue) {
667                locoToRoster.processQueue();
668            }
669        }
670        if (line.contains("addr")) {
671            tmploco.setLocoAddress(GetEcosObjectNumber.getEcosObjectNumber(line, "addr[", "]"));
672            if (tmploco.getCV(7) == -1) {
673                tmploco.setCV(7, 0);
674                getEcosCVs(tmploco);
675            }
676        }
677        if (line.contains("name")) {
678            String name = EcosReply.getContentDetails(line, "name").trim();
679            name = name.substring(1, name.length() - 1);
680            tmploco.setEcosDescription(name);
681        }
682        if (line.contains("protocol")) {
683            tmploco.setProtocol(EcosReply.getContentDetails(line, "protocol"));
684        }
685        if (line.contains("speed")) {
686            tmploco.setSpeed(Integer.parseInt(EcosReply.getContentDetails(line, "speed")));
687        }
688
689        if (line.contains("dir")) {
690            boolean newDirection = false;
691            if (EcosReply.getContentDetails(line, "dir").equals("0")) {
692                newDirection = true;
693            }
694            tmploco.setDirection(newDirection);
695        }
696        register(tmploco);
697        if (p.getAddLocoToJMRI() != EcosPreferences.NO && addToRoster) {
698            locoToRoster.addToQueue(tmploco);
699        }
700    }
701
702    /* This is used after an event update form the ecos informing us of a change in the
703     * loco list, we have to determine if it is an addition or delete.
704     * We should only ever do either a remove or an add in one go, if we are adding the loco
705     * to the roster otherwise this causes a problem with the roster list.
706     */
707    void checkLocoList(String[] ecoslines) {
708        log.debug("Checking loco list");
709        String loco;
710        for (String ecosline : ecoslines) {
711            loco = ecosline;
712            loco = loco.replaceAll("[\\n\\r]", "");
713            if (getByEcosObject(loco) == null) {
714                log.debug("We are to add loco {} to the Ecos Loco List", loco);
715                EcosMessage mout = new EcosMessage("get(" + loco + ", addr, name, protocol)");
716                tc.sendEcosMessage(mout, this);
717            }
718        }
719
720        String[] jmrilist = getEcosObjectArray();
721        boolean nomatch = true;
722        for (String entry : jmrilist) {
723            nomatch = true;
724            for (String ecosline : ecoslines) {
725                loco = ecosline;
726                loco = loco.replaceAll("[\\n\\r]", "");
727                if (loco.equals(entry)) {
728                    nomatch = false;
729                    break;
730                }
731            }
732            if (nomatch) {
733                // We do not have a match, therefore this should be deleted from the Ecos loco Manager " + jmrilist[i]
734                log.debug("Loco not found so need to remove from register");
735                if (getByEcosObject(entry).getRosterId() != null) {
736                    final String rosterid = getByEcosObject(entry).getRosterId();
737                    final Roster _roster = Roster.getDefault();
738                    final RosterEntry re = _roster.entryFromTitle(rosterid);
739                    re.deleteAttribute(p.getRosterAttribute());
740                    re.writeFile(null, null);
741                    Roster.getDefault().writeRoster();
742                    if (p.getRemoveLocoFromJMRI() == EcosPreferences.YES) {
743                        _roster.removeEntry(re);
744                        Roster.getDefault().writeRoster();
745                    } else if (p.getRemoveLocoFromJMRI() == EcosPreferences.ASK) {
746                        try {
747                            final JDialog dialog = new JDialog();
748                            dialog.setTitle(Bundle.getMessage("RemoveRosterEntryTitle"));
749                            dialog.setLocation(300, 200);
750                            dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
751                            JPanel container = new JPanel();
752                            container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
753                            container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
754
755                            JLabel question = new JLabel(Bundle.getMessage("RemoveRosterEntryX", rosterid));
756                            question.setAlignmentX(Component.CENTER_ALIGNMENT);
757                            container.add(question);
758                            final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting"));
759                            remember.setFont(remember.getFont().deriveFont(10f));
760                            remember.setAlignmentX(Component.CENTER_ALIGNMENT);
761                            //user preferences do not have the save option, but once complete the following line can be removed
762                            //Need to get the method to save connection configuration.
763                            remember.setVisible(true);
764                            JButton yesButton = new JButton(Bundle.getMessage("ButtonYes"));
765                            JButton noButton = new JButton(Bundle.getMessage("ButtonNo"));
766                            JPanel button = new JPanel();
767                            button.setAlignmentX(Component.CENTER_ALIGNMENT);
768                            button.add(yesButton);
769                            button.add(noButton);
770                            container.add(button);
771
772                            noButton.addActionListener(new ActionListener() {
773                                @Override
774                                public void actionPerformed(ActionEvent e) {
775                                    if (remember.isSelected()) {
776                                        p.setRemoveLocoFromJMRI(EcosPreferences.ASK);
777                                    }
778                                    dialog.dispose();
779                                }
780                            });
781
782                            yesButton.addActionListener(new ActionListener() {
783                                @Override
784                                public void actionPerformed(ActionEvent e) {
785                                    if (remember.isSelected()) {
786                                        p.setRemoveLocoFromJMRI(EcosPreferences.YES);
787                                    }
788                                    setLocoToRoster();
789                                    _roster.removeEntry(re);
790                                    Roster.getDefault().writeRoster();
791                                    dialog.dispose();
792                                }
793                            });
794                            container.add(remember);
795                            container.setAlignmentX(Component.CENTER_ALIGNMENT);
796                            container.setAlignmentY(Component.CENTER_ALIGNMENT);
797                            dialog.getContentPane().add(container);
798                            dialog.pack();
799                            dialog.setModal(true);
800                            dialog.setVisible(true);
801
802                        } catch (HeadlessException he) {
803                            // silently ignore inability to display dialog
804                        }
805                    }
806                }
807                // Even if we do not delete the loco from the roster, we need to remove it from the ecos list.
808                deregister(getByEcosObject(entry));
809            }
810        }
811    }
812
813    @Override
814    public void message(EcosMessage m) {
815
816    }
817    /**
818     * The purpose of this is to get some of the basic cv details that are required
819     * for selecting the decoder mfg and family in the roster file.
820     * This might work as sending a single request rather than multiple.
821     */
822    private void getEcosCVs(EcosLocoAddress tmploco) {
823        tc.addEcosListener(this);
824        // ask to be notified
825        // We won't look to add new locos created on the ecos yet this can be added in at a later date.
826
827        EcosMessage m = new EcosMessage("get(" + tmploco.getEcosObject() + ", cv[7])");
828        tc.sendEcosMessage(m, this);
829
830        m = new EcosMessage("get(" + tmploco.getEcosObject() + ", cv[8])");
831        tc.sendEcosMessage(m, this);
832
833    }
834
835    private class WaitPrefLoad implements Runnable {
836
837        @Override
838        public void run() {
839            boolean result = true;
840            log.debug("Waiting for the Ecos preferences to be loaded before loading the loco database on the Ecos");
841            while (!wait) {
842                result = waitForPrefLoad();
843            }
844            if (result) {
845                loadData();
846            } else {
847                log.debug("waitForPrefLoad requested skip loadData()");
848            }
849        }
850
851        boolean wait = false;
852        int count = 0;
853
854        /**
855         * @return true if OK to proceed to load data, false if should abort
856         */
857        private boolean waitForPrefLoad() {
858            try {
859                Thread.sleep(100);
860            } catch (InterruptedException e) {
861                log.trace("waitForPrefLoad received InterruptedException, honoring termination request");
862                wait = true;
863                return false;
864            } catch (Exception e) {
865                wait = true;
866                log.error(e.toString());
867                return false;
868            }
869            wait = p.getPreferencesLoaded() && rcm.isInitialized(ProfileManager.getDefault().getActiveProfile());
870            if (count >= 1000) {
871                wait = true;
872                log.warn("Timeout {} occurred on waiting for the Ecos preferences to be loaded", count);
873                return false;
874            }
875            count++;
876            return true;
877        }
878    }
879
880    public void refreshItems() {
881        // ask to be notified about newly created locos on the layout.
882        EcosMessage m = new EcosMessage("request(10, view)");
883        tc.sendEcosMessage(m, this);
884        if (monitorState) {
885            List<String> objects = getEcosObjectList();
886            for (int x = 0; x < objects.size(); x++) {
887                // Do a release before anything else.
888                m = new EcosMessage("release(" + getByEcosObject(objects.get(x)) + ", view, control)");
889                tc.sendEcosMessage(m, this);
890            }
891            for (int x = 0; x < objects.size(); x++) {
892                //Re-request view on loco
893                m = new EcosMessage("request(" + getByEcosObject(objects.get(x)) + ", view)");
894                tc.sendEcosMessage(m, this);
895
896                m = new EcosMessage("get(" + getByEcosObject(objects.get(x)) + ", speed)");
897                tc.sendEcosMessage(m, this);
898
899                m = new EcosMessage("get(" + getByEcosObject(objects.get(x)) + ", dir)");
900                tc.sendEcosMessage(m, this);
901            }
902        }
903        //monitorLocos(monitorState);
904    }
905
906    @Override
907    @Nonnull
908    public String getBeanTypeHandled(boolean plural) {
909        return Bundle.getMessage("EcosLocoAddresses");
910    }
911
912    private final static Logger log = LoggerFactory.getLogger(EcosLocoAddressManager.class);
913
914}