001package jmri.implementation;
002
003import java.util.Arrays;
004
005import jmri.SignalHead;
006import jmri.Turnout;
007import jmri.util.ArrayUtil;
008import jmri.util.StringUtil;
009
010import javax.annotation.Nonnull;
011
012/**
013 * Abstract class providing the basic logic of the SignalHead interface.
014 *
015 * @author Bob Jacobsen Copyright (C) 2001
016 */
017public abstract class AbstractSignalHead extends AbstractNamedBean
018        implements SignalHead, java.beans.VetoableChangeListener {
019
020    public AbstractSignalHead(String systemName, String userName) {
021        super(systemName, userName);
022    }
023
024    public AbstractSignalHead(String systemName) {
025        super(systemName);
026    }
027
028    /**
029     * {@inheritDoc}
030     */
031    @Nonnull
032    @Override
033    public String getAppearanceName(int appearance) {
034        String ret = jmri.util.StringUtil.getNameFromState(
035                appearance, getValidStates(), getValidStateNames());
036        if (ret != null) {
037            return ret;
038        }
039        return ("");
040    }
041
042    /**
043     * {@inheritDoc}
044     */
045    @Nonnull
046    @Override
047    public String getAppearanceName() {
048        return getAppearanceName(getAppearance());
049    }
050
051    /**
052     * {@inheritDoc}
053     */
054    @Nonnull
055    @Override
056    public String getAppearanceKey(int appearance) {
057        String ret = StringUtil.getNameFromState(
058                appearance, getValidStates(), getValidStateKeys());
059        if (ret != null) {
060            return ret;
061        }
062        return ("");
063    }
064
065    /**
066     * {@inheritDoc}
067     */
068    @Nonnull
069    @Override
070    public String getAppearanceKey() {
071        return getAppearanceKey(getAppearance());
072    }
073
074    protected int mAppearance = DARK;
075
076    /**
077     * {@inheritDoc}
078     */
079    @Override
080    public int getAppearance() {
081        return mAppearance;
082    }
083
084    /**
085     * Determine whether this signal shows an aspect or appearance
086     * that allows travel past it, e.g. it's "been cleared".
087     * This might be a yellow or green appearance, or an Approach or Clear
088     * aspect
089     */
090    @Override
091    public boolean isCleared() { return !isAtStop() && !isShowingRestricting() && getAppearance()!=DARK; }
092
093    /**
094     * Determine whether this signal shows an aspect or appearance
095     * that allows travel past it only at restricted speed.
096     * This might be a flashing red appearance, or a
097     * Restricting aspect.
098     */
099    @Override
100    public boolean isShowingRestricting() { return getAppearance() == FLASHRED || getAppearance() == LUNAR || getAppearance() == FLASHLUNAR; }
101
102    /**
103     * Determine whether this signal shows an aspect or appearance
104     * that forbid travel past it.
105     * This might be a red appearance, or a
106     * Stop aspect. Stop-and-Proceed or Restricting would return false here.
107     */
108    @Override
109    public boolean isAtStop()  { return getAppearance() == RED; }
110
111
112    // implementing classes will typically have a function/listener to get
113    // updates from the layout, which will then call
114    //  public void firePropertyChange(String propertyName,
115    //      Object oldValue,
116    //      Object newValue)
117    // _once_ if anything has changed state
118    /**
119     * By default, signals are lit.
120     */
121    protected boolean mLit = true;
122
123    /**
124     * Default behavior for "lit" parameter is to track value and return it.
125     * {@inheritDoc}
126     * @return is lit
127     */
128    @Override
129    public boolean getLit() {
130        return mLit;
131    }
132
133    /**
134     * By default, signals are not held.
135     */
136    protected boolean mHeld = false;
137
138    /**
139     * "Held" parameter is just tracked and notified.
140     * {@inheritDoc}
141     * @return is held
142     */
143    @Override
144    public boolean getHeld() {
145        return mHeld;
146    }
147
148    /**
149     * Implement a shorter name for setAppearance.
150     * <p>
151     * This generally shouldn't be used by Java code; use setAppearance instead.
152     * The is provided to make Jython script access easier to read.
153     * @param s new state
154     */
155    @Override
156    public void setState(int s) {
157        setAppearance(s);
158    }
159
160    /**
161     * Implement a shorter name for getAppearance.
162     * <p>
163     * This generally shouldn't be used by Java code; use getAppearance instead.
164     * The is provided to make Jython script access easier to read.
165     * @return current state
166     */
167    @Override
168    public int getState() {
169        return getAppearance();
170    }
171
172    public static int[] getDefaultValidStates() {
173        return Arrays.copyOf(validStates, validStates.length);
174    }
175
176    public static String[] getDefaultValidStateNames() {
177        String[] stateNames = new String[validStateKeys.length];
178        int i = 0;
179        for (String stateKey : validStateKeys) {
180            stateNames[i++] = Bundle.getMessage(stateKey);
181        }
182        return stateNames;
183    }
184
185    /**
186     * Get a localized text describing appearance from the corresponding state index.
187     *
188     * @param appearance the index of the appearance
189     * @return translated name for appearance
190     */
191    @Nonnull
192    public static String getDefaultStateName(int appearance) {
193        String ret = jmri.util.StringUtil.getNameFromState(
194                appearance, getDefaultValidStates(), getDefaultValidStateNames());
195        return ( ret != null ? ret : "" );
196    }
197
198    private static final int[] validStates = new int[]{
199        DARK,
200        RED,
201        YELLOW,
202        GREEN,
203        LUNAR,
204        FLASHRED,
205        FLASHYELLOW,
206        FLASHGREEN,
207        FLASHLUNAR
208    };
209
210    private static final String[] validStateKeys = new String[]{
211        "SignalHeadStateDark",
212        "SignalHeadStateRed",
213        "SignalHeadStateYellow",
214        "SignalHeadStateGreen",
215        "SignalHeadStateLunar",
216        "SignalHeadStateFlashingRed",
217        "SignalHeadStateFlashingYellow",
218        "SignalHeadStateFlashingGreen",
219        "SignalHeadStateFlashingLunar"
220    };
221
222    /**
223     * {@inheritDoc}
224     */
225    @Override
226    public int[] getValidStates() {
227        return Arrays.copyOf(validStates, validStates.length); // includes int for Lunar
228    }
229
230    /**
231     * {@inheritDoc}
232     */
233    @Override
234    public String[] getValidStateKeys() {
235        return Arrays.copyOf(validStateKeys, validStateKeys.length); // includes int for Lunar
236    }
237
238    /**
239     * {@inheritDoc}
240     */
241    @Override
242    public String[] getValidStateNames() {
243        return getDefaultValidStateNames();
244    }
245
246    /**
247     * Describe SignalHead state.
248     * Does not have to be a valid state for this SignalHead instance hence
249     * suitable for state error logging.
250     * Can include multiple head states, with exception of DARK,
251     * the only state which must exist on its own.
252     * Includes the HELD state if present.
253     * @see SignalHead#getAppearanceName(int)
254     * @param state the state to describe.
255     * @return description of state from Bundle.
256     */
257    @Override
258    public String describeState(int state) {
259        var bundleStrings = StringUtil.getNamesFromState(state, allStateNumbers, allStateNames);
260        StringBuilder sb = new StringBuilder();
261        for (String str : bundleStrings) {
262            sb.append(Bundle.getMessage(str));
263            sb.append(" ");
264        }
265        return sb.toString().trim();
266    }
267
268    // all states, including HELD
269    private static final int[] allStateNumbers = ArrayUtil.appendArray(validStates, new int[]{HELD});
270
271    // all state names, including HELD
272    private static final String[] allStateNames = ArrayUtil.appendArray(validStateKeys, new String[]{"SignalHeadStateHeld"});
273
274    /**
275     * Check if a given turnout is used on this head.
276     *
277     * @param t Turnout object to check
278     * @return true if turnout is configured as output or driver of head
279     */
280    public abstract boolean isTurnoutUsed(Turnout t);
281
282    /**
283     * {@inheritDoc}
284     */
285    @Override
286    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
287        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
288            if (isTurnoutUsed((Turnout) evt.getOldValue())) {
289                java.beans.PropertyChangeEvent e = new java.beans.PropertyChangeEvent(this, "DoNotDelete", null, null);
290                throw new java.beans.PropertyVetoException(Bundle.getMessage("InUseTurnoutSignalHeadVeto", getDisplayName()), e); // NOI18N
291            }
292        }
293    }
294
295    /**
296     * {@inheritDoc}
297     */
298    @Override
299    public @Nonnull String getBeanType() {
300        return Bundle.getMessage("BeanNameSignalHead");
301    }
302
303//     private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSignalHead.class);
304
305}