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}