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