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}