001package jmri.managers; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.io.File; 006import java.io.IOException; 007import java.lang.reflect.Constructor; 008import java.net.URISyntaxException; 009import java.net.URL; 010import java.util.ArrayList; 011import java.util.List; 012 013import javax.annotation.CheckForNull; 014import javax.annotation.Nonnull; 015 016import jmri.NamedBean; 017import jmri.SignalSystem; 018import jmri.SignalSystemManager; 019import jmri.implementation.DefaultSignalSystem; 020import jmri.jmrit.XmlFile; 021import jmri.jmrix.internal.InternalSystemConnectionMemo; 022import jmri.util.FileUtil; 023 024import org.jdom2.Element; 025import org.jdom2.JDOMException; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * Default implementation of a SignalSystemManager. 031 * <p> 032 * This loads automatically the first time used. 033 * 034 * @author Bob Jacobsen Copyright (C) 2009 035 */ 036public class DefaultSignalSystemManager extends AbstractManager<SignalSystem> 037 implements SignalSystemManager { 038 039 public DefaultSignalSystemManager(InternalSystemConnectionMemo memo) { 040 super(memo); 041 042 // load when created, which will generally 043 // be the first time referenced 044 load(); 045 } 046 047 @Override 048 public int getXMLOrder() { 049 return 65400; 050 } 051 052 /** 053 * Don't want to store this information 054 */ 055 @Override 056 @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER", 057 justification = "This method intentionally doesn't do anything") 058 protected void registerSelf() { 059 } 060 061 @Override 062 public char typeLetter() { 063 return 'F'; 064 } 065 066 /** 067 * {@inheritDoc} 068 * @param name to search, by UserName then SystemName. 069 */ 070 @CheckForNull 071 @Override 072 public SignalSystem getSystem(String name) { 073 SignalSystem t = getByUserName(name); 074 return ( t!=null ? t : getBySystemName(name)); 075 } 076 077 final void load() { 078 List<String> list = getListOfNames(); 079 for (int i = 0; i < list.size(); i++) { 080 try { 081 SignalSystem s = makeBean(list.get(i)); 082 register(s); 083 } 084 catch (IllegalArgumentException ex){} // error already logged 085 } 086 } 087 088 @Nonnull 089 protected List<String> getListOfNames() { 090 List<String> retval = new ArrayList<>(); 091 // first locate the signal system directory 092 // and get names of systems 093 File signalDir = null; 094 // First get the default pre-configured signalling systems 095 try { 096 signalDir = new File(FileUtil.findURL("xml/signals", FileUtil.Location.INSTALLED).toURI()); 097 } catch (URISyntaxException | NullPointerException ex) { 098 log.error("Unable to get installed signals.", ex); 099 } 100 if (signalDir != null) { 101 File[] files = signalDir.listFiles(); 102 if (files != null) { // null if not a directory 103 for (File file : files) { 104 if (file.isDirectory()) { 105 // check that there's an aspects.xml file 106 File aspects = new File(file.getPath() + File.separator + "aspects.xml"); 107 if (aspects.exists()) { 108 log.debug("found system: {}", file.getName()); 109 retval.add(file.getName()); 110 } 111 } 112 } 113 } 114 } 115 // Now get the user defined systems. 116 try { 117 URL dir = FileUtil.findURL("signals", FileUtil.Location.USER, "resources", "xml"); 118 if (dir == null) { 119 try { 120 if (!(new File(FileUtil.getUserFilesPath(), "xml/signals")).mkdirs()) { 121 log.error("Error while creating xml/signals directory"); 122 } 123 } catch (Exception ex) { 124 log.error("Unable to create user's signals directory.", ex); 125 } 126 dir = FileUtil.findURL("xml/signals", FileUtil.Location.USER); 127 } 128 signalDir = new File(dir.toURI()); 129 } catch (URISyntaxException ex) { 130 log.error("Unable to get installed signals.", ex); 131 } 132 if (signalDir != null) { 133 File[] files = signalDir.listFiles(); 134 if (files != null) { // null if not a directory 135 for (File file : files) { 136 if (file.isDirectory()) { 137 // check that there's an aspects.xml file 138 File aspects = new File(file.getPath() + File.separator + "aspects.xml"); 139 log.trace("checking for {}", aspects); 140 if ((aspects.exists()) && (!retval.contains(file.getName()))) { 141 log.debug("found user system: {}", file.getName()); 142 retval.add(file.getName()); 143 } 144 } 145 } 146 } 147 } 148 return retval; 149 } 150 151 @Nonnull 152 protected SignalSystem makeBean(String name) throws IllegalArgumentException { 153 154 URL path; 155 XmlFile xf; 156 157 // First check to see if the bean is in the user directory resources/signals/, then xml/signals 158 path = FileUtil.findURL("signals/" + name + "/aspects.xml", FileUtil.Location.USER, "resources", "xml"); 159 log.debug("load from {}", path); 160 if (path != null) { 161 xf = new AspectFile(); 162 try { 163 log.debug(" successful"); 164 Element root = xf.rootFromURL(path); 165 DefaultSignalSystem s = new DefaultSignalSystem(name); 166 loadBean(s, root); 167 return s; 168 } catch (IOException | JDOMException e) { 169 log.error("Could not parse aspect file \"{}\" due to", path, e); 170 } 171 } 172 173 throw new IllegalArgumentException("Unable to parse aspect file "+path); 174 } 175 176 void loadBean(DefaultSignalSystem s, Element root) { 177 List<Element> l = root.getChild("aspects").getChildren("aspect"); 178 179 // set user name from system name element 180 s.setUserName(root.getChild("name").getText()); 181 182 // find all aspects, include them by name, 183 // add all other sub-elements as key/value pairs 184 for (int i = 0; i < l.size(); i++) { 185 String name = l.get(i).getChild("name").getText(); 186 log.debug("aspect name {}", name); 187 188 List<Element> c = l.get(i).getChildren(); 189 190 for (int j = 0; j < c.size(); j++) { 191 // note: includes setting name; redundant, but needed 192 s.setProperty(name, c.get(j).getName(), c.get(j).getText()); 193 } 194 } 195 196 if (root.getChild("imagetypes") != null) { 197 List<Element> t = root.getChild("imagetypes").getChildren("imagetype"); 198 for (int i = 0; i < t.size(); i++) { 199 String type = t.get(i).getAttribute("type").getValue(); 200 s.setImageType(type); 201 } 202 } 203 //loadProperties(s, root); 204 if (root.getChild("properties") != null) { 205 for (Object next : root.getChild("properties").getChildren("property")) { 206 Element e = (Element) next; 207 208 try { 209 Class<?> cl; 210 Constructor<?> ctor; 211 212 // create key string 213 String key = e.getChild("key").getText(); 214 215 // check for non-String key. Warn&proceed if found. 216 // Pre-JMRI 4.3, keys in NamedBean parameters could be Objects 217 // constructed from Strings, similar to the value code below. 218 if (! ( 219 e.getChild("key").getAttributeValue("class") == null 220 || e.getChild("key").getAttributeValue("class").isEmpty() 221 || e.getChild("key").getAttributeValue("class").equals("java.lang.String") 222 )) { 223 224 log.warn("SignalSystem {} property key of invalid non-String type {} not supported", 225 s.getSystemName(), e.getChild("key").getAttributeValue("class")); 226 } 227 228 // create value object 229 Object value = null; 230 if (e.getChild("value") != null) { 231 cl = Class.forName(e.getChild("value").getAttributeValue("class")); 232 ctor = cl.getConstructor(new Class<?>[]{String.class}); 233 value = ctor.newInstance(new Object[]{e.getChild("value").getText()}); 234 } 235 236 // store 237 s.setProperty(key, value); 238 } catch (ClassNotFoundException 239 | NoSuchMethodException | InstantiationException 240 | IllegalAccessException | java.lang.reflect.InvocationTargetException ex) { 241 log.error("Error loading properties", ex); 242 } 243 } 244 } 245 } 246 247 void loadProperties(NamedBean t, Element elem) { 248 // do nothing 249 } 250 251 /** 252 * XmlFile is abstract, so this extends for local use 253 */ 254 static class AspectFile extends XmlFile { 255 } 256 257 @Override 258 public String getBeanTypeHandled(boolean plural) { 259 return Bundle.getMessage(plural ? "BeanNameSignalSystems" : "BeanNameSignalSystem"); 260 } 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override 266 public Class<SignalSystem> getNamedBeanClass() { 267 return SignalSystem.class; 268 } 269 270 private final static Logger log = LoggerFactory.getLogger(DefaultSignalSystemManager.class); 271}