001package jmri.implementation;
002
003import java.beans.PropertyChangeListener;
004import java.util.ArrayList;
005import java.util.List;
006import jmri.InstanceManager;
007import jmri.NamedBean;
008import jmri.NamedBeanHandle;
009import jmri.NamedBeanHandleManager;
010import jmri.NamedBeanUsageReport;
011import jmri.Sensor;
012import jmri.SignalHead;
013import jmri.SignalMast;
014import jmri.Turnout;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018/**
019 * A Conditional type to provide Signal Groups (n Signal Heads w/Conditionals
020 * for a main Mast).
021 *
022 * @see jmri.SignalGroup SignalGroup
023 * @author Pete Cressman Copyright (C) 2009
024 * @author Egbert Broerse 2017
025 */
026public class DefaultSignalGroup extends AbstractNamedBean implements jmri.SignalGroup {
027
028    /**
029     * Constructor for SignalGroup instance.
030     *
031     * @param systemName suggested system name
032     * @param userName   provided user name
033     */
034    public DefaultSignalGroup(String systemName, String userName) {
035        super(systemName, userName);
036    }
037
038    /**
039     * Constructor for SignalGroup instance.
040     *
041     * @param systemName suggested system name
042     */
043    public DefaultSignalGroup(String systemName) {
044        super(systemName, null);
045        log.debug("default SignalGroup {} created", systemName);
046    }
047
048    @Override
049    public String getBeanType() {
050        return Bundle.getMessage("BeanNameSignalGroup");
051    }
052
053    ArrayList<String> _signalMastAspects = new ArrayList<String>();
054
055    private NamedBeanHandle<SignalMast> _signalMast;
056
057    private boolean headactive = false;
058
059    private boolean enabled = true;
060
061    @Override
062    public void setEnabled(boolean boo) {
063        enabled = boo;
064    }
065
066    @Override
067    public boolean getEnabled() {
068        return enabled;
069    }
070
071    @Override
072    public void setSignalMast(String pName) {
073        SignalMast mMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getBySystemName(pName);
074        if (mMast == null) {
075            mMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getByUserName(pName);
076        }
077        if (mMast == null) {
078            log.warn("did not find a Signal Mast named {}", pName);
079            return;
080        }
081        setSignalMast(mMast, pName);
082    }
083
084    @Override
085    public void setSignalMast(SignalMast signalMast, String mastName) {
086        if (_signalMast != null) {
087            getSignalMast().removePropertyChangeListener(mSignalMastListener);
088        }
089        _signalMast = InstanceManager.getDefault(NamedBeanHandleManager.class)
090                .getNamedBeanHandle(mastName, signalMast);
091        getSignalMast().addPropertyChangeListener(mSignalMastListener = new java.beans.PropertyChangeListener() {
092            @Override
093            public void propertyChange(java.beans.PropertyChangeEvent e) {
094                if (e.getPropertyName().equals("Aspect")) {
095                    String now = ((String) e.getNewValue());
096                    if (isSignalMastAspectIncluded(now)) {
097                        setHead();
098                    } else {
099                        resetHeads();
100                    }
101                }
102            }
103        });
104    }
105
106    @Override
107    public SignalMast getSignalMast() {
108        return _signalMast.getBean();
109    }
110
111    @Override
112    public String getSignalMastName() {
113        return _signalMast.getName();
114    }
115
116    @Override
117    public void addSignalMastAspect(String aspect) {
118        if (isSignalMastAspectIncluded(aspect)) {
119            return;
120        }
121        _signalMastAspects.add(aspect);
122    }
123
124    @Override
125    public boolean isSignalMastAspectIncluded(String aspect) {
126        for (int i = 0; i < _signalMastAspects.size(); i++) {
127            if (_signalMastAspects.get(i).equals(aspect)) {
128                // Found Aspect
129                return true;
130            }
131        }
132        return false;
133    }
134
135    @Override
136    public void deleteSignalMastAspect(String aspect) {
137        _signalMastAspects.remove(aspect);
138    }
139
140    @Override
141    public int getNumSignalMastAspects() {
142        return _signalMastAspects.size();
143    }
144
145    @Override
146    public String getSignalMastAspectByIndex(int x) {
147        try {
148            return _signalMastAspects.get(x);
149        } catch (IndexOutOfBoundsException ioob) {
150            return null;
151        }
152    }
153
154    @Override
155    public void clearSignalMastAspect() {
156        _signalMastAspects = new ArrayList<String>();
157    }
158
159    @Override
160    public void addSignalHead(NamedBeanHandle<SignalHead> headBean) {
161        SignalHeadItem shi = new SignalHeadItem(headBean);
162        _signalHeadItem.add(shi);
163    }
164
165    /**
166     * Add a new Signal Head to the group by name.
167     *
168     * @param pName system or username of existing signal head to add to group
169     */
170    public void addSignalHead(String pName) {
171        SignalHead mHead = InstanceManager.getDefault(jmri.SignalHeadManager.class).getBySystemName(pName);
172        if (mHead == null) {
173            mHead = InstanceManager.getDefault(jmri.SignalHeadManager.class).getByUserName(pName);
174        }
175        if (mHead == null) {
176            log.warn("did not find a SignalHead named {}", pName);
177        } else {
178            addSignalHead(InstanceManager.getDefault(NamedBeanHandleManager.class)
179                    .getNamedBeanHandle(pName, mHead));
180        }
181    }
182
183    @Override
184    public void addSignalHead(SignalHead signalHead) {
185        addSignalHead(InstanceManager.getDefault(NamedBeanHandleManager.class)
186                .getNamedBeanHandle(signalHead.getDisplayName(), signalHead));
187    }
188
189    protected PropertyChangeListener mSignalMastListener = null;
190
191    @Override
192    public void setHeadAlignTurnout(SignalHead signalHead, Turnout turnout, int state) {
193        SignalHeadItem shi = getHeadItem(signalHead);
194        shi.addTurnout(turnout, state);
195    }
196
197    @Override
198    public void setHeadAlignSensor(SignalHead signalHead, Sensor sensor, int state) {
199        SignalHeadItem shi = getHeadItem(signalHead);
200        shi.addSensor(sensor, state);
201    }
202
203    private SignalHeadItem getHeadItemByIndex(int x) {
204        try {
205            return _signalHeadItem.get(x);
206        } catch (IndexOutOfBoundsException ioob) {
207            return null;
208        }
209    }
210
211    @Override
212    public String getHeadItemNameByIndex(int x) {
213        try {
214            return getHeadItemByIndex(x).getName();
215        } catch (IndexOutOfBoundsException ioob) {
216            return null;
217        }
218    }
219
220    @Override
221    public SignalHead getHeadItemBeanByIndex(int x) {
222        try {
223            return getHeadItemByIndex(x).getSignalHead();
224        } catch (IndexOutOfBoundsException ioob) {
225            return null;
226        }
227    }
228
229    @Override
230    public int getNumHeadItems() {
231        return _signalHeadItem.size();
232    }
233
234    @Override
235    public int getHeadOffState(SignalHead headBean) {
236        try {
237            return getHeadItem(headBean).getOffAppearance();
238        } catch (NullPointerException e) {
239            return -1;
240        }
241    }
242
243    @Override
244    public int getHeadOnState(SignalHead headBean) {
245        try {
246            return getHeadItem(headBean).getOnAppearance();
247        } catch (NullPointerException e) {
248            return -1;
249        }
250    }
251
252    @Override
253    public int getHeadOnStateByIndex(int x) {
254        try {
255            return getHeadItemByIndex(x).getOnAppearance();
256        } catch (IndexOutOfBoundsException ioob) {
257            return -1;
258        }
259    }
260
261    @Override
262    public int getHeadOffStateByIndex(int x) {
263        try {
264            return getHeadItemByIndex(x).getOffAppearance();
265        } catch (IndexOutOfBoundsException ioob) {
266            return -1;
267        }
268    }
269
270    @Override
271    public void deleteSignalHead(SignalHead sh) {
272        _signalHeadItem.remove(getHeadItem(sh));
273    }
274
275    @Override
276    public void deleteSignalHead(NamedBeanHandle<SignalHead> headBean) {
277        _signalHeadItem.remove(getHeadItem(headBean.getName()));
278    }
279
280    @Override
281    public void setHeadOnState(SignalHead head, int state) {
282        getHeadItem(head).setOnAppearance(state);
283        firePropertyChange("UpdateCondition", null, null);
284    }
285
286    @Override
287    public void setHeadOffState(SignalHead head, int state) {
288        getHeadItem(head).setOffAppearance(state);
289        firePropertyChange("UpdateCondition", null, null);
290    }
291
292    @Override
293    public boolean isHeadIncluded(SignalHead signalHead) {
294        for (int i = 0; i < _signalHeadItem.size(); i++) {
295            if (_signalHeadItem.get(i).getSignalHead() == signalHead) {
296                // Found head
297                return true;
298            }
299        }
300        return false;
301    }
302
303    /**
304     * Get a Signal Head item by its name from the Signal Group
305     */
306    private SignalHeadItem getHeadItem(String name) {
307        for (int i = 0; i < _signalHeadItem.size(); i++) {
308            if (_signalHeadItem.get(i).getName().equals(name)) {
309                return _signalHeadItem.get(i);
310            }
311        }
312        return null;
313    }
314
315    /**
316     * Get a Signal Head item by its Bean from the Signal Group
317     */
318    private SignalHeadItem getHeadItem(NamedBean headBean) {
319        for (int i = 0; i < _signalHeadItem.size(); i++) {
320            if (_signalHeadItem.get(i).getSignalHead().equals(headBean)) {
321                return _signalHeadItem.get(i);
322            }
323        }
324        return null;
325    }
326
327    @Override
328    public boolean isTurnoutIncluded(SignalHead signalHead, Turnout turnout) {
329        return getHeadItem(signalHead).isTurnoutIncluded(turnout);
330    }
331
332    @Override
333    public int getTurnoutState(SignalHead signalHead, Turnout turnout) {
334        SignalHeadItem shi = getHeadItem(signalHead);
335        if (shi != null) {
336            return shi.getTurnoutState(turnout);
337        }
338        return -1;
339    }
340
341    @Override
342    public int getTurnoutStateByIndex(int x, Turnout turnout) {
343        try {
344            return getHeadItemByIndex(x).getTurnoutState(turnout);
345        } catch (IndexOutOfBoundsException ioob) {
346            return -1;
347        }
348    }
349
350    @Override
351    public int getTurnoutStateByIndex(int x, int pTurnout) {
352        try {
353            return getHeadItemByIndex(x).getTurnoutState(pTurnout);
354        } catch (IndexOutOfBoundsException ioob) {
355            return -1;
356        }
357    }
358
359    @Override
360    public String getTurnoutNameByIndex(int x, int pTurnout) {
361        try {
362            return getHeadItemByIndex(x).getTurnoutName(pTurnout);
363        } catch (IndexOutOfBoundsException ioob) {
364            return null;
365        }
366    }
367
368    @Override
369    public Turnout getTurnoutByIndex(int x, int pTurnout) {
370        try {
371            return getHeadItemByIndex(x).getTurnout(pTurnout);
372        } catch (IndexOutOfBoundsException ioob) {
373            return null;
374        }
375    }
376
377    @Override
378    public int getSensorStateByIndex(int x, int pSensor) {
379        try {
380            return getHeadItemByIndex(x).getSensorState(pSensor);
381        } catch (IndexOutOfBoundsException ioob) {
382            return -1;
383        }
384    }
385
386    @Override
387    public String getSensorNameByIndex(int x, int pSensor) {
388        try {
389            return getHeadItemByIndex(x).getSensorName(pSensor);
390        } catch (IndexOutOfBoundsException ioob) {
391            return null;
392        }
393    }
394
395    @Override
396    public Sensor getSensorByIndex(int x, int pSensor) {
397        try {
398            return getHeadItemByIndex(x).getSensor(pSensor);
399        } catch (IndexOutOfBoundsException ioob) {
400            return null;
401        }
402    }
403
404    @Override
405    public boolean isSensorIncluded(SignalHead signalHead, Sensor sensor) {
406        return getHeadItem(signalHead).isSensorIncluded(sensor);
407    }
408
409    @Override
410    public int getSensorState(SignalHead signalHead, Sensor sensor) {
411        SignalHeadItem shi = getHeadItem(signalHead);
412        if (shi != null) {
413            return shi.getSensorState(sensor);
414        }
415        return -1;
416    }
417
418    @Override
419    public boolean getSensorTurnoutOper(SignalHead signalHead) {
420        return getHeadItem(signalHead).getSensorTurnoutOper();
421    }
422
423    @Override
424    public boolean getSensorTurnoutOperByIndex(int x) {
425        return getHeadItemByIndex(x).getSensorTurnoutOper();
426    }
427
428    @Override
429    public void setSensorTurnoutOper(SignalHead signalHead, boolean boo) {
430        getHeadItem(signalHead).setSensorTurnoutOper(boo);
431        firePropertyChange("UpdateCondition", null, null);
432    }
433
434    @Override
435    public void clearHeadTurnout(SignalHead signalHead) {
436        getHeadItem(signalHead).clearSignalTurnouts();
437    }
438
439    @Override
440    public void clearHeadSensor(SignalHead signalHead) {
441        getHeadItem(signalHead).clearSignalSensors();
442    }
443
444    private void resetHeads() {
445        if (!headactive) {
446            return;
447        }
448        for (int i = 0; i < _signalHeadItem.size(); i++) {
449            _signalHeadItem.get(i).getSignalHead().setAppearance(_signalHeadItem.get(i).getOffAppearance());
450        }
451        headactive = false;
452    }
453
454    private void setHead() {
455        boolean active = false;
456        for (int i = 0; i < _signalHeadItem.size(); i++) {
457            if (_signalHeadItem.get(i).checkActive()) {
458                if (active) {
459                    log.warn("two signal heads in the group should not be active at once");
460                }
461                active = true;
462                headactive = true;
463            }
464        }
465    }
466
467    @Override
468    public int getNumHeadSensorsByIndex(int x) {
469        try {
470
471            return getHeadItemByIndex(x).getNumSensors();
472        } catch (IndexOutOfBoundsException ioob) {
473            return -1;
474        }
475    }
476
477    @Override
478    public int getNumHeadTurnoutsByIndex(int x) {
479        try {
480            return getHeadItemByIndex(x).getNumTurnouts();
481        } catch (IndexOutOfBoundsException ioob) {
482            return -1;
483        }
484    }
485    ArrayList<SignalHeadItem> _signalHeadItem = new ArrayList<SignalHeadItem>();
486
487    private static class SignalHeadItem {
488
489        SignalHeadItem(NamedBeanHandle<SignalHead> sh) {
490            namedHead = sh;
491            if (namedHead.getBean().getClass().getName().contains("SingleTurnoutSignalHead")) {
492                jmri.implementation.SingleTurnoutSignalHead stsh = (jmri.implementation.SingleTurnoutSignalHead) namedHead.getBean();
493                if ((onAppearance == 0x00) && (offAppearance == 0x00)) {
494                    onAppearance = stsh.getOnAppearance();
495                    offAppearance = stsh.getOffAppearance();
496                }
497            }
498        }
499
500        private NamedBeanHandle<SignalHead> namedHead;
501
502        public String getName() {
503            return namedHead.getName();
504        }
505
506        public SignalHead getSignalHead() {
507            return namedHead.getBean();
508        }
509
510        private int onAppearance = 0x00;
511        private int offAppearance = 0x00;
512
513        public void setOnAppearance(int app) {
514            onAppearance = app;
515        }
516
517        public int getOnAppearance() {
518            return onAppearance;
519        }
520
521        public void setOffAppearance(int app) {
522            offAppearance = app;
523        }
524
525        public int getOffAppearance() {
526            return offAppearance;
527        }
528        //Used to determine if we are using an AND or OR when testing the Sensors and Signals
529        private boolean turnoutSensorOper = true;
530
531        public boolean getSensorTurnoutOper() {
532            return turnoutSensorOper;
533        }
534
535        /**
536         * Set whether the sensors and turnouts should be treated as separate
537         * calculations (OR) or as one (AND), when determining if the Signal
538         * Head in this item should be On or Off.
539         *
540         * @param boo Provide true for AND, false for OR
541         */
542        public void setSensorTurnoutOper(boolean boo) {
543            turnoutSensorOper = boo;
544        }
545
546        // Don't yet have the AND or OR set.
547        public boolean checkActive() {
548            boolean state = false;
549            for (int x = 0; x < _signalTurnoutList.size(); x++) {
550                log.debug("Real state {} {} state we testing for {}", _signalTurnoutList.get(x).getName(), _signalTurnoutList.get(x).getTurnout().getKnownState(), _signalTurnoutList.get(x).getState());
551                if (_signalTurnoutList.get(x).getTurnout().getKnownState() == _signalTurnoutList.get(x).getState()) {
552                    state = true;
553                } else {
554                    state = false;
555                    break;
556                }
557            }
558            for (int x = 0; x < _signalSensorList.size(); x++) {
559                if (_signalSensorList.get(x).getSensor().getKnownState() == _signalSensorList.get(x).getState()) {
560                    state = true;
561                } else {
562                    state = false;
563                    break;
564                }
565            }
566            if (state) {
567                getSignalHead().setAppearance(onAppearance);
568            } else {
569                getSignalHead().setAppearance(offAppearance);
570            }
571            return state;
572        }
573
574        ArrayList<SignalTurnout> _signalTurnoutList = new ArrayList<SignalTurnout>();
575
576        private static class SignalTurnout {
577
578            NamedBeanHandle<Turnout> _turnout;
579            int _state;
580
581            SignalTurnout(Turnout turn, int state) {
582                _turnout = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turn.getDisplayName(), turn);
583                setState(state);
584            }
585
586            String getName() {
587                if (_turnout != null) {
588                    return _turnout.getName();
589                }
590                return null;
591            }
592
593            boolean setState(int state) {
594                if (_turnout == null) {
595                    return false;
596                }
597                if ((state != Turnout.THROWN) && (state != Turnout.CLOSED)) {
598                    log.warn("Illegal Turnout state {} for : {}", state, getName());
599                    return false;
600                }
601                _state = state;
602                return true;
603            }
604
605            int getState() {
606                return _state;
607            }
608
609            Turnout getTurnout() {
610                return _turnout.getBean();
611            }
612        }
613
614        void addTurnout(Turnout turn, int state) {
615            SignalTurnout signalTurnout = new SignalTurnout(turn, state);
616            _signalTurnoutList.add(signalTurnout);
617        }
618
619        Turnout getTurnout(int x) {
620            return _signalTurnoutList.get(x).getTurnout();
621        }
622
623        int getTurnoutState(Turnout turn) {
624            for (int i = 0; i < _signalTurnoutList.size(); i++) {
625                if (_signalTurnoutList.get(i).getTurnout() == turn) {
626                    return _signalTurnoutList.get(i).getState();
627                }
628            }
629            return -1;
630        }
631
632        int getNumTurnouts() {
633            return _signalTurnoutList.size();
634        }
635
636        String getTurnoutName(int x) {
637            return _signalTurnoutList.get(x).getName();
638        }
639
640        int getTurnoutState(int x) {
641            return _signalTurnoutList.get(x).getState();
642        }
643
644        boolean isTurnoutIncluded(Turnout turnout) {
645            for (int i = 0; i < _signalTurnoutList.size(); i++) {
646                if (_signalTurnoutList.get(i).getTurnout() == turnout) {
647                    return true;
648                }
649            }
650            return false;
651        }
652
653        void clearSignalTurnouts() {
654            _signalTurnoutList = new ArrayList<SignalTurnout>();
655        }
656
657        void clearSignalSensors() {
658            _signalSensorList = new ArrayList<SignalSensor>();
659        }
660
661        ArrayList<SignalSensor> _signalSensorList = new ArrayList<SignalSensor>();
662
663        private static class SignalSensor {
664
665            NamedBeanHandle<Sensor> _Sensor;
666            int _state;
667
668            SignalSensor(Sensor sen, int state) {
669                _Sensor = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(sen.getDisplayName(), sen);
670                setState(state);
671            }
672
673            String getName() {
674                if (_Sensor != null) {
675                    return _Sensor.getName();
676                }
677                return null;
678            }
679
680            boolean setState(int state) {
681                if (_Sensor == null) {
682                    return false;
683                }
684                if ((state != Sensor.ACTIVE) && (state != Sensor.INACTIVE)) {
685                    log.warn("Illegal Sensor state {} for : {}", state, getName());
686                    return false;
687                }
688                _state = state;
689                return true;
690            }
691
692            int getState() {
693                return _state;
694            }
695
696            Sensor getSensor() {
697                return _Sensor.getBean();
698            }
699
700        }
701
702        void addSensor(Sensor sen, int state) {
703            SignalSensor signalSensor = new SignalSensor(sen, state);
704            _signalSensorList.add(signalSensor);
705        }
706
707        int getSensorState(Sensor sen) {
708            for (int i = 0; i < _signalSensorList.size(); i++) {
709                if (_signalSensorList.get(i).getSensor() == sen) {
710                    return _signalSensorList.get(i).getState();
711                }
712            }
713            return -1;
714        }
715
716        int getNumSensors() {
717            return _signalSensorList.size();
718        }
719
720        /*SignalSensor getSignalSensorByIndex(int x){
721         return _signalSensorList.get(x);
722         }*/
723        String getSensorName(int x) {
724            return _signalSensorList.get(x).getName();
725        }
726
727        Sensor getSensor(int x) {
728            return _signalSensorList.get(x).getSensor();
729        }
730
731        int getSensorState(int x) {
732            return _signalSensorList.get(x).getState();
733        }
734
735        boolean isSensorIncluded(Sensor sensor) {
736            for (int i = 0; i < _signalSensorList.size(); i++) {
737                if (_signalSensorList.get(i).getSensor() == sensor) {
738                    // Found Sensor
739                    return true;
740                }
741            }
742            return false;
743        }
744    }
745
746    @Override
747    public int getState() {
748        return 0x00;
749    }
750
751    @Override
752    public void setState(int state) {
753    }
754
755    @Override
756    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
757        List<NamedBeanUsageReport> report = new ArrayList<>();
758        if (bean != null) {
759            if (bean.equals(getSignalMast())) {
760                report.add(new NamedBeanUsageReport("SignalGroupMast"));  // NOI18N
761            }
762            for (int i = 0; i < getNumHeadItems(); i++) {
763                if (bean.equals(getHeadItemBeanByIndex(i))) {
764                    report.add(new NamedBeanUsageReport("SignalGroupHead"));  // NOI18N
765                }
766                for (int j = 0; j < getNumHeadSensorsByIndex(i); j++) {
767                    if (bean.equals(getSensorByIndex(i, j))) {
768                        report.add(new NamedBeanUsageReport("SignalGroupHeadSensor"));  // NOI18N
769                    }
770                }
771                for (int k = 0; k < getNumHeadTurnoutsByIndex(i); k++) {
772                    if (bean.equals(getTurnoutByIndex(i, k))) {
773                        report.add(new NamedBeanUsageReport("SignalGroupHeadTurnout"));  // NOI18N
774                    }
775                }
776            }
777        }
778        return report;
779    }
780
781    private final static Logger log = LoggerFactory.getLogger(DefaultSignalGroup.class);
782
783}