001package jmri.jmrit.ctc;
002
003import java.beans.PropertyChangeListener;
004import jmri.InstanceManager;
005import jmri.NamedBeanHandle;
006import jmri.NamedBeanHandleManager;
007import jmri.SignalAppearanceMap;
008import jmri.SignalMast;
009import jmri.SignalHead;
010import jmri.jmrit.ctc.ctcserialdata.*;
011
012/**
013 * Provide access to both signal masts and signal heads for the CTC system.
014 * <p>
015 * This class combines the NBHAbstractSignalCommon, NBHSignalMast and NBHSignalHead
016 * classes.  OtherData _mSignalSystemType determines whether masts or heads are enabled.
017 * @author Dave Sand Copyright (C) 2020
018 */
019public class NBHSignal {
020
021//  Standard sane return values for the types indicated:
022    public static final Object DEFAULT_OBJECT_RV = null;       // For any function that returns something derived from Java's Object.
023    public static final boolean DEFAULT_BOOLEAN_RV = false;    // For any function that returns boolean.
024    public static final int DEFAULT_INT_RV = 0;                // For any function that returns int.
025    public static final String DEFAULT_STRING_RV = "UNKNOWN";  // NOI18N  For any function that returns String.
026
027//  The "things" we're protecting:
028    private final NamedBeanHandle<SignalMast> _mNamedBeanHandleSignalMast;
029    private final NamedBeanHandle<SignalHead> _mNamedBeanHandleSignalHead;
030
031    private final boolean isSignalMast;   // True for signal mast, false for signal head
032    private final String _mDangerAppearance;  //  The string to determine "Is the Signal all Red":
033
034    /**
035     * Create the named bean handle for either a signal mast or signal head.
036     * @param signal The signal name.
037     */
038    public NBHSignal(String signal) {
039        isSignalMast = setSignalType();
040        if (!ProjectsCommonSubs.isNullOrEmptyString(signal)) {
041            if (isSignalMast) {
042                // Cannot use a constant Instance manager reference due to the dynamic nature of tests.
043                SignalMast signalMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getSignalMast(signal);
044                if (signalMast != null) {
045                    _mNamedBeanHandleSignalMast = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(signal, signalMast);
046                    String temp = getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER);
047                    if (temp == null) temp = "Stop"; // NOI18N // Safety
048                    _mDangerAppearance = temp;
049                    _mNamedBeanHandleSignalHead = null;
050                    if (valid()) InstanceManager.getDefault(CtcManager.class).putNBHSignal(signal, this);
051                    return;
052                }
053            } else {
054                // Cannot use a constant Instance manager reference due to the dynamic nature of tests.
055                SignalHead signalHead = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(signal);
056                if (signalHead != null) {
057                    _mNamedBeanHandleSignalHead = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(signal, signalHead);
058                    _mDangerAppearance = "Stop";            // NOI18N // Never used, just required for "final"
059                    _mNamedBeanHandleSignalMast = null;
060                    if (valid()) InstanceManager.getDefault(CtcManager.class).putNBHSignal(signal, this);
061                    return;
062                }
063            }
064        }
065        _mDangerAppearance = "Stop";            // NOI18N // Never used, just required for "final"
066        _mNamedBeanHandleSignalMast = null;
067        _mNamedBeanHandleSignalHead = null;
068    }
069
070    /**
071     * Set signal type using {@link OtherData#_mSignalSystemType}.
072     * @return true for mast, false if head.
073     */
074    private boolean setSignalType() {
075        OtherData otherData = InstanceManager.getDefault(CtcManager.class).getOtherData();
076        return otherData._mSignalSystemType == OtherData.SIGNAL_SYSTEM_TYPE.SIGNALMAST ? true : false;
077
078    }
079
080    public boolean valid() {
081        return _mNamedBeanHandleSignalMast != null || _mNamedBeanHandleSignalHead != null;
082    }  // For those that want to know the internal state.
083
084    public Object getBean() {
085        if (!valid()) return null;
086        return isSignalMast ? _mNamedBeanHandleSignalMast.getBean() : _mNamedBeanHandleSignalHead.getBean();
087    }
088
089    public Object getBeanHandle() {
090        if (!valid()) return null;
091        return isSignalMast ? _mNamedBeanHandleSignalMast : _mNamedBeanHandleSignalHead;
092    }
093
094    /**
095     * @return The signal's handle name.
096     */
097    public String getHandleName() {
098        if (!valid()) return null;
099        return isSignalMast ? _mNamedBeanHandleSignalMast.getName() : _mNamedBeanHandleSignalHead.getName();
100    }
101
102    public String getDisplayName() {
103        if (isSignalMast) {
104            if (_mNamedBeanHandleSignalMast == null) return DEFAULT_STRING_RV;
105            return _mNamedBeanHandleSignalMast.getBean().getDisplayName();
106        } else {
107            if (_mNamedBeanHandleSignalHead == null) return DEFAULT_STRING_RV;
108            return _mNamedBeanHandleSignalHead.getBean().getDisplayName();
109        }
110    }
111
112    public boolean isDanger() {
113        if (getHeld()) return true;     // Safety.  Problem in signal head, maybe same problem here?
114        return isSignalMast ? getAspect().equals(_mDangerAppearance) : SignalHead.RED == getAppearance();
115    }
116
117    public void setCTCHeld(boolean held) {
118        setHeld(held);
119    }
120
121    public boolean getHeld() {
122        if (isSignalMast) {
123            if (_mNamedBeanHandleSignalMast == null) return DEFAULT_BOOLEAN_RV;
124            return _mNamedBeanHandleSignalMast.getBean().getHeld();
125        } else {
126            if (_mNamedBeanHandleSignalHead == null) return DEFAULT_BOOLEAN_RV;
127            return _mNamedBeanHandleSignalHead.getBean().getHeld();
128        }
129    }
130
131    public void setHeld(boolean newHeld) {
132        if (isSignalMast) {
133            if (_mNamedBeanHandleSignalMast == null) return;
134            _mNamedBeanHandleSignalMast.getBean().setHeld(newHeld);
135            if (newHeld) {
136                _mNamedBeanHandleSignalMast.getBean().setPermissiveSmlDisabled(true);
137            }
138        } else {
139            if (_mNamedBeanHandleSignalHead == null) return;
140            _mNamedBeanHandleSignalHead.getBean().setHeld(newHeld);
141        }
142    }
143
144    public void allowPermissiveSML() {
145        _mNamedBeanHandleSignalMast.getBean().setPermissiveSmlDisabled(false);
146    }
147
148    public void addPropertyChangeListener(PropertyChangeListener l) {
149        if (isSignalMast) {
150            if (_mNamedBeanHandleSignalMast == null) return;
151            _mNamedBeanHandleSignalMast.getBean().addPropertyChangeListener(l);
152        } else {
153            if (_mNamedBeanHandleSignalHead == null) return;
154            _mNamedBeanHandleSignalHead.getBean().addPropertyChangeListener(l);
155        }
156    }
157
158    public void removePropertyChangeListener(PropertyChangeListener l) {
159        if (isSignalMast) {
160            if (_mNamedBeanHandleSignalMast == null) return;
161            _mNamedBeanHandleSignalMast.getBean().removePropertyChangeListener(l);
162        } else {
163            if (_mNamedBeanHandleSignalHead == null) return;
164            _mNamedBeanHandleSignalHead.getBean().removePropertyChangeListener(l);
165        }
166    }
167
168    /**
169     *
170     * Function to insure that a non null aspect value is always returned to the caller.
171     *
172     * Background (regarding the value contained in "_mDangerAppearance"):
173     * In this objects constructor, "_mDangerAppearance" is set to getAppearanceMap().getSpecificAppearance(SignalAppearanceMap.DANGER).
174     * If "...getSpecificAppearance..." returns "null" (undocumented in JMRI documents as of 9/18/2019),
175     * "_mDangerAppearance" is set to "Stop" for safety.
176     * So "_mDangerAppearance" will NEVER be null for use as follows:
177     *
178     * SignalMast.getAspect() can return "null" (undocumented in JMRI documents as of 9/18/2019) if (for instance) the signal has no
179     * rules (i.e. no "Discover" done yet, or the signal is shown on the screen as a big red "X").
180     * In this case, we return "_mDangerAppearance".
181     *
182     * @return  Return a guaranteed non null aspect name.
183     */
184    public String getAspect() {
185        if (!isSignalMast) log.info("NBHSignal: getAspect called by signal head", new Exception("traceback"));
186        if (_mNamedBeanHandleSignalMast == null) return DEFAULT_STRING_RV;
187        String returnAspect = _mNamedBeanHandleSignalMast.getBean().getAspect();
188        if (returnAspect == null) return _mDangerAppearance;    // Safety
189        return returnAspect;
190    }
191
192    public SignalAppearanceMap getAppearanceMap() {
193        if (!isSignalMast) log.info("NBHSignal: getAppearanceMap called by signal head", new Exception("traceback"));
194        if (_mNamedBeanHandleSignalMast == null) return null;
195        return _mNamedBeanHandleSignalMast.getBean().getAppearanceMap();
196    }
197
198    public int getAppearance() {
199        if (isSignalMast) log.info("NBHSignal: getAppearance called by signal mast", new Exception("traceback"));
200        if (_mNamedBeanHandleSignalHead == null) return DEFAULT_INT_RV;
201        return _mNamedBeanHandleSignalHead.getBean().getAppearance();
202    }
203
204    public void setAppearance(int newAppearance) {
205        if (isSignalMast) log.info("NBHSignal: setAppearance called by signal mast", new Exception("traceback"));
206        if (_mNamedBeanHandleSignalHead == null) return;
207        _mNamedBeanHandleSignalHead.getBean().setAppearance(newAppearance);
208    }
209
210    /**
211     * Get an array of appearance indexes valid for the mast type.
212     *
213     * @return array of appearance state values available on this mast type
214     */
215    public int[] getValidStates() {
216        if (isSignalMast) log.info("NBHSignal: getValidStates called by signal mast", new Exception("traceback"));
217        if (_mNamedBeanHandleSignalHead == null) return new int[0];
218        return _mNamedBeanHandleSignalHead.getBean().getValidStates();
219    }
220
221    /**
222     * Get an array of non-localized appearance keys valid for the mast type.
223     * For GUI application consider using (capitalized) {@link #getValidStateNames()}
224     *
225     * @return array of translated appearance names available on this mast type
226     */
227    public String[] getValidStateKeys() {
228        if (isSignalMast) log.info("NBHSignal: getValidStateKeys called by signal mast", new Exception("traceback"));
229        if (_mNamedBeanHandleSignalHead == null) return new String[0];
230        return _mNamedBeanHandleSignalHead.getBean().getValidStateKeys();
231    }
232
233    /**
234     * Get an array of localized appearance descriptions valid for the mast type.
235     * For persistance and comparison consider using {@link #getValidStateKeys()}
236     *
237     * @return array of translated appearance names
238     */
239    public String[] getValidStateNames() {
240        if (isSignalMast) log.info("NBHSignal: getValidStateNames called by signal mast", new Exception("traceback"));
241        if (_mNamedBeanHandleSignalHead == null) return new String[0];
242        return _mNamedBeanHandleSignalHead.getBean().getValidStateNames();
243    }
244
245    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NBHSignal.class);
246}
247