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}