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