001package jmri.implementation; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.List; 006import javax.annotation.Nonnull; 007import jmri.InstanceManager; 008import jmri.NamedBean; 009import jmri.NamedBeanHandle; 010import jmri.NamedBeanHandleManager; 011import jmri.SignalHead; 012import jmri.SignalMast; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016/** 017 * SignalMast implemented via one SignalHead object. 018 * <p> 019 * System name specifies the creation information: 020 * <pre> 021 * IF$shsm:basic:one-searchlight(IH1)(IH2) 022 * </pre> 023 * The name is a colon-separated series of terms: 024 * <ul> 025 * <li>IF$shsm - defines signal masts of this type 026 * <li>basic - name of the signaling system 027 * <li>one-searchlight - name of the particular aspect map 028 * <li>(IH1)(IH2) - List of signal head names in parentheses. Note: There is no colon between the mast name and the head names. 029 * </ul> 030 * There was an older form where the SignalHead names were also colon separated: 031 * IF$shsm:basic:one-searchlight:IH1:IH2 This was deprecated because colons appear in 032 * e.g. SE8c system names. 033 * <ul> 034 * <li>IF$shsm - defines signal masts of this type 035 * <li>basic - name of the signaling system 036 * <li>one-searchlight - name of the particular aspect map 037 * <li>IH1:IH2 - colon-separated list of names for SignalHeads 038 * </ul> 039 * 040 * @author Bob Jacobsen Copyright (C) 2009 041 */ 042public class SignalHeadSignalMast extends AbstractSignalMast { 043 044 public SignalHeadSignalMast(String systemName, String userName) { 045 super(systemName, userName); 046 configureFromName(systemName); 047 } 048 049 public SignalHeadSignalMast(String systemName) { 050 super(systemName); 051 configureFromName(systemName); 052 } 053 054 private static final String THE_MAST_TYPE = "IF$shsm"; 055 056 private void configureFromName(String systemName) { 057 // split out the basic information 058 String[] parts = systemName.split(":"); 059 if (parts.length < 3) { 060 log.error("SignalMast system name needs at least three parts: {}", systemName); 061 throw new IllegalArgumentException("System name needs at least three parts: " + systemName); 062 } 063 if (!parts[0].equals(THE_MAST_TYPE)) { 064 log.warn("SignalMast system name should start with {} but is {}", THE_MAST_TYPE, systemName); 065 } 066 String prefix = parts[0]; 067 String system = parts[1]; 068 String mast = parts[2]; 069 070 // if "mast" contains (, it's a new style 071 if (mast.indexOf('(') == -1) { 072 // old style 073 setMastType(mast); 074 configureSignalSystemDefinition(system); 075 configureAspectTable(system, mast); 076 configureHeads(parts, 3); 077 } else { 078 // new style 079 mast = mast.substring(0, mast.indexOf("(")); 080 setMastType(mast); 081 String interim = systemName.substring(prefix.length() + 1 + system.length() + 1); 082 String parenstring = interim.substring(interim.indexOf("("), interim.length()); 083 java.util.List<String> parens = jmri.util.StringUtil.splitParens(parenstring); 084 configureSignalSystemDefinition(system); 085 configureAspectTable(system, mast); 086 String[] heads = new String[parens.size()]; 087 int i = 0; 088 for (String p : parens) { 089 heads[i] = p.substring(1, p.length() - 1); 090 i++; 091 } 092 configureHeads(heads, 0); 093 } 094 } 095 096 private void configureHeads(String parts[], int start) { 097 heads = new ArrayList<>(); 098 for (int i = start; i < parts.length; i++) { 099 String name = parts[i]; 100 // check head exists 101 SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name); 102 if (head == null) { 103 log.warn("Attempting to create Mast from non-existant signal head {}", name); 104 continue; 105 } 106 NamedBeanHandle<SignalHead> s 107 = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, head); 108 heads.add(s); 109 } 110 } 111 112 @Override 113 public void setAspect(@Nonnull String aspect) { 114 // check it's a choice 115 if (!map.checkAspect(aspect)) { 116 // not a valid aspect 117 log.warn("attempting to set invalid aspect: {} on mast: {}", aspect, getDisplayName()); 118 throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName()); 119 } else if (disabledAspects.contains(aspect)) { 120 log.warn("attempting to set an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName()); 121 throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName()); 122 } 123 124 // set the outputs 125 if (log.isDebugEnabled()) { 126 log.debug("setAspect \"{}\", numHeads= {}", aspect, heads.size()); 127 } 128 setAppearances(aspect); 129 // do standard processing 130 super.setAspect(aspect); 131 } 132 133 @Override 134 public void setHeld(boolean state) { 135 // set all Heads to state 136 for (NamedBeanHandle<SignalHead> h : heads) { 137 try { 138 h.getBean().setHeld(state); 139 } catch (java.lang.NullPointerException ex) { 140 log.error("NPE caused when trying to set Held due to missing signal head in mast {}", getDisplayName()); 141 } 142 } 143 super.setHeld(state); 144 } 145 146 @Override 147 public void setLit(boolean state) { 148 // set all Heads to state 149 for (NamedBeanHandle<SignalHead> h : heads) { 150 try { 151 h.getBean().setLit(state); 152 } catch (java.lang.NullPointerException ex) { 153 log.error("NPE caused when trying to set Lit due to missing signal head in mast {}", getDisplayName()); 154 } 155 } 156 super.setLit(state); 157 } 158 159 private List<NamedBeanHandle<SignalHead>> heads; 160 161 public List<NamedBeanHandle<SignalHead>> getHeadsUsed() { 162 return heads; 163 } 164 165 // taken out of the defaultsignalappearancemap 166 public void setAppearances(String aspect) { 167 if (map == null) { 168 log.error("No appearance map defined, unable to set appearance {}", getDisplayName()); 169 return; 170 } 171 if (map.getSignalSystem() != null && map.getSignalSystem().checkAspect(aspect) && map.getAspectSettings(aspect) != null) { 172 log.warn("Attempt to set {} to undefined aspect: {}", getSystemName(), aspect); 173 } else if ((map.getAspectSettings(aspect) != null) && (heads.size() > map.getAspectSettings(aspect).length)) { 174 log.warn("setAppearance to \"{}\" finds {} heads but only {} settings", aspect, heads.size(), map.getAspectSettings(aspect).length); 175 } 176 177 int delay = 0; 178 try { 179 if (map.getProperty(aspect, "delay") != null) { 180 delay = Integer.parseInt(map.getProperty(aspect, "delay")); 181 } 182 } catch (Exception e) { 183 log.debug("No delay set"); 184 //can be considered normal if does not exists or is invalid 185 } 186 HashMap<SignalHead, Integer> delayedSet = new HashMap<>(heads.size()); 187 for (int i = 0; i < heads.size(); i++) { 188 // some extensive checking 189 boolean error = false; 190 if (heads.get(i) == null) { 191 log.error("Head {} unexpectedly null in setAppearances while setting aspect \"{}\" for {}", i, aspect, getSystemName()); 192 error = true; 193 } 194 if (heads.get(i).getBean() == null) { 195 log.error("Head {} getBean() unexpectedly null in setAppearances while setting aspect \"{}\" for {}", i, aspect, getSystemName()); 196 error = true; 197 } 198 if (map.getAspectSettings(aspect) == null) { 199 log.error("Couldn't get table array for aspect \"{}\" in setAppearances for {}", aspect, getSystemName()); 200 error = true; 201 } 202 203 if (!error) { 204 SignalHead head = heads.get(i).getBean(); 205 int[] dsam = map.getAspectSettings(aspect); 206 if (i < dsam.length) { 207 int toSet = dsam[i]; 208 if (delay == 0) { 209 head.setAppearance(toSet); 210 log.debug("Setting {} to {}", head.getSystemName(), 211 head.getAppearanceName(toSet)); 212 } else { 213 delayedSet.put(head, toSet); 214 } 215 } else { 216 log.error(" head '{}' appearance not set for aspect '{}'", head.getSystemName(), aspect); 217 } 218 } else { 219 log.error(" head appearance not set due to above error"); 220 } 221 } 222 if (delay != 0) { 223 // If a delay is required we will fire this off into a seperate thread and let it get on with it. 224 final HashMap<SignalHead, Integer> thrDelayedSet = delayedSet; 225 final int thrDelay = delay; 226 Runnable r = new Runnable() { 227 @Override 228 public void run() { 229 setDelayedAppearances(thrDelayedSet, thrDelay); 230 } 231 }; 232 Thread thr = jmri.util.ThreadingUtil.newThread(r); 233 thr.setName(getDisplayName() + " delayed set appearance"); 234 thr.setDaemon(true); 235 try { 236 thr.start(); 237 } catch (java.lang.IllegalThreadStateException ex) { 238 log.error("Illegal Thread Sate: {}",getDisplayName(), ex); 239 } 240 } 241 } 242 243 private void setDelayedAppearances(final HashMap<SignalHead, Integer> delaySet, final int delay) { 244 for (SignalHead head : delaySet.keySet()) { 245 final SignalHead thrHead = head; 246 Runnable r = new Runnable() { 247 @Override 248 public void run() { 249 try { 250 thrHead.setAppearance(delaySet.get(thrHead)); 251 if (log.isDebugEnabled()) { 252 log.debug("Setting {} to {}", thrHead.getSystemName(), 253 thrHead.getAppearanceName(delaySet.get(thrHead))); 254 } 255 Thread.sleep(delay); 256 } catch (InterruptedException ex) { 257 Thread.currentThread().interrupt(); 258 } 259 } 260 }; 261 262 Thread thr = jmri.util.ThreadingUtil.newThread(r); 263 thr.setName(getDisplayName()); 264 thr.setDaemon(true); 265 try { 266 thr.start(); 267 thr.join(); 268 } catch (java.lang.IllegalThreadStateException | InterruptedException ex) { 269 log.error("Exception: ", ex); 270 } 271 } 272 } 273 274 public static List<SignalHead> getSignalHeadsUsed() { 275 List<SignalHead> headsUsed = new ArrayList<>(); 276 for (SignalMast mast : InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBeanSet()) { 277 if (mast instanceof jmri.implementation.SignalHeadSignalMast) { 278 java.util.List<NamedBeanHandle<SignalHead>> masthead = ((jmri.implementation.SignalHeadSignalMast) mast).getHeadsUsed(); 279 for (NamedBeanHandle<SignalHead> bean : masthead) { 280 headsUsed.add(bean.getBean()); 281 } 282 } 283 } 284 return headsUsed; 285 } 286 287 public static String isHeadUsed(SignalHead head) { 288 for (SignalMast mast : InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBeanSet()) { 289 if (mast instanceof jmri.implementation.SignalHeadSignalMast) { 290 java.util.List<NamedBeanHandle<SignalHead>> masthead = ((jmri.implementation.SignalHeadSignalMast) mast).getHeadsUsed(); 291 for (NamedBeanHandle<SignalHead> bean : masthead) { 292 if ((bean.getBean()) == head) { 293 return mast.getDisplayName(); 294 } 295 } 296 } 297 } 298 return null; 299 } 300 301 @Override 302 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 303 NamedBean nb = (NamedBean) evt.getOldValue(); 304 if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N 305 if (nb instanceof SignalHead) { 306 for (NamedBeanHandle<SignalHead> bean : getHeadsUsed()) { 307 if (bean.getBean().equals(nb)) { 308 java.beans.PropertyChangeEvent e = new java.beans.PropertyChangeEvent(this, "DoNotDelete", null, null); 309 throw new java.beans.PropertyVetoException(Bundle.getMessage("InUseSignalHeadSignalMastVeto", getDisplayName()), e); 310 } 311 } 312 } 313 } 314 } 315 316 private final static Logger log = LoggerFactory.getLogger(SignalHeadSignalMast.class); 317 318}