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