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