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() {
101        return getAppearance() == FLASHRED || getAppearance() == LUNAR || getAppearance() == FLASHLUNAR;
102    }
103
104    /**
105     * Determine whether this signal shows an aspect or appearance
106     * that forbid travel past it.
107     * This might be a red appearance, or a
108     * Stop aspect. Stop-and-Proceed or Restricting would return false here.
109     */
110    @Override
111    public boolean isAtStop()  { return getAppearance() == RED; }
112
113
114    // implementing classes will typically have a function/listener to get
115    // updates from the layout, which will then call
116    //  public void firePropertyChange(String propertyName,
117    //      Object oldValue,
118    //      Object newValue)
119    // _once_ if anything has changed state
120    /**
121     * By default, signals are lit.
122     */
123    protected boolean mLit = true;
124
125    /**
126     * Default behavior for "lit" parameter is to track value and return it.
127     * {@inheritDoc}
128     * @return is lit
129     */
130    @Override
131    public boolean getLit() {
132        return mLit;
133    }
134
135    /**
136     * By default, signals are not held.
137     */
138    protected boolean mHeld = false;
139
140    /**
141     * "Held" parameter is just tracked and notified.
142     * {@inheritDoc}
143     * @return is held
144     */
145    @Override
146    public boolean getHeld() {
147        return mHeld;
148    }
149
150    /**
151     * Implement a shorter name for setAppearance.
152     * <p>
153     * This generally shouldn't be used by Java code; use setAppearance instead.
154     * The is provided to make Jython script access easier to read.
155     * @param s new state
156     */
157    @Override
158    public void setState(int s) {
159        setAppearance(s);
160    }
161
162    /**
163     * Implement a shorter name for getAppearance.
164     * <p>
165     * This generally shouldn't be used by Java code; use getAppearance instead.
166     * The is provided to make Jython script access easier to read.
167     * @return current state
168     */
169    @Override
170    public int getState() {
171        return getAppearance();
172    }
173
174    public static int[] getDefaultValidStates() {
175        return Arrays.copyOf(validStates, validStates.length);
176    }
177
178    public static String[] getDefaultValidStateNames() {
179        String[] stateNames = new String[validStateKeys.length];
180        int i = 0;
181        for (String stateKey : validStateKeys) {
182            stateNames[i++] = Bundle.getMessage(stateKey);
183        }
184        return stateNames;
185    }
186
187    /**
188     * Get a localized text describing appearance from the corresponding state index.
189     *
190     * @param appearance the index of the appearance
191     * @return translated name for appearance
192     */
193    @Nonnull
194    public static String getDefaultStateName(int appearance) {
195        String ret = jmri.util.StringUtil.getNameFromState(
196                appearance, getDefaultValidStates(), getDefaultValidStateNames());
197        return ( ret != null ? ret : "" );
198    }
199
200    private static final int[] validStates = new int[]{
201        DARK,
202        RED,
203        YELLOW,
204        GREEN,
205        LUNAR,
206        FLASHRED,
207        FLASHYELLOW,
208        FLASHGREEN,
209        FLASHLUNAR
210    };
211
212    private static final String[] validStateKeys = new String[]{
213        "SignalHeadStateDark",
214        "SignalHeadStateRed",
215        "SignalHeadStateYellow",
216        "SignalHeadStateGreen",
217        "SignalHeadStateLunar",
218        "SignalHeadStateFlashingRed",
219        "SignalHeadStateFlashingYellow",
220        "SignalHeadStateFlashingGreen",
221        "SignalHeadStateFlashingLunar"
222    };
223
224    /**
225     * {@inheritDoc}
226     */
227    @Override
228    public int[] getValidStates() {
229        return Arrays.copyOf(validStates, validStates.length); // includes int for Lunar
230    }
231
232    /**
233     * {@inheritDoc}
234     */
235    @Override
236    public String[] getValidStateKeys() {
237        return Arrays.copyOf(validStateKeys, validStateKeys.length); // includes int for Lunar
238    }
239
240    /**
241     * {@inheritDoc}
242     */
243    @Override
244    public String[] getValidStateNames() {
245        return getDefaultValidStateNames();
246    }
247
248    /**
249     * Describe SignalHead state.
250     * Does not have to be a valid state for this SignalHead instance hence
251     * suitable for state error logging.
252     * Can include multiple head states, with exception of DARK,
253     * the only state which must exist on its own.
254     * Includes the HELD state if present.
255     * @see SignalHead#getAppearanceName(int)
256     * @param state the state to describe.
257     * @return description of state from Bundle.
258     */
259    @Override
260    public String describeState(int state) {
261        var bundleStrings = StringUtil.getNamesFromState(state, allStateNumbers, allStateNames);
262        StringBuilder sb = new StringBuilder();
263        for (String str : bundleStrings) {
264            sb.append(Bundle.getMessage(str));
265            sb.append(" ");
266        }
267        return sb.toString().trim();
268    }
269
270    // all states, including HELD
271    private static final int[] allStateNumbers = ArrayUtil.appendArray(validStates, new int[]{HELD});
272
273    // all state names, including HELD
274    private static final String[] allStateNames = ArrayUtil.appendArray(validStateKeys, new String[]{"SignalHeadStateHeld"});
275
276    /**
277     * Check if a given turnout is used on this head.
278     *
279     * @param t Turnout object to check
280     * @return true if turnout is configured as output or driver of head
281     */
282    public abstract boolean isTurnoutUsed(Turnout t);
283
284    /**
285     * {@inheritDoc}
286     */
287    @Override
288    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
289        if (jmri.Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) {
290            if (isTurnoutUsed((Turnout) evt.getOldValue())) {
291                var e = new java.beans.PropertyChangeEvent(this, jmri.Manager.PROPERTY_DO_NOT_DELETE, null, null);
292                throw new java.beans.PropertyVetoException(
293                    Bundle.getMessage("InUseTurnoutSignalHeadVeto", getDisplayName()), e); // NOI18N
294            }
295        }
296    }
297
298    /**
299     * {@inheritDoc}
300     */
301    @Override
302    public @Nonnull String getBeanType() {
303        return Bundle.getMessage("BeanNameSignalHead");
304    }
305
306//     private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSignalHead.class);
307
308}