001package jmri.implementation; 002 003import java.net.URL; 004import java.util.*; 005 006import jmri.SignalHead; 007import jmri.SignalSystem; 008import jmri.util.FileUtil; 009import org.jdom2.Element; 010import org.jdom2.JDOMException; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * Default implementation of a basic signal mast aspect - appearance mapping. 016 * <p> 017 * The default contents are taken from the NamedBeanBundle properties file. This 018 * makes creation a little more heavy-weight, but speeds operation. 019 * 020 * @author Bob Jacobsen Copyright (C) 2009 021 */ 022public class DefaultSignalAppearanceMap extends AbstractNamedBean implements jmri.SignalAppearanceMap { 023 024 public DefaultSignalAppearanceMap(String systemName, String userName) { 025 super(systemName, userName); 026 } 027 028 public DefaultSignalAppearanceMap(String systemName) { 029 super(systemName); 030 } 031 032 @Override 033 public String getBeanType() { 034 return Bundle.getMessage("BeanNameSignalAppMap"); 035 } 036 037 static public DefaultSignalAppearanceMap getMap(String signalSystemName, String aspectMapName) { 038 log.debug("getMap signalSystem= \"{}\", aspectMap= \"{}\"", signalSystemName, aspectMapName); 039 DefaultSignalAppearanceMap map = maps.get("map:" + signalSystemName + ":" + aspectMapName); 040 if (map == null) { 041 log.debug("not located, request loadMap signalSystem= \"{}\", aspectMap= \"{}\"", signalSystemName, aspectMapName); 042 map = loadMap(signalSystemName, aspectMapName); 043 } 044 return map; 045 } 046 047 // added 3.9.7 so CATS can create own implementations 048 protected void registerMap() { 049 maps.put(getSystemName(), this); 050 } 051 052 // added 3.9.7 so CATS can create own implementations 053 static public DefaultSignalAppearanceMap findMap(String systemName) { 054 return maps.get(systemName); 055 } 056 057 static DefaultSignalAppearanceMap loadMap(String signalSystemName, String aspectMapName) { 058 DefaultSignalAppearanceMap map 059 = new DefaultSignalAppearanceMap("map:" + signalSystemName + ":" + aspectMapName); 060 maps.put("map:" + signalSystemName + ":" + aspectMapName, map); 061 062 String path = "signals/" + signalSystemName + "/appearance-" + aspectMapName + ".xml"; 063 URL file = FileUtil.findURL(path, "resources", "xml"); 064 if (file == null) { 065 log.error("appearance file (xml/{}) doesn't exist", path); 066 throw new IllegalArgumentException("appearance file (xml/" + path + ") doesn't exist"); 067 } 068 jmri.jmrit.XmlFile xf = new jmri.jmrit.XmlFile() { 069 }; 070 Element root; 071 try { 072 root = xf.rootFromURL(file); 073 // get appearances 074 075 List<Element> l = root.getChild("appearances").getChildren("appearance"); 076 077 // find all appearances, include them by aspect name, 078 log.debug(" reading {} aspectname elements", l.size()); 079 for (int i = 0; i < l.size(); i++) { 080 String name = l.get(i).getChild("aspectname").getText(); 081 log.debug("aspect name {}", name); 082 083 // add 'show' sub-elements as ints 084 List<Element> c = l.get(i).getChildren("show"); 085 086 int[] appearances = new int[c.size()]; 087 for (int j = 0; j < c.size(); j++) { 088 // note: includes setting name; redundant, but needed 089 int ival; 090 String sval = c.get(j).getText().toUpperCase(); 091 if (sval.equals("LUNAR")) { 092 ival = SignalHead.LUNAR; 093 } else if (sval.equals("GREEN")) { 094 ival = SignalHead.GREEN; 095 } else if (sval.equals("YELLOW")) { 096 ival = SignalHead.YELLOW; 097 } else if (sval.equals("RED")) { 098 ival = SignalHead.RED; 099 } else if (sval.equals("FLASHLUNAR")) { 100 ival = SignalHead.FLASHLUNAR; 101 } else if (sval.equals("FLASHGREEN")) { 102 ival = SignalHead.FLASHGREEN; 103 } else if (sval.equals("FLASHYELLOW")) { 104 ival = SignalHead.FLASHYELLOW; 105 } else if (sval.equals("FLASHRED")) { 106 ival = SignalHead.FLASHRED; 107 } else if (sval.equals("DARK")) { 108 ival = SignalHead.DARK; 109 } else { 110 log.error("found invalid content: {}", sval); 111 throw new JDOMException("invalid content: " + sval); 112 } 113 114 appearances[j] = ival; 115 } 116 map.addAspect(name, appearances); 117 118 List<Element> img = l.get(i).getChildren("imagelink"); 119 loadImageMaps(img, name, map); 120 121 // now add the rest of the attributes 122 Hashtable<String, String> hm = new Hashtable<String, String>(); 123 124 List<Element> a = l.get(i).getChildren(); 125 126 for (int j = 0; j < a.size(); j++) { 127 String key = a.get(j).getName(); 128 String value = a.get(j).getText(); 129 hm.put(key, value); 130 } 131 132 map.aspectAttributeMap.put(name, hm); 133 } 134 loadSpecificMap(signalSystemName, aspectMapName, map, root); 135 loadAspectRelationMap(signalSystemName, aspectMapName, map, root); 136 log.debug("loading complete"); 137 } catch (java.io.IOException | org.jdom2.JDOMException e) { 138 log.error("error reading file {}", file.getPath(), e); 139 return null; 140 } 141 142 return map; 143 } 144 145 static void loadImageMaps(List<Element> img, String name, DefaultSignalAppearanceMap map) { 146 Hashtable<String, String> images = new Hashtable<String, String>(); 147 for (int j = 0; j < img.size(); j++) { 148 String key = "default"; 149 if ((img.get(j).getAttribute("type")) != null) { 150 key = img.get(j).getAttribute("type").getValue(); 151 } 152 String value = img.get(j).getText(); 153 images.put(key, value); 154 } 155 map.aspectImageMap.put(name, images); 156 } 157 158 static void loadSpecificMap(String signalSystemName, String aspectMapName, DefaultSignalAppearanceMap SMmap, Element root) { 159 log.debug("load specific signalSystem= \"{}\", aspectMap= \"{}\"", signalSystemName, aspectMapName); 160 loadSpecificAspect(signalSystemName, aspectMapName, HELD, SMmap, root); 161 loadSpecificAspect(signalSystemName, aspectMapName, DANGER, SMmap, root); 162 loadSpecificAspect(signalSystemName, aspectMapName, PERMISSIVE, SMmap, root); 163 loadSpecificAspect(signalSystemName, aspectMapName, DARK, SMmap, root); 164 } 165 166 static void loadSpecificAspect(String signalSystemName, String aspectMapName, int aspectType, DefaultSignalAppearanceMap SMmap, Element root) { 167 168 String child; 169 switch (aspectType) { 170 case HELD: 171 child = "held"; 172 break; 173 case DANGER: 174 child = "danger"; 175 break; 176 case PERMISSIVE: 177 child = "permissive"; 178 break; 179 case DARK: 180 child = "dark"; 181 break; 182 default: 183 child = "danger"; 184 } 185 186 String appearance = null; 187 if (root.getChild("specificappearances") == null || root.getChild("specificappearances").getChild(child) == null) { 188 log.debug("appearance not configured {}", child); 189 return; 190 } 191 try { 192 appearance = root.getChild("specificappearances").getChild(child).getChild("aspect").getText(); 193 SMmap.specificMaps.put(aspectType, appearance); 194 } catch (java.lang.NullPointerException e) { 195 log.debug("aspect for specific appearance not configured {}", child); 196 } 197 198 try { 199 List<Element> img = root.getChild("specificappearances").getChild(child).getChildren("imagelink"); 200 String name = "$" + child; 201 if (img.size() == 0) { 202 if (appearance != null) { 203 //We do not have any specific images created, therefore we use the 204 //those associated with the aspect. 205 List<String> app = SMmap.getImageTypes(appearance); 206 Hashtable<String, String> images = new Hashtable<String, String>(); 207 String type = ""; 208 for (int i = 0; i < app.size(); i++) { 209 type = SMmap.getImageLink(appearance, app.get(i)); 210 images.put(app.get(i), type); 211 } 212 //We will register the last aspect as a default. 213 images.put("default", type); 214 SMmap.aspectImageMap.put(name, images); 215 } 216 } else { 217 loadImageMaps(img, name, SMmap); 218 Hashtable<String, String> hm = new Hashtable<String, String>(); 219 220 //Register the last aspect as the default 221 String key = img.get(img.size() - 1).getName(); 222 String value = img.get(img.size() - 1).getText(); 223 hm.put(key, value); 224 225 SMmap.aspectAttributeMap.put(name, hm); 226 } 227 } catch (java.lang.NullPointerException e) { 228 //Considered Normal if held aspect uses default signal appearance 229 } 230 } 231 232 static void loadAspectRelationMap(String signalSystemName, String aspectMapName, DefaultSignalAppearanceMap SMmap, Element root) { 233 if (log.isDebugEnabled()) { 234 log.debug("load aspect relation map signalSystem= \"{}\", aspectMap= \"{}\"", signalSystemName, aspectMapName); 235 } 236 237 try { 238 List<Element> l = root.getChild("aspectMappings").getChildren("aspectMapping"); 239 for (int i = 0; i < l.size(); i++) { 240 String advanced = l.get(i).getChild("advancedAspect").getText(); 241 242 List<Element> o = l.get(i).getChildren("ourAspect"); 243 String[] appearances = new String[o.size()]; 244 for (int j = 0; j < o.size(); j++) { 245 appearances[j] = o.get(j).getText(); 246 } 247 SMmap.aspectRelationshipMap.put(advanced, appearances); 248 } 249 250 } catch (java.lang.NullPointerException e) { 251 log.debug("appearance not configured"); 252 return; 253 } 254 } 255 256 /** 257 * Get a property associated with a specific aspect. 258 */ 259 @Override 260 public String getProperty(String aspect, String key) { 261 return aspectAttributeMap.get(aspect).get(key); 262 } 263 264 @Override 265 public String getImageLink(String aspect, String type) { 266 if (type == null || type.equals("")) { 267 type = "default"; 268 } 269 String value; 270 try { 271 value = aspectImageMap.get(aspect).get(type); 272 //if we don't return a valid image set, then we will use which ever set is loaded in the getProperty 273 if (value == null) { 274 value = getProperty(aspect, "imagelink"); 275 } 276 } catch (java.lang.NullPointerException e) { 277 /* Can be considered normal for situations where a specific aspect 278 has been asked for but it hasn't yet been loaded or configured */ 279 value = ""; 280 } 281 return value; 282 } 283 284 @Override 285 public Vector<String> getImageTypes(String aspect) { 286 if (!checkAspect(aspect)) { 287 return new Vector<String>(); 288 } 289 Enumeration<String> e = aspectImageMap.get(aspect).keys(); 290 Vector<String> v = new Vector<String>(); 291 while (e.hasMoreElements()) { 292 v.add(e.nextElement()); 293 } 294 return v; 295 } 296 297 protected Hashtable<String, Hashtable<String, String>> aspectAttributeMap 298 = new Hashtable<String, Hashtable<String, String>>(); 299 300 protected Hashtable<String, Hashtable<String, String>> aspectImageMap 301 = new Hashtable<String, Hashtable<String, String>>(); 302 303 static HashMap<String, DefaultSignalAppearanceMap> maps 304 = new LinkedHashMap<String, DefaultSignalAppearanceMap>(); 305 306 protected Hashtable<Integer, String> specificMaps 307 = new Hashtable<Integer, String>(); 308 309 protected Hashtable<String, String[]> aspectRelationshipMap 310 = new Hashtable<String, String[]>(); 311 312 public void loadDefaults() { 313 314 log.debug("start loadDefaults"); 315 316 String ra; 317 ra = Bundle.getMessage("SignalAspectDefaultRed"); 318 if (ra != null) { 319 addAspect(ra, new int[]{SignalHead.RED}); 320 } else { 321 log.error("no default red aspect"); 322 } 323 324 ra = Bundle.getMessage("SignalAspectDefaultYellow"); 325 if (ra != null) { 326 addAspect(ra, new int[]{SignalHead.YELLOW}); 327 } else { 328 log.error("no default yellow aspect"); 329 } 330 331 ra = Bundle.getMessage("SignalAspectDefaultGreen"); 332 if (ra != null) { 333 addAspect(ra, new int[]{SignalHead.GREEN}); 334 } else { 335 log.error("no default green aspect"); 336 } 337 } 338 339 @Override 340 public boolean checkAspect(String aspect) { 341 if (aspect == null) { 342 return false; 343 } 344 return table.containsKey(aspect);// != null; 345 } 346 347 public void addAspect(String aspect, int[] appearances) { 348 if (log.isDebugEnabled()) { 349 log.debug("add aspect \"{}\" for {} heads {}", aspect, appearances.length, appearances[0]); 350 } 351 table.put(aspect, appearances); 352 } 353 354 /** 355 * Provide the Aspect elements to GUI and store methods. 356 * 357 * @return all aspects in this signal mast appearance map, in the order defined in xml definition 358 */ 359 @Override 360 public Enumeration<String> getAspects() { 361 log.debug("list of aspects provided"); 362 return new Vector<String>(table.keySet()).elements(); // this will be greatly simplified when we can just return keySet 363 } 364 365 @Override 366 public String getSpecificAppearance(int appearance) { 367 if (specificMaps.containsKey(appearance)) { 368 return specificMaps.get(appearance); 369 } 370 return null; 371 } 372 373 /** 374 * {@inheritDoc} 375 */ 376 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS", 377 justification = "null returned is documented to mean no valid result") 378 @Override 379 public String[] getValidAspectsForAdvancedAspect(String advancedAspect) { 380 if (aspectRelationshipMap == null) { 381 log.error("aspect relationships have not been defined or loaded"); 382 throw new IllegalArgumentException("aspect relationships have not been defined or loaded"); 383 } 384 if (advancedAspect == null) { 385 String[] danger = new String[1]; 386 danger[0] = getSpecificAppearance(DANGER); 387 return danger; 388 } 389 if (aspectRelationshipMap.containsKey(advancedAspect)) { 390 //String[] validAspects = aspectRelationMap.get(advancedAspect); 391 return aspectRelationshipMap.get(advancedAspect); 392 } 393 return null; 394 } 395 396 @Override 397 public SignalSystem getSignalSystem() { 398 return systemDefn; 399 } 400 401 public void setSignalSystem(SignalSystem t) { 402 systemDefn = t; 403 } 404 protected SignalSystem systemDefn; 405 406 /** 407 * {@inheritDoc} 408 * 409 * This method returns a constant result on the DefaultSignalAppearanceMap. 410 * 411 * @return {@link jmri.NamedBean#INCONSISTENT} 412 */ 413 @Override 414 public int getState() { 415 return INCONSISTENT; 416 } 417 418 /** 419 * {@inheritDoc} 420 * 421 * This method has no effect on the DefaultSignalAppearanceMap. 422 */ 423 @Override 424 public void setState(int s) { 425 // do nothing 426 } 427 428 public int[] getAspectSettings(String aspect) { 429 return table.get(aspect); 430 } 431 432 protected HashMap<String, int[]> table = new LinkedHashMap<String, int[]>(); 433 434 @Override 435 /** 436 * {@inheritDoc} 437 */ 438 public String summary() { 439 StringBuilder retval = new StringBuilder(); 440 retval.append(toString()); 441 retval.append("\n BeanType: "+getBeanType()); 442 443 retval.append("\n aspects:"); 444 Enumeration<String> values = getAspects(); 445 while (values.hasMoreElements()) { 446 String aspect = values.nextElement(); 447 retval.append("\n aspect: "+aspect); 448 retval.append("\n len aspectSettings: "+getAspectSettings(aspect).length); 449 retval.append("\n attribute map:"); 450 Enumeration<String> keys = aspectAttributeMap.get(aspect).keys(); 451 while (keys.hasMoreElements()) { 452 String key = keys.nextElement(); 453 retval.append("\n key: "+key+" value: "+aspectAttributeMap.get(aspect).get(key)); 454 } 455 } 456 457 retval.append("\n SignalSystem = "+getSignalSystem()); 458 459 return new String(retval); 460 } 461 462 private final static Logger log = LoggerFactory.getLogger(DefaultSignalAppearanceMap.class); 463 464}