001package jmri.implementation; 002 003import java.util.*; 004import javax.annotation.Nonnull; 005import jmri.NamedBeanHandle; 006import jmri.Turnout; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * SignalMast implemented via Turnout objects. 012 * <p> 013 * A SignalMast that is built up using turnouts to control a specific 014 * appearance. System name specifies the creation information: 015 * <pre> 016 * IF$tsm:basic:one-searchlight(IT1)(IT2) 017 * </pre> The name is a colon-separated series of terms: 018 * <ul> 019 * <li>IF$tsm - defines signal masts of this type 020 * <li>basic - name of the signaling system 021 * <li>one-searchlight - name of the particular aspect map 022 * <li>(IT1)(IT2) - colon-separated list of names for Turnouts 023 * </ul> 024 * 025 * @author Bob Jacobsen Copyright (C) 2009, 2014 026 */ 027public class TurnoutSignalMast extends AbstractSignalMast { 028 029 public TurnoutSignalMast(String systemName, String userName) { 030 super(systemName, userName); 031 configureFromName(systemName); 032 } 033 034 public TurnoutSignalMast(String systemName) { 035 super(systemName); 036 configureFromName(systemName); 037 } 038 039 private static final String THE_MAST_TYPE = "IF$tsm"; 040 041 private void configureFromName(String systemName) { 042 // split out the basic information 043 String[] parts = systemName.split(":"); 044 if (parts.length < 3) { 045 log.error("SignalMast system name needs at least three parts: {}", systemName); 046 throw new IllegalArgumentException("System name needs at least three parts: " + systemName); 047 } 048 if (!parts[0].equals(THE_MAST_TYPE)) { 049 log.warn("SignalMast system name should start with {} but is {}", THE_MAST_TYPE, systemName); 050 } 051 String system = parts[1]; 052 String mast = parts[2]; 053 054 mast = mast.substring(0, mast.indexOf("(")); 055 setMastType(mast); 056 057 String tmp = parts[2].substring(parts[2].indexOf("($") + 2, parts[2].indexOf(")")); 058 try { 059 int autoNumber = Integer.parseInt(tmp); 060 if (autoNumber > getLastRef()) { 061 setLastRef(autoNumber); 062 } 063 } catch (NumberFormatException e) { 064 log.warn("Auto generated SystemName {} is not in the correct format", systemName); 065 } 066 067 configureSignalSystemDefinition(system); 068 configureAspectTable(system, mast); 069 } 070 071 @Override 072 public void setAspect(@Nonnull String aspect) { 073 // check it's a choice 074 if (!map.checkAspect(aspect)) { 075 // not a valid aspect 076 log.warn("attempting to set invalid aspect: {} on mast: {}", aspect, getDisplayName()); 077 throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName()); 078 } else if (disabledAspects.contains(aspect)) { 079 log.warn("attempting to set an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName()); 080 throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName()); 081 } 082 083 084 if (getLit()) { // If the signalmast is lit, then send the commands to change the aspect. 085 086 // reset all states before setting this one, including this one 087 if (resetPreviousStates) { 088 // Clear all the current states, this will result in the signalmast going blank for a very short time. 089 for (Map.Entry<String, TurnoutAspect> entry : turnouts.entrySet()) { 090 String appearance = entry.getKey(); 091 TurnoutAspect aspt = entry.getValue(); 092 if (!isAspectDisabled(appearance)) { 093 int setState = Turnout.CLOSED; 094 if (aspt.getTurnoutState() == Turnout.CLOSED) { 095 setState = Turnout.THROWN; 096 } 097 if (aspt.getTurnout() != null ) { 098 if (aspt.getTurnout().getKnownState() != setState) { 099 aspt.getTurnout().setCommandedState(setState); 100 } 101 } else { 102 log.error("Trying to reset \"{}\" on signal mast \"{}\" which has not been configured", appearance, getDisplayName()); 103 } 104 } 105 } 106 } 107 108 // set the finel state if possible 109 if (turnouts.get(aspect) != null && turnouts.get(aspect).getTurnout() != null) { 110 Turnout turnToSet = turnouts.get(aspect).getTurnout(); 111 int stateToSet = turnouts.get(aspect).getTurnoutState(); 112 turnToSet.setCommandedState(stateToSet); 113 } else { 114 log.error("Trying to set \"{}\" on signal mast \"{}\" which has not been configured", aspect, getDisplayName()); 115 } 116 117 } else if (log.isDebugEnabled()) { 118 log.debug("Mast set to unlit, will not send aspect change to hardware"); 119 } 120 super.setAspect(aspect); 121 } 122 123 private TurnoutAspect unLit = null; 124 125 public void setUnLitTurnout(String turnoutName, int turnoutState) { 126 unLit = new TurnoutAspect(turnoutName, turnoutState); 127 } 128 129 public String getUnLitTurnoutName() { 130 if (unLit != null) { 131 return unLit.getTurnoutName(); 132 } 133 return null; 134 } 135 136 public Turnout getUnLitTurnout() { 137 if (unLit != null) { 138 return unLit.getTurnout(); 139 } 140 return null; 141 } 142 143 public int getUnLitTurnoutState() { 144 if (unLit != null) { 145 return unLit.getTurnoutState(); 146 } 147 return -1; 148 } 149 150 @Override 151 public void setLit(boolean newLit) { 152 if (!allowUnLit() || newLit == getLit()) { 153 return; 154 } 155 super.setLit(newLit); 156 if (newLit) { 157 // This will force the signalmast to send out the commands to set the aspect again. 158 String litAspect = getAspect(); 159 if (litAspect != null ) { 160 setAspect(litAspect); 161 } 162 } else { 163 if (unLit != null) { 164 // there is a specific unlit output defined 165 Turnout t = unLit.getTurnout(); 166 if (t != null && t.getKnownState() != getUnLitTurnoutState()) { 167 t.setCommandedState(getUnLitTurnoutState()); 168 } 169 } else { 170 // turn everything off 171 for (TurnoutAspect tAspect : turnouts.values()) { 172 int setState = Turnout.CLOSED; 173 if (tAspect.getTurnoutState() == Turnout.CLOSED) { 174 setState = Turnout.THROWN; 175 } 176 if (tAspect.getTurnout().getKnownState() != setState) { 177 tAspect.getTurnout().setCommandedState(setState); 178 } 179 } 180 } 181 } 182 } 183 184 public String getTurnoutName(String appearance) { 185 TurnoutAspect tAspect = turnouts.get(appearance); 186 if (tAspect != null) { 187 return tAspect.getTurnoutName(); 188 } 189 return ""; 190 } 191 192 public int getTurnoutState(String appearance) { 193 TurnoutAspect tAspect = turnouts.get(appearance); 194 if (tAspect != null) { 195 return tAspect.getTurnoutState(); 196 } 197 return -1; 198 } 199 200 public void setTurnout(String appearance, String turn, int state) { 201 if (turnouts.containsKey(appearance)) { 202 log.debug("Appearance {} is already defined so will override", appearance); 203 turnouts.remove(appearance); 204 } 205 turnouts.put(appearance, new TurnoutAspect(turn, state)); 206 } 207 208 HashMap<String, TurnoutAspect> turnouts = new HashMap<>(); 209 210 private boolean resetPreviousStates = false; 211 212 /** 213 * If the signal mast driver requires the previous state to be cleared down 214 * before the next state is set. 215 * 216 * @param boo true if prior states should be cleared; false otherwise 217 */ 218 public void resetPreviousStates(boolean boo) { 219 resetPreviousStates = boo; 220 } 221 222 public boolean resetPreviousStates() { 223 return resetPreviousStates; 224 } 225 226 static class TurnoutAspect { 227 228 NamedBeanHandle<Turnout> namedTurnout; 229 int state; 230 231 TurnoutAspect(String turnoutName, int turnoutState) { 232 if (turnoutName != null && !turnoutName.equals("")) { 233 state = turnoutState; 234 Turnout turn = jmri.InstanceManager.turnoutManagerInstance().getTurnout(turnoutName); 235 if (turn == null) { 236 log.error("TurnoutAspect couldn't locate turnout {}", turnoutName); 237 return; 238 } 239 namedTurnout = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutName, turn); 240 } 241 } 242 243 Turnout getTurnout() { 244 if (namedTurnout == null) { 245 return null; 246 } 247 return namedTurnout.getBean(); 248 } 249 250 String getTurnoutName() { 251 if (namedTurnout == null) { 252 return null; 253 } 254 return namedTurnout.getName(); 255 } 256 257 int getTurnoutState() { 258 return state; 259 } 260 } 261 262 public boolean isTurnoutUsed(Turnout t) { 263 for (TurnoutAspect ta : turnouts.values()) { 264 if (t.equals(ta.getTurnout())) { 265 return true; 266 } 267 } 268 return t.equals(getUnLitTurnout()); 269 } 270 271 public List<NamedBeanHandle<Turnout>> getHeadsUsed() { 272 return new ArrayList<>(); 273 } 274 275 /** 276 * 277 * @param newVal for ordinal of all TurnoutSignalMasts in use 278 */ 279 protected static void setLastRef(int newVal) { 280 lastRef = newVal; 281 } 282 283 /** 284 * @return highest ordinal of all TurnoutSignalMasts in use 285 */ 286 public static int getLastRef() { 287 return lastRef; 288 } 289 290 /** 291 * Ordinal of all TurnoutSignalMasts to create unique system name. 292 */ 293 protected static volatile int lastRef = 0; 294 // TODO narrow access to static, once jmri/jmrit/beantable/signalmast/TurnoutSignalMastAddPane uses setLastRef(n) 295 //private static volatile int lastRef = 0; 296 297 @Override 298 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 299 if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N 300 if (evt.getOldValue() instanceof Turnout) { 301 if (isTurnoutUsed((Turnout) evt.getOldValue())) { 302 java.beans.PropertyChangeEvent e = new java.beans.PropertyChangeEvent(this, "DoNotDelete", null, null); 303 throw new java.beans.PropertyVetoException(Bundle.getMessage("InUseTurnoutSignalMastVeto", getDisplayName()), e); // NOI18N 304 } 305 } 306 } 307 } 308 309 private final static Logger log = LoggerFactory.getLogger(TurnoutSignalMast.class); 310 311}